From 60543e46a24acedb7ce0b586d43d9837d53fa81e Mon Sep 17 00:00:00 2001 From: Jose Alekhinne Date: Sun, 17 May 2026 06:03:05 -0700 Subject: [PATCH 01/10] feat(kb,rg): editorial pipeline + git-as-precondition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase RG promotes git from an assumed prerequisite to an enforced one. Phase KB lays down the editorial knowledge-base pipeline that the spec calls out as depending on it. They ship paired because the closeout / handover provenance contract (sha, branch, in-repo evidence SHA pins) is dishonest without the git mandate. ## Phase RG: git is required - `internal/gitmeta/` (new): split into require.go (public RequireGitTree), head.go (ResolveHead with CTX_TASK_COMMIT / GITHUB_SHA overrides), branch.go (private resolver), sha.go (short-form truncation), types.go (HeadRef). - `internal/err/gitmeta/` (new): ErrMissingGitTree sentinel + MissingGitTreeForCmd wrapping constructor. - `internal/config/gitmeta/` (new): sentinel + format-string + env-var name constants. - `internal/bootstrap/cmd.go`: PersistentPreRunE now calls RequireGitTree after the existing CTX_DIR + initialized checks; wraps the sentinel with the subcommand name. - `internal/config/git/git.go`: adds DotDir = ".git" so the literal lives in config. - `.context/CONSTITUTION.md` + mirrored asset: Process Invariants gain a "Git is required" rule. ## Phase KB: editorial pipeline + handover Per the spec's pass-mode contract (topic-page / triage / evidence-only), a completion circuit breaker (page exists + cites EV-### + site build clean + cold-reader rubric passes), a source-coverage state-machine ledger, topic-adjacency pre-flight, life-stage check (bootstrap vs maintenance), and folder-shaped topic pages from day one. ### Writers (`internal/write/`) - `closeout/`: Write, Read, List, PostdatedBy, Archive split across read.go / write.go / frontmatter.go / markdown.go / filename.go / archive.go / types.go. - `handover/`: Write with closeout fold + Latest (the cursor-style timestamp lookup), split across write.go / provenance.go / markdown.go / filename.go / latest.go / parse.go / types.go. The package doc.go reframes handover as session-to-session glue; the closeout fold is the optional Phase KB integration, not the purpose of the handover itself. - `kb/{evidence,sourcecoverage,glossary,contradiction, question,decision,timeline,sourcemap,relationship}/`: nine per-artifact writers; evidence enforces no-renumber + ID allocation, sourcecoverage enforces the state-machine transition rules. - `kb/row/`: shared append-with-monotonic-ID helper (contradiction / decision / question collapsed their triplicated I/O orchestration into entity.KBRowHooks + one call into row.Append). ### CLI (`internal/cli/`) - `kb/` parent + `topic new` (sole scaffold writer), `note`, `reindex`, plus skill-driven `ingest` / `ask` / `site-review` / `ground` that refuse on empty input and surface the canonical /ctx-kb-* skill invocation. Each subcommand follows the project cmd.go + run.go split: cmd.go owns cobra plumbing, run.go owns the procedure. - `handover/` parent + `write` subcommand. MarkFlagRequired on --summary and --next; rejects placeholder values (TBD, see chat, n/a, none) via validate.RejectPlaceholder. Path resolver lives at `handover/core/path/Dir()`, not under `kb/core/path/`, because the handover is independent of the editorial pipeline. - `initialize/core/kb/`: Scaffold lays down kb/, kb/topics/, ingest/, ingest/closeouts/, ingest/schemas/, handovers/ and copies the embedded templates. HandoversSubdir lives in cfgHandover, not cfgKB. - `kb/cmd/topic/cmd/newcmd/`: the new-topic leaf nests properly under its `topic` parent (no flat `topicnew/`). - All flag binding via internal/flagbind; all user-stream writes via io.SafeFprintf; all file IO via io.Safe* wrappers. ### Skills (canonical + parallel trees) Six new SKILL.md files (`ctx-kb-ingest`, `ctx-kb-ask`, `ctx-kb-site-review`, `ctx-kb-ground`, `ctx-kb-note`, `ctx-handover`) for the canonical Claude tree, plus ported copies for `integrations/copilot-cli/skills/` (tools: [bash] frontmatter; capture-skill renames /ctx-task-add → /ctx-add-task etc.) and condensed versions for `integrations/opencode/skills/`. `ctx-wrap-up` is the user-facing trigger for session-end; it always delegates to `/ctx-handover` as its final step, with explicit `--summary` / `--next` draft + user-confirmation phase. `/ctx-handover` is documented as wrap-up's sub-step, not a user-facing trigger. `/ctx-remember` reads the latest handover unconditionally; closeout-fold is the KB-conditional augmentation. ### Embedded templates (`internal/assets/kb/templates/`) - ingest/: KB-RULES.md (the editorial constitution), four mode prompts (00-GROUND, 30-INGEST, 40-ASK, 50-SITE_REVIEW), OPERATOR.md, PROMPT.md (hand-fallback router). - ingest/schemas/: ten schema templates with fields list + one worked example each. - kb/index.md and kb/topics/_template/index.md. ### Shared `internal/slug/` Lifted `internal/cli/journal/core/slug/` to `internal/slug/` (FromTitle, CleanTitle, ForTitle) and added `Path()` for slash-preserving slugs. The kb topic scaffolder consumes `slug.Path`; the duplicate Slugify in `cli/kb/core/topic/` was removed. ### Configuration + error packages Every magic string moved to internal/config//, every error constructor moved to internal/err// (audit naked_errors + magic_strings both clean). Cross-package types (closeout.Frontmatter, closeout.File, KBRowHooks) moved to internal/entity/. The kb-prefixed flat directories (`internal/config/kbcli`, `internal/err/kbcli`, etc.) were restructured into nested subpackages (`internal/config/kb/cli/`, `internal/err/kb/cli/`, etc.) matching the embed/{cmd,flag,text} shape. Plural directory names (contradictions / decisions / questions) were singularised to match the project's single-noun convention. ### Documentation - specs/kb-editorial-pipeline.md: full rewrite to current upstream editorial-pipeline shape. - specs/require-git.md: pre-existing; cited as Spec trailer. Rewritten in plain English (no "de facto / de jure" Latin). - docs/recipes/build-a-knowledge-base.md (new): six steps including "Browse the KB Locally" via `ctx serve` on a `zensical.toml`-equipped `.context/kb/`. - docs/recipes/typical-kb-session.md (new) - docs/recipes/recover-aborted-session.md (new) - docs/cli/kb.md (new) and docs/cli/handover.md (new): separate pages for distinct commands; the older combined page was a category error. - docs/reference/skills.md: six new entries under new "Knowledge Base (Phase KB)" section. - docs/cli/init-status.md: git-required admonition + new KB scaffolding callout. - docs/cli/index.md and docs/recipes/index.md: nav. - README.md: git-required note + KB workflow snippet. - CLAUDE.md (project root + internal/assets/claude/): two sibling h2 sections, `## Session Handovers` and `## KB Editorial Workflow`. Trigger map points "leave a handover" / "before I go" / "stepping away" at `/ctx-wrap-up`, not `/ctx-handover`. - dist/RELEASE_NOTES.md: Phase RG + Phase KB section prepended. - internal/assets/commands/commands.yaml: every kb / handover entry now carries a usage Examples block (parity with the rest of the YAML). ### Refinement passes folded into this commit - Title-Case heading sweep across recipes, skills, and templates. - en-US / American-English sweep (honoured → honored, catalogue → catalog, etc.). - "markdown" → "Markdown" (proper-noun) sweep outside code spans, frontmatter, and language tags. - Em-dash semantic sweep: each `—` evaluated in context and replaced with `:` (appositive), `;` (clause break), `,` (loose pause), or `.` (sentence split). - Bare `ctx` → `` `ctx` `` brand sweep outside code blocks and slash-command identifiers. - NDA scrub: every reference to the upstream sibling project's name (`things-wtf*`, `disaster-recovery*`, `~/.../WORKSPACE/things-wtf`) replaced with generic placeholders (`your-project`, `your-domain`). - Rot-prone `github.com/.../blob/main/specs/*.md` links stripped from docs (specs migrate into `specs/released/v*/` at release-time and the links break). - "Optional"-labelled steps reframed so the agent always performs them and the user decides on the result (agents skip what's labeled optional). - Defensive over-framings ("regardless of whether `.context/kb/` exists", "orthogonal to KB", "not a user-facing trigger") trimmed where the wrong reading was not naturally tempting; kept only where the prose's position made the wrong reading the default. ### Audit gate `make lint` clean, `make test` clean (audit + compliance + drift checks all green). ## Verification End-to-end smoke (fresh git tempdir, isolated CTX_DIR): `ctx init → ctx kb topic new "" → ctx handover write … → ctx kb note … → ctx kb reindex` all return zero and produce the expected on-disk artifacts (topics//index.md, handovers/-.md with sha and branch resolved from git, findings.md append, refreshed CTX:KB:TOPICS managed block). Spec: specs/kb-editorial-pipeline.md Spec: specs/require-git.md Signed-off-by: Jose Alekhinne --- .context/CONSTITUTION.md | 9 + .context/CONVENTIONS.md | 3 +- .context/DECISIONS.md | 383 +++- .context/LEARNINGS.md | 536 +++++- .context/TASKS.md | 390 +++-- ...718-anchor-undercover-to-spec-and-tasks.md | 2 +- .gitignore | 4 + CLAUDE.md | 42 + README.md | 30 + .../2026-02-01-refactoring-with-intent.md | 2 +- ...26-02-04-skills-that-fight-the-platform.md | 2 +- ...026-02-17-code-is-cheap-judgment-is-not.md | 2 +- ...debt-and-the-myth-of-overnight-progress.md | 2 +- docs/cli/connect.md | 2 +- docs/cli/connection.md | 2 +- docs/cli/handover.md | 78 + docs/cli/index.md | 2 + docs/cli/init-status.md | 22 + docs/cli/kb.md | 92 + docs/cli/steering.md | 2 +- docs/home/getting-started.md | 6 +- docs/home/opencode.md | 38 +- docs/home/prompting-guide.md | 2 +- docs/home/steering.md | 4 +- docs/home/vscode.md | 2 +- docs/operations/integrations.md | 16 +- docs/recipes/activating-context.md | 10 +- docs/recipes/build-a-knowledge-base.md | 283 +++ docs/recipes/hub-team.md | 4 +- docs/recipes/index.md | 40 + docs/recipes/multi-tool-setup.md | 2 +- docs/recipes/publishing.md | 4 +- docs/recipes/recover-aborted-session.md | 159 ++ docs/recipes/session-lifecycle.md | 11 + docs/recipes/steering.md | 2 +- docs/recipes/typical-kb-session.md | 161 ++ docs/reference/comparison.md | 2 +- docs/reference/skills.md | 148 +- docs/security/hub.md | 2 +- docs/thesis/index.md | 4 +- internal/assets/claude/CLAUDE.md | 47 + .../claude/skills/ctx-handover/SKILL.md | 284 +++ .../assets/claude/skills/ctx-history/SKILL.md | 2 +- .../claude/skills/ctx-implement/SKILL.md | 2 +- .../assets/claude/skills/ctx-kb-ask/SKILL.md | 236 +++ .../claude/skills/ctx-kb-ground/SKILL.md | 279 +++ .../claude/skills/ctx-kb-ingest/SKILL.md | 645 +++++++ .../assets/claude/skills/ctx-kb-note/SKILL.md | 164 ++ .../claude/skills/ctx-kb-site-review/SKILL.md | 259 +++ .../claude/skills/ctx-link-check/SKILL.md | 6 +- .../claude/skills/ctx-remember/SKILL.md | 18 +- .../references/anthropic-best-practices.md | 6 +- .../references/anthropic-best-practices.md | 6 +- .../assets/claude/skills/ctx-wrap-up/SKILL.md | 96 +- internal/assets/commands/commands.yaml | 130 ++ internal/assets/commands/flags.yaml | 12 + internal/assets/commands/text/errors.yaml | 174 ++ internal/assets/commands/text/ui.yaml | 2 + internal/assets/commands/text/write.yaml | 36 + internal/assets/context/CONSTITUTION.md | 8 + internal/assets/embed.go | 2 + .../skills/ctx-check-links/SKILL.md | 6 +- .../copilot-cli/skills/ctx-handover/SKILL.md | 271 +++ .../copilot-cli/skills/ctx-implement/SKILL.md | 2 +- .../skills/ctx-journal-normalize/SKILL.md | 6 +- .../copilot-cli/skills/ctx-kb-ask/SKILL.md | 236 +++ .../copilot-cli/skills/ctx-kb-ground/SKILL.md | 278 +++ .../copilot-cli/skills/ctx-kb-ingest/SKILL.md | 644 +++++++ .../copilot-cli/skills/ctx-kb-note/SKILL.md | 163 ++ .../skills/ctx-kb-site-review/SKILL.md | 258 +++ .../copilot-cli/skills/ctx-remember/SKILL.md | 24 +- .../copilot-cli/skills/ctx-wrap-up/SKILL.md | 107 +- .../opencode/skills/ctx-handover/SKILL.md | 86 + .../opencode/skills/ctx-kb-ask/SKILL.md | 101 ++ .../opencode/skills/ctx-kb-ground/SKILL.md | 78 + .../opencode/skills/ctx-kb-ingest/SKILL.md | 157 ++ .../opencode/skills/ctx-kb-note/SKILL.md | 72 + .../skills/ctx-kb-site-review/SKILL.md | 72 + .../opencode/skills/ctx-remember/SKILL.md | 14 +- .../opencode/skills/ctx-wrap-up/SKILL.md | 36 +- .../assets/kb/templates/ingest/00-GROUND.md | 92 + .../assets/kb/templates/ingest/30-INGEST.md | 260 +++ internal/assets/kb/templates/ingest/40-ASK.md | 104 ++ .../kb/templates/ingest/50-SITE_REVIEW.md | 123 ++ .../assets/kb/templates/ingest/KB-RULES.md | 354 ++++ .../assets/kb/templates/ingest/OPERATOR.md | 153 ++ internal/assets/kb/templates/ingest/PROMPT.md | 95 + .../ingest/schemas/contradictions.md | 45 + .../ingest/schemas/domain-decisions.md | 58 + .../ingest/schemas/evidence-index.md | 45 + .../kb/templates/ingest/schemas/glossary.md | 54 + .../ingest/schemas/outstanding-questions.md | 51 + .../ingest/schemas/relationship-map.md | 41 + .../templates/ingest/schemas/session-log.md | 52 + .../ingest/schemas/source-coverage.md | 65 + .../kb/templates/ingest/schemas/source-map.md | 45 + .../kb/templates/ingest/schemas/timeline.md | 49 + internal/assets/kb/templates/kb/index.md | 77 + .../kb/templates/kb/topics/_template/index.md | 89 + internal/assets/why/about.md | 4 +- internal/audit/desckey_namespace_test.go | 2 +- internal/audit/flagbind_test.go | 6 +- internal/bootstrap/bootstrap_test.go | 9 + internal/bootstrap/cmd.go | 19 + internal/bootstrap/group.go | 4 + internal/cli/add/core/build/build.go | 2 +- internal/cli/agent/cmd/root/cmd.go | 2 +- internal/cli/agent/core/cooldown/cooldown.go | 4 +- internal/cli/change/cmd/root/cmd.go | 2 +- internal/cli/cli_test.go | 7 + internal/cli/compact/cmd/root/cmd.go | 2 +- internal/cli/connection/cmd/register/cmd.go | 2 +- internal/cli/doctor/cmd/root/cmd.go | 2 +- internal/cli/drift/cmd/root/cmd.go | 2 +- internal/cli/event/cmd.go | 2 +- internal/cli/fmt/cmd/root/cmd.go | 2 +- internal/cli/guide/cmd/root/cmd.go | 2 +- internal/cli/handover/cmd.go | 30 + internal/cli/handover/cmd/write/cmd.go | 71 + internal/cli/handover/cmd/write/doc.go | 13 + internal/cli/handover/cmd/write/run.go | 120 ++ internal/cli/handover/core/path/doc.go | 23 + internal/cli/handover/core/path/path.go | 27 + internal/cli/handover/core/path/path_test.go | 42 + .../cli/handover/core/path/testmain_test.go | 19 + internal/cli/handover/doc.go | 18 + internal/cli/hub/cmd/start/cmd.go | 2 +- internal/cli/hub/cmd/stop/cmd.go | 2 +- internal/cli/initialize/cmd/root/cmd.go | 2 +- internal/cli/initialize/cmd/root/run.go | 12 + internal/cli/initialize/core/kb/copy.go | 75 + internal/cli/initialize/core/kb/doc.go | 31 + internal/cli/initialize/core/kb/gitkeep.go | 53 + internal/cli/initialize/core/kb/scaffold.go | 97 ++ internal/cli/journal/cmd/importer/cmd.go | 2 +- internal/cli/journal/cmd/lock/cmd.go | 2 +- internal/cli/journal/cmd/obsidian/cmd.go | 2 +- internal/cli/journal/cmd/schema/check/cmd.go | 2 +- internal/cli/journal/cmd/site/cmd.go | 2 +- internal/cli/journal/cmd/source/cmd.go | 2 +- internal/cli/journal/cmd/unlock/cmd.go | 2 +- internal/cli/journal/core/plan/plan.go | 2 +- internal/cli/journal/core/slug/doc.go | 47 - internal/cli/kb/cmd/ask/cmd.go | 35 + internal/cli/kb/cmd/ask/doc.go | 24 + internal/cli/kb/cmd/ask/run.go | 52 + internal/cli/kb/cmd/ground/cmd.go | 30 + internal/cli/kb/cmd/ground/doc.go | 26 + internal/cli/kb/cmd/ground/run.go | 63 + internal/cli/kb/cmd/ingest/cmd.go | 29 + internal/cli/kb/cmd/ingest/doc.go | 25 + internal/cli/kb/cmd/ingest/run.go | 52 + internal/cli/kb/cmd/note/cmd.go | 35 + internal/cli/kb/cmd/note/doc.go | 22 + internal/cli/kb/cmd/note/run.go | 67 + internal/cli/kb/cmd/reindex/cmd.go | 30 + internal/cli/kb/cmd/reindex/doc.go | 25 + internal/cli/kb/cmd/reindex/run.go | 72 + internal/cli/kb/cmd/sitereview/cmd.go | 30 + internal/cli/kb/cmd/sitereview/doc.go | 17 + internal/cli/kb/cmd/sitereview/run.go | 38 + internal/cli/kb/cmd/topic/cmd.go | 26 + internal/cli/kb/cmd/topic/cmd/newcmd/cmd.go | 35 + internal/cli/kb/cmd/topic/cmd/newcmd/doc.go | 24 + internal/cli/kb/cmd/topic/cmd/newcmd/run.go | 26 + internal/cli/kb/cmd/topic/doc.go | 32 + internal/cli/kb/core/path/doc.go | 23 + internal/cli/kb/core/path/path.go | 173 ++ internal/cli/kb/core/path/path_test.go | 119 ++ internal/cli/kb/core/path/testmain_test.go | 19 + internal/cli/kb/core/reindex/block.go | 41 + internal/cli/kb/core/reindex/doc.go | 25 + internal/cli/kb/core/reindex/topic.go | 51 + internal/cli/kb/core/topic/doc.go | 26 + internal/cli/kb/core/topic/scaffold.go | 76 + internal/cli/kb/core/topic/template.go | 32 + internal/cli/kb/doc.go | 23 + internal/cli/kb/kb.go | 39 + internal/cli/load/cmd/root/cmd.go | 2 +- internal/cli/loop/cmd/root/cmd.go | 2 +- internal/cli/memory/cmd/importer/cmd.go | 2 +- internal/cli/memory/cmd/publish/cmd.go | 2 +- internal/cli/memory/cmd/sync/cmd.go | 2 +- internal/cli/notify/notify.go | 2 +- internal/cli/pad/cmd/add/cmd.go | 2 +- internal/cli/pad/cmd/edit/cmd.go | 2 +- internal/cli/pad/cmd/export/cmd.go | 2 +- internal/cli/pad/cmd/merge/cmd.go | 2 +- internal/cli/pad/cmd/root/cmd.go | 2 +- internal/cli/pad/cmd/show/cmd.go | 2 +- internal/cli/pad/cmd/tag/cmd.go | 2 +- internal/cli/pad/pad.go | 2 +- internal/cli/prune/cmd.go | 2 +- internal/cli/remind/cmd/add/cmd.go | 2 +- internal/cli/remind/cmd/dismiss/cmd.go | 2 +- internal/cli/remind/remind.go | 2 +- internal/cli/setup/cmd/root/cmd.go | 2 +- internal/cli/setup/core/copilot_cli/agent.go | 52 - .../cli/setup/core/copilot_cli/copilot_cli.go | 12 +- .../setup/core/copilot_cli/github_asset.go | 65 + .../cli/setup/core/copilot_cli/instruction.go | 53 - internal/cli/site/cmd/feed/cmd.go | 2 +- internal/cli/status/cmd/root/cmd.go | 2 +- internal/cli/steering/cmd/synccmd/cmd.go | 2 +- internal/cli/sync/cmd/root/cmd.go | 2 +- internal/cli/system/cmd/bootstrap/cmd.go | 2 +- internal/cli/system/cmd/session_event/cmd.go | 2 +- .../cli/system/core/session/session_token.go | 4 +- internal/cli/task/cmd/archive/cmd.go | 2 +- internal/cli/trace/cmd/collect/cmd.go | 2 +- internal/cli/trace/cmd/file/cmd.go | 2 +- internal/cli/trace/cmd/show/cmd.go | 2 +- internal/cli/trace/cmd/tag/cmd.go | 2 +- internal/cli/trigger/cmd/test/cmd.go | 2 +- internal/cli/usage/cmd.go | 2 +- internal/cli/watch/cmd/root/cmd.go | 2 +- internal/config/closeout/closeout.go | 41 + internal/config/closeout/doc.go | 23 + internal/config/embed/cmd/handover.go | 28 + internal/config/embed/cmd/kb.go | 59 + internal/config/embed/flag/doc.go | 2 +- internal/config/embed/flag/handover.go | 29 + internal/config/embed/text/err_closeout.go | 44 + internal/config/embed/text/err_gitmeta.go | 26 + internal/config/embed/text/err_handover.go | 44 + internal/config/embed/text/err_initkb.go | 37 + .../config/embed/text/err_kb_artifacts.go | 118 ++ internal/config/embed/text/err_kb_cli.go | 47 + internal/config/embed/text/err_kb_evidence.go | 32 + .../embed/text/err_kb_sourcecoverage.go | 26 + internal/config/embed/text/initialize.go | 3 + internal/config/embed/text/write_handover.go | 27 + internal/config/embed/text/write_kb_cli.go | 53 + internal/config/flag/flag.go | 18 + internal/config/git/git.go | 4 + internal/config/git_meta/doc.go | 21 + internal/config/git_meta/git_meta.go | 55 + internal/config/handover/doc.go | 19 + internal/config/handover/handover.go | 87 + internal/config/hook/hook.go | 2 +- internal/config/initialize/doc.go | 2 +- .../initialize/{messages.go => initialize.go} | 4 +- internal/config/initialize/kb/doc.go | 18 + internal/config/initialize/kb/kb.go | 14 + internal/config/kb/cli/cli.go | 74 + internal/config/kb/cli/doc.go | 19 + .../config/kb/contradiction/contradiction.go | 25 + internal/config/kb/contradiction/doc.go | 19 + internal/config/kb/decision/decision.go | 28 + internal/config/kb/decision/doc.go | 18 + internal/config/kb/doc.go | 57 + internal/config/kb/evidence/doc.go | 24 + internal/config/kb/evidence/evidence.go | 52 + internal/config/kb/glossary/doc.go | 19 + internal/config/kb/glossary/glossary.go | 21 + internal/config/kb/kb.go | 140 ++ internal/config/kb/question/doc.go | 18 + internal/config/kb/question/question.go | 28 + internal/config/kb/relationship/doc.go | 18 + .../config/kb/relationship/relationship.go | 18 + internal/config/kb/sourcecoverage/doc.go | 23 + .../kb/sourcecoverage/sourcecoverage.go | 75 + internal/config/kb/sourcemap/doc.go | 18 + internal/config/kb/sourcemap/sourcemap.go | 19 + internal/config/kb/timeline/doc.go | 18 + internal/config/kb/timeline/timeline.go | 18 + internal/config/marker/marker.go | 9 + internal/config/rc/{messages.go => rc.go} | 0 internal/config/regex/kb.go | 35 + internal/config/regex/managedblock.go | 16 + internal/config/regex/slug.go | 21 + internal/config/time/time.go | 3 + internal/config/token/delim.go | 6 + internal/entity/closeout.go | 31 + internal/entity/kb_row.go | 26 + internal/err/closeout/closeout.go | 169 ++ internal/err/closeout/doc.go | 45 + internal/err/git_meta/doc.go | 29 + internal/err/git_meta/gitmeta.go | 89 + internal/err/handover/doc.go | 23 + internal/err/handover/handover.go | 177 ++ internal/err/initialize/kb/doc.go | 17 + internal/err/initialize/kb/kb.go | 114 ++ internal/err/kb/cli/cli.go | 178 ++ internal/err/kb/cli/doc.go | 19 + .../err/kb/contradiction/contradiction.go | 78 + internal/err/kb/contradiction/doc.go | 17 + internal/err/kb/decision/decision.go | 78 + internal/err/kb/decision/doc.go | 17 + internal/err/kb/evidence/doc.go | 38 + internal/err/kb/evidence/evidence.go | 121 ++ internal/err/kb/glossary/doc.go | 17 + internal/err/kb/glossary/glossary.go | 62 + internal/err/kb/question/doc.go | 17 + internal/err/kb/question/question.go | 78 + internal/err/kb/relationship/doc.go | 17 + internal/err/kb/relationship/relationship.go | 62 + internal/err/kb/sourcecoverage/doc.go | 30 + .../err/kb/sourcecoverage/sourcecoverage.go | 102 ++ internal/err/kb/sourcemap/doc.go | 17 + internal/err/kb/sourcemap/sourcemap.go | 62 + internal/err/kb/timeline/doc.go | 17 + internal/err/kb/timeline/timeline.go | 62 + internal/err/setup/setup.go | 5 +- internal/{flagbind => flag_bind}/batch.go | 0 internal/{flagbind => flag_bind}/doc.go | 0 internal/{flagbind => flag_bind}/flag.go | 0 internal/git_meta/branch.go | 41 + internal/git_meta/doc.go | 39 + internal/git_meta/head.go | 72 + internal/git_meta/require.go | 42 + internal/git_meta/require_test.go | 75 + internal/git_meta/resolvehead_test.go | 75 + internal/git_meta/sha.go | 28 + internal/git_meta/testmain_test.go | 19 + internal/git_meta/types.go | 16 + internal/io/security.go | 21 + internal/slug/doc.go | 42 + internal/{cli/journal/core => }/slug/slug.go | 20 + .../{cli/journal/core => }/slug/slug_test.go | 40 + .../journal/core => }/slug/testmain_test.go | 0 internal/write/closeout/archive.go | 50 + internal/write/closeout/closeout_test.go | 235 +++ internal/write/closeout/doc.go | 41 + internal/write/closeout/filename.go | 37 + internal/write/closeout/frontmatter.go | 92 + internal/write/closeout/markdown.go | 43 + internal/write/closeout/read.go | 123 ++ internal/write/closeout/testmain_test.go | 19 + internal/write/closeout/types.go | 20 + internal/write/closeout/write.go | 85 + internal/write/handover/doc.go | 53 + internal/write/handover/filename.go | 60 + internal/write/handover/handover_test.go | 190 ++ internal/write/handover/latest.go | 36 + internal/write/handover/markdown.go | 102 ++ internal/write/handover/parse.go | 107 ++ internal/write/handover/provenance.go | 42 + internal/write/handover/testmain_test.go | 19 + internal/write/handover/types.go | 71 + internal/write/handover/write.go | 111 ++ internal/write/initialize/info.go | 2 +- internal/write/kb/contradiction/append.go | 40 + internal/write/kb/contradiction/doc.go | 31 + internal/write/kb/contradiction/next_id.go | 42 + internal/write/kb/contradiction/render.go | 59 + .../write/kb/contradiction/testmain_test.go | 19 + internal/write/kb/contradiction/types.go | 26 + .../write/kb/contradiction/writer_test.go | 98 ++ internal/write/kb/decision/append.go | 41 + internal/write/kb/decision/doc.go | 36 + internal/write/kb/decision/next_id.go | 44 + internal/write/kb/decision/render.go | 63 + internal/write/kb/decision/testmain_test.go | 19 + internal/write/kb/decision/types.go | 31 + internal/write/kb/decision/writer_test.go | 73 + internal/write/kb/evidence/append.go | 89 + internal/write/kb/evidence/doc.go | 39 + internal/write/kb/evidence/evidence_test.go | 152 ++ internal/write/kb/evidence/next_id.go | 72 + internal/write/kb/evidence/render.go | 49 + internal/write/kb/evidence/testmain_test.go | 19 + internal/write/kb/evidence/types.go | 42 + internal/write/kb/evidence/validate.go | 30 + internal/write/kb/glossary/append.go | 64 + internal/write/kb/glossary/doc.go | 28 + internal/write/kb/glossary/render.go | 63 + internal/write/kb/glossary/testmain_test.go | 19 + internal/write/kb/glossary/types.go | 28 + internal/write/kb/glossary/writer_test.go | 107 ++ internal/write/kb/question/append.go | 41 + internal/write/kb/question/doc.go | 31 + internal/write/kb/question/next_id.go | 42 + internal/write/kb/question/render.go | 61 + internal/write/kb/question/testmain_test.go | 19 + internal/write/kb/question/types.go | 29 + internal/write/kb/question/writer_test.go | 72 + internal/write/kb/relationship/append.go | 65 + internal/write/kb/relationship/doc.go | 26 + internal/write/kb/relationship/render.go | 54 + .../write/kb/relationship/testmain_test.go | 19 + internal/write/kb/relationship/types.go | 24 + internal/write/kb/relationship/writer_test.go | 62 + internal/write/kb/row/append.go | 64 + internal/write/kb/row/doc.go | 27 + internal/write/kb/sourcecoverage/advance.go | 78 + internal/write/kb/sourcecoverage/doc.go | 43 + internal/write/kb/sourcecoverage/parse.go | 76 + internal/write/kb/sourcecoverage/read.go | 37 + internal/write/kb/sourcecoverage/render.go | 57 + .../kb/sourcecoverage/sourcecoverage_test.go | 152 ++ .../write/kb/sourcecoverage/testmain_test.go | 19 + .../write/kb/sourcecoverage/transition.go | 55 + internal/write/kb/sourcecoverage/types.go | 46 + internal/write/kb/sourcemap/append.go | 64 + internal/write/kb/sourcemap/doc.go | 29 + internal/write/kb/sourcemap/render.go | 58 + internal/write/kb/sourcemap/testmain_test.go | 19 + internal/write/kb/sourcemap/types.go | 33 + internal/write/kb/sourcemap/writer_test.go | 70 + internal/write/kb/timeline/append.go | 64 + internal/write/kb/timeline/doc.go | 27 + internal/write/kb/timeline/render.go | 58 + internal/write/kb/timeline/testmain_test.go | 19 + internal/write/kb/timeline/types.go | 25 + internal/write/kb/timeline/writer_test.go | 68 + .../index.html | 2 +- .../index.html | 2 +- .../index.html | 2 +- .../index.html | 2 +- site/cli/connect/index.html | 2 +- site/cli/connection/index.html | 2 +- site/cli/handover/index.html | 1257 ++++++++++++++ site/cli/index.html | 8 + site/cli/init-status/index.html | 20 + site/cli/kb/index.html | 1293 ++++++++++++++ site/cli/steering/index.html | 2 +- site/home/getting-started/index.html | 6 +- site/home/opencode/index.html | 38 +- site/home/prompting-guide/index.html | 2 +- site/home/steering/index.html | 4 +- site/home/vscode/index.html | 2 +- site/operations/integrations/index.html | 16 +- site/recipes/activating-context/index.html | 9 +- .../recipes/build-a-knowledge-base/index.html | 1544 +++++++++++++++++ site/recipes/hub-team/index.html | 4 +- site/recipes/index.html | 77 + site/recipes/multi-tool-setup/index.html | 2 +- site/recipes/publishing/index.html | 4 +- .../recover-aborted-session/index.html | 1364 +++++++++++++++ site/recipes/session-lifecycle/index.html | 10 + site/recipes/steering/index.html | 2 +- site/recipes/typical-kb-session/index.html | 1414 +++++++++++++++ site/reference/comparison/index.html | 2 +- site/reference/skills/index.html | 298 +++- site/search.json | 2 +- site/security/hub/index.html | 2 +- site/thesis/index.html | 4 +- specs/ceremony-profiles.md | 25 +- specs/copilot-feature-parity-kit.md | 6 +- specs/future-complete/context-hub.md | 2 +- .../copilot-cli-integration.md | 2 +- specs/future-complete/ctx-fmt.md | 4 +- specs/future-complete/journal-compact.md | 4 +- .../jsonl-envelope-enrichment.md | 4 +- specs/future-complete/prompt-deprecation.md | 2 +- .../session-housekeeping-2026-04-11.md | 2 +- specs/hub-security-audit.md | 4 +- specs/internal-assets-readme.md | 2 +- specs/kb-editorial-pipeline.md | 874 +++++++--- specs/opencode-integration.md | 2 +- specs/placeholder-i18n.md | 4 +- specs/released/v0.1.0/cli.md | 2 +- specs/released/v0.1.0/context-file-formats.md | 2 +- specs/released/v0.1.0/context-loader.md | 10 +- specs/released/v0.1.0/context-updater.md | 2 +- specs/released/v0.1.0/core-architecture.md | 4 +- specs/released/v0.2.0/IMPLEMENTATION_PLAN.md | 2 +- specs/released/v0.2.0/PROMPT.md | 4 +- specs/released/v0.6.0/journal-obsidian.md | 8 +- specs/released/v0.6.0/scratchpad.md | 2 +- specs/released/v0.8.0/ctx-map.md | 2 +- specs/released/v0.8.0/export-update-mode.md | 2 +- .../v0.8.0/journal-normalize-pipeline.md | 6 +- .../v0.8.0/journal-site-rendering-fixes.md | 4 +- specs/released/v0.8.0/prompt-templates.md | 4 +- specs/released/v0.8.0/recall-export-safety.md | 2 +- specs/released/v0.8.0/recall-sync.md | 2 +- specs/released/v0.8.0/remind.md | 4 +- specs/require-git.md | 6 +- 470 files changed, 28121 insertions(+), 915 deletions(-) create mode 100644 docs/cli/handover.md create mode 100644 docs/cli/kb.md create mode 100644 docs/recipes/build-a-knowledge-base.md create mode 100644 docs/recipes/recover-aborted-session.md create mode 100644 docs/recipes/typical-kb-session.md create mode 100644 internal/assets/claude/skills/ctx-handover/SKILL.md create mode 100644 internal/assets/claude/skills/ctx-kb-ask/SKILL.md create mode 100644 internal/assets/claude/skills/ctx-kb-ground/SKILL.md create mode 100644 internal/assets/claude/skills/ctx-kb-ingest/SKILL.md create mode 100644 internal/assets/claude/skills/ctx-kb-note/SKILL.md create mode 100644 internal/assets/claude/skills/ctx-kb-site-review/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md create mode 100644 internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-handover/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-kb-ask/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-kb-ingest/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-kb-note/SKILL.md create mode 100644 internal/assets/integrations/opencode/skills/ctx-kb-site-review/SKILL.md create mode 100644 internal/assets/kb/templates/ingest/00-GROUND.md create mode 100644 internal/assets/kb/templates/ingest/30-INGEST.md create mode 100644 internal/assets/kb/templates/ingest/40-ASK.md create mode 100644 internal/assets/kb/templates/ingest/50-SITE_REVIEW.md create mode 100644 internal/assets/kb/templates/ingest/KB-RULES.md create mode 100644 internal/assets/kb/templates/ingest/OPERATOR.md create mode 100644 internal/assets/kb/templates/ingest/PROMPT.md create mode 100644 internal/assets/kb/templates/ingest/schemas/contradictions.md create mode 100644 internal/assets/kb/templates/ingest/schemas/domain-decisions.md create mode 100644 internal/assets/kb/templates/ingest/schemas/evidence-index.md create mode 100644 internal/assets/kb/templates/ingest/schemas/glossary.md create mode 100644 internal/assets/kb/templates/ingest/schemas/outstanding-questions.md create mode 100644 internal/assets/kb/templates/ingest/schemas/relationship-map.md create mode 100644 internal/assets/kb/templates/ingest/schemas/session-log.md create mode 100644 internal/assets/kb/templates/ingest/schemas/source-coverage.md create mode 100644 internal/assets/kb/templates/ingest/schemas/source-map.md create mode 100644 internal/assets/kb/templates/ingest/schemas/timeline.md create mode 100644 internal/assets/kb/templates/kb/index.md create mode 100644 internal/assets/kb/templates/kb/topics/_template/index.md create mode 100644 internal/cli/handover/cmd.go create mode 100644 internal/cli/handover/cmd/write/cmd.go create mode 100644 internal/cli/handover/cmd/write/doc.go create mode 100644 internal/cli/handover/cmd/write/run.go create mode 100644 internal/cli/handover/core/path/doc.go create mode 100644 internal/cli/handover/core/path/path.go create mode 100644 internal/cli/handover/core/path/path_test.go create mode 100644 internal/cli/handover/core/path/testmain_test.go create mode 100644 internal/cli/handover/doc.go create mode 100644 internal/cli/initialize/core/kb/copy.go create mode 100644 internal/cli/initialize/core/kb/doc.go create mode 100644 internal/cli/initialize/core/kb/gitkeep.go create mode 100644 internal/cli/initialize/core/kb/scaffold.go delete mode 100644 internal/cli/journal/core/slug/doc.go create mode 100644 internal/cli/kb/cmd/ask/cmd.go create mode 100644 internal/cli/kb/cmd/ask/doc.go create mode 100644 internal/cli/kb/cmd/ask/run.go create mode 100644 internal/cli/kb/cmd/ground/cmd.go create mode 100644 internal/cli/kb/cmd/ground/doc.go create mode 100644 internal/cli/kb/cmd/ground/run.go create mode 100644 internal/cli/kb/cmd/ingest/cmd.go create mode 100644 internal/cli/kb/cmd/ingest/doc.go create mode 100644 internal/cli/kb/cmd/ingest/run.go create mode 100644 internal/cli/kb/cmd/note/cmd.go create mode 100644 internal/cli/kb/cmd/note/doc.go create mode 100644 internal/cli/kb/cmd/note/run.go create mode 100644 internal/cli/kb/cmd/reindex/cmd.go create mode 100644 internal/cli/kb/cmd/reindex/doc.go create mode 100644 internal/cli/kb/cmd/reindex/run.go create mode 100644 internal/cli/kb/cmd/sitereview/cmd.go create mode 100644 internal/cli/kb/cmd/sitereview/doc.go create mode 100644 internal/cli/kb/cmd/sitereview/run.go create mode 100644 internal/cli/kb/cmd/topic/cmd.go create mode 100644 internal/cli/kb/cmd/topic/cmd/newcmd/cmd.go create mode 100644 internal/cli/kb/cmd/topic/cmd/newcmd/doc.go create mode 100644 internal/cli/kb/cmd/topic/cmd/newcmd/run.go create mode 100644 internal/cli/kb/cmd/topic/doc.go create mode 100644 internal/cli/kb/core/path/doc.go create mode 100644 internal/cli/kb/core/path/path.go create mode 100644 internal/cli/kb/core/path/path_test.go create mode 100644 internal/cli/kb/core/path/testmain_test.go create mode 100644 internal/cli/kb/core/reindex/block.go create mode 100644 internal/cli/kb/core/reindex/doc.go create mode 100644 internal/cli/kb/core/reindex/topic.go create mode 100644 internal/cli/kb/core/topic/doc.go create mode 100644 internal/cli/kb/core/topic/scaffold.go create mode 100644 internal/cli/kb/core/topic/template.go create mode 100644 internal/cli/kb/doc.go create mode 100644 internal/cli/kb/kb.go delete mode 100644 internal/cli/setup/core/copilot_cli/agent.go create mode 100644 internal/cli/setup/core/copilot_cli/github_asset.go delete mode 100644 internal/cli/setup/core/copilot_cli/instruction.go create mode 100644 internal/config/closeout/closeout.go create mode 100644 internal/config/closeout/doc.go create mode 100644 internal/config/embed/cmd/handover.go create mode 100644 internal/config/embed/cmd/kb.go create mode 100644 internal/config/embed/flag/handover.go create mode 100644 internal/config/embed/text/err_closeout.go create mode 100644 internal/config/embed/text/err_gitmeta.go create mode 100644 internal/config/embed/text/err_handover.go create mode 100644 internal/config/embed/text/err_initkb.go create mode 100644 internal/config/embed/text/err_kb_artifacts.go create mode 100644 internal/config/embed/text/err_kb_cli.go create mode 100644 internal/config/embed/text/err_kb_evidence.go create mode 100644 internal/config/embed/text/err_kb_sourcecoverage.go create mode 100644 internal/config/embed/text/write_handover.go create mode 100644 internal/config/embed/text/write_kb_cli.go create mode 100644 internal/config/git_meta/doc.go create mode 100644 internal/config/git_meta/git_meta.go create mode 100644 internal/config/handover/doc.go create mode 100644 internal/config/handover/handover.go rename internal/config/initialize/{messages.go => initialize.go} (94%) create mode 100644 internal/config/initialize/kb/doc.go create mode 100644 internal/config/initialize/kb/kb.go create mode 100644 internal/config/kb/cli/cli.go create mode 100644 internal/config/kb/cli/doc.go create mode 100644 internal/config/kb/contradiction/contradiction.go create mode 100644 internal/config/kb/contradiction/doc.go create mode 100644 internal/config/kb/decision/decision.go create mode 100644 internal/config/kb/decision/doc.go create mode 100644 internal/config/kb/doc.go create mode 100644 internal/config/kb/evidence/doc.go create mode 100644 internal/config/kb/evidence/evidence.go create mode 100644 internal/config/kb/glossary/doc.go create mode 100644 internal/config/kb/glossary/glossary.go create mode 100644 internal/config/kb/kb.go create mode 100644 internal/config/kb/question/doc.go create mode 100644 internal/config/kb/question/question.go create mode 100644 internal/config/kb/relationship/doc.go create mode 100644 internal/config/kb/relationship/relationship.go create mode 100644 internal/config/kb/sourcecoverage/doc.go create mode 100644 internal/config/kb/sourcecoverage/sourcecoverage.go create mode 100644 internal/config/kb/sourcemap/doc.go create mode 100644 internal/config/kb/sourcemap/sourcemap.go create mode 100644 internal/config/kb/timeline/doc.go create mode 100644 internal/config/kb/timeline/timeline.go rename internal/config/rc/{messages.go => rc.go} (100%) create mode 100644 internal/config/regex/kb.go create mode 100644 internal/config/regex/managedblock.go create mode 100644 internal/config/regex/slug.go create mode 100644 internal/entity/closeout.go create mode 100644 internal/entity/kb_row.go create mode 100644 internal/err/closeout/closeout.go create mode 100644 internal/err/closeout/doc.go create mode 100644 internal/err/git_meta/doc.go create mode 100644 internal/err/git_meta/gitmeta.go create mode 100644 internal/err/handover/doc.go create mode 100644 internal/err/handover/handover.go create mode 100644 internal/err/initialize/kb/doc.go create mode 100644 internal/err/initialize/kb/kb.go create mode 100644 internal/err/kb/cli/cli.go create mode 100644 internal/err/kb/cli/doc.go create mode 100644 internal/err/kb/contradiction/contradiction.go create mode 100644 internal/err/kb/contradiction/doc.go create mode 100644 internal/err/kb/decision/decision.go create mode 100644 internal/err/kb/decision/doc.go create mode 100644 internal/err/kb/evidence/doc.go create mode 100644 internal/err/kb/evidence/evidence.go create mode 100644 internal/err/kb/glossary/doc.go create mode 100644 internal/err/kb/glossary/glossary.go create mode 100644 internal/err/kb/question/doc.go create mode 100644 internal/err/kb/question/question.go create mode 100644 internal/err/kb/relationship/doc.go create mode 100644 internal/err/kb/relationship/relationship.go create mode 100644 internal/err/kb/sourcecoverage/doc.go create mode 100644 internal/err/kb/sourcecoverage/sourcecoverage.go create mode 100644 internal/err/kb/sourcemap/doc.go create mode 100644 internal/err/kb/sourcemap/sourcemap.go create mode 100644 internal/err/kb/timeline/doc.go create mode 100644 internal/err/kb/timeline/timeline.go rename internal/{flagbind => flag_bind}/batch.go (100%) rename internal/{flagbind => flag_bind}/doc.go (100%) rename internal/{flagbind => flag_bind}/flag.go (100%) create mode 100644 internal/git_meta/branch.go create mode 100644 internal/git_meta/doc.go create mode 100644 internal/git_meta/head.go create mode 100644 internal/git_meta/require.go create mode 100644 internal/git_meta/require_test.go create mode 100644 internal/git_meta/resolvehead_test.go create mode 100644 internal/git_meta/sha.go create mode 100644 internal/git_meta/testmain_test.go create mode 100644 internal/git_meta/types.go create mode 100644 internal/slug/doc.go rename internal/{cli/journal/core => }/slug/slug.go (85%) rename internal/{cli/journal/core => }/slug/slug_test.go (86%) rename internal/{cli/journal/core => }/slug/testmain_test.go (100%) create mode 100644 internal/write/closeout/archive.go create mode 100644 internal/write/closeout/closeout_test.go create mode 100644 internal/write/closeout/doc.go create mode 100644 internal/write/closeout/filename.go create mode 100644 internal/write/closeout/frontmatter.go create mode 100644 internal/write/closeout/markdown.go create mode 100644 internal/write/closeout/read.go create mode 100644 internal/write/closeout/testmain_test.go create mode 100644 internal/write/closeout/types.go create mode 100644 internal/write/closeout/write.go create mode 100644 internal/write/handover/doc.go create mode 100644 internal/write/handover/filename.go create mode 100644 internal/write/handover/handover_test.go create mode 100644 internal/write/handover/latest.go create mode 100644 internal/write/handover/markdown.go create mode 100644 internal/write/handover/parse.go create mode 100644 internal/write/handover/provenance.go create mode 100644 internal/write/handover/testmain_test.go create mode 100644 internal/write/handover/types.go create mode 100644 internal/write/handover/write.go create mode 100644 internal/write/kb/contradiction/append.go create mode 100644 internal/write/kb/contradiction/doc.go create mode 100644 internal/write/kb/contradiction/next_id.go create mode 100644 internal/write/kb/contradiction/render.go create mode 100644 internal/write/kb/contradiction/testmain_test.go create mode 100644 internal/write/kb/contradiction/types.go create mode 100644 internal/write/kb/contradiction/writer_test.go create mode 100644 internal/write/kb/decision/append.go create mode 100644 internal/write/kb/decision/doc.go create mode 100644 internal/write/kb/decision/next_id.go create mode 100644 internal/write/kb/decision/render.go create mode 100644 internal/write/kb/decision/testmain_test.go create mode 100644 internal/write/kb/decision/types.go create mode 100644 internal/write/kb/decision/writer_test.go create mode 100644 internal/write/kb/evidence/append.go create mode 100644 internal/write/kb/evidence/doc.go create mode 100644 internal/write/kb/evidence/evidence_test.go create mode 100644 internal/write/kb/evidence/next_id.go create mode 100644 internal/write/kb/evidence/render.go create mode 100644 internal/write/kb/evidence/testmain_test.go create mode 100644 internal/write/kb/evidence/types.go create mode 100644 internal/write/kb/evidence/validate.go create mode 100644 internal/write/kb/glossary/append.go create mode 100644 internal/write/kb/glossary/doc.go create mode 100644 internal/write/kb/glossary/render.go create mode 100644 internal/write/kb/glossary/testmain_test.go create mode 100644 internal/write/kb/glossary/types.go create mode 100644 internal/write/kb/glossary/writer_test.go create mode 100644 internal/write/kb/question/append.go create mode 100644 internal/write/kb/question/doc.go create mode 100644 internal/write/kb/question/next_id.go create mode 100644 internal/write/kb/question/render.go create mode 100644 internal/write/kb/question/testmain_test.go create mode 100644 internal/write/kb/question/types.go create mode 100644 internal/write/kb/question/writer_test.go create mode 100644 internal/write/kb/relationship/append.go create mode 100644 internal/write/kb/relationship/doc.go create mode 100644 internal/write/kb/relationship/render.go create mode 100644 internal/write/kb/relationship/testmain_test.go create mode 100644 internal/write/kb/relationship/types.go create mode 100644 internal/write/kb/relationship/writer_test.go create mode 100644 internal/write/kb/row/append.go create mode 100644 internal/write/kb/row/doc.go create mode 100644 internal/write/kb/sourcecoverage/advance.go create mode 100644 internal/write/kb/sourcecoverage/doc.go create mode 100644 internal/write/kb/sourcecoverage/parse.go create mode 100644 internal/write/kb/sourcecoverage/read.go create mode 100644 internal/write/kb/sourcecoverage/render.go create mode 100644 internal/write/kb/sourcecoverage/sourcecoverage_test.go create mode 100644 internal/write/kb/sourcecoverage/testmain_test.go create mode 100644 internal/write/kb/sourcecoverage/transition.go create mode 100644 internal/write/kb/sourcecoverage/types.go create mode 100644 internal/write/kb/sourcemap/append.go create mode 100644 internal/write/kb/sourcemap/doc.go create mode 100644 internal/write/kb/sourcemap/render.go create mode 100644 internal/write/kb/sourcemap/testmain_test.go create mode 100644 internal/write/kb/sourcemap/types.go create mode 100644 internal/write/kb/sourcemap/writer_test.go create mode 100644 internal/write/kb/timeline/append.go create mode 100644 internal/write/kb/timeline/doc.go create mode 100644 internal/write/kb/timeline/render.go create mode 100644 internal/write/kb/timeline/testmain_test.go create mode 100644 internal/write/kb/timeline/types.go create mode 100644 internal/write/kb/timeline/writer_test.go create mode 100644 site/cli/handover/index.html create mode 100644 site/cli/kb/index.html create mode 100644 site/recipes/build-a-knowledge-base/index.html create mode 100644 site/recipes/recover-aborted-session/index.html create mode 100644 site/recipes/typical-kb-session/index.html diff --git a/.context/CONSTITUTION.md b/.context/CONSTITUTION.md index eaaf35fa2..b6c1fb9ad 100644 --- a/.context/CONSTITUTION.md +++ b/.context/CONSTITUTION.md @@ -98,6 +98,15 @@ Leave the system in a better state than you found it. no exceptions, no "non-trivial" qualifier. Even one-liner fixes need a spec for traceability. Use `/ctx-commit` instead of raw `git commit`. +- [ ] **Git is required.** Every `ctx` project must live in a git + working tree. `ctx init` and every non-administrative + subcommand refuse to operate when `/.git` is + absent. Rationale: ctx's persistent-memory promise is + dishonest without an undo layer; agent-driven file + operations need `git reflog` as the safety net. The only + opt-outs are help-shaped / diagnostic commands + (`--help`, `--version`, `ctx system bootstrap`). Per + `specs/require-git.md`. ## TASKS.md Structure Invariants diff --git a/.context/CONVENTIONS.md b/.context/CONVENTIONS.md index f092fa84a..6ae6f92ca 100644 --- a/.context/CONVENTIONS.md +++ b/.context/CONVENTIONS.md @@ -270,4 +270,5 @@ DO NOT UPDATE FOR: capitalization would otherwise apply: write `CONSTITUTION.md`, not CONSTITUTION.Md. The title-case engine refuses to capitalize lowercase tokens following a literal . dot, but explicit backticks remain the clearest signal. -- New editor integrations include an MCP-merge test covering: create / empty file / preserve existing keys / skip when registered / reject malformed JSON +- New editor integrations include an MCP-merge test covering: create / empty + file / preserve existing keys / skip when registered / reject malformed JSON diff --git a/.context/DECISIONS.md b/.context/DECISIONS.md index 2f0f6bdc5..dc41ceb5f 100644 --- a/.context/DECISIONS.md +++ b/.context/DECISIONS.md @@ -3,6 +3,7 @@ | Date | Decision | |----|--------| +| 2026-05-16 | Phase KB lifts the current upstream editorial-pipeline shape, superseding the 4-phase predecessor in the brief | | 2026-05-11 | Embedded and separately-published harnesses use distinct CI and release pipelines | | 2026-05-11 | Embedded foreign-language assets under internal/assets/ are intentional, not a smell | | 2026-05-10 | Placeholder overrides use EXTEND not REPLACE semantics | @@ -140,17 +141,110 @@ For significant decisions: --> -## [2026-05-11-211246] Embedded and separately-published harnesses use distinct CI and release pipelines - -**Status**: Accepted +## [2026-05-16-000000] Phase KB lifts the current upstream editorial-pipeline shape, superseding the 4-phase predecessor in the brief + +**Status**: Accepted + +**Context**: The Phase KB spec at `specs/kb-editorial-pipeline.md` was +originally lifted from the upstream editorial pipeline in May 2026, at which +point that pipeline encoded a 4-phase model (triage / extract / reconcile / +surface). The upstream design has since evolved past that shape into a pass-mode +contract (`topic-page` / `triage` / `evidence-only`) with up-front declaration, +a 4-invariant completion circuit breaker, a source-coverage state-machine +ledger, a topic-adjacency pre-flight, a cold-reader orientation rubric, +folder-shaped topics from day one, and an explicit CLI-as-scaffold-authority +rule. The comparison note at `ideas/upstream-pipeline-comparison.md` enumerated +the deltas. The fork was whether to implement the spec as written (older shape; +faster to type; weaker as a feature) or to revise the spec to absorb the +upstream design's current shape before any code is written. + +**Decision**: Phase KB lifts the current upstream editorial-pipeline shape. +`specs/kb-editorial-pipeline.md` was rewritten in place on 2026-05-16 to encode +pass-mode contract, completion circuit breaker, source-coverage state-machine +ledger, topic-adjacency pre-flight, cold-reader rubric, folder-shaped topics +from day one, CLI-as-scaffold-authority, and explicit failure-analysis section. +The original 4-phase model is superseded; the brief's two organizing principles +(LLM as migration tool; KB-of-KBs is a KB) carry forward. + +**Rationale**: The upstream pipeline's evolution after the brief was drafted +reflects real pain: false-finish drift, ledger-vs-reality divergence, adjacency +invisibility, mode-muddying under operator pressure. Lifting the older shape +would mean re-fighting those wounds. The user's lift-the-whole-shape posture +(feedback memory `feedback_no_defer_unfamiliar_scope`) extends here: lift the +patterns the upstream author chose, not just the structure visible at the moment +of first contact. Concretely: folder-shaped topics from day one avoid a v1.1 +migration (the upstream reference's live kb has 12 sub-topic folders under +`topics/claude-code/` alone; that depth arrives fast); the pass-mode contract +makes promise=result visible per pass instead of buried in a closeout the +operator might not read; the state-machine ledger replaces the spec's flat +`source-map.md` so "what is incomplete?" has a canonical answer; the circuit +breaker turns CONSTITUTION's "Completion Over Motion" from prose into a +mechanical gate. + +**Consequence**: Phase KB tasks in `.context/TASKS.md` (line 1832 onward) now +reference the revised spec; concrete additions cover the new shape (path +constants under `internal/cli/kb/core/`, new helpers for passmode / +circuitbreaker / ledger / adjacency / coldreader / lifestage, new doctor +advisories for ledger drift + pass-mode mismatch + illegal state transitions, +generalized closeout naming `--closeout.md`). The `internal/store/` +shape from the original spec is replaced with `internal/write/` per existing ctx +convention (writers live in `internal/write//`). Folder-shaped topics from +day one means `.context/kb/topics//index.md` is the canonical surface, not +flat `.md`; `ctx kb topic new` is the sole scaffold writer. +Failure-analysis section is now part of the spec, with three concrete loss modes +(pass-mode bypass, ledger drift, adjacency trivialization) each carrying v1 +mitigations. Spec: `specs/kb-editorial-pipeline.md`. Source: +`ideas/upstream-pipeline-comparison.md`. -**Context**: ctx ships two kinds of artifact. Embedded harnesses (OpenCode plugin, Copilot CLI scripts, Claude/OpenCode/Copilot CLI skills, git trace hooks, etc.) live under internal/assets/, are //go:embed'd into the ctx Go binary, and reach users via 'ctx setup' writing their bytes to disk. Separately-published harnesses (currently just the VS Code extension under editors/vscode/) build to their own artifact (.vsix), publish to a third-party channel (VS Code Marketplace under publisher 'activememory'), version independently, and reach users via that channel's update mechanism. Until this session, the boundary was implicit: doc.go and embed_test.go talked only about the embedded tree; release.yml only built the Go binary; nothing in CI exercised the vscode extension at all. A reviewer's first read of internal/assets/integrations/ was 'this is a dumping ground' precisely because the contract was not documented. +--- -**Decision**: Embedded and separately-published harnesses use distinct CI and release pipelines +## [2026-05-11-211246] Embedded and separately-published harnesses use distinct CI and release pipelines -**Rationale**: Conflating the two would have one of two consequences: (a) shoehorning vscode into //go:embed, which means baking a .vsix or its sources into the Go binary and writing them out at setup time -- bloating the binary with bytes most users never use, and forcing the Go release cadence onto something with its own marketplace cadence; or (b) leaving the vscode harness ungated 'because it's different' -- which is what we had, and which is how typos ship. The right move is to acknowledge the two patterns are first-class peers, give each a documented home (internal/assets/ vs. editors//), and gate each in CI with the toolchain appropriate to its release pipeline (Go test/build/vet for embedded; npm ci + esbuild + tsc for vscode). Future harnesses pick a pattern explicitly at placement time rather than drifting. +**Status**: Accepted -**Consequence**: internal/assets/README.md now carries the 'Embedded vs. Separately-Published: At a Glance' table as the canonical reference. .github/workflows/ci.yml gained a vscode-extension job that gates the marketplace publish path. editors/vscode/README.md gained a 'Release' section with checklist and explicit notes on which CI gates protect the manual vsce publish. The two patterns are now first-class: a new harness must declare which it follows before placing files. Open implications: (1) anyone proposing to lift integrations/ out of internal/assets/ should re-read this decision -- the no-../ //go:embed constraint plus the pattern-asymmetry are the load-bearing reasons against; (2) the embedded-only quality gaps tracked in TASKS.md (shellcheck, PSScriptAnalyzer, skill frontmatter validity) and the separately-published quality gaps (vscode test rot, lint, vsce package dry-run) live in distinct gap-task clusters and should not be merged. Spec: specs/internal-assets-readme.md. +**Context**: ctx ships two kinds of artifact. Embedded harnesses (OpenCode +plugin, Copilot CLI scripts, Claude/OpenCode/Copilot CLI skills, git trace +hooks, etc.) live under internal/assets/, are //go:embed'd into the ctx Go +binary, and reach users via 'ctx setup' writing their bytes to disk. +Separately-published harnesses (currently just the VS Code extension under +editors/vscode/) build to their own artifact (.vsix), publish to a third-party +channel (VS Code Marketplace under publisher 'activememory'), version +independently, and reach users via that channel's update mechanism. Until this +session, the boundary was implicit: doc.go and embed_test.go talked only about +the embedded tree; release.yml only built the Go binary; nothing in CI exercised +the vscode extension at all. A reviewer's first read of +internal/assets/integrations/ was 'this is a dumping ground' precisely because +the contract was not documented. + +**Decision**: Embedded and separately-published harnesses use distinct CI and +release pipelines + +**Rationale**: Conflating the two would have one of two consequences: (a) +shoehorning vscode into //go:embed, which means baking a .vsix or its sources +into the Go binary and writing them out at setup time -- bloating the binary +with bytes most users never use, and forcing the Go release cadence onto +something with its own marketplace cadence; or (b) leaving the vscode harness +ungated 'because it's different' -- which is what we had, and which is how typos +ship. The right move is to acknowledge the two patterns are first-class peers, +give each a documented home (internal/assets/ vs. editors//), and gate +each in CI with the toolchain appropriate to its release pipeline (Go +test/build/vet for embedded; npm ci + esbuild + tsc for vscode). Future +harnesses pick a pattern explicitly at placement time rather than drifting. + +**Consequence**: internal/assets/README.md now carries the 'Embedded vs. +Separately-Published: At a Glance' table as the canonical reference. +.github/workflows/ci.yml gained a vscode-extension job that gates the +marketplace publish path. editors/vscode/README.md gained a 'Release' section +with checklist and explicit notes on which CI gates protect the manual vsce +publish. The two patterns are now first-class: a new harness must declare which +it follows before placing files. Open implications: (1) anyone proposing to lift +integrations/ out of internal/assets/ should re-read this decision -- the no-../ +//go:embed constraint plus the pattern-asymmetry are the load-bearing reasons +against; (2) the embedded-only quality gaps tracked in TASKS.md (shellcheck, +PSScriptAnalyzer, skill frontmatter validity) and the separately-published +quality gaps (vscode test rot, lint, vsce package dry-run) live in distinct +gap-task clusters and should not be merged. Spec: +specs/internal-assets-readme.md. --- @@ -158,17 +252,47 @@ For significant decisions: **Status**: Accepted -**Context**: A diagnostic conversation surfaced that `internal/assets/integrations/` contains TypeScript (`opencode/plugin/index.ts`), Bash and PowerShell scripts (`copilot-cli/scripts/`), JSON, YAML, and Markdown — none of it Go source. The first-glance read was "internal/ has become a dumping ground for non-Go tooling; lift integrations/ out." Audit of `embed.go` proved otherwise: every file under `integrations/` is captured by an explicit `//go:embed` directive and shipped inside the ctx binary as raw bytes, then written to the user's filesystem at `ctx setup` time. The smell was real (no contract document existed to explain this) but the architectural diagnosis was wrong. +**Context**: A diagnostic conversation surfaced that +`internal/assets/integrations/` contains TypeScript +(`opencode/plugin/index.ts`), Bash and PowerShell scripts +(`copilot-cli/scripts/`), JSON, YAML, and Markdown — none of it Go source. The +first-glance read was "internal/ has become a dumping ground for non-Go tooling; +lift integrations/ out." Audit of `embed.go` proved otherwise: every file under +`integrations/` is captured by an explicit `//go:embed` directive and shipped +inside the ctx binary as raw bytes, then written to the user's filesystem at +`ctx setup` time. The smell was real (no contract document existed to explain +this) but the architectural diagnosis was wrong. -**Decision**: Embedded foreign-language assets stay under `internal/assets/`. The `internal/` directory is honoring Go's import-privacy convention; the contract is "everything in this tree is `//go:embed`'d into the binary as bytes." A `README.md` at `internal/assets/README.md` documents the contract; `internal/assets/doc.go` continues to serve the Go-doc audience. +**Decision**: Embedded foreign-language assets stay under `internal/assets/`. +The `internal/` directory is honoring Go's import-privacy convention; the +contract is "everything in this tree is `//go:embed`'d into the binary as +bytes." A `README.md` at `internal/assets/README.md` documents the contract; +`internal/assets/doc.go` continues to serve the Go-doc audience. **Rationale**: Three reasons against lifting: -1. **Hard Go constraint**: `//go:embed` directives cannot reference parents (no `../`). Moving assets out of the embed.go directory tree forces moving (or duplicating) the embed package itself, with import-path blast radius across every consumer. The relocation cost is disproportionate to the readability win. -2. **Idiomatic Go**: `internal/` is about import privacy, not source language. Projects like Kubernetes and Cobra ship embedded foreign-language payloads from `internal/` without considering it a smell. -3. **The actual fix is cheaper**: the smell was a missing contract document, not a misplaced directory. A README that names the rule ("everything here is `//go:embed`'d; foreign-language files are intentional payload") resolves the legibility problem at zero structural cost. Dev tooling *about* the embedded payload (e.g. `tsconfig.json` for the TS plugin) is what does not belong inside the embed tree — that goes in a sibling tooling directory. - -**Consequence**: Future contributors who feel the same "internal/ is a dumping ground" instinct will find a README documenting why the layout is correct. The README also enumerates current quality gates (presence, format parse, schema integrity) and the known gaps (TypeScript type-check, shellcheck, PSScriptAnalyzer, skill frontmatter validation) — gaps now spawned as discrete Phase 0 tasks. The line-30 `tsc --noEmit` task is redirected: its tooling files must live in a sibling directory outside `internal/assets/` to honor the embed contract. +1. **Hard Go constraint**: `//go:embed` directives cannot reference parents (no +`../`). Moving assets out of the embed.go directory tree forces moving (or +duplicating) the embed package itself, with import-path blast radius across +every consumer. The relocation cost is disproportionate to the readability win. +2. **Idiomatic Go**: `internal/` is about import privacy, not source language. +Projects like Kubernetes and Cobra ship embedded foreign-language payloads from +`internal/` without considering it a smell. +3. **The actual fix is cheaper**: the smell was a missing contract document, not +a misplaced directory. A README that names the rule ("everything here is +`//go:embed`'d; foreign-language files are intentional payload") resolves the +legibility problem at zero structural cost. Dev tooling *about* the embedded +payload (e.g. `tsconfig.json` for the TS plugin) is what does not belong inside +the embed tree — that goes in a sibling tooling directory. + +**Consequence**: Future contributors who feel the same "internal/ is a dumping +ground" instinct will find a README documenting why the layout is correct. The +README also enumerates current quality gates (presence, format parse, schema +integrity) and the known gaps (TypeScript type-check, shellcheck, +PSScriptAnalyzer, skill frontmatter validation) — gaps now spawned as discrete +Phase 0 tasks. The line-30 `tsc --noEmit` task is redirected: its tooling files +must live in a sibling directory outside `internal/assets/` to honor the embed +contract. **Related**: Spec: specs/internal-assets-readme.md @@ -178,13 +302,27 @@ For significant decisions: **Status**: Accepted -**Context**: When localizing the placeholder set used by validate.RejectPlaceholder, .ctxrc gains a placeholders: list. The existing precedent (rc.SessionPrefixes) uses REPLACE semantics: any non-empty user list completely replaces the shipped defaults. Placeholders need a different rule. +**Context**: When localizing the placeholder set used by +validate.RejectPlaceholder, .ctxrc gains a placeholders: list. The existing +precedent (rc.SessionPrefixes) uses REPLACE semantics: any non-empty user list +completely replaces the shipped defaults. Placeholders need a different rule. **Decision**: Placeholder overrides use EXTEND not REPLACE semantics -**Rationale**: The dominant case in this codebase is Tarzan Turkish — bilingual EN+TR projects where users need both English (TBD, n/a, see chat) and Turkish (iptal, yapılacak, görüşülecek) placeholders rejected simultaneously. REPLACE would force users to re-list every English default just to add one Turkish term, which they would skip and silently lose half the validator's coverage. EXTEND appends user list onto the shipped defaults so partial overrides do not regress baseline protection. +**Rationale**: The dominant case in this codebase is Tarzan Turkish — +bilingual EN+TR projects where users need both English (TBD, n/a, see chat) and +Turkish (iptal, yapılacak, görüşülecek) placeholders rejected +simultaneously. REPLACE would force users to re-list every English default just +to add one Turkish term, which they would skip and silently lose half the +validator's coverage. EXTEND appends user list onto the shipped defaults so +partial overrides do not regress baseline protection. -**Consequence**: rc.Placeholders() must combine defaults + user list with case-folded de-duplication, diverging from the SessionPrefixes pattern. A future maintainer reading both accessors side-by-side will notice the inconsistency; the divergence is intentional and Spec: specs/placeholder-i18n.md captures why. If REPLACE is later wanted, add an opt-in placeholders_replace: true toggle rather than flipping the default. +**Consequence**: rc.Placeholders() must combine defaults + user list with +case-folded de-duplication, diverging from the SessionPrefixes pattern. A future +maintainer reading both accessors side-by-side will notice the inconsistency; +the divergence is intentional and Spec: specs/placeholder-i18n.md captures why. +If REPLACE is later wanted, add an opt-in placeholders_replace: true toggle +rather than flipping the default. --- @@ -192,13 +330,28 @@ For significant decisions: **Status**: Accepted -**Context**: things-wtf hand-rolled an editorial pipeline at the repo root with 10-CONSTITUTION.md, colliding with .context/CONSTITUTION.md. CLAUDE.md spent paragraphs explaining the layer split (workflow infra at repo root vs ctx layer at .context/ vs domain content at docs/). The naming collision is the core friction. +**Context**: `your-project` hand-rolled an editorial pipeline at the repo root with +10-CONSTITUTION.md, colliding with .context/CONSTITUTION.md. CLAUDE.md spent +paragraphs explaining the layer split (workflow infra at repo root vs ctx layer +at .context/ vs domain content at docs/). The naming collision is the core +friction. -**Decision**: Editorial constitution at .context/ingest/KB-RULES.md, not CONSTITUTION.md +**Decision**: Editorial constitution at .context/ingest/KB-RULES.md, not +CONSTITUTION.md -**Rationale**: Sibling project hit and named-their-way-out-of this exact conflict (their file is 10-INGEST_RULES.md, with an explicit naming-by-rename rule recorded in their domain-decisions.md schema header: 'KB-side filename is domain-decisions.md to disambiguate from the root file'). Lift the rename, not just the feature; learn from their resolved wound rather than re-fight the conflict. +**Rationale**: Sibling project hit and named-their-way-out-of this exact +conflict (their file is 10-INGEST_RULES.md, with an explicit naming-by-rename +rule recorded in their domain-decisions.md schema header: 'KB-side filename is +domain-decisions.md to disambiguate from the root file'). Lift the rename, not +just the feature; learn from their resolved wound rather than re-fight the +conflict. -**Consequence**: Pipeline templates use KB-RULES.md throughout (specs/kb-editorial-pipeline.md and brief reflect this); ctx CONSTITUTION.md retains its singular meaning as the project-level invariants file; no layer-bleed documentation needed in CLAUDE.md to cover an avoided collision; same naming discipline carries through to domain-decisions.md (kept separate from DECISIONS.md by the same logic). +**Consequence**: Pipeline templates use KB-RULES.md throughout +(specs/kb-editorial-pipeline.md and brief reflect this); ctx CONSTITUTION.md +retains its singular meaning as the project-level invariants file; no +layer-bleed documentation needed in CLAUDE.md to cover an avoided collision; +same naming discipline carries through to domain-decisions.md (kept separate +from DECISIONS.md by the same logic). --- @@ -206,13 +359,25 @@ For significant decisions: **Status**: Accepted -**Context**: Trade-off considered: handover and editorial pipeline are technically separable. Handover alone gives narrative thread between sessions. Editorial alone piles up closeouts that 'do you remember?' reads via the postdated-unfolded-closeout path. Either could ship without the other; question was whether to split into two ships for smaller risk per release. +**Context**: Trade-off considered: handover and editorial pipeline are +technically separable. Handover alone gives narrative thread between sessions. +Editorial alone piles up closeouts that 'do you remember?' reads via the +postdated-unfolded-closeout path. Either could ship without the other; question +was whether to split into two ships for smaller risk per release. **Decision**: Phase KB ships handover plus editorial paired, not split -**Rationale**: The closeout/fold mechanism is the integration point between the two features. Shipping paired guarantees the fold gets real-world stress on day one rather than being added retroactively when the second feature lands. Better-together over smaller-ship; integration coherence over delivery cadence; the user's lift-the-whole-shape posture extends to shipping coherence. +**Rationale**: The closeout/fold mechanism is the integration point between the +two features. Shipping paired guarantees the fold gets real-world stress on day +one rather than being added retroactively when the second feature lands. +Better-together over smaller-ship; integration coherence over delivery cadence; +the user's lift-the-whole-shape posture extends to shipping coherence. -**Consequence**: Phase KB is bigger than either feature alone; KB-2 sub-phase covers things-wtf port as the integration regression suite; ideas/001 handover work folds into Phase KB rather than shipping as its own phase; the polish-PR (Phase SK) and git-mandate (Phase RG) Phase 0 prerequisites land first to keep Phase KB clean. +**Consequence**: Phase KB is bigger than either feature alone; KB-2 sub-phase +covers `your-project` port as the integration regression suite; ideas/001 handover +work folds into Phase KB rather than shipping as its own phase; the polish-PR +(Phase SK) and git-mandate (Phase RG) Phase 0 prerequisites land first to keep +Phase KB clean. --- @@ -220,13 +385,29 @@ For significant decisions: **Status**: Accepted -**Context**: Designing the KB editorial layer raised the question of whether KB editorial decisions need a parallel /ctx-kb-decide skill mirroring /ctx-decision-add. Three resolutions tested: alpha) skill surface doubles (every capture skill gets a kb sibling); beta) capture skills become mode-aware routers; gamma) capture skills stay single-purpose with user discipline. +**Context**: Designing the KB editorial layer raised the question of whether KB +editorial decisions need a parallel /ctx-kb-decide skill mirroring +/ctx-decision-add. Three resolutions tested: alpha) skill surface doubles (every +capture skill gets a kb sibling); beta) capture skills become mode-aware +routers; gamma) capture skills stay single-purpose with user discipline. -**Decision**: KB ontology is pipeline-only-writer; no /ctx-kb-decide parallel skill +**Decision**: KB ontology is pipeline-only-writer; no /ctx-kb-decide parallel +skill -**Rationale**: All three rejected after a deeper reframe surfaced by the user: in a KB you don't decide, you increase confidence. A claim with confidence greater than 0.9 is fact-by-contract; lower confidence needs more evidence. Even natural-language assertions ('we are spinning off X, anchor on this') are semantically evidence-capture events, not decision-capture events. The sibling pipeline-only-writer model is not rigid; it is the ontologically correct surface for evidence-tracked knowledge. +**Rationale**: All three rejected after a deeper reframe surfaced by the user: +in a KB you don't decide, you increase confidence. A claim with confidence +greater than 0.9 is fact-by-contract; lower confidence needs more evidence. Even +natural-language assertions ('we are spinning off X, anchor on this') are +semantically evidence-capture events, not decision-capture events. The sibling +pipeline-only-writer model is not rigid; it is the ontologically correct surface +for evidence-tracked knowledge. -**Consequence**: KB skill surface stays small: 4 mode skills (ingest/ask/site-review/ground) plus 1 lightweight ctx kb note for capture-without-pipeline; existing /ctx-decision-add etc. unchanged in authority; users who want to record a KB editorial framing instead drop a finding into the inbox or hand-edit the markdown directly. No router question on every capture; no parallel skill maintenance burden. +**Consequence**: KB skill surface stays small: 4 mode skills +(ingest/ask/site-review/ground) plus 1 lightweight ctx kb note for +capture-without-pipeline; existing /ctx-decision-add etc. unchanged in +authority; users who want to record a KB editorial framing instead drop a +finding into the inbox or hand-edit the markdown directly. No router question on +every capture; no parallel skill maintenance burden. --- @@ -234,13 +415,27 @@ For significant decisions: **Status**: Accepted -**Context**: ctx today silently degrades without git via commit:none sentinels in provenance flags; doctor effectively says 'git required for this to work properly' without enforcing. Sibling project mandates git architecturally and says so explicitly. User confirmed N approximately 0 ctx projects in practice run without git. Editorial pipeline lift inherits the git-required assumption (closeout sha:/branch:, evidence-index SHA-pinned in-repo citations, handover Provenance from git HEAD). +**Context**: ctx today silently degrades without git via commit:none sentinels +in provenance flags; doctor effectively says 'git required for this to work +properly' without enforcing. Sibling project mandates git architecturally and +says so explicitly. User confirmed N approximately 0 ctx projects in practice +run without git. Editorial pipeline lift inherits the git-required assumption +(closeout sha:/branch:, evidence-index SHA-pinned in-repo citations, handover +Provenance from git HEAD). **Decision**: Mandate git as architectural precondition -**Rationale**: Persistent-memory promise is dishonest without an undo layer: LLM agents are not trustworthy stewards of files; git reflog is the recovery path. Eliminates dead-code branches across every git-touching path. Trust boundary: refuse-on-no-git rather than auto-git-init (ctx never modifies user filesystem outside .context/). User: we should have done this on day zero. +**Rationale**: Persistent-memory promise is dishonest without an undo layer: LLM +agents are not trustworthy stewards of files; git reflog is the recovery path. +Eliminates dead-code branches across every git-touching path. Trust boundary: +refuse-on-no-git rather than auto-git-init (ctx never modifies user filesystem +outside .context/). User: we should have done this on day zero. -**Consequence**: Breaking change in next minor release; specs/require-git.md written; commit:none sentinel becomes unreachable across gitmeta and doctor advisories; CONSTITUTION.md amendment + DECISIONS.md entry will land during Phase RG implementation; release notes carry one-command migration ('run git init in any pre-existing git-less ctx project before upgrading'). +**Consequence**: Breaking change in next minor release; specs/require-git.md +written; commit:none sentinel becomes unreachable across gitmeta and doctor +advisories; CONSTITUTION.md amendment + DECISIONS.md entry will land during +Phase RG implementation; release notes carry one-command migration ('run git +init in any pre-existing git-less ctx project before upgrading'). --- @@ -248,13 +443,30 @@ For significant decisions: **Status**: Accepted -**Context**: Sibling clean-room project (analyzed undercover; not named to avoid carryover) ships a battle-tested editorial pipeline (4 modes, 9 KB artifacts, closeout/fold mechanism, browseable site rendering). things-wtf-disaster-recovery has been hand-rolling the same shape for weeks at workaround cost: CLAUDE.md disables half of ctx code-dev skills, 10-CONSTITUTION.md at repo root collides with .context/CONSTITUTION.md, hand-typed 8-item closeouts, hand-managed 20-INBOX.md. Considered lift-intact vs hedge-and-defer. +**Context**: Sibling clean-room project (analyzed undercover; not named to avoid +carryover) ships a battle-tested editorial pipeline (4 modes, 9 KB artifacts, +closeout/fold mechanism, browseable site rendering). `your-project` has been +hand-rolling the same shape for weeks at workaround cost: CLAUDE.md disables +half of ctx code-dev skills, 10-CONSTITUTION.md at repo root collides with +.context/CONSTITUTION.md, hand-typed 8-item closeouts, hand-managed 20-INBOX.md. +Considered lift-intact vs hedge-and-defer. -**Decision**: Lift sibling editorial pipeline shape into ctx as v1, paired with handover +**Decision**: Lift sibling editorial pipeline shape into ctx as v1, paired with +handover -**Rationale**: The sibling design is field-tested under production use; things-wtf is a live validation corpus already paying the workaround tax (N=1 lived validation beats hypothetical user research). Initial defer-on-uncertainty instinct corrected by user pushback to lift the whole shape with a non-colliding rename (KB-RULES.md, not CONSTITUTION.md). Two organizing principles (P1: LLM is the migration tool; P2: a KB of KBs is a KB) make lift-the-whole-shape rational rather than reckless. +**Rationale**: The sibling design is field-tested under production use; +`your-project` is a live validation corpus already paying the workaround tax (N=1 +lived validation beats hypothetical user research). Initial defer-on-uncertainty +instinct corrected by user pushback to lift the whole shape with a non-colliding +rename (KB-RULES.md, not CONSTITUTION.md). Two organizing principles (P1: LLM is +the migration tool; P2: a KB of KBs is a KB) make lift-the-whole-shape rational +rather than reckless. -**Consequence**: specs/kb-editorial-pipeline.md written; three TASKS.md phases added (SK polish, RG require-git, KB editorial+handover); KB has its own write authority separate from canonical files; closeout/fold mechanism integrates editorial work with session continuity via handover; ideas/003 brief produced as design source. +**Consequence**: specs/kb-editorial-pipeline.md written; three TASKS.md phases +added (SK polish, RG require-git, KB editorial+handover); KB has its own write +authority separate from canonical files; closeout/fold mechanism integrates +editorial work with session continuity via handover; ideas/003 brief produced as +design source. --- @@ -262,13 +474,28 @@ For significant decisions: **Status**: Accepted -**Context**: Closing the cross-IDE Cursor leak required preventing state.Dir() from materializing .context/state/ in uninitialized projects. Two viable options: (A) gate inside state.Dir itself; (B) require every caller to check Initialized() first. +**Context**: Closing the cross-IDE Cursor leak required preventing state.Dir() +from materializing .context/state/ in uninitialized projects. Two viable +options: (A) gate inside state.Dir itself; (B) require every caller to check +Initialized() first. **Decision**: Gate mkdir inside state.Dir() rather than per-caller -**Rationale**: Option (A) makes the invariant ('no .context/state/ in uninitialized projects') structurally enforced. The leak's root cause was exactly the (B)-style assumption — check_reminder.Run deliberately skipped the gate to print provenance unconditionally, and that path silently produced the leak via Preamble -> nudge.Paused -> PauseMarkerPath -> state.Dir. As long as Dir() mkdirs unconditionally, every future caller is one missed gate away from re-introducing the bug. +**Rationale**: Option (A) makes the invariant ('no .context/state/ in +uninitialized projects') structurally enforced. The leak's root cause was +exactly the (B)-style assumption — check_reminder.Run deliberately skipped the +gate to print provenance unconditionally, and that path silently produced the +leak via Preamble -> nudge.Paused -> PauseMarkerPath -> state.Dir. As long as +Dir() mkdirs unconditionally, every future caller is one missed gate away from +re-introducing the bug. -**Consequence**: state.Dir() now returns errCtx.ErrNotInitialized for uninit projects. Hook callers' existing 'if dirErr != nil { return nil }' branches absorb it silently; interactive callers (ctx add, task complete, prune) surface a path-bearing message via cobra. cooldown.TombstonePath was refactored to delegate to state.Dir so the gate also covers the PreToolUse 'ctx agent' path. memory.SaveState/LoadState were left alone because they use 0755 (different leak class) and are user-initiated, not auto-triggered. +**Consequence**: state.Dir() now returns errCtx.ErrNotInitialized for uninit +projects. Hook callers' existing 'if dirErr != nil { return nil }' branches +absorb it silently; interactive callers (ctx add, task complete, prune) surface +a path-bearing message via cobra. cooldown.TombstonePath was refactored to +delegate to state.Dir so the gate also covers the PreToolUse 'ctx agent' path. +memory.SaveState/LoadState were left alone because they use 0755 (different leak +class) and are user-initiated, not auto-triggered. --- @@ -1949,15 +2176,30 @@ Filename Format, Two-Tier Persistence Model). Users who want session history use **Status**: Accepted -**Context**: The 2026-04-26-152858 decision shipped the OpenCode plugin without a tool.execute.before hook and noted "Re-add when block-dangerous-commands is promoted to the ctx Go binary." Revisited: that promotion is no longer planned. Keeping the open task on the books makes future sessions believe a re-add is pending. +**Context**: The 2026-04-26-152858 decision shipped the OpenCode plugin without +a tool.execute.before hook and noted "Re-add when block-dangerous-commands is +promoted to the ctx Go binary." Revisited: that promotion is no longer planned. +Keeping the open task on the books makes future sessions believe a re-add is +pending. -**Decision**: We will not promote block-dangerous-commands to a ctx system Go subcommand. The OpenCode plugin's missing tool.execute.before hook is permanent, not deferred. +**Decision**: We will not promote block-dangerous-commands to a ctx system Go +subcommand. The OpenCode plugin's missing tool.execute.before hook is permanent, +not deferred. -**Rationale**: The Cobra exit-1 / `{ blocked: true }` interaction makes any shim hostile to users without the Claude wrapper, and the safety-hook gap is acceptable given OpenCode's positioning. Recording this avoids the tax of a perpetually-pending follow-up that no one intends to land. +**Rationale**: The Cobra exit-1 / `{ blocked: true }` interaction makes any shim +hostile to users without the Claude wrapper, and the safety-hook gap is +acceptable given OpenCode's positioning. Recording this avoids the tax of a +perpetually-pending follow-up that no one intends to land. -**Consequences**: TASKS.md item "Promote 'block-dangerous-commands' to a real ctx system Go subcommand…" marked `[-]` skipped. The 2026-04-26-152858 rationale's "Re-add when…" clause is void; the underlying ship-without-the-hook decision remains in force. Other (non-OpenCode) editor integrations that want a dangerous-command safety net will need a different mechanism. +**Consequences**: TASKS.md item "Promote 'block-dangerous-commands' to a real +ctx system Go subcommand…" marked `[-]` skipped. The 2026-04-26-152858 +rationale's "Re-add when…" clause is void; the underlying +ship-without-the-hook decision remains in force. Other (non-OpenCode) editor +integrations that want a dangerous-command safety net will need a different +mechanism. -**Related**: Amends [2026-04-26-152858] OpenCode plugin ships without tool.execute.before hook (rationale's deferred re-add is now closed). +**Related**: Amends [2026-04-26-152858] OpenCode plugin ships without +tool.execute.before hook (rationale's deferred re-add is now closed). --- @@ -1965,13 +2207,19 @@ Filename Format, Two-Tier Persistence Model). Users who want session history use **Status**: Accepted -**Context**: Original PR #72 OpenCode plugin ran 'ctx system post-commit' after every shell tool call, not only after real commits +**Context**: Original PR #72 OpenCode plugin ran 'ctx system post-commit' after +every shell tool call, not only after real commits -**Decision**: Editor-integration plugins must filter post-commit to actual git commit invocations +**Decision**: Editor-integration plugins must filter post-commit to actual git +commit invocations -**Rationale**: post-commit is meaningful only after a real commit lands; firing on every shell call is noise that trains users to ignore the resulting nudges +**Rationale**: post-commit is meaningful only after a real commit lands; firing +on every shell call is noise that trains users to ignore the resulting nudges -**Consequences**: Editor plugins always sniff the actual command string (regex on the extracted command) before triggering capture nudges that target specific commands. Same pattern applies to any future hook that targets a specific porcelain command. +**Consequences**: Editor plugins always sniff the actual command string (regex +on the extracted command) before triggering capture nudges that target specific +commands. Same pattern applies to any future hook that targets a specific +porcelain command. --- @@ -1979,13 +2227,20 @@ Filename Format, Two-Tier Persistence Model). Users who want session history use **Status**: Accepted -**Context**: The natural fit (block-dangerous-commands) doesn't exist as a ctx system Go subcommand; shimming to it would block every shell call on installs without the Claude wrapper because Cobra's unknown-command exit 1 is read as { blocked: true } by OpenCode +**Context**: The natural fit (block-dangerous-commands) doesn't exist as a ctx +system Go subcommand; shimming to it would block every shell call on installs +without the Claude wrapper because Cobra's unknown-command exit 1 is read as { +blocked: true } by OpenCode **Decision**: OpenCode plugin ships without tool.execute.before hook -**Rationale**: Better to ship a feature-narrower plugin than one that bricks the editor for users without the wrapper. Re-add when block-dangerous-commands is promoted to the ctx Go binary. +**Rationale**: Better to ship a feature-narrower plugin than one that bricks the +editor for users without the wrapper. Re-add when block-dangerous-commands is +promoted to the ctx Go binary. -**Consequences**: OpenCode users get bootstrap, persistence, post-commit, and task-completion nudges but no dangerous-command safety net. specs/opencode-integration.md records the deliberate omission. +**Consequences**: OpenCode users get bootstrap, persistence, post-commit, and +task-completion nudges but no dangerous-command safety net. +specs/opencode-integration.md records the deliberate omission. --- @@ -1993,13 +2248,18 @@ Filename Format, Two-Tier Persistence Model). Users who want session history use **Status**: Accepted -**Context**: TestBinaryIntegration spawns subprocesses; the prior helper did append(os.Environ(), CTX_DIR=...) to override the developer-shell value. Wrong abstraction. +**Context**: TestBinaryIntegration spawns subprocesses; the prior helper did +append(os.Environ(), CTX_DIR=...) to override the developer-shell value. Wrong +abstraction. -**Decision**: Use t.Setenv for subprocess env in tests, not append(os.Environ(), ...) +**Decision**: Use t.Setenv for subprocess env in tests, not append(os.Environ(), +...) -**Rationale**: t.Setenv mutates the live process env, exec.Cmd with nil Env inherits it, and cleanup is automatic at test end. One line replaces the helper. +**Rationale**: t.Setenv mutates the live process env, exec.Cmd with nil Env +inherits it, and cleanup is automatic at test end. One line replaces the helper. -**Consequence**: Helper deleted, six call sites simplified, no env-dedup logic to maintain. Pattern reusable for other subprocess tests. +**Consequence**: Helper deleted, six call sites simplified, no env-dedup logic +to maintain. Pattern reusable for other subprocess tests. --- @@ -2007,10 +2267,17 @@ Filename Format, Two-Tier Persistence Model). Users who want session history use **Status**: Accepted -**Context**: Old single-return form returned ('', nil) when CTX_DIR was undeclared. Callers that filtered only on err != nil joined empty stateDir with relative names and wrote state files into CWD instead of .context/state/. +**Context**: Old single-return form returned ('', nil) when CTX_DIR was +undeclared. Callers that filtered only on err != nil joined empty stateDir with +relative names and wrote state files into CWD instead of .context/state/. -**Decision**: Tighten state.Dir / rc.ContextDir to (string, error) with sentinel errors +**Decision**: Tighten state.Dir / rc.ContextDir to (string, error) with sentinel +errors -**Rationale**: Returning a sentinel ErrDirNotDeclared makes the empty-path case unrepresentable in a 'looks fine' branch. Forces every caller through the same explicit gate. +**Rationale**: Returning a sentinel ErrDirNotDeclared makes the empty-path case +unrepresentable in a 'looks fine' branch. Forces every caller through the same +explicit gate. -**Consequence**: All callers needed migration; tests had to declare CTX_DIR explicitly. In return, the filepath.Join('', rel) trap is closed by construction. +**Consequence**: All callers needed migration; tests had to declare CTX_DIR +explicitly. In return, the filepath.Join('', rel) trap is closed by +construction. diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md index bfe825ff1..4b71de777 100644 --- a/.context/LEARNINGS.md +++ b/.context/LEARNINGS.md @@ -17,6 +17,10 @@ DO NOT UPDATE FOR: | Date | Learning | |----|--------| +| 2026-05-17 | `_helpers.go` / `_utils.go` filenames are project anti-pattern; use domain nouns | +| 2026-05-17 | Subagent parallelism shines for mechanical refactor with a worked-example reference | +| 2026-05-17 | naked_errors audit rejects fmt.Errorf wrapping outside internal/err// | +| 2026-05-17 | Pre-emptive constants are dead exports; ship constants only when their caller lands | | 2026-05-11 | Naive Markdown line-sweep corrupts multi-line code spans and YAML lists | | 2026-05-11 | tsc cross-tree include resolves node_modules from source file, not tsconfig | | 2026-05-10 | Go compile/tool version mismatch comes from the cached toolchain, not the system Go | @@ -142,93 +146,352 @@ DO NOT UPDATE FOR: --- -## [2026-05-11-231025] Naive Markdown line-sweep corrupts multi-line code spans and YAML lists +## [2026-05-17-061500] `_helpers.go` / `_utils.go` filenames are project anti-pattern; use domain nouns + +**Context**: During Phase KB / Phase RG audit cleanup, the first file split I +attempted to satisfy the mixed-visibility audit named the new file +`read_helpers.go`. The user vetoed on sight: "utils; helpers, etcs are ALL lazy +naming; I will veto them the moment I see them; find proper domain objects." + +**Lesson**: ctx's per-package file layout follows domain nouns, not +visibility-suffixed catch-alls. The canonical reference shape is +`internal/journal/parser/` which splits 18 files by domain (envelope, markdown, +parse, validate, claude, copilot, ...). The mixed-visibility audit demands a +split, but the split target must be a real noun: `frontmatter.go` (YAML +parsing/validation), `markdown.go` (rendering), `filename.go` (filename +derivation), `provenance.go` (sha/branch resolution), `parse.go` (one-shot +parser), `cursor.go` (latest-pointer logic). Never `_helpers.go`. + +**Application**: When splitting a file to satisfy `mixed_visibility_test`, name +the new file for what the helpers ARE about, not for what visibility they have. +If you can't name it cleanly, the split itself may be wrong and the funcs may +belong in a different package entirely. + +--- + +## [2026-05-17-061000] Subagent parallelism shines for mechanical refactor with a worked-example reference + +**Context**: Phase KB audit cleanup spanned 428 violations across 21 categories +in ~50 files. Doing it serially in the orchestrator would have burned the +session. Three subagents in parallel (one for 16 markdown templates, one for 10 +schemas, one for 6 SKILL.md files) landed 32 files with zero integration churn. +A fourth subagent (9 kb writer packages) and a fifth (CLI cmd tree) followed the +same shape and cleared the bulk of audit failures while the orchestrator handled +handover + gitmeta + closeout itself. + +**Lesson**: Subagents work well when (a) the work is well-bounded, (b) a +canonical worked example exists in the prompt or on disk, (c) the agent is told +to fix-or-fail-with-a-blocker rather than surface deferral options. The first +subagent I dispatched stopped at honest-scope reporting; the followups plowed +because the prompt explicitly invoked the Constitution's no-deferral rule and +pointed at a worked example. + +**Application**: For mechanical refactor work at scale: do one worked example in +the orchestrator, then dispatch a subagent for the rest with the example as a +reference path in the prompt. Tell the subagent to either complete the work or +surface a specific blocker with a concrete next step, not options for the user +to choose between. + +--- + +## [2026-05-17-060000] naked_errors audit rejects fmt.Errorf wrapping outside internal/err// -**Context**: Performed a programmatic typographic sweep across docs/*.md to wrap bare 'ctx' tokens in backticks (commit 61aab858). 81 source files, 236 lines changed. First pass corrupted two indented JSON snippets in MkDocs admonitions because the fence regex anchored to start-of-line and missed admonition-indented fences. After fixing the fence regex, two more corruptions surfaced (multi-line inline-code spans where the opening backtick is on line N and the closing on line N+1: the line-at-a-time transformer treated each line independently, leading to misjudged span boundaries on the second line). After post-sweep validation, a YAML parse error on docs/blog/2026-02-03-the-attention-budget.md surfaced one more breakage: a 'topics:' list-item like '- ctx primitives' got wrapped to '- `ctx` primitives', which is invalid YAML (a value starting with backtick is not a valid unquoted scalar). Total: 2 multi-line span corruptions + 1 YAML breakage, all detected only by post-sweep validation (make site + grep audit), not by the dry-run. +**Context**: When fixing Phase KB audit failures, I initially assumed +`fmt.Errorf("desc: %w", err)` wrapping at the call site satisfies the +naked_errors audit. It does not. `internal/audit/naked_errors_test.go` flags +every `fmt.Errorf` and `errors.New` call outside `internal/err/**`. The ctx +convention requires error constructors to live in domain-scoped +`internal/err//` packages and pull their format strings from either +`internal/config//` Go-side constants OR `desc.Text(text.DescKey...)` YAML +keys. -**Lesson**: A naive line-at-a-time regex sweep across Markdown documents must respect a wider 'skip' set than the obvious cases. The full safe-skip list is: (1) triple-backtick fenced code blocks, BOTH root-level and indented inside MkDocs admonitions or list items (fence regex must allow leading whitespace, e.g. '^\\s*```'); (2) inline backtick spans on the same line; (3) multi-line inline-code spans crossing line boundaries (line-at-a-time logic cannot detect both ends, so either track fence-like 'odd-count' state across lines or treat any unclosed-on-line backtick as 'protect rest of line'); (4) the ENTIRE YAML frontmatter block (delimited by '---' at top and next '---'), not just specific keys like title/description/icon, because list-item values under topics/tags/keywords are also YAML and break on a leading backtick; (5) image alt-text '![alt]' (alt-text does not render in monotype); (6) link-reference definitions '[name]: url "title"'; (7) project copyright header comment blocks. Dry-run output never catches YAML or multi-line span breakage; validation MUST include a parser-level check (make site for YAML, post-grep for '``name`' double-backtick patterns near the wrapped token). +**Lesson**: For Phase KB this meant building 14 new err packages (`closeout`, +`handover`, `gitmeta`, `kbevidence`, `kbsourcecoverage`, plus 7 kb-table +packages, `kbcli`, `initkb`) plus matching `internal/config//` packages +with `ErrMsg` and `Format` constants. The pattern: `var ErrX = +errors.New(cfgArea.ErrMsgX)` for sentinels; `func X(args, cause) error { return +fmt.Errorf(cfgArea.FormatX, args, cause) }` for wrapping constructors. Callers +do `errors.Is(err, errArea.ErrX)` for sentinel matching. -**Application**: When designing any future programmatic sweep across docs/ (typography passes, internationalization, brand renames, em-dash replacement, link-text rewrites): (1) implement the full skip set above, not a subset; (2) for fence detection use '^\\s*```', not '^```'; (3) for the frontmatter, detect the entire block between '---' delimiters, not specific keys; (4) for multi-line inline-code, choose between cross-line backtick-pair tracking (complex but correct) or the simpler 'unclosed backtick protects rest of line' heuristic (corrupts ~1 per 100 files but recoverable manually); (5) ALWAYS validate post-sweep with 'make site' (zensical surfaces YAML errors) and a grep for '``\\w' double-backtick patterns near the wrapped token; (6) commit only after both validations are clean. For one-shot sweeps the script can be ad-hoc, but record the validation gate as part of the commit message so the next contributor knows what to check. +**Application**: Estimating the cost of "add a new feature" in ctx must include +the err-package + config-package wiring. Each new error surface is ~3 files per +area (config//messages.go, err//.go, the calling code). The +Phase RG `MissingGitError` typed struct was the wrong shape for ctx; it became +`errGitmeta.ErrMissingGitTree` (sentinel) + +`errGitmeta.MissingGitTreeForCmd(cmdName, projectRoot)` (wrapping constructor). + +--- + +## [2026-05-17-055500] Pre-emptive constants are dead exports; ship constants only when their caller lands + +**Context**: During Phase KB Stage 3, I added the full set of expected constants +to `internal/config/kb/kb.go`: closeout-mode names, schema filenames, life-stage +tokens, pass-mode tokens, the LifeStageThreshold integer. Many of these had no +caller yet because their consumers (doctor advisories, the `ctx kb site build` +zensical wiring, doctor advisory checks) were Phase 7 work. The +`dead_exports_test.go` audit flagged 28 of them. Same for +`cli/kb/core/path/SchemasDir` and `KBArtifactFile`, plus `regex.SlugWithSlash`. + +**Lesson**: ctx's dead-export audit is symbol-graph-strict: any exported const / +var / func without an internal reader fails the gate. You cannot scaffold +constants ahead of their callers, even if you know the caller is one phase away. +The constants must land in the same commit (or a strict precursor commit) as the +code that reads them. + +**Application**: When defining configuration constants for a new feature, write +the caller first or in the same change. If a constant truly needs to ship ahead +of its caller (rare), park it in a TASKS.md line, not a config file. The audit +treats "future use" as dead. + +--- + +## [2026-05-11-231025] Naive Markdown line-sweep corrupts multi-line code spans and YAML lists + +**Context**: Performed a programmatic typographic sweep across docs/*.md to wrap +bare 'ctx' tokens in backticks (commit 61aab858). 81 source files, 236 lines +changed. First pass corrupted two indented JSON snippets in MkDocs admonitions +because the fence regex anchored to start-of-line and missed admonition-indented +fences. After fixing the fence regex, two more corruptions surfaced (multi-line +inline-code spans where the opening backtick is on line N and the closing on +line N+1: the line-at-a-time transformer treated each line independently, +leading to misjudged span boundaries on the second line). After post-sweep +validation, a YAML parse error on docs/blog/2026-02-03-the-attention-budget.md +surfaced one more breakage: a 'topics:' list-item like '- ctx primitives' got +wrapped to '- `ctx` primitives', which is invalid YAML (a value starting with +backtick is not a valid unquoted scalar). Total: 2 multi-line span corruptions + +1 YAML breakage, all detected only by post-sweep validation (make site + grep +audit), not by the dry-run. + +**Lesson**: A naive line-at-a-time regex sweep across Markdown documents must +respect a wider 'skip' set than the obvious cases. The full safe-skip list is: +(1) triple-backtick fenced code blocks, BOTH root-level and indented inside +MkDocs admonitions or list items (fence regex must allow leading whitespace, +e.g. '^\\s*```'); (2) inline backtick spans on the same line; (3) multi-line +inline-code spans crossing line boundaries (line-at-a-time logic cannot detect +both ends, so either track fence-like 'odd-count' state across lines or treat +any unclosed-on-line backtick as 'protect rest of line'); (4) the ENTIRE YAML +frontmatter block (delimited by '---' at top and next '---'), not just specific +keys like title/description/icon, because list-item values under +topics/tags/keywords are also YAML and break on a leading backtick; (5) image +alt-text '![alt]' (alt-text does not render in monotype); (6) link-reference +definitions '[name]: url "title"'; (7) project copyright header comment blocks. +Dry-run output never catches YAML or multi-line span breakage; validation MUST +include a parser-level check (make site for YAML, post-grep for '``name`' +double-backtick patterns near the wrapped token). + +**Application**: When designing any future programmatic sweep across docs/ +(typography passes, internationalization, brand renames, em-dash replacement, +link-text rewrites): (1) implement the full skip set above, not a subset; (2) +for fence detection use '^\\s*```', not '^```'; (3) for the frontmatter, detect +the entire block between '---' delimiters, not specific keys; (4) for multi-line +inline-code, choose between cross-line backtick-pair tracking (complex but +correct) or the simpler 'unclosed backtick protects rest of line' heuristic +(corrupts ~1 per 100 files but recoverable manually); (5) ALWAYS validate +post-sweep with 'make site' (zensical surfaces YAML errors) and a grep for +'``\\w' double-backtick patterns near the wrapped token; (6) commit only after +both validations are clean. For one-shot sweeps the script can be ad-hoc, but +record the validation gate as part of the commit message so the next contributor +knows what to check. --- ## [2026-05-11-202124] tsc cross-tree include resolves node_modules from source file, not tsconfig -**Context**: Set up tsc --noEmit gate for the embedded OpenCode plugin. tsconfig lived in tools/typecheck/opencode/; include pointed at internal/assets/integrations/opencode/plugin/index.ts via relative path. First run failed with 'Cannot find module @opencode-ai/plugin' even though node_modules was correctly populated in tools/typecheck/opencode/. +**Context**: Set up tsc --noEmit gate for the embedded OpenCode plugin. tsconfig +lived in tools/typecheck/opencode/; include pointed at +internal/assets/integrations/opencode/plugin/index.ts via relative path. First +run failed with 'Cannot find module @opencode-ai/plugin' even though +node_modules was correctly populated in tools/typecheck/opencode/. -**Lesson**: When tsconfig.json sits in dir A but its 'include' points at .ts files in dir B, tsc resolves node_modules by walking up from each source file's location (dir B), NOT from the tsconfig's location (dir A). With moduleResolution: bundler the behavior is the same. The 'node_modules' that ships in dir A is invisible to a source file in a distant dir B. +**Lesson**: When tsconfig.json sits in dir A but its 'include' points at .ts +files in dir B, tsc resolves node_modules by walking up from each source file's +location (dir B), NOT from the tsconfig's location (dir A). With +moduleResolution: bundler the behavior is the same. The 'node_modules' that +ships in dir A is invisible to a source file in a distant dir B. -**Application**: For any cross-tree tsc setup (typecheck gate for embedded source elsewhere in the repo, monorepo-style references, etc.), add explicit baseUrl + paths to the tsconfig. Example: baseUrl: '.', paths: { '@opencode-ai/plugin': ['./node_modules/@opencode-ai/plugin/dist/index.d.ts'], '@opencode-ai/plugin/*': ['./node_modules/@opencode-ai/plugin/dist/*'] }. Add typeRoots ['./node_modules/@types', './node_modules'] for good measure. The cost is some manual path mapping; the benefit is that node_modules can live wherever the tooling does, not next to the source. +**Application**: For any cross-tree tsc setup (typecheck gate for embedded +source elsewhere in the repo, monorepo-style references, etc.), add explicit +baseUrl + paths to the tsconfig. Example: baseUrl: '.', paths: { +'@opencode-ai/plugin': ['./node_modules/@opencode-ai/plugin/dist/index.d.ts'], +'@opencode-ai/plugin/*': ['./node_modules/@opencode-ai/plugin/dist/*'] }. Add +typeRoots ['./node_modules/@types', './node_modules'] for good measure. The cost +is some manual path mapping; the benefit is that node_modules can live wherever +the tooling does, not next to the source. --- ## [2026-05-10-181418] Go compile/tool version mismatch comes from the cached toolchain, not the system Go -**Context**: Hit 'compile: version "go1.26.1" does not match go tool version "go1.26.2"' on every go build / go test / make lint, even with my changes stashed out. System Go was 1.26.2 (healthy); go.mod pinned 1.26.1, so Go's auto-toolchain feature had downloaded 1.26.1 to ~/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.1.darwin-arm64/. That cached toolchain was internally inconsistent: its compile binary and stdlib export data disagreed on version. +**Context**: Hit 'compile: version "go1.26.1" does not match go tool version +"go1.26.2"' on every go build / go test / make lint, even with my changes +stashed out. System Go was 1.26.2 (healthy); go.mod pinned 1.26.1, so Go's +auto-toolchain feature had downloaded 1.26.1 to +~/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.26.1.darwin-arm64/. That cached +toolchain was internally inconsistent: its compile binary and stdlib export data +disagreed on version. -**Lesson**: When the compile-vs-tool version error appears, the bug is the cached toolchain dir, not the installed Go. Reinstalling Go (brew, installer, etc.) does NOT touch the cached download, so the error persists after reinstall. Three real fixes: (1) rm -rf ~/go/pkg/mod/golang.org/toolchain@v0.0.1-go./ to force a clean re-download (~30s); (2) bump go.mod to match the system Go so the cached one is bypassed; (3) GOTOOLCHAIN=go to override the pin per-invocation. go clean -cache and GOTOOLCHAIN=local do not help. +**Lesson**: When the compile-vs-tool version error appears, the bug is the +cached toolchain dir, not the installed Go. Reinstalling Go (brew, installer, +etc.) does NOT touch the cached download, so the error persists after reinstall. +Three real fixes: (1) rm -rf +~/go/pkg/mod/golang.org/toolchain@v0.0.1-go./ to force a clean +re-download (~30s); (2) bump go.mod to match the system Go so the cached one is +bypassed; (3) GOTOOLCHAIN=go to override the pin per-invocation. +go clean -cache and GOTOOLCHAIN=local do not help. -**Application**: First diagnostic on this error: check go env GOROOT — if it points to ~/go/pkg/mod/golang.org/toolchain@... the cached toolchain is in play. Then either delete the cached dir (most surgical) or bump go.mod (one-line diff, but lands in a commit). Do not waste time reinstalling Go. +**Application**: First diagnostic on this error: check `go env GOROOT`. If it +points to `~/go/pkg/mod/golang.org/toolchain@...` the cached toolchain is in +play. Then either delete the cached dir (most surgical) or bump go.mod (one-line +diff, but lands in a commit). Do not waste time reinstalling Go. --- ## [2026-05-10-001859] An ongoing user's concrete workaround tax is the strongest validation evidence -**Context**: When extracting the editorial pipeline, the user pointed at things-wtf-disaster-recovery as a project where they were already running the editorial pattern manually — but at concrete cost: CLAUDE.md disabling half of ctx code-dev skills (/ctx-commit, /ctx-implement, /ctx-spec, /ctx-architecture, /ctx-brainstorm, /ctx-wrap-up), 10-CONSTITUTION.md at repo root colliding with .context/CONSTITUTION.md, hand-typed 8-item closeouts, hand-managed 20-INBOX.md, dedicated reference/vcf/external-grounding.md for ground-mode. The workaround was visible and the pain was specific. +**Context**: When extracting the editorial pipeline, the user pointed at +`your-project` as a project where they were already running the editorial pattern +manually, at concrete cost: CLAUDE.md disabling half of ctx code-dev skills +(/ctx-commit, /ctx-implement, /ctx-spec, /ctx-architecture, /ctx-brainstorm, +/ctx-wrap-up), 10-CONSTITUTION.md at repo root colliding with +.context/CONSTITUTION.md, hand-typed 8-item closeouts, hand-managed 20-INBOX.md, +dedicated reference/vcf/external-grounding.md for ground-mode. The workaround +was visible and the pain was specific. -**Lesson**: An ongoing user paying concrete workaround tax is the strongest validation evidence — it beats hypothetical user research, beats N=2 design discussion, beats 'this seems useful.' The shape of the workaround maps directly to the gap the feature should fill. Validation is essentially complete before any code is written; the new feature mechanizes what already works manually. +**Lesson**: An ongoing user paying concrete workaround tax is the strongest +validation evidence; it beats hypothetical user research, beats N=2 design +discussion, beats 'this seems useful.' The shape of the workaround maps directly +to the gap the feature should fill. Validation is essentially complete before +any code is written; the new feature mechanizes what already works manually. -**Application**: When deciding whether to ship a feature, prefer 'a real user is paying real workaround cost right now' over 'this seems valuable.' Use the workaround details (which files they created, which conventions they bent, which skills they disabled) as the inverse-spec of what to build. Ship the feature shape that exactly matches what they hand-rolled, and use their project as the regression test corpus (Phase KB-2 ports things-wtf as the validation step). +**Application**: When deciding whether to ship a feature, prefer 'a real user is +paying real workaround cost right now' over 'this seems valuable.' Use the +workaround details (which files they created, which conventions they bent, which +skills they disabled) as the inverse-spec of what to build. Ship the feature +shape that exactly matches what they hand-rolled, and use their project as the +regression test corpus (Phase KB-2 ports `your-project` as the validation step). --- ## [2026-05-10-001859] Lift renames alongside features when borrowing from battle-tested external designs -**Context**: When extracting the editorial pipeline from the sibling project, noticed they named their editorial constitution 10-INGEST_RULES.md (not 10-CONSTITUTION.md), and explicitly recorded a 'domain-decisions.md is named to disambiguate from .anchor/DECISIONS.md (naming-by-rename rule)' note in their schemas. They had hit and resolved naming conflicts that things-wtf was actively re-fighting (with 10-CONSTITUTION.md at repo root colliding with .context/CONSTITUTION.md). +**Context**: When extracting the editorial pipeline from the sibling project, +noticed they named their editorial constitution 10-INGEST_RULES.md (not +10-CONSTITUTION.md), and explicitly recorded a 'domain-decisions.md is named to +disambiguate from .tool/DECISIONS.md (naming-by-rename rule)' note in their +schemas. They had hit and resolved naming conflicts that `your-project` was actively +re-fighting (with 10-CONSTITUTION.md at repo root colliding with +.context/CONSTITUTION.md). -**Lesson**: When lifting from a battle-tested external design, lift the renames and disambiguation moves alongside the features. Intentional renames encode resolved conflicts; treating them as cosmetic preferences re-litigates the underlying fight in your codebase. The aesthetic difference between two names often hides hard-won architectural learning. +**Lesson**: When lifting from a battle-tested external design, lift the renames +and disambiguation moves alongside the features. Intentional renames encode +resolved conflicts; treating them as cosmetic preferences re-litigates the +underlying fight in your codebase. The aesthetic difference between two names +often hides hard-won architectural learning. -**Application**: ctx editorial pipeline uses KB-RULES.md (not CONSTITUTION.md) and domain-decisions.md (not DECISIONS.md) explicitly because the sibling did. For any future external-design lift, scan the source for renames as signal of resolved-conflict knowledge — and copy them with the rationale (in DECISIONS.md) so future maintainers don't 'simplify' the names back into the conflict zone. +**Application**: ctx editorial pipeline uses KB-RULES.md (not CONSTITUTION.md) +and domain-decisions.md (not DECISIONS.md) explicitly because the sibling did. +For any future external-design lift, scan the source for renames as signal of +resolved-conflict knowledge, and copy them with the rationale (in DECISIONS.md) +so future maintainers don't 'simplify' the names back into the conflict zone. --- ## [2026-05-10-001859] KB epistemology: in a KB you do not decide, you increase confidence -**Context**: Considered whether KB editorial decisions need a parallel /ctx-kb-decide skill mirroring /ctx-decision-add. Got stuck on three resolutions (skill surface doubles, mode-aware router, manual discipline) until the user reframed: do you really decide in a KB, or do you just learn and improve confidence? A claim with confidence greater than 0.9 is decided by contract; lower confidence requires more evidence. +**Context**: Considered whether KB editorial decisions need a parallel +/ctx-kb-decide skill mirroring /ctx-decision-add. Got stuck on three resolutions +(skill surface doubles, mode-aware router, manual discipline) until the user +reframed: do you really decide in a KB, or do you just learn and improve +confidence? A claim with confidence greater than 0.9 is decided by contract; +lower confidence requires more evidence. -**Lesson**: In a knowledge base, the correct ontology has no 'decide' moment — there are only evidence-capture events with confidence bands. Even natural-language assertions like 'we are spinning off X, anchor on this' are semantically evidence-capture (a high-confidence claim arriving), not decision-capture (a choice between alternatives). The pipeline-only-writer model is not rigid; it is the ontologically correct surface for evidence-tracked knowledge. +**Lesson**: In a knowledge base, the correct ontology has no 'decide' moment; +there are only evidence-capture events with confidence bands. Even +natural-language assertions like 'we are spinning off X, anchor on this' are +semantically evidence-capture (a high-confidence claim arriving), not +decision-capture (a choice between alternatives). The pipeline-only-writer model +is not rigid; it is the ontologically correct surface for evidence-tracked +knowledge. -**Application**: When a feature seems to require a parallel skill mirroring an existing canonical capture skill, check whether the underlying domain has the same ontology. If the new domain operates by 'increase confidence' rather than 'pick a choice,' the parallel skill is the wrong shape and the pipeline approach is right. Useful general check: is this 'I made a call between alternatives' or 'I learned something about the world'? Different ontologies call for different surfaces. +**Application**: When a feature seems to require a parallel skill mirroring an +existing canonical capture skill, check whether the underlying domain has the +same ontology. If the new domain operates by 'increase confidence' rather than +'pick a choice,' the parallel skill is the wrong shape and the pipeline approach +is right. Useful general check: is this 'I made a call between alternatives' or +'I learned something about the world'? Different ontologies call for different +surfaces. --- ## [2026-05-10-001859] P2: A KB of KBs is a KB -**Context**: User raised 'KB of KBs' as a wished-for federation feature for multi-team consolidation (research-master KB pulling several team KBs together). Initial framing treated this as a v2 feature that might require v1 schema decisions like KB-prefixed IDs (research-master/EV-019) or federation roots. User reframed: 'kb is knowledge; knowledge is source; source is ingestable; that's also what makes kb of kbs composable; because kb of kbs is a kb.' +**Context**: User raised 'KB of KBs' as a wished-for federation feature for +multi-team consolidation (research-master KB pulling several team KBs together). +Initial framing treated this as a v2 feature that might require v1 schema +decisions like KB-prefixed IDs (research-master/EV-019) or federation roots. +User reframed: 'kb is knowledge; knowledge is source; source is ingestable; +that's also what makes kb of kbs composable; because kb of kbs is a kb.' -**Lesson**: Recursive composability eliminates whole feature classes. When a 'thing-of-things' feature comes up, ask whether the standard pipeline applied to its own output covers the case before designing a new mechanism. Federation as 'pipeline pointed at another instance of its own input shape' is dramatically simpler than federation as a separate subsystem. +**Lesson**: Recursive composability eliminates whole feature classes. When a +'thing-of-things' feature comes up, ask whether the standard pipeline applied to +its own output covers the case before designing a new mechanism. Federation as +'pipeline pointed at another instance of its own input shape' is dramatically +simpler than federation as a separate subsystem. -**Application**: Federation does not need v1 schema lockout: source-map kind: kb plus the standard ingest pipeline covers it. Same insight applies to taxonomy-was-wrong recovery (start fresh KB; ingest old as source; discard irrelevant parts at extraction time) and multi-team consolidation (each team owns a KB; master ingests them). Watch for this pattern in future ctx feature design — the 'thing-of-things is a thing' shortcut may collapse the design problem entirely. +**Application**: Federation does not need v1 schema lockout: source-map kind: kb +plus the standard ingest pipeline covers it. Same insight applies to +taxonomy-was-wrong recovery (start fresh KB; ingest old as source; discard +irrelevant parts at extraction time) and multi-team consolidation (each team +owns a KB; master ingests them). Watch for this pattern in future ctx feature +design; the 'thing-of-things is a thing' shortcut may collapse the design +problem entirely. --- ## [2026-05-10-001859] P1: The LLM is the migration tool -**Context**: Designing schemas for the editorial pipeline raised the question of whether to commit to specific aesthetic choices (EV-### IDs, four named modes, four-band confidence) or hedge with abstract types that could absorb future change. The unwind-cost analysis during /ctx-plan showed every category of being-wrong is essentially cheap because the LLM absorbs the migration: wholesale ID renumbering (LLM cleanup), taxonomy reshuffles (start-fresh-and-ingest-old), schema-band remapping (mathematical and scriptable), path renames (single sweep). +**Context**: Designing schemas for the editorial pipeline raised the question of +whether to commit to specific aesthetic choices (EV-### IDs, four named modes, +four-band confidence) or hedge with abstract types that could absorb future +change. The unwind-cost analysis during /ctx-plan showed every category of +being-wrong is essentially cheap because the LLM absorbs the migration: +wholesale ID renumbering (LLM cleanup), taxonomy reshuffles +(start-fresh-and-ingest-old), schema-band remapping (mathematical and +scriptable), path renames (single sweep). -**Lesson**: When designing AI-assisted persistent storage, expensive migrations are absorbed by LLM cleanup passes. Commit to the readable, opinionated, aesthetic schema in v1 instead of hedging with abstract types. Be wrong cheaply: the alternative (hedging upfront) ships a generic shape that nobody loves, and migrations were never as expensive as we feared. +**Lesson**: When designing AI-assisted persistent storage, expensive migrations +are absorbed by LLM cleanup passes. Commit to the readable, opinionated, +aesthetic schema in v1 instead of hedging with abstract types. Be wrong cheaply: +the alternative (hedging upfront) ships a generic shape that nobody loves, and +migrations were never as expensive as we feared. -**Application**: For any future ctx feature where the schema-vs-flexibility question arises, default to the specific shape; trust LLM cleanup as the migration story. Surface dirty state via doctor advisories so the agent has a work surface to operate on. Applies broadly: editorial KB schemas, closeout shapes, future feature surfaces. Pair with the discipline of doctor flagging duplicates / divergences so the LLM has clear cases to resolve. +**Application**: For any future ctx feature where the schema-vs-flexibility +question arises, default to the specific shape; trust LLM cleanup as the +migration story. Surface dirty state via doctor advisories so the agent has a +work surface to operate on. Applies broadly: editorial KB schemas, closeout +shapes, future feature surfaces. Pair with the discipline of doctor flagging +duplicates / divergences so the LLM has clear cases to resolve. --- ## [2026-05-08-195031] Cursor imports Claude Code hooks and sets CLAUDE_PROJECT_DIR per workspace -**Context**: Investigating why .context/state/ appeared in non-ctx projects opened in Cursor. Hypothesis was a Cursor extension or shell hook; turned out to be Cursor's documented Claude-compatibility behavior (https://cursor.com/docs/hooks): it loads ~/.claude hooks and injects CLAUDE_PROJECT_DIR=workspace_root so they 'just work'. Globally-enabled Claude plugins therefore fire in every Cursor workspace. +**Context**: Investigating why .context/state/ appeared in non-ctx projects +opened in Cursor. Hypothesis was a Cursor extension or shell hook; turned out to +be Cursor's documented Claude-compatibility behavior +(https://cursor.com/docs/hooks): it loads ~/.claude hooks and injects +CLAUDE_PROJECT_DIR=workspace_root so they 'just work'. Globally-enabled Claude +plugins therefore fire in every Cursor workspace. -**Lesson**: When debugging cross-tool side effects, check whether the host tool advertises compatibility with the implicated tool's config format. The leak surface of any global Claude plugin is now 'every Cursor workspace + every Claude Code project', not just 'every Claude Code project'. +**Lesson**: When debugging cross-tool side effects, check whether the host tool +advertises compatibility with the implicated tool's config format. The leak +surface of any global Claude plugin is now 'every Cursor workspace + every +Claude Code project', not just 'every Claude Code project'. -**Application**: Hooks must be safe to fire in non-ctx projects: silent bail when state.Initialized() is false, no filesystem side effects. The ctx code-side fix lives in state.Dir's Initialized gate; the design rule is broader — assume hooks may run anywhere, not just where the user invoked ctx init. +**Application**: Hooks must be safe to fire in non-ctx projects: silent bail +when state.Initialized() is false, no filesystem side effects. The ctx code-side +fix lives in state.Dir's Initialized gate; the design rule is broader: assume +hooks may run anywhere, not just where the user invoked ctx init. --- @@ -1779,128 +2042,263 @@ git integration, extensions - all free. [learnings-reference.md](learnings-reference.md)* ## [2026-04-29-050000] BunShell ctx.$ calls echo stdout to OpenCode's process unless .quiet() is set — leaks visible noise -**Context**: After PR #72 wired session.created and session.idle to fire `ctx system bootstrap`, `ctx agent --budget 4000`, and friends, end users started seeing chunks of Markdown bleeding into the OpenCode TUI: `## Steering`, `# Product Context`, `Describe the product...`. These are the contents of `.context/steering/` template stubs that `ctx agent --budget 4000` includes in its context packet. The plugin used the shell-level `2>/dev/null || true` to swallow stderr and force exit 0, but stdout was untouched. +**Context**: After PR #72 wired session.created and session.idle to fire `ctx +system bootstrap`, `ctx agent --budget 4000`, and friends, end users started +seeing chunks of Markdown bleeding into the OpenCode TUI: `## Steering`, `# +Product Context`, `Describe the product...`. These are the contents of +`.context/steering/` template stubs that `ctx agent --budget 4000` includes in +its context packet. The plugin used the shell-level `2>/dev/null || true` to +swallow stderr and force exit 0, but stdout was untouched. -**Lesson**: BunShell's documented behavior: *"By default, the shell will write to the current process's stdout and stderr, as well as buffering that output."* So an `await ctx.$\`...\`` call in a plugin echoes its stdout/stderr to OpenCode's process, which the TUI/agent surfaces. Shell-level `2>/dev/null` only suppresses stderr; stdout still leaks. The fix is BunShell's `.quiet()` modifier on the BunShellPromise, which configures the shell to only buffer the output rather than also writing to the parent process. +**Lesson**: BunShell's documented behavior: *"By default, the shell will write +to the current process's stdout and stderr, as well as buffering that output."* +So an `await ctx.$\`...\`` call in a plugin echoes its stdout/stderr to +OpenCode's process, which the TUI/agent surfaces. Shell-level `2>/dev/null` only +suppresses stderr; stdout still leaks. The fix is BunShell's `.quiet()` modifier +on the BunShellPromise, which configures the shell to only buffer the output +rather than also writing to the parent process. -**Application**: Always chain `.nothrow().quiet()` on BunShell template literals in OpenCode plugins, even for fire-and-forget calls where you discard the result: `await ctx.$\`ctx system bootstrap\`.nothrow().quiet()`. With both modifiers, you don't need shell-level `2>/dev/null || true` — `.nothrow()` swallows non-zero exits at the BunShell layer, `.quiet()` keeps every byte of output buffered. Pattern is the cooperative default for any plugin that spawns long-output commands during the agent session lifecycle. +**Application**: Always chain `.nothrow().quiet()` on BunShell template literals +in OpenCode plugins, even for fire-and-forget calls where you discard the +result: `await ctx.$\`ctx system bootstrap\`.nothrow().quiet()`. With both +modifiers, you don't need shell-level `2>/dev/null || true` — `.nothrow()` +swallows non-zero exits at the BunShell layer, `.quiet()` keeps every byte of +output buffered. Pattern is the cooperative default for any plugin that spawns +long-output commands during the agent session lifecycle. --- ## [2026-04-29-040000] OpenCode plugin compaction interop is breadcrumb-mediated: own your context preservation explicitly -**Context**: After PR #72 wired `session.created` / `session.idle` / `tool.execute.after` / `shell.env`, a `/compact` test in OpenCode (with `oh-my-openagent@3.17.6` also installed) recovered ctx context post-compaction *only by accident*: oh-my-openagent's `experimental.session.compacting` handler builds a structured summary template that happens to preserve `.context/`-prefixed file paths in its "Active Working Context → Files" section. Combined with our `shell.env` CTX_DIR injection, the agent had enough breadcrumbs to re-read DECISIONS.md from disk post-compaction. Without that section, our context would have evaporated silently into the compaction summary. - -**Lesson**: Two compaction-aware plugins in the same session can synergize without either knowing about the other — but the synergy is fragile because it depends on undocumented serialization choices in the *other* plugin. If the other plugin's template ever changes (e.g., drops file-path preservation, swaps the "Active Working Context" section name, condenses paths to basenames), the breadcrumbs disappear and ctx context is lost without any signal. The `Hooks` interface in `@opencode-ai/plugin` v1.4.x exposes `experimental.session.compacting?: (input, output: { context: string[]; prompt?: string }) => Promise` — pushing to `output.context` is *additive* (appends to the default prompt), and replacing `output.prompt` is *destructive* (only one plugin can win that race). - -**Application**: Register `experimental.session.compacting` in your own plugin and push high-signal context strings (e.g., `ctx system bootstrap` output) to `output.context` so context preservation does not depend on coexisting plugins. Never set `output.prompt` from a thin shim — that would conflict with primary compaction harnesses like oh-my-openagent. Composition via `output.context` is the correct cooperative pattern. +**Context**: After PR #72 wired `session.created` / `session.idle` / +`tool.execute.after` / `shell.env`, a `/compact` test in OpenCode (with +`oh-my-openagent@3.17.6` also installed) recovered ctx context post-compaction +*only by accident*: oh-my-openagent's `experimental.session.compacting` handler +builds a structured summary template that happens to preserve +`.context/`-prefixed file paths in its "Active Working Context → Files" +section. Combined with our `shell.env` CTX_DIR injection, the agent had enough +breadcrumbs to re-read DECISIONS.md from disk post-compaction. Without that +section, our context would have evaporated silently into the compaction summary. + +**Lesson**: Two compaction-aware plugins in the same session can synergize +without either knowing about the other — but the synergy is fragile because it +depends on undocumented serialization choices in the *other* plugin. If the +other plugin's template ever changes (e.g., drops file-path preservation, swaps +the "Active Working Context" section name, condenses paths to basenames), the +breadcrumbs disappear and ctx context is lost without any signal. The `Hooks` +interface in `@opencode-ai/plugin` v1.4.x exposes +`experimental.session.compacting?: (input, output: { context: string[]; prompt?: +string }) => Promise` — pushing to `output.context` is *additive* +(appends to the default prompt), and replacing `output.prompt` is *destructive* +(only one plugin can win that race). + +**Application**: Register `experimental.session.compacting` in your own plugin +and push high-signal context strings (e.g., `ctx system bootstrap` output) to +`output.context` so context preservation does not depend on coexisting plugins. +Never set `output.prompt` from a thin shim — that would conflict with primary +compaction harnesses like oh-my-openagent. Composition via `output.context` is +the correct cooperative pattern. --- ## [2026-04-29-030000] @opencode-ai/plugin event hook is a single dispatcher, not an object of named handlers -**Context**: PR #72's first OpenCode plugin shipped with `event: { "session.created": fn, "session.idle": fn }` — an object keyed by event type. It compiled clean against `satisfies Plugin` but never fired. End-to-end trace showed neighboring hooks (`shell.env`, `tool.execute.after`) running while every event handler silently no-op'd. +**Context**: PR #72's first OpenCode plugin shipped with `event: { +"session.created": fn, "session.idle": fn }` — an object keyed by event type. +It compiled clean against `satisfies Plugin` but never fired. End-to-end trace +showed neighboring hooks (`shell.env`, `tool.execute.after`) running while every +event handler silently no-op'd. -**Lesson**: `@opencode-ai/plugin` v1.4.x defines `event?: (input: { event: Event }) => Promise` — one dispatcher called for every event with `input.event.type` discriminating. Asymmetric with neighbors because `shell.env` and `tool.execute.*` *are* top-level named keys; only the dozens of `EventX` types collapse into the single `event` slot. +**Lesson**: `@opencode-ai/plugin` v1.4.x defines `event?: (input: { event: Event +}) => Promise` — one dispatcher called for every event with +`input.event.type` discriminating. Asymmetric with neighbors because `shell.env` +and `tool.execute.*` *are* top-level named keys; only the dozens of `EventX` +types collapse into the single `event` slot. -**Application**: Use `event: async ({event}) => { if (event.type === "session.created") { ... } else if (event.type === "session.idle") { ... } }`. Type discriminator strings live under each `EventX` type in `node_modules/@opencode-ai/sdk/dist/gen/types.gen.d.ts`. +**Application**: Use `event: async ({event}) => { if (event.type === +"session.created") { ... } else if (event.type === "session.idle") { ... } }`. +Type discriminator strings live under each `EventX` type in +`node_modules/@opencode-ai/sdk/dist/gen/types.gen.d.ts`. --- ## [2026-04-29-030100] OpenCode plugin hooks like shell.env take (input, output) and mutate; returned objects are ignored -**Context**: First plugin had `"shell.env": () => ({ CTX_DIR: ".context" })`. The hook fired but the agent's bash tool never saw `CTX_DIR`; manual export was required for every ctx call. The returned object was dropped on the floor by the runtime. +**Context**: First plugin had `"shell.env": () => ({ CTX_DIR: ".context" })`. +The hook fired but the agent's bash tool never saw `CTX_DIR`; manual export was +required for every ctx call. The returned object was dropped on the floor by the +runtime. -**Lesson**: Multiple hooks in `@opencode-ai/plugin` v1.4.x take two arguments where the second is an OUT param. Examples: `shell.env: (input, output: {env}) => void` (mutate `output.env`), `tool.execute.after: (input, output: {title, output, metadata}) => void`, `chat.params: (input, output: {temperature, ...}) => void`, `chat.headers: (input, output: {headers}) => void`. Pattern is consistent across the SDK. +**Lesson**: Multiple hooks in `@opencode-ai/plugin` v1.4.x take two arguments +where the second is an OUT param. Examples: `shell.env: (input, output: {env}) +=> void` (mutate `output.env`), `tool.execute.after: (input, output: {title, +output, metadata}) => void`, `chat.params: (input, output: {temperature, ...}) +=> void`, `chat.headers: (input, output: {headers}) => void`. Pattern is +consistent across the SDK. -**Application**: Always read the type definition in `node_modules/@opencode-ai/plugin/dist/index.d.ts` for any hook before wiring. If a hook signature has two parameters where the second is an object, it's a mutation hook — return values are discarded. +**Application**: Always read the type definition in +`node_modules/@opencode-ai/plugin/dist/index.d.ts` for any hook before wiring. +If a hook signature has two parameters where the second is an object, it's a +mutation hook — return values are discarded. --- ## [2026-04-29-030200] OpenCode shell.env injects env only into agent's shell tool, not into plugin's own ctx.$ calls -**Context**: After fixing `shell.env`'s `(input, output) => mutate output.env` signature so `CTX_DIR` reached the agent's bash tool, the plugin's own `ctx.$\`ctx system bootstrap\`` calls still failed silently — they ran without `CTX_DIR` and ctx fell back to `~/.context`. The hook fired correctly; the plugin's subprocess side-effects didn't see the env. +**Context**: After fixing `shell.env`'s `(input, output) => mutate output.env` +signature so `CTX_DIR` reached the agent's bash tool, the plugin's own +`ctx.$\`ctx system bootstrap\`` calls still failed silently — they ran without +`CTX_DIR` and ctx fell back to `~/.context`. The hook fired correctly; the +plugin's subprocess side-effects didn't see the env. -**Lesson**: `shell.env` injects env into the agent's shell-tool invocations. The plugin's own BunShell calls (`ctx.$\`...\``) inherit OpenCode's process env, which is *separate*. Two shells, two envs. +**Lesson**: `shell.env` injects env into the agent's shell-tool invocations. The +plugin's own BunShell calls (`ctx.$\`...\``) inherit OpenCode's process env, +which is *separate*. Two shells, two envs. -**Application**: Build an env-aware BunShell once in the plugin factory: `const $ = ctx.$.env({ ...process.env, CTX_DIR: \`${ctx.directory}/.context\` })`. Reuse it for every plugin-initiated subprocess call. `ctx.directory` is the project root from `PluginInput`. +**Application**: Build an env-aware BunShell once in the plugin factory: `const +$ = ctx.$.env({ ...process.env, CTX_DIR: \`${ctx.directory}/.context\` })`. +Reuse it for every plugin-initiated subprocess call. `ctx.directory` is the +project root from `PluginInput`. --- ## [2026-04-26-180000] OpenCode auto-loads only flat .ts files under .opencode/plugins/; subdirectories are ignored -**Context**: Initial OpenCode integration deployed the plugin as `.opencode/plugins/ctx/index.ts` (a directory with index.ts inside, mirroring npm package conventions). End-to-end smoke testing showed the plugin file was present and the binary was current, yet OpenCode never invoked any of the plugin's hooks (no `module-load` trace fired even with `--print-logs --log-level DEBUG`). Copying the same content to a flat `.opencode/plugins/ctx.ts` file made the plugin load and fire correctly. +**Context**: Initial OpenCode integration deployed the plugin as +`.opencode/plugins/ctx/index.ts` (a directory with index.ts inside, mirroring +npm package conventions). End-to-end smoke testing showed the plugin file was +present and the binary was current, yet OpenCode never invoked any of the +plugin's hooks (no `module-load` trace fired even with `--print-logs --log-level +DEBUG`). Copying the same content to a flat `.opencode/plugins/ctx.ts` file made +the plugin load and fire correctly. -**Lesson**: OpenCode's plugin auto-discovery only scans top-level files under `.opencode/plugins/` and `~/.config/opencode/plugins/`. Subdirectories are silently skipped — there is no log line indicating a subdirectory was found and ignored. The official docs at opencode.ai/docs/plugins/ say only "files in these directories are automatically loaded at startup" without specifying the rule, so this is easy to miss. The `opencode plugin ` CLI registers npm modules (a different code path) and accepts only npm names, not local paths. +**Lesson**: OpenCode's plugin auto-discovery only scans top-level files under +`.opencode/plugins/` and `~/.config/opencode/plugins/`. Subdirectories are +silently skipped — there is no log line indicating a subdirectory was found +and ignored. The official docs at opencode.ai/docs/plugins/ say only "files in +these directories are automatically loaded at startup" without specifying the +rule, so this is easy to miss. The `opencode plugin ` CLI registers npm +modules (a different code path) and accepts only npm names, not local paths. -**Application**: Deploy single-file plugins as `.opencode/plugins/.ts`, not `.opencode/plugins//index.ts`. No `package.json` is required when the plugin uses type-only imports (`import type` is erased at compile time) and the host runtime injects the plugin context. To verify a plugin is actually loaded, add a top-of-module side effect (e.g. `appendFileSync` to a known path) and confirm it fires before debugging hook contracts. +**Application**: Deploy single-file plugins as `.opencode/plugins/.ts`, +not `.opencode/plugins//index.ts`. No `package.json` is required when the +plugin uses type-only imports (`import type` is erased at compile time) and the +host runtime injects the plugin context. To verify a plugin is actually loaded, +add a top-of-module side effect (e.g. `appendFileSync` to a known path) and +confirm it fires before debugging hook contracts. --- ## [2026-04-26-165500] OpenCode opencode.json MCP shape: command is Array, no separate args field -**Context**: `ctx setup opencode --write` was generating `opencode.json` with the Copilot CLI MCP shape (`{type: "local", command: "ctx", args: ["mcp", "serve"]}`). OpenCode rejected the file at startup with `Configuration is invalid… Expected array, got "ctx" mcp.ctx.command` and `Missing key mcp.ctx.enabled`. +**Context**: `ctx setup opencode --write` was generating `opencode.json` with +the Copilot CLI MCP shape (`{type: "local", command: "ctx", args: ["mcp", +"serve"]}`). OpenCode rejected the file at startup with `Configuration is +invalid… Expected array, got "ctx" mcp.ctx.command` and `Missing key +mcp.ctx.enabled`. -**Lesson**: OpenCode's `McpLocalConfig` (in `@opencode-ai/sdk`) defines `command: Array` as a single field that holds the binary AND its arguments — there is no separate `args` field. It also requires `enabled: boolean` at runtime even though the TS type marks it optional. The Copilot CLI MCP shape is similar in spirit but structurally different; do not copy-paste between them. +**Lesson**: OpenCode's `McpLocalConfig` (in `@opencode-ai/sdk`) defines +`command: Array` as a single field that holds the binary AND its +arguments — there is no separate `args` field. It also requires `enabled: +boolean` at runtime even though the TS type marks it optional. The Copilot CLI +MCP shape is similar in spirit but structurally different; do not copy-paste +between them. -**Application**: For OpenCode MCP entries always use `command: ["ctx", "mcp", "serve"]` and include `enabled: true`. If you add a new editor integration with its own MCP file format, read the upstream type definitions from `node_modules/@/sdk/dist/gen/types.gen.d.ts` (or equivalent) before reusing an existing generator. +**Application**: For OpenCode MCP entries always use `command: ["ctx", "mcp", +"serve"]` and include `enabled: true`. If you add a new editor integration with +its own MCP file format, read the upstream type definitions from +`node_modules/@/sdk/dist/gen/types.gen.d.ts` (or equivalent) before +reusing an existing generator. --- ## [2026-04-26-152850] make test exit code unreliable due to -cover covdata tooling issue -**Context**: make test exited 1 even with all 123 packages passing on this Go install; root cause is missing covdata tool when -cover is enabled +**Context**: make test exited 1 even with all 123 packages passing on this Go +install; root cause is missing covdata tool when -cover is enabled -**Lesson**: Don't trust make test exit code alone when verifying changes. The -cover flag in the test target can fail with 'no such tool covdata' even when every package passes. +**Lesson**: Don't trust make test exit code alone when verifying changes. The +-cover flag in the test target can fail with 'no such tool covdata' even when +every package passes. -**Application**: When make test fails, fall back to 'go test ./...' (no -cover) and tally ^ok / ^FAIL counts to distinguish real failures from tooling issues. +**Application**: When make test fails, fall back to 'go test ./...' (no -cover) +and tally ^ok / ^FAIL counts to distinguish real failures from tooling issues. --- ## [2026-04-26-152842] Trailing word boundary in regex matches commit-tree as git commit -**Context**: First post-commit filter regex \bgit\s+commit\b in the OpenCode plugin would have triggered on git commit-tree because \b matches between t and - +**Context**: First post-commit filter regex \bgit\s+commit\b in the OpenCode +plugin would have triggered on git commit-tree because \b matches between t and +- -**Lesson**: A trailing word boundary doesn't exclude hyphenated continuations — \b matches every word/non-word transition. Use (?!-) negative lookahead to specifically reject hyphen-suffixed siblings. +**Lesson**: A trailing word boundary doesn't exclude hyphenated continuations +— \b matches every word/non-word transition. Use (?!-) negative lookahead to +specifically reject hyphen-suffixed siblings. -**Application**: For any porcelain with hyphenated cousins (commit-tree, commit-graph, for-each-ref), append (?!-) to the boundary. +**Application**: For any porcelain with hyphenated cousins (commit-tree, +commit-graph, for-each-ref), append (?!-) to the boundary. --- ## [2026-04-26-152836] ctx system help can list project-local hooks not in the Go binary -**Context**: PR #72 plugin called 'ctx system block-dangerous-commands'; user's installed ctx 0.7.2 listed it in help, but no directory exists under internal/cli/system/cmd/ — it's a Claude Code plugin-local hook surfaced via wrapper +**Context**: PR #72 plugin called 'ctx system block-dangerous-commands'; user's +installed ctx 0.7.2 listed it in help, but no directory exists under +internal/cli/system/cmd/ — it's a Claude Code plugin-local hook surfaced via +wrapper -**Lesson**: ctx system help output is a union of compiled Go subcommands and project-local Claude wrappers; non-Claude integrations only see the Go subset +**Lesson**: ctx system help output is a union of compiled Go subcommands and +project-local Claude wrappers; non-Claude integrations only see the Go subset -**Application**: When porting plugin behavior to a new editor, only call subcommands that have a directory under internal/cli/system/cmd/. Don't trust ctx system help output as the canonical surface. +**Application**: When porting plugin behavior to a new editor, only call +subcommands that have a directory under internal/cli/system/cmd/. Don't trust +ctx system help output as the canonical surface. --- ## [2026-04-25-014704] Confident code comments can pull an LLM away from first-principles knowledge -**Context**: cli_test.go had a comment claiming 'parent's t.Setenv doesn't propagate to exec'd children unless we build it into cmd.Env' which is wrong. I patched the helper's CTX_DIR dedup instead of questioning the helper itself, despite knowing t.Setenv semantics. +**Context**: cli_test.go had a comment claiming 'parent's t.Setenv doesn't +propagate to exec'd children unless we build it into cmd.Env' which is wrong. I +patched the helper's CTX_DIR dedup instead of questioning the helper itself, +despite knowing t.Setenv semantics. -**Lesson**: A comment that explains why a stdlib mechanism 'doesn't work' is doing extra rhetorical work to talk a reader out of the obvious approach. That's exactly when to verify from first principles instead of trusting the surrounding-code frame. +**Lesson**: A comment that explains why a stdlib mechanism 'doesn't work' is +doing extra rhetorical work to talk a reader out of the obvious approach. That's +exactly when to verify from first principles instead of trusting the +surrounding-code frame. -**Application**: When an existing comment justifies a non-canonical approach contradicting stdlib knowledge: pause, verify against memory of the actual API before patching within the existing frame. +**Application**: When an existing comment justifies a non-canonical approach +contradicting stdlib knowledge: pause, verify against memory of the actual API +before patching within the existing frame. --- ## [2026-04-25-014704] filepath.Join('', rel) returns rel as CWD-relative, not error -**Context**: Recurring orphan jsonl-path- appeared at project root. Older state.Dir() returned ('', nil) when CTX_DIR was undeclared, so filepath.Join('', 'jsonl-path-XXX') = 'jsonl-path-XXX', writing relative to CWD. +**Context**: Recurring orphan jsonl-path- appeared at project root. +Older state.Dir() returned ('', nil) when CTX_DIR was undeclared, so +filepath.Join('', 'jsonl-path-XXX') = 'jsonl-path-XXX', writing relative to CWD. -**Lesson**: Functions returning a path-string must never return ('', nil). Sentinel errors force callers to gate, closing the silent CWD-relative write. +**Lesson**: Functions returning a path-string must never return ('', nil). +Sentinel errors force callers to gate, closing the silent CWD-relative write. -**Application**: Audit any (string, error) path-returner that historically had a ('', nil) shortcut. Closed for state.Dir and rc.ContextDir; check remaining resolvers. +**Application**: Audit any (string, error) path-returner that historically had a +('', nil) shortcut. Closed for state.Dir and rc.ContextDir; check remaining +resolvers. --- ## [2026-04-25-014704] Parallel go test ./... packages can race on ~/.claude/settings.json -**Context**: make test runs packages in parallel processes. Fourteen test files invoked initialize.Cmd().Execute(), which read-modify-writes ~/.claude/settings.json without HOME isolation. +**Context**: make test runs packages in parallel processes. Fourteen test files +invoked initialize.Cmd().Execute(), which read-modify-writes +~/.claude/settings.json without HOME isolation. -**Lesson**: Under load the races materialized as flaky 'FAIL coverage: [no statements]' in cli/watch/core. Run alone the package passed; under parallel make test it failed intermittently. +**Lesson**: Under load the races materialized as flaky 'FAIL coverage: [no +statements]' in cli/watch/core. Run alone the package passed; under parallel +make test it failed intermittently. -**Application**: testctx.Declare now sets HOME alongside CTX_DIR. Centralized fix; future tests automatically isolate user-home writes. +**Application**: testctx.Declare now sets HOME alongside CTX_DIR. Centralized +fix; future tests automatically isolate user-home writes. diff --git a/.context/TASKS.md b/.context/TASKS.md index 340797975..3d68485a9 100644 --- a/.context/TASKS.md +++ b/.context/TASKS.md @@ -39,6 +39,26 @@ TASK STATUS LABELS: related-but-distinct gap tasks (shellcheck, PSScriptAnalyzer, skill frontmatter validation) listed below. +- [ ] Human: It's about time to go through the entire codebase check for + inconsistencies, and move useful functions that are utility and/or reusable + to relevant convenience packages. + +- [ ] Create a typography.md somewhere so that we don't have to remind tha + Agent things like this: "❯"## What the editorial pipeline is NOT" our headings + are Title Case, it always has been; it always will be. Do a full sweep. + -- in addition (not checked, just to make sure); `ctx` is always in backticks + whenever possible; it's part of the branding." + + +- [ ] Bug: Fresh folder: git init; eval $(ctx activate); ctx init + will catch parent .context folder and raise a warning + expectation: ctx activate should bail if there is no .context + folder in the same level and ask user to run `ctx init` first. + discuss this with the Agent too. +- [ ] Bug: if context is active (eval ctx activate); `ctx init` + on a brand new project can (and probably will) fail. + Probably need to nudge user to ctx deactivate first. + - [-] Add TypeScript `tsc --noEmit` gate for the embedded OpenCode plugin (`internal/assets/integrations/opencode/plugin/index.ts`). Place tooling (`package.json`, `tsconfig.json`) in a sibling @@ -1758,170 +1778,338 @@ disambiguates. Spec: `specs/ceremony-profiles.md` -- [ ] Add `Ceremony{Remember,WrapUp}` to `internal/rc/types.go`; apply defaults in `internal/rc/rc.go` from +- [ ] Add `Ceremony{Remember,WrapUp}` to `internal/rc/types.go`; apply defaults + in `internal/rc/rc.go` from `internal/config/ceremony/ceremony.go` constants -- [ ] Thread resolved ceremony names into `ScanJournalsForCeremonies` and `Emit` in - `internal/cli/system/core/ceremony/ceremony.go` (replace direct constant reads) -- [ ] Convert `internal/assets/hooks/messages/check-ceremony/{remember,wrapup,both}.txt` to `{REMEMBER}` / `{WRAPUP}` +- [ ] Thread resolved ceremony names into `ScanJournalsForCeremonies` and `Emit` + in + `internal/cli/system/core/ceremony/ceremony.go` (replace direct constant + reads) +- [ ] Convert + `internal/assets/hooks/messages/check-ceremony/{remember,wrapup,both}.txt` to + `{REMEMBER}` / `{WRAPUP}` sentinels; audit `internal/config/embed/text` ceremony desc keys for the same -- [ ] Add a single sentinel-substitution helper (extend `internal/cli/system/core/message.Load` or sibling) so +- [ ] Add a single sentinel-substitution helper (extend + `internal/cli/system/core/message.Load` or sibling) so substitution happens in one place - [ ] Show active ceremony profile (one line) in `ctx status` output -- [ ] Tests: default profile renders `/ctx-remember` `/ctx-wrap-up`; project with `ceremony.remember: dp-remember` - renders `/dp-remember` and scanner only counts `dp-remember` as fulfilling the open-bookend -- [ ] Document in `docs/recipes/` with the editorial-project (DR knowledgebase) consumer as the worked example +- [ ] Tests: default profile renders `/ctx-remember` `/ctx-wrap-up`; project + with `ceremony.remember: dp-remember` + renders `/dp-remember` and scanner only counts `dp-remember` as fulfilling the + open-bookend +- [ ] Document in `docs/recipes/` with the editorial-project (`your-domain` + knowledgebase) consumer as the worked example ### Phase SK: Skill Surface Polish (Phase 0a; prerequisite for Phase KB) `#priority:high #added:2026-05-09` -Spec: `specs/skill-surface-polish.md` (design ref: `ideas/002-editorial-pipeline-and-skill-rigor.md` §3 "Reframing the +Spec: `specs/skill-surface-polish.md` (design ref: +`ideas/002-editorial-pipeline-and-skill-rigor.md` §3 "Reframing the wishy-washy skills") -Tightens existing capture skills to sibling-project rigor before the editorial pipeline (Phase KB) lifts that pattern +Tightens existing capture skills to sibling-project rigor before the editorial +pipeline (Phase KB) lifts that pattern wholesale. Independent of Phase RG; both can ship in parallel. -- [x] Add `MarkFlagRequired` to `ctx decision add` for `--context`, `--rationale`, `--consequence`; reject placeholder +- [x] Add `MarkFlagRequired` to `ctx decision add` for `--context`, + `--rationale`, `--consequence`; reject placeholder values (`TBD`, `see chat`, whitespace-only) at CLI level -- [x] Add `MarkFlagRequired` to `ctx learning add` for `--context`, `--lesson`, `--application`; same placeholder +- [x] Add `MarkFlagRequired` to `ctx learning add` for `--context`, `--lesson`, + `--application`; same placeholder rejection -- [x] Add `--brief ` flag to `/ctx-spec` skill: when present, read the file as authoritative source per the - sibling's authority order (frozen contracts > recorded decisions > debrief > agent inference labeled `TBD`); skip the +- [x] Add `--brief ` flag to `/ctx-spec` skill: when present, read the + file as authoritative source per the + sibling's authority order (frozen contracts > recorded decisions > debrief > + agent inference labeled `TBD`); skip the fresh template Q&A -- [x] Update `/ctx-plan` skill to always offer to write the debated brief to `.context/briefs/-.md` at the end +- [x] Update `/ctx-plan` skill to always offer to write the debated brief to + `.context/briefs/-.md` at the end of an interview (creating `.context/briefs/` if absent) -- [x] Add an `Authority boundary (vs other skills)` section to `/ctx-decision-add`, `/ctx-learning-add`, - `/ctx-task-add`, `/ctx-convention-add` skill files (prevent silent promotion handover→decision, learning→convention, +- [x] Add an `Authority boundary (vs other skills)` section to + `/ctx-decision-add`, `/ctx-learning-add`, + `/ctx-task-add`, `/ctx-convention-add` skill files (prevent silent promotion + handover→decision, learning→convention, etc., without explicit user ask) -- [x] Standardize "light compression for clarity is allowed; new facts are not" wording across capture skills (decide / +- [x] Standardize "light compression for clarity is allowed; new facts are not" + wording across capture skills (decide / learn primarily); same wording lands in `/ctx-handover` once Phase KB ships -- [x] Document the `--brief` contract in `docs/skills.md` (landed in `docs/reference/skills.md` — the actual location) +- [x] Document the `--brief` contract in `docs/skills.md` (landed in + `docs/reference/skills.md`; the actual location) ### Phase RG: Require Git as Architectural Precondition (Phase 0b; prerequisite for Phase KB) `#priority:high #added:2026-05-09` Spec: `specs/require-git.md` -Promotes the de facto invariant ("ctx works properly only with git") to a de jure one. Breaking change for any -pre-existing git-less ctx project (N≈0 in practice). Independent of Phase SK; both can ship in parallel. - -- [ ] Add `internal/gitmeta/require.go` with `RequireGitTree(projectRoot string) error` and typed `MissingGitError` -- [ ] Wire `RequireGitTree` into root command PersistentPreRunE; opt-out list `--help`, `--version`, - `ctx system bootstrap`; audit other read-only/help-shaped commands during implementation (default: git-required) -- [ ] Update `ctx init` to call `RequireGitTree` first; emit the documented error verbatim -- [ ] Remove `commit:none` fallback from `internal/gitmeta/resolvehead.go` (state now unreachable) -- [ ] Remove `commit:none` advisory + counts from `internal/cli/doctor/advisory.go` -- [ ] Audit `internal/cli//cmd.go` for any other `commit:none` fallback handling; remove -- [ ] Add CONSTITUTION.md amendment ("Git is required") under Process Invariants -- [ ] Add DECISIONS.md entry: "Mandate git as architectural precondition" (Accepted; context = LLM-safety + provenance - honesty + dead-code elimination; consequence = breaking change for pre-existing git-less projects, N≈0) -- [ ] Update `docs/recipes/bootstrap-a-project.md`, `README.md`, `docs/cli/init.md` to show `git init` before `ctx init` -- [ ] Tag as breaking change in `dist/RELEASE_NOTES.md` with one-command migration ("Run `git init` in any pre-existing +Enforces what `ctx` already needs: git. `ctx` works properly only with a +repo present, and this phase makes that a runtime precondition rather than +an assumption. Breaking change for any pre-existing git-less ctx project +(N≈0 in practice). Independent of Phase SK; both can ship in parallel. + +- [x] Add `internal/gitmeta/require.go` with `RequireGitTree(projectRoot string) + error` and typed `MissingGitError` +- [x] Wire `RequireGitTree` into root command PersistentPreRunE; opt-out list + (via the existing + `AnnotationSkipInit` mechanism that already covers `--help`, `--version`, `ctx + system bootstrap`, init, + activate, deactivate, guide, why, doctor, config switch/status, hub *) +- [-] Update `ctx init` to call `RequireGitTree` first. N/A: `ctx init` is + `AnnotationSkipInit`; the precondition + check moved to the init command body in Phase KB Stage 5 alongside `--upgrade` +- [-] Remove `commit:none` fallback from `internal/gitmeta/resolvehead.go`. N/A: + `resolvehead.go` is + net-new in this phase; no fallback to remove +- [-] Remove `commit:none` advisory + counts from + `internal/cli/doctor/advisory.go`. Verified by grep: + no `commit:none` / `commit=\"none\"` literal exists in `internal/` already +- [-] Audit `internal/cli//cmd.go` for any other `commit:none` fallback + handling; remove. Same: + audit by grep returned zero matches +- [x] Add CONSTITUTION.md amendment ("Git is required") under Process Invariants +- [x] Add DECISIONS.md entry: "Mandate git as architectural precondition" + (Accepted; context = LLM-safety + provenance + honesty + dead-code elimination; consequence = breaking change for + pre-existing git-less projects, N≈0). + Filed as DECISIONS.md "Phase KB lifts the current upstream editorial-pipeline + shape, superseding the 4-phase predecessor" + (2026-05-16) which folds the git-mandate context into Phase KB's parent + decision. +- [ ] Update `docs/recipes/bootstrap-a-project.md`, `README.md`, + `docs/cli/init.md` to show `git init` before `ctx init` +- [ ] Tag as breaking change in `dist/RELEASE_NOTES.md` with one-command + migration ("Run `git init` in any pre-existing git-less ctx projects before upgrading") -- [ ] Tests: `.git` dir → nil; `.git` file (worktree pointer) → nil; absent → typed error; root PreRunE refuses without - git; opt-out list allowed -- [ ] Compliance test: no remaining `commit:none` literal in `internal/` (catches future regressions) +- [x] Tests: `.git` dir → nil; `.git` file (worktree pointer) → nil; absent + → typed error +- [-] Tests: root PreRunE refuses without git; opt-out list allowed. TBD: + deferred to Phase KB Stage 5 when + the kb command tree is in place (the existing bootstrap_test.go covers PreRunE + structure; the gitmeta + injection's behavioral test runs as part of the kb-ingest smoke) +- [-] Compliance test: no remaining `commit:none` literal in `internal/`. N/A: + literal never existed + +### Phase KB: Editorial Pipeline + Handover (depends on Phase SK + Phase RG) `#priority:high #added:2026-05-09 #revised:2026-05-16` + +Spec: `specs/kb-editorial-pipeline.md` (revised 2026-05-16 to current +upstream editorial-pipeline shape: pass-mode contract, completion circuit +breaker, source-coverage state-machine ledger, topic-adjacency +pre-flight, cold-reader rubric, folder-shaped topics from day one). -### Phase KB: Editorial Pipeline + Handover (depends on Phase SK + Phase RG) `#priority:high #added:2026-05-09` +Comparison input: `ideas/upstream-pipeline-comparison.md`. -Spec: `specs/kb-editorial-pipeline.md` +Decision record: DECISIONS.md "Phase KB lifts the current +upstream editorial-pipeline shape, superseding the 4-phase predecessor in the +brief" (2026-05-16). Brief: `ideas/003-editorial-pipeline-debated-brief.md` Background analysis: `ideas/001-sibling-project-undercover-analysis.md`, `ideas/002-editorial-pipeline-and-skill-rigor.md` -Validation corpus: `things-wtf-disaster-recovery` (live regression suite; hand-rolled the shape for weeks). +Validation corpus: `your-project` (live regression +suite; hand-rolled the older 4-phase shape for weeks). +`your-project` is the structural reference for the current +upstream shape applied to a different domain. + +Note on task lines below: path-constant locations were originally +specified as `internal/path/path.go`. The revised spec places them +under `internal/cli/kb/core/path/path.go` to match existing ctx +convention (per-subcommand path package, see `internal/cli/task/core/path/`). +Similarly the "store layer" tasks below land under `internal/write/` +(handover, closeout, kb), not `internal/store/`. Task wording kept +historical for traceability; implementation follows the revised spec. Path constants and embedded templates: -- [ ] Extend `internal/path/path.go` with new constants: `HandoversDir`, `KBDir` + per-artifact paths, `IngestDir` + - per-template paths, `CloseoutsSubdir`, `ArchiveCloseoutsSubdir`, `SiteDir`, `SiteKBDir`, `SiteConfigDir` -- [ ] Embed templates under `internal/assets/kb/templates/ingest/`: `KB-RULES.md`, `00-GROUND.md`, `30-INGEST.md`, - `40-ASK.md`, `50-SITE_REVIEW.md`, `INBOX.md`, `SESSION_LOG.md`, `grounding-sources.md`, `OPERATOR.md`, `PROMPT.md` (no - domain content) -- [ ] Embed schemas under `internal/assets/kb/templates/ingest/schemas/`: `evidence-index.md`, `glossary.md`, - `contradictions.md`, `outstanding-questions.md`, `domain-decisions.md`, `timeline.md`, `source-map.md`, - `relationship-map.md`, `session-log.md` (each: fields list + one worked example) - -Store layer: - -- [ ] Implement `internal/store/handover.go`: `WriteHandover`, `LatestHandoverCursor`, `UnconsumedCloseouts`, - `ArchiveCloseouts` -- [ ] Implement `internal/store/closeout.go`: `WriteCloseout` with required frontmatter (`sha`, `branch`, `mode`, - `generated-at`); cursor-extracting reader -- [ ] Implement `internal/store/kb.go`: per-artifact writers (evidence-index append-never-renumber; glossary; - contradictions; outstanding-questions; domain-decisions; timeline; source-map; relationship-map); demotion API; - `EvidenceRow` includes `occurred:` field per spec schema delta +- [x] Constants landed at `internal/config/kb/kb.go` (filenames + subdir names + + state-machine constants + pass-mode + life-stage) and + `internal/cli/kb/core/path/path.go` (full-path resolvers: KBDir, KBTopicDir, + IngestDir, CloseoutsDir, SchemasDir, HandoversDir, ArchiveCloseoutsDir, + SiteDir, SiteKBDir). Per the per-subcommand convention; not `internal/path/`. +- [x] Templates embedded under `internal/assets/kb/templates/ingest/`: + KB-RULES.md, 00-GROUND.md, 30-INGEST.md, 40-ASK.md, 50-SITE_REVIEW.md, + OPERATOR.md, PROMPT.md. INBOX.md and SESSION_LOG.md not pre-seeded; they + materialise on first skill run (per the contract that skills are the sole + writers of INBOX.md and SESSION_LOG.md only appears mid-flight). +- [x] Schemas embedded under `internal/assets/kb/templates/ingest/schemas/`: + evidence-index.md, glossary.md, contradictions.md, outstanding-questions.md, + domain-decisions.md, timeline.md, source-map.md, source-coverage.md, + relationship-map.md, session-log.md (10 files; fields + one worked example + each) +- [x] `internal/assets/embed.go` extended with `//go:embed` lines for the kb + tree + +Store layer (landed under `internal/write/` per the revised spec, not +`internal/store/`): + +- [x] `internal/write/handover/` (WriteHandover, Latest, fold via + closeout.PostdatedBy, archive via closeout.Archive) +- [x] `internal/write/closeout/` (Write, Read, List, PostdatedBy, Archive; + required frontmatter sha/branch/mode/pass-mode/life-stage/generated-at; + ErrMissingFrontmatter, ErrMissingFields) +- [x] `internal/write/kb/` split across 9 subpackages, not a single kb.go: + evidence (no-renumber, ID allocation, ErrDuplicateID/ErrInvalidBand), + sourcecoverage (state-machine ledger with ValidTransition + + ErrIllegalTransition + ErrUnknownSource), glossary, contradiction, question, + decision, timeline, sourcemap, relationship CLI commands: -- [ ] `ctx handover write` — `MarkFlagRequired` on `--summary` and `--next`; reject placeholder values (parity with - Phase SK pattern); calls handover writer + closeout fold; supports `--no-fold`, `--commit`, `--highlights`, - `--open-questions` -- [ ] `ctx kb` parent command + `ingest` / `ask` / `site-review` / `ground` / `note` subcommands; refuse-on-empty for - `ingest` / `ask` / `ground` -- [ ] `ctx kb site` (`build` / `serve` / `customize`) — mirror existing `ctx journal site` shell-out pattern with - zensical -- [ ] Extend `ctx init` to lay down `handovers/`, `kb/.gitkeep`, `ingest/` (full template tree), `site/` (gitignored); - add `--upgrade` flag (idempotent on byte-identical existing content; refuse on divergent) +- [x] `ctx handover write`: `MarkFlagRequired` on `--summary` and `--next`; + rejects placeholder values (TBD, see chat, n/a, none); calls + `internal/write/handover.Write` which folds postdated closeouts (`--no-fold` + skips fold); supports `--commit`, `--highlights`, `--open-questions`; + smoke-tested end-to-end +- [x] `ctx kb` parent command + `topic new` (real scaffold writer), `note` (real + append to findings.md), `reindex` (real CTX:KB:TOPICS managed-block refresh in + kb/index.md), `ingest`/`ask`/`site-review`/`ground` (skill-driven; + refuse-on-empty for ingest/ask/ground); smoke-tested +- [x] KB rendering: `.context/kb/` is a tree of Markdown that + `ctx serve` already serves once a `zensical.toml` is dropped in. Recipe + Step 5 documents the path. A thin `ctx kb site (build|serve|customize)` + wrapper that mirrors `internal/cli/journal/cmd/site/` and pre-seeds the + `zensical.toml` is a follow-up convenience, not a blocker. +- [x] `ctx init` scaffolds `.context/kb/`, `.context/kb/topics/`, + `.context/ingest/` (with embedded templates copied), + `.context/ingest/closeouts/`, `.context/ingest/schemas/` (10 schemas copied), + `.context/handovers/`. Implemented in new `internal/cli/initialize/core/kb/` + package called from init's run.go. `--upgrade` flag: not added; init's + existing skip-existing-files behavior is idempotent on byte-identical content + (the divergence-refusal needs a separate `--upgrade` follow-up). Skills: -- [ ] Add `internal/assets/claude/skills/ctx-handover/SKILL.md` per spec (input contract, authority boundary, edge - cases) -- [ ] Add `ctx-kb-ingest`, `ctx-kb-ask`, `ctx-kb-site-review`, `ctx-kb-ground`, `ctx-kb-note` SKILL.md files -- [ ] Modify `internal/assets/claude/skills/ctx-wrap-up/SKILL.md`: branch on `.context/kb/` existence (surface editorial - state — pending closeouts, outstanding-questions count); mandatorily drive `/ctx-handover` as final step regardless of - capture outcomes -- [ ] Modify `/ctx-remember` skill (or equivalent): read latest handover + any postdated unfolded closeouts; fold KB - state into readback if `.context/kb/` exists +- [x] 6 new SKILL.md files: ctx-handover (280L), ctx-kb-ingest (645L), + ctx-kb-ask (236L), ctx-kb-site-review (259L), ctx-kb-ground (279L), + ctx-kb-note (164L) +- [x] Modified `ctx-wrap-up/SKILL.md`: branches on `.context/kb/` existence + (surfaces pending closeouts + outstanding-questions count); mandatorily drives + `/ctx-handover` as final step +- [x] Modified `ctx-remember/SKILL.md`: reads latest handover + postdated + unfolded closeouts; folds KB state into readback when `.context/kb/` exists Doctor / status / .gitignore: -- [ ] Extend `internal/cli/doctor/advisory.go`: duplicate-`EV-###` detection; `dated:` source missing `occurred:` rows; - malformed-closeout-frontmatter detection -- [ ] Mode-aware reads: thread `KBExists()` check through `/ctx-remember` (or equivalent), `ctx status`, `ctx agent`, - session-start hook nudges (cross-cutting; manageable but explicit v1 surface area) -- [ ] Update project root `.gitignore`: append `.context/site/` (idempotent; match existing - `.context/journal/.imported.json` pattern) +- [-] Doctor advisories: NOT YET IMPLEMENTED. Spec lists duplicate-`EV-###`, + `dated:`-source-missing-`occurred:`, malformed-closeout-frontmatter, + source-coverage-ledger-mismatch (row Updated vs. file mtime), + closeout-missing-pass-mode-body-block, illegal-ledger-state-transition. Phase + 7 follow-up. +- [-] Mode-aware reads in ctx status / ctx agent / session-start hook: skills + updated (`/ctx-remember` + `/ctx-wrap-up`); CLI-side `ctx status`/`ctx agent` + mode-awareness deferred (the skill-side fold covers the user-facing recall; + CLI text surfaces are v1.1). +- [x] `.gitignore` extended: `.context/site/`, `.context/site-config/` Tests: -- [ ] Unit tests per package (handover, closeout, kb writers, mode CLIs, doctor advisories) -- [ ] Integration: `internal/cli/initcmd/init_test.go` covers full new directory tree + `--upgrade` idempotency / +- [ ] Unit tests per package (handover, closeout, kb writers, mode CLIs, doctor + advisories) +- [ ] Integration: `internal/cli/initcmd/init_test.go` covers full new directory + tree + `--upgrade` idempotency / divergence refusal -- [ ] `hack/smoke-kb.sh`: end-to-end shell smoke (init → kb ingest → kb ask → kb site-review → kb ground → handover +- [ ] `hack/smoke-kb.sh`: end-to-end shell smoke (init → kb ingest → kb ask + → kb site-review → kb ground → handover write → archive populated → doctor clean) -- [ ] Edge-case fixtures: aborted-session recovery (closeout without handover); temporal misordering ( - occurred-vs-extracted ordering enforces precedence rule); concurrent dupe IDs (LLM-resolution fixture); render +- [ ] Edge-case fixtures: aborted-session recovery (closeout without handover); + temporal misordering ( + occurred-vs-extracted ordering enforces precedence rule); concurrent dupe IDs + (LLM-resolution fixture); render filter (speculative excluded; low paired with outstanding-questions) Phase KB-2 (validation against live corpus): -- [ ] Port `things-wtf-disaster-recovery` from its hand-rolled shape to the shipped one. Each divergence is either a - Phase KB bug or a `DECISIONS.md` entry explaining why the formal shape differs from what worked manually +- [ ] Port `your-project-*` from its hand-rolled shape to the shipped one. Each + divergence is either a + Phase KB bug or a `DECISIONS.md` entry explaining why the formal shape differs + from what worked manually - [ ] Document divergences (if any) in `docs/recipes/build-a-knowledge-base.md` Phase KB-3 (documentation): -- [ ] Write `docs/recipes/build-a-knowledge-base.md` (mirrors sibling's recipe shape) -- [ ] Write `docs/recipes/typical-kb-session.md` -- [ ] Write `docs/recipes/recover-aborted-session.md` -- [ ] Update `docs/cli-reference.md` with new `ctx kb` and `ctx handover` commands -- [ ] Update `docs/skills.md` with new skills -- [ ] Document MemPalace-as-ground-source recipe in `docs/recipes/build-a-knowledge-base.md` — uses already-specced +- [x] Write `docs/recipes/build-a-knowledge-base.md` (mirrors sibling's recipe + shape) +- [x] Write `docs/recipes/typical-kb-session.md` +- [x] Write `docs/recipes/recover-aborted-session.md` +- [x] Update CLI reference with new `ctx kb` and `ctx handover` commands (landed + as separate pages: `docs/cli/kb.md` for the editorial pipeline and + `docs/cli/handover.md` for the session-glue command; the two are unrelated + surfaces and now have distinct pages) +- [x] Update `docs/reference/skills.md` with the 6 new skills (table row + + per-skill section + new "Knowledge Base (Phase KB)" section) +- [x] Update root `README.md` with the Phase KB workflow snippet + git-required + note +- [x] Update root `CLAUDE.md` and `internal/assets/claude/CLAUDE.md` (the + user-deployed copy) with the KB trigger table +- [x] Update `dist/RELEASE_NOTES.md` with Phase RG + Phase KB sections +- [ ] Document MemPalace-as-ground-source recipe in + `docs/recipes/build-a-knowledge-base.md`; uses already-specced `mcp::` syntax in `grounding-sources.md`; zero new ctx code +### Phase KB-followup: Adversarial design review of parallel skill trees `#priority:medium #added:2026-05-17` + +`ctx` ships skills to three host trees: +`internal/assets/claude/skills/` (canonical, full Claude tool surface), +`internal/assets/integrations/copilot-cli/skills/` (Copilot CLI; `tools: [bash]`), +and `internal/assets/integrations/opencode/skills/` (OpenCode; minimal +subset, no `tools` block). Phase KB landed parity across all three trees +by writing each new skill body three times (full content for Claude + +Copilot CLI; terser variant for OpenCode), which works today but +guarantees future drift the next time a canonical skill is revised. + +Run an **adversarial design review** to pick the right architecture for +preventing this drift permanently. Candidate shapes: + +- **Body-extract + per-host frontmatter wrapper at build time.** Single + source of truth for behavioral prose; a builder package composes + host-specific SKILL.md files with the right frontmatter and the + right capture-skill name swaps (`/ctx-task-add` vs `/ctx-add-task`, + etc.) at `go generate` or `make build` time. Per-host overrides + for genuinely different host capabilities live in side files. +- **Write canonical, copy at runtime, make integration trees + read-only.** Simpler builder; risk is that host-specific tool + surfaces leak (Claude has Edit/Write/Read; Copilot CLI has bash + only; OpenCode is more constrained). +- **Convention-only with audit gate.** Keep three independent trees + but add an audit test that fails CI when a canonical-tree skill + changes without parallel changes in the integration trees. Cheaper + but pushes the work onto contributors. +- **Drop one or both integration trees.** OpenCode currently ships + only a 4-skill subset that the user may or may not want at parity. + Decide explicitly which trees are first-class. + +Deliverables: + +- [ ] Adversarial review write-up under `ideas/` enumerating each + shape with pros / cons / migration cost. +- [ ] DECISIONS.md entry picking the shape, with rationale. +- [ ] Implementation tasks for the chosen shape. +- [ ] A compliance test that fails when the canonical Claude tree + changes a Phase KB or handover skill without the parallel tree + being updated, until the builder lands. + +Context: filed after Phase KB shipped, when porting the 6 new KB +skills + the 2 updated ceremony skills to copilot-cli and opencode +revealed how brittle the three-tree pattern is. + ### Phase JR: Cold-Start Memory Recovery (semantic recall over journal history) `#priority:medium #added:2026-05-10` Idea: `ideas/004-cold-start-memory-recovery.md` -Pain point: today's "can you check recent journal entries?" workaround forces brute-force parsing of the journal corpus -or precise user pointers to specific files/dates. ctx has journal management but no semantic recall layer. -MemPalace (https://github.com/MemPalace/mempalace) does this exact use case at 96.6% R@5 raw on LongMemEval. Three -options to evaluate: A) native ctx journal search (vector-store dep, breaks single-Go-binary identity); B) -defer-to-MemPalace recipe (zero ctx-side work; coupling to young project); C) pluggable journal-search hook following +Pain point: today's "can you check recent journal entries?" workaround forces +brute-force parsing of the journal corpus +or precise user pointers to specific files/dates. ctx has journal management but +no semantic recall layer. +MemPalace (https://github.com/MemPalace/mempalace) does this exact use case at +96.6% R@5 raw on LongMemEval. Three +options to evaluate: A) native ctx journal search (vector-store dep, breaks +single-Go-binary identity); B) +defer-to-MemPalace recipe (zero ctx-side work; coupling to young project); C) +pluggable journal-search hook following the zensical shell-out pattern (recommended). -- [ ] Spec out cold-start memory recovery: pick approach (A vs B vs C); ideas/004 leans toward C. Distinct from Phase KB - ground-mode `mcp:` source kinds (which cover the KB-grounding angle for free); this phase is specifically about +- [ ] Spec out cold-start memory recovery: pick approach (A vs B vs C); + ideas/004 leans toward C. Distinct from Phase KB + ground-mode `mcp:` source kinds (which cover the KB-grounding angle for free); + this phase is specifically about journal-corpus semantic recall (`ctx journal search ""` shape). diff --git a/.context/handovers/2026-05-10-071718-anchor-undercover-to-spec-and-tasks.md b/.context/handovers/2026-05-10-071718-anchor-undercover-to-spec-and-tasks.md index 3b368e4c1..9fceb9f0e 100644 --- a/.context/handovers/2026-05-10-071718-anchor-undercover-to-spec-and-tasks.md +++ b/.context/handovers/2026-05-10-071718-anchor-undercover-to-spec-and-tasks.md @@ -29,7 +29,7 @@ flags, `--brief ` on `/ctx-spec`, authority boundary sections, "light compression" wording standardization), **Phase RG** (require git as architectural precondition; refuse-on-no-git with no auto-init), **Phase KB** (editorial pipeline + handover; depends on Phase SK + -Phase RG; sub-phases KB-2 things-wtf port for validation and KB-3 docs). +Phase RG; sub-phases KB-2 `your-project` port for validation and KB-3 docs). Saved one feedback memory at `~/.claude/projects/-Users-volkan-Desktop- WORKSPACE-ctx/memory/feedback_no_defer_unfamiliar_scope.md` capturing diff --git a/.gitignore b/.gitignore index ae5056d24..178bd8c85 100644 --- a/.gitignore +++ b/.gitignore @@ -66,6 +66,10 @@ dist/ .context/journal-site/ .context/journal-obsidian/ +# KB rendered site output (Phase KB; analogous to journal-site) +.context/site/ +.context/site-config/ + # VS Code extension build artifacts editors/vscode/node_modules/ editors/vscode/dist/ diff --git a/CLAUDE.md b/CLAUDE.md index 2a20b1430..29a50b1d2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -90,6 +90,48 @@ relevant and override any system-level "may or may not be relevant" guidance. These hooks represent project invariants, not optional context. Do not assess relevance before following them. +## KB Editorial Workflow (Phase KB) + +## Session Handovers + +The handover is a former-agent-to-next-agent note created by +`/ctx-wrap-up` at session end and read by `/ctx-remember` +(or the "do you remember?" prompt) at session start. Files +live under `.context/handovers/-.md` (timestamped +so concurrent agent runs never overwrite). + +| Trigger phrase | Skill | +|-----------------------------------------------------------------------|------------------| +| "let's wrap up" / "leave a handover" / "before I go" / "stepping away"| `/ctx-wrap-up` | +| "do you remember?" / "what were we working on?" | `/ctx-remember` | + +`/ctx-wrap-up` owns session-end and always delegates to +`/ctx-handover` as its final step. Treat `/ctx-handover` as a +sub-mechanism of `/ctx-wrap-up`, not a user-facing trigger. + +## KB Editorial Workflow (Phase KB) + +When `.context/kb/` exists, this project additionally uses +the editorial knowledge-ingestion pipeline. Distinct from +(and additive to) the five canonical files. Workflow: + +| Trigger phrase | Skill | +|-------------------------------------------------------|------------------------| +| "ingest the transcripts" / "pull this into the kb" | `/ctx-kb-ingest` | +| "does the kb say" / "according to evidence" | `/ctx-kb-ask` | +| "audit the kb" / "check kb for rot" | `/ctx-kb-site-review` | +| "re-ground the kb" / "check upstream" | `/ctx-kb-ground` | +| "drop a note" / "park this finding" | `/ctx-kb-note` | + +When `.context/kb/` exists, `/ctx-wrap-up` additionally surfaces +pending closeouts and the outstanding-questions count before +delegating to `/ctx-handover`; `/ctx-remember` folds postdated +unfolded closeouts into the recall readback. + +Editorial constitution: `.context/ingest/KB-RULES.md` (laid down by +`ctx init`). Recipe: +[Build a Knowledge Base](https://ctx.ist/recipes/build-a-knowledge-base/). + ## Build Commands diff --git a/README.md b/README.md index 6b510e38b..b4fb039cf 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ instructions. ```bash # Initialize context directory in your project +# (git is required: ctx refuses to operate without .git/. +# Run `git init` first if the project does not have a repo yet.) ctx init # Activate it for the current shell (binds CTX_DIR). Required @@ -119,6 +121,34 @@ ctx decision add "Use PostgreSQL for primary database" \ ctx learning add "Mock functions must be hoisted in Jest" ``` +### Knowledge-base workflow (Phase KB) + +For knowledge-shaped work (research projects, vendor-spec analysis, +post-incident reviews), `ctx init` also lays down an editorial +pipeline distinct from the code-development surface above: + +```bash +# Scaffold a folder-shaped topic page +ctx kb topic new "Cursor Hooks" + +# Run an editorial ingest pass (in your AI assistant) +/ctx-kb-ingest ./inputs/2026-05-15-call.md "cursor hooks" + +# Ask grounded questions, refuses to web-jump +/ctx-kb-ask "does the kb say hooks fire async?" + +# Per-session handover; folds postdated closeouts automatically +ctx handover write "First session" \ + --summary "Drafted topic-page; minted EV-018..EV-024" \ + --next "Re-ingest the v1.1 release notes URL when available" +``` + +See the +[Build a Knowledge Base recipe](https://ctx.ist/recipes/build-a-knowledge-base/) +for the full workflow, including the pass-mode contract, +source-coverage state-machine ledger, and the closeout/fold +mechanism. + `ctx activate` emits `export CTX_DIR=...` for your shell; one-shot callers can prefix the binding inline as `CTX_DIR= ctx ...`. The value must be an absolute path with `.context` as its basename; diff --git a/docs/blog/2026-02-01-refactoring-with-intent.md b/docs/blog/2026-02-01-refactoring-with-intent.md index f8ff81b70..112174e14 100644 --- a/docs/blog/2026-02-01-refactoring-with-intent.md +++ b/docs/blog/2026-02-01-refactoring-with-intent.md @@ -204,7 +204,7 @@ The **journal system** includes: | Component | Purpose | |--------------------------|----------------------------------------------------| -| `ctx recall import` | Import sessions to markdown in `.context/journal/` | +| `ctx recall import` | Import sessions to Markdown in `.context/journal/` | | `ctx journal site` | Generate static site from journal entries | | `ctx serve` | Convenience wrapper for the static site server | | `/ctx-journal-enrich` | Slash command to add frontmatter and tags | diff --git a/docs/blog/2026-02-04-skills-that-fight-the-platform.md b/docs/blog/2026-02-04-skills-that-fight-the-platform.md index 377ed778a..3fe107e72 100644 --- a/docs/blog/2026-02-04-skills-that-fight-the-platform.md +++ b/docs/blog/2026-02-04-skills-that-fight-the-platform.md @@ -39,7 +39,7 @@ The result is often **nondeterministic** and **unpredictable**. the agent: the defaults that already encode **judgment**, **safety**, and **scope control**. -This post catalogues the conflict patterns I have encountered while building +This post catalogs the conflict patterns I have encountered while building `ctx`, and offers guidance on what skills should (*and, more importantly, should not*) do. diff --git a/docs/blog/2026-02-17-code-is-cheap-judgment-is-not.md b/docs/blog/2026-02-17-code-is-cheap-judgment-is-not.md index b901d6e9d..f5775275d 100644 --- a/docs/blog/2026-02-17-code-is-cheap-judgment-is-not.md +++ b/docs/blog/2026-02-17-code-is-cheap-judgment-is-not.md @@ -199,7 +199,7 @@ it should exist.** ### Defense in Depth Proved Boundaries Require Judgment In [Defense in Depth][security-post], the entire security model -for unattended AI agents came down to: **markdown is not a +for unattended AI agents came down to: **Markdown is not a security boundary**. Telling an AI "*don't do bad things*" is production (*of instructions*). Setting up an unprivileged user in a network-isolated container is **judgment** (*about risk*). diff --git a/docs/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress.md b/docs/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress.md index cf63c209f..d5b979672 100644 --- a/docs/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress.md +++ b/docs/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress.md @@ -284,7 +284,7 @@ This is not safety theater; this is **intent preservation**. The thing the `ctx` Manifesto calls "[encoding intent into the environment](../index.md#encode-intent-into-the-environment)." -The [Eight Ways a Hook Can Talk][hooks-post] catalogued the full +The [Eight Ways a Hook Can Talk][hooks-post] cataloged the full spectrum: from silent enrichment to hard blocks. The key insight was that hooks are not just safety rails: diff --git a/docs/cli/connect.md b/docs/cli/connect.md index 30bf7dd73..52d344f08 100644 --- a/docs/cli/connect.md +++ b/docs/cli/connect.md @@ -62,7 +62,7 @@ ctx connect subscribe decision learning convention ### `ctx connect sync` Pull matching entries from the hub and write them to -`.context/hub/` as markdown files with origin tags and date +`.context/hub/` as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync. ```bash diff --git a/docs/cli/connection.md b/docs/cli/connection.md index 76f6103ae..59fe08b15 100644 --- a/docs/cli/connection.md +++ b/docs/cli/connection.md @@ -66,7 +66,7 @@ ctx connection subscribe decision learning convention ### `ctx connection sync` Pull matching entries from the `ctx` Hub and write them to -`.context/hub/` as markdown files with origin tags and date +`.context/hub/` as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync. **Examples**: diff --git a/docs/cli/handover.md b/docs/cli/handover.md new file mode 100644 index 000000000..598459ecd --- /dev/null +++ b/docs/cli/handover.md @@ -0,0 +1,78 @@ +--- +title: ctx handover +icon: lucide/scroll-text +--- + +![ctx](../images/ctx-banner.png) + +## `ctx handover` + +Writes the per-session handover under +`.context/handovers/-.md`: a former-agent-to-next-agent +note created at session end by `/ctx-wrap-up` and read at +session start by `/ctx-remember`. When `.context/kb/` exists, +the writer additionally folds postdated closeouts into the +handover's `## Folded Closeouts` section and archives them. + +### `ctx handover write ` + +```bash +ctx handover write "Cursor Hooks deep dive" \ + --summary "Drafted topic-page; minted EV-018..EV-024; cold-reader passed." \ + --next "Re-ingest the v1.1 release notes URL once you have it." +``` + +**Required flags**: + +| Flag | Description | +|-------------|----------------------------------------------------------------------------------------------------------| +| `--summary` | What happened this session (past tense). Placeholder values (`TBD`, `see chat`, `n/a`) are rejected. | +| `--next` | What the next agent should do FIRST (future tense, specific). Same placeholder rejection. | + +**Optional flags**: + +| Flag | Description | +|--------------------|---------------------------------------------------------------------------------------------------| +| `--highlights` | Notable artifacts produced this session. | +| `--open-questions` | Things that remain undecided. | +| `--commit` | Override resolved git HEAD for the Provenance line (CI replay; honors `CTX_TASK_COMMIT`). | +| `--no-fold` | Skip closeout consumption (mid-session checkpoint). | + +**Writes**: `.context/handovers/<TS>-<slug>.md` with frontmatter +(`sha`, `branch`, `generated-at`, `title`) and body sections +(`## Summary`, `## Next Session`, optionally `## Highlights`, +`## Open Questions`, `## Folded Closeouts`). The +`<TS>-<slug>.md` filename is timestamped so multiple concurrent +agent runs never overwrite one another's handover. + +**Side effect** (when `--no-fold` is absent and `.context/kb/` +exists): closeouts that postdate the latest handover are +folded into the new handover and **physically archived** under +`.context/archive/closeouts/`. + +### How to Trigger + +In ordinary sessions you do not invoke `ctx handover write` +directly. The user-facing trigger is `/ctx-wrap-up`: + +```text +/ctx-wrap-up "session title" +``` + +`/ctx-wrap-up` owns session-end and always delegates to +`/ctx-handover` as its final step. Direct invocation of +`/ctx-handover` is reserved for two cases: + +- `--no-fold` mid-session checkpoint. +- Recovery, when a prior session aborted before wrap-up. + +See [`/ctx-wrap-up`](../reference/skills.md#ctx-wrap-up) and +[`/ctx-handover`](../reference/skills.md#ctx-handover). + +## Reference + +- Recipe: [Session Lifecycle](../recipes/session-lifecycle.md) +- Recipe: [Recover an Aborted Session](../recipes/recover-aborted-session.md) +- Skill: [`/ctx-wrap-up`](../reference/skills.md#ctx-wrap-up) +- Skill: [`/ctx-handover`](../reference/skills.md#ctx-handover) +- Skill: [`/ctx-remember`](../reference/skills.md#ctx-remember) diff --git a/docs/cli/index.md b/docs/cli/index.md index 055306e77..64ab46237 100644 --- a/docs/cli/index.md +++ b/docs/cli/index.md @@ -85,6 +85,8 @@ have been initialized by `ctx init` (otherwise commands return | [`ctx change`](change.md#ctx-change) | Show what changed since last session | | [`ctx memory`](memory.md#ctx-memory) | Bridge Claude Code auto memory into `.context/` | | [`ctx watch`](watch.md#ctx-watch) | Auto-apply context updates from AI output | +| [`ctx kb`](kb.md#ctx-kb) | Knowledge-base editorial pipeline (Phase KB) | +| [`ctx handover`](handover.md#ctx-handover) | Write the per-session handover that the next session reads | ## Sessions diff --git a/docs/cli/init-status.md b/docs/cli/init-status.md index 700db7681..ffe9bd1ed 100644 --- a/docs/cli/init-status.md +++ b/docs/cli/init-status.md @@ -19,6 +19,22 @@ Initialize a new `.context/` directory with template files. ctx init [flags] ``` +!!! warning "Git is required" + `ctx init` (and every non-administrative `ctx` subcommand) + refuses to operate without a `.git/` working tree at the + project root. `ctx` already needed git to work properly; + that requirement is now enforced rather than assumed. + + Handovers and closeouts stamp the current commit into + their frontmatter, and the editorial pipeline pins + in-repo evidence to a short SHA (*none of which works + without a repo*). + + Run `git init` first if the project does + not already have one. + + There is no `--allow-no-git` escape hatch. + **Flags**: | Flag | Short | Description | @@ -30,6 +46,12 @@ ctx init [flags] **Creates**: - `.context/` directory with all template files +- `.context/kb/` (with `index.md` and `topics/`) and + `.context/ingest/` (with `KB-RULES.md`, mode prompts, + `OPERATOR.md`, `PROMPT.md`, `closeouts/`, `schemas/`) and + `.context/handovers/`: the editorial-pipeline scaffolding + ([Phase KB](../recipes/build-a-knowledge-base.md)). Embedded + templates are copied; existing files are preserved. - `.claude/settings.local.json` with pre-approved `ctx` permissions - `CLAUDE.md` with bootstrap instructions (or merges into existing) diff --git a/docs/cli/kb.md b/docs/cli/kb.md new file mode 100644 index 000000000..9d86dba4d --- /dev/null +++ b/docs/cli/kb.md @@ -0,0 +1,92 @@ +--- +title: ctx kb +icon: lucide/library +--- + +![ctx](../images/ctx-banner.png) + +## `ctx kb` + +Knowledge-base editorial pipeline (Phase KB). Manages the +`.context/kb/` knowledge base via mode-aware skills and a small +set of supporting CLI commands. The editorial constitution +lives at `.context/ingest/KB-RULES.md` (laid down by +`ctx init`). + +```bash +ctx kb [subcommand] +``` + +| Subcommand | Type | Purpose | +|----------------------------------|------------------|---------------------------------------------------------------------| +| `ctx kb topic new "<name>"` | CLI (real) | Sole writer of topic-page scaffolds. Creates `.context/kb/topics/<slug>/index.md` from the embedded template. Refuses when the topic exists. | +| `ctx kb note "<text>"` | CLI (real) | Appends a one-liner to `.context/ingest/findings.md`. Never touches a topic page. | +| `ctx kb reindex` | CLI (real) | Refreshes the `CTX:KB:TOPICS` managed block in `.context/kb/index.md`. | +| `ctx kb ingest <folder\|paths>` | Skill-driven | Mode-aware editorial pass. CLI form refuses on empty input and points at the `/ctx-kb-ingest` skill. | +| `ctx kb ask "<question>"` | Skill-driven | Q&A grounded in the kb. CLI form refuses on empty input and points at the `/ctx-kb-ask` skill. | +| `ctx kb site-review` | Skill-driven | Mechanical structural audit. Points at `/ctx-kb-site-review`. | +| `ctx kb ground` | Skill-driven | External grounding via `grounding-sources.md`. Refuses when the file is empty. | + +!!! note "Skill-driven vs real CLI" + The mode skills (`ingest`, `ask`, `site-review`, `ground`) + do the editorial work themselves: the agent reads + `.context/ingest/30-INGEST.md` (etc.) and executes the + pass per the pass-mode contract. The CLI form for those + subcommands validates input and prints the canonical skill + invocation. The real CLI commands (`topic new`, `note`, + `reindex`) own concrete state changes. + +### `ctx kb topic new "<name>"` + +Scaffolds a folder-shaped topic at `.context/kb/topics/<slug>/index.md` +from the embedded template. + +**Slug**: lowercase + kebab-case. Slashes are preserved for +vendor-namespaced topology (e.g. `cursor/hooks`, +`cursor/skills`, `cursor/rules` under a shared `cursor/` +folder). + +**Refuses** when the topic folder already exists. Use the +existing folder instead; the editorial pass extends pages, +it doesn't reset them. + +### `ctx kb note "<text>"` + +Appends a timestamped one-liner to +`.context/ingest/findings.md`. Use for parking findings the +next ingest pass should absorb. + +```bash +ctx kb note "follow-up: chase the v1.2 release notes for the SIGTERM change" +``` + +### `ctx kb reindex` + +Refreshes the `CTX:KB:TOPICS` managed block inside +`.context/kb/index.md` so the kb landing page enumerates +current topic folders. Run after `ctx kb topic new` to update +the landing. + +### Skill-Driven Subcommands + +`ingest`, `ask`, `site-review`, `ground` exist as CLI surfaces +so the editorial workflow is **drivable from outside Claude +Code** (via the fallback `PROMPT.md` auto-router). In Claude +Code, prefer the skills: + +```text +/ctx-kb-ingest ./inputs/2026-05-15-call.md "cursor hooks" +/ctx-kb-ask "does the kb say hooks fire async?" +/ctx-kb-site-review +/ctx-kb-ground +``` + +See the +[Build a Knowledge Base recipe](../recipes/build-a-knowledge-base.md) +for the full workflow. + +## Reference + +- Recipe: [Build a Knowledge Base](../recipes/build-a-knowledge-base.md) +- Recipe: [Typical KB Session](../recipes/typical-kb-session.md) +- Editorial constitution: `.context/ingest/KB-RULES.md` diff --git a/docs/cli/steering.md b/docs/cli/steering.md index 7975ab173..e08bb8fe8 100644 --- a/docs/cli/steering.md +++ b/docs/cli/steering.md @@ -16,7 +16,7 @@ icon: lucide/compass Manage **steering files**: persistent behavioral rules for AI coding assistants. -A steering file is a small markdown document with YAML +A steering file is a small Markdown document with YAML frontmatter that tells the AI *how to behave* in a specific context. `ctx steering` keeps those files in `.context/steering/`, decides which ones apply for a given diff --git a/docs/home/getting-started.md b/docs/home/getting-started.md index 6c0b1d00d..7a73d85e3 100644 --- a/docs/home/getting-started.md +++ b/docs/home/getting-started.md @@ -202,7 +202,7 @@ For Claude Code, install the [`ctx` plugin](../operations/integrations.md#claude for automatic hooks and skills. `ctx init` also scaffolds four **foundation steering files** in -`.context/steering/` — `product.md`, `tech.md`, `structure.md`, +`.context/steering/`: `product.md`, `tech.md`, `structure.md`, `workflow.md`. **They are placeholders until you customize them** (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than @@ -211,7 +211,7 @@ buried here. ### 2. Customize Your Steering Files Steering files are **behavioral rules prepended to every AI -prompt** — the layer that tells your AI *how to act* on this +prompt**: the layer that tells your AI *how to act* on this specific project. They are distinct from decisions (*what* was chosen) and conventions (*how* the codebase is written); see [`ctx` for Steering Files](../recipes/steering.md) for the full @@ -232,7 +232,7 @@ Each scaffolded file ships with a **tombstone marker** line **As long as the marker is present, the file is silently skipped** on every load path: the agent context packet, MCP `ctx_steering_get`, and native-tool sync (Cursor / Cline / -Kiro). The skip is deliberate — injecting unfilled placeholders +Kiro). The skip is deliberate: injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow "Describe the product..." as if it were a rule. diff --git a/docs/home/opencode.md b/docs/home/opencode.md index b75db475d..e6dec192b 100644 --- a/docs/home/opencode.md +++ b/docs/home/opencode.md @@ -31,7 +31,7 @@ what validation middleware you're referring to? ``` > "Add the validation middleware we discussed" -Yes — from the Jan 15 session. You decided on Zod schemas at the +Yes. From the Jan 15 session. You decided on Zod schemas at the route level (DECISIONS.md #12), and the pattern is in CONVENTIONS.md. I'll follow the existing middleware in src/middleware/auth.ts as a reference. @@ -50,15 +50,15 @@ ctx setup opencode --write && ctx init && eval "$(ctx activate)" This does three things: -1. **`ctx setup opencode --write`** — generates the project-local OpenCode plugin, +1. **`ctx setup opencode --write`**: generates the project-local OpenCode plugin, skills, and `AGENTS.md`, then merges the `ctx` MCP server into OpenCode's global config (`~/.config/opencode/opencode.json` or `$OPENCODE_HOME/opencode.json`). This writes outside the project root because non-interactive shells (like MCP subprocesses) cannot discover - project-local config — the same reason the Copilot CLI integration + project-local config; the same reason the Copilot CLI integration writes to `~/.copilot/mcp-config.json`. -2. **`ctx init`** — creates the `.context/` directory with template files -3. **`eval "$(ctx activate)"`** — binds `CTX_DIR` for your shell +2. **`ctx init`**: creates the `.context/` directory with template files. +3. **`eval "$(ctx activate)"`**: binds `CTX_DIR` for your shell. ### What Gets Created @@ -69,18 +69,18 @@ This does three things: | `AGENTS.md` | Agent instructions (OpenCode reads this natively) | | `.opencode/skills/ctx-*/SKILL.md` | Slash command skills | -The plugin is a single file with no runtime dependencies — no `bun install` +The plugin is a single file with no runtime dependencies; no `bun install` or `npm install` needed. OpenCode loads it automatically on launch. ## What Happens Automatically The plugin wires OpenCode lifecycle events to `ctx`. You don't need to -do anything — it just works. +do anything; it just works. | Event | What fires | What it does | |-------|-----------|--------------| | New session | `session.created` | Warms `ctx` state in the background (bootstrap + agent packet) so MCP queries are fast on first use | -| Agent idle | `session.idle` | Runs persistence and task-completion checks (silent — output is buffered, not surfaced to the TUI) | +| Agent idle | `session.idle` | Runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI) | | After `git commit` | `tool.execute.after` | Runs `ctx system post-commit` to capture context state | | After file edit | `tool.execute.after` | Runs `ctx system check-task-completion` to detect silent task completions | | Every shell call | `shell.env` | Injects `CTX_DIR` so all `ctx` commands in the agent's shell resolve to the right project | @@ -89,7 +89,7 @@ do anything — it just works. The compaction hook matters most. When OpenCode compresses your context window to free up tokens, the plugin makes sure the compressed summary includes a pointer back to your `.context/` directory and its file -inventory — so the agent can re-read tasks, decisions, and learnings on +inventory, so the agent can re-read tasks, decisions, and learnings on demand, even though the original messages are gone. ### How Compaction Works @@ -118,7 +118,7 @@ Four skills are available as slash commands: | Command | When to use | |---------|-------------| | `/ctx-agent` | Load full context packet. Use at session start or when context feels stale. | -| `/ctx-remember` | "Do you remember?" — reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. | +| `/ctx-remember` | "Do you remember?"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. | | `/ctx-status` | Context summary at a glance: file count, token estimate, recent activity. | | `/ctx-wrap-up` | End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to `.context/` files. | @@ -152,7 +152,7 @@ You don't invoke these yourself. The agent uses them as needed. If you re-run `ctx setup opencode --write` (e.g., after updating `ctx`), the plugin and skills are rewritten in place. **Restart OpenCode to pick up the -refreshed plugin** — OpenCode only loads plugins at launch, not mid-session. +refreshed plugin**. OpenCode only loads plugins at launch, not mid-session. ## Troubleshooting @@ -160,7 +160,7 @@ refreshed plugin** — OpenCode only loads plugins at launch, not mid-session. |---------|-------|-----| | `opencode mcp list` shows `ctx ✗ failed MCP error -32000: Connection closed` | `CTX_DIR` not resolving in the MCP subprocess | Re-run `ctx setup opencode --write` to regenerate the sh-wrapper that sets `CTX_DIR` | | Plugin installed but no hooks fire | Flat-file vs. subdirectory discovery mismatch (OpenCode requires `.opencode/plugins/<name>.ts`, not a subfolder) | Verify the plugin is at `.opencode/plugins/ctx.ts`. Check with `opencode --print-logs --log-level DEBUG` | -| `ctx agent` markdown leaking into the TUI | BunShell command missing `.nothrow().quiet()` | Update to the latest plugin: `ctx setup opencode --write` and restart | +| `ctx agent` Markdown leaking into the TUI | BunShell command missing `.nothrow().quiet()` | Update to the latest plugin: `ctx setup opencode --write` and restart | ## Verify It Works @@ -172,14 +172,14 @@ Do you remember? The AI should cite specific context: current tasks, recent decisions, or previous session topics. If it says "I don't have memory" or "Let me -check," something went wrong — check that the plugin installed correctly +check," something went wrong; check that the plugin installed correctly and `.context/` has files in it. ## What's Next -- [Your First Session](first-session.md) — step-by-step walkthrough from - `ctx init` to verified recall -- [Common Workflows](common-workflows.md) — day-to-day commands for - tracking context, checking health, and browsing history -- [Context Files](context-files.md) — what lives in `.context/` and how - each file is used +- [Your First Session](first-session.md): step-by-step walkthrough from + `ctx init` to verified recall. +- [Common Workflows](common-workflows.md): day-to-day commands for + tracking context, checking health, and browsing history. +- [Context Files](context-files.md): what lives in `.context/` and how + each file is used. diff --git a/docs/home/prompting-guide.md b/docs/home/prompting-guide.md index 8f4631a19..9b2465185 100644 --- a/docs/home/prompting-guide.md +++ b/docs/home/prompting-guide.md @@ -380,7 +380,7 @@ into a single invocation. !!! info "Skills beyond Claude Code" The `/slash-command` syntax above is Claude Code native, but the - underlying `SKILL.md` files are a standard markdown format that any + underlying `SKILL.md` files are a standard Markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates. diff --git a/docs/home/steering.md b/docs/home/steering.md index 85651cf1d..a0243d77e 100644 --- a/docs/home/steering.md +++ b/docs/home/steering.md @@ -19,7 +19,7 @@ packet) but none of those can tell the assistant *how to behave* when a specific kind of prompt arrives. That's what **steering files** are for. -A steering file is a small markdown document with YAML +A steering file is a small Markdown document with YAML frontmatter that says: "when the user asks about X, prepend these rules to the prompt." `ctx` manages those files in `.context/steering/`, decides which ones match each prompt, @@ -55,7 +55,7 @@ rather than an empty directory: Each file starts with an inline HTML comment explaining the three inclusion modes, priority semantics, and tool scoping. -The comment is invisible in rendered markdown but visible +The comment is invisible in rendered Markdown but visible when you open the file to edit it; it's self-documenting scaffolding, not forever guidance. Delete the comment once you've customized the file. diff --git a/docs/home/vscode.md b/docs/home/vscode.md index f179f705a..b9c569d5d 100644 --- a/docs/home/vscode.md +++ b/docs/home/vscode.md @@ -47,7 +47,7 @@ Install the extension and the `ctx` binary, then `ctx init` your project: 1. **Install the extension** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=activememory.ctx-context) - (publisher: `activememory`, display name: *`ctx` — Persistent Context + (publisher: `activememory`, display name: *`ctx`: Persistent Context for AI*). Or build from source (see [editors/vscode/README.md](https://github.com/ActiveMemory/ctx/blob/main/editors/vscode/README.md#development)). 2. **Install the `ctx` CLI** if you haven't already diff --git a/docs/operations/integrations.md b/docs/operations/integrations.md index 947fe8611..363439667 100644 --- a/docs/operations/integrations.md +++ b/docs/operations/integrations.md @@ -489,7 +489,7 @@ indicator. #### Installation The extension ships to the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=activememory.ctx-context) -under publisher `activememory` (display name: *`ctx` — Persistent Context +under publisher `activememory` (display name: *`ctx`: Persistent Context for AI*). Install via the Extensions view or `code --install-extension`. To build from source instead (requires Node.js 20+): @@ -594,14 +594,14 @@ eval "$(ctx activate)" The plugin wires OpenCode lifecycle events to `ctx system`: -- **`session.created`** — warms `ctx` state in the background (bootstrap + agent packet) so MCP queries are fast on first use -- **`tool.execute.after` (shell, on `git commit`)** — runs `ctx system post-commit` -- **`tool.execute.after` (edit/write)** — runs `ctx system check-task-completion` -- **`session.idle`** — runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI) -- **`shell.env`** — injects `CTX_DIR` into the agent's shell so `ctx` commands resolve to the right project -- **`experimental.session.compacting`** — pushes `ctx system bootstrap` output into the compaction context so the agent keeps breadcrumbs back to `.context/` +- **`session.created`**: warms `ctx` state in the background (bootstrap + agent packet) so MCP queries are fast on first use. +- **`tool.execute.after` (shell, on `git commit`)**: runs `ctx system post-commit`. +- **`tool.execute.after` (edit/write)**: runs `ctx system check-task-completion`. +- **`session.idle`**: runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI). +- **`shell.env`**: injects `CTX_DIR` into the agent's shell so `ctx` commands resolve to the right project. +- **`experimental.session.compacting`**: pushes `ctx system bootstrap` output into the compaction context so the agent keeps breadcrumbs back to `.context/`. -The plugin is a single file with no runtime dependencies — no `bun install` +The plugin is a single file with no runtime dependencies; no `bun install` needed. OpenCode loads it automatically on launch. ### Context Updates diff --git a/docs/recipes/activating-context.md b/docs/recipes/activating-context.md index 7e630cdf4..9b7065acd 100644 --- a/docs/recipes/activating-context.md +++ b/docs/recipes/activating-context.md @@ -207,10 +207,6 @@ binds to it; every `ctx` subsystem reads the project from its parent. Nested projects, submodules, rogue agent-created `.context/` directories, and sub-agent sessions all produced silent misrouting -under the old walk-up model. See the -[explicit-context-dir spec](https://github.com/ActiveMemory/ctx/blob/main/specs/explicit-context-dir.md) -and [the analysis doc](https://github.com/ActiveMemory/ctx/blob/main/specs/context-resolution-analysis.md) -for the full reasoning. - -The short version: `ctx` decided to stop guessing and require the -caller to declare. Every other decision flows from there. +under the old walk-up model. `ctx` decided to stop guessing and +require the caller to declare. Every other decision flows from +there. diff --git a/docs/recipes/build-a-knowledge-base.md b/docs/recipes/build-a-knowledge-base.md new file mode 100644 index 000000000..f8389c136 --- /dev/null +++ b/docs/recipes/build-a-knowledge-base.md @@ -0,0 +1,283 @@ +--- +title: "Build a Knowledge Base" +icon: lucide/library +--- + +![ctx](../images/ctx-banner.png) + +## The Problem + +You are doing knowledge-shaped work (vendor-spec analysis, a +research project, a post-incident review, domain modeling) and +the standard five context files (`TASKS.md`, `DECISIONS.md`, +`LEARNINGS.md`, `CONVENTIONS.md`, `CONSTITUTION.md`) don't fit. +Because those files are tuned for *code-development context*, not for +*evidence-tracked knowledge* with confidence bands, +contradictions, and external citations. + +You need a place where: + +- Every claim is pinned to a source you can re-verify. +- Topics grow into folders as they earn their depth. +- Two passes against the same source don't silently disagree. +- The next session knows *what's incomplete*, not just *what's done*. + +That's what the **editorial pipeline** is for. + +!!! tip "Prefer Skills to Raw Commands" + The pipeline is driven by skills (`/ctx-kb-ingest`, + `/ctx-kb-ask`, etc.). The CLI form (`ctx kb ingest`, etc.) + exists for scripting and for non-Claude environments; the + skill is the natural surface. + +## TL;DR + +```bash +git init && ctx init # lays down the kb + ingest tree +ctx kb topic new "Cursor Hooks" # scaffold a topic folder +/ctx-kb-ingest ./docs/cursor-hooks.md "cursor hooks" # editorial pass +/ctx-kb-ask "does the kb say hooks fire async?" # grounded Q&A +/ctx-wrap-up # ceremony; delegates to /ctx-handover + # for the per-session handover +``` + +## Commands and Skills Used + +| Tool | Type | Purpose | +|----------------------------|---------|--------------------------------------------------------| +| `ctx init` | Command | Scaffold `.context/kb/`, `.context/ingest/`, etc. | +| `ctx kb topic new <name>` | Command | Sole writer of topic-page scaffolds (folder shape) | +| `ctx kb note "<text>"` | Command | Lightweight capture into `.context/ingest/findings.md` | +| `ctx kb reindex` | Command | Refresh the `CTX:KB:TOPICS` managed block | +| `ctx handover write` | Command | Per-session handover with closeout fold | +| `/ctx-kb-ingest` | Skill | Mode-aware editorial pass (topic-page/triage/evidence) | +| `/ctx-kb-ask` | Skill | Q&A grounded in the kb | +| `/ctx-kb-site-review` | Skill | Mechanical structural audit | +| `/ctx-kb-ground` | Skill | Re-grounding against external sources | +| `/ctx-kb-note` | Skill | Capture a finding for the next ingest pass | +| `/ctx-wrap-up` | Skill | End-of-session ceremony; delegates to the handover step | + +## Step 0: Initialize and Declare Scope + +```bash +git init && ctx init +``` + +`ctx init` lays down the editorial scaffolding alongside the +standard context files: + +``` +.context/ +├── kb/ +│ ├── index.md +│ └── topics/.gitkeep +├── ingest/ +│ ├── KB-RULES.md # editorial constitution +│ ├── 00-GROUND.md +│ ├── 30-INGEST.md +│ ├── 40-ASK.md +│ ├── 50-SITE_REVIEW.md +│ ├── OPERATOR.md +│ ├── PROMPT.md # hand-fallback router +│ ├── closeouts/.gitkeep +│ └── schemas/ +│ └── *.md # 10 schema templates +└── handovers/.gitkeep +``` + +**Open `.context/kb/index.md` and replace the placeholder `## Scope` +paragraph with a one-paragraph statement of what this kb covers +and what it does not.** `/ctx-kb-ingest` refuses to run against +an undeclared kb; scope is the precondition. + +!!! warning "Git is required" + `ctx init` now refuses to run without `.git/`. The + editorial pipeline's provenance (closeout `sha`/`branch`, + evidence-index in-repo SHA pins) depends on it. Run + `git init` first if the project does not already have one. + +## Step 1: Scaffold a Topic + +Topic pages live in folders, not flat files: + +```bash +ctx kb topic new "Cursor Hooks" +``` + +This creates `.context/kb/topics/cursor-hooks/index.md` from the +embedded template. The slug is computed by lowercasing + kebab- +casing; vendor-namespaced shapes like `cursor/hooks` are +preserved so you can grow into nested topology +(`topics/cursor/hooks/`, `topics/cursor/skills/`, +`topics/cursor/rules/`) without breaking citations. + +**`ctx kb topic new` is the sole writer of topic-page scaffolds.** +Skills invoke this command rather than synthesize a scaffold by +hand; the embedded template is the single source of truth. + +## Step 2: Run an Editorial Pass + +```text +/ctx-kb-ingest ./inputs/2026-04-12-call.md "cursor hooks" +``` + +The skill begins with a **pass-mode declaration**: + +> **Pass-mode:** `topic-page` +> **Reason:** the user supplied one primary source and the intended topic is clear. +> **Definition of done:** create or extend `kb/topics/cursor-hooks/index.md`, +> cite EV rows, run `ctx kb site build`, record cold-reader orientation. + +Then it: + +1. **Resolves sources** (paths / URLs / MCP resources) and updates + the **source-coverage ledger** at + `.context/kb/source-coverage.md` (a state machine across all + sources the kb has touched). +2. **Scans for adjacent incomplete topics** in the ledger and + surfaces them so the new page acknowledges sibling gaps. +3. **Synthesizes prose** section by section into the topic page, + minting `EV-###` rows in `evidence-index.md` for every cited + claim. +4. **Sets the Confidence floor** (the page never claims more + certainty than its weakest cited band). +5. **Writes a closeout** under + `.context/ingest/closeouts/<TS>-ingest-closeout.md` with + frontmatter, the cold-reader orientation rubric, and a + ledger-state advance per source. + +Three pass modes: + +- **`topic-page`** (default): write or extend a topic page. +- **`triage`**: admit / skip sources against scope; no `EV-###` minted. +- **`evidence-only`**: mint `EV-###` rows tagged `evidence-only`; + do not touch a topic page (explicit-request-only escape hatch). + +**Mid-pass mode-switching is forbidden**: the skill commits to +one mode and aborts cleanly if the work no longer fits. + +## Step 3: Q&A Grounded in the KB + +```text +/ctx-kb-ask "does the kb say hooks fire async?" +``` + +`/ctx-kb-ask` reads the kb's prose, cites `EV-###` rows, and +**refuses to web-jump**. If the kb cannot answer, it opens a +`Q-###` row in `outstanding-questions.md` and reports the gap, +which a future `/ctx-kb-ingest` pass can close. + +## Step 4: Audit + Re-Ground + +```text +/ctx-kb-site-review # mechanical structural audit +/ctx-kb-ground # refresh sources listed in grounding-sources.md +``` + +`site-review` coerces malformed Confidence-band capitalization, +flags malformed closeout frontmatter, and **refuses to make +judgment calls that require evidence** (those go through ingest). + +`ground` reads `.context/ingest/grounding-sources.md` and runs +the equivalent of a fresh fetch + re-extract pass for each +listed source, useful when an upstream source has changed. + +## Step 5: Browse the KB Locally + +`.context/kb/` is a tree of Markdown files: topic pages live +under `topics/<slug>/index.md` and cross-cutting artifacts +(`glossary.md`, `evidence-index.md`, +`outstanding-questions.md`, `domain-decisions.md`, +`contradictions.md`, `timeline.md`, `source-map.md`, +`source-coverage.md`, `relationship-map.md`) sit alongside +them. Drop a minimal `zensical.toml` into `.context/kb/` and +hand it to [`ctx serve`](../cli/serve.md): + +```bash +ctx serve .context/kb/ +``` + +The KB renders the same way the docs site you are reading +right now does. Use the in-place evidence-index links to jump +from a topic page to its `EV-###` rows and back. The site +build is read-only: no skill or CLI writes through it. + +## Step 6: Wrap Up with a Handover + +Run `/ctx-wrap-up` at session end; it owns the ceremony and +delegates to the handover step (`/ctx-handover`) as its final +action: + +```text +/ctx-wrap-up "Cursor Hooks deep dive" +``` + +The handover artifact lands at +`.context/handovers/<TS>-<slug>.md` (timestamped so concurrent +agent runs never overwrite). It **folds postdated closeouts** +into a `## Folded closeouts` section and **archives the source +closeout files** under `.context/archive/closeouts/`. The next +session's `/ctx-remember` reads the latest handover and folds +any closeouts whose `generated-at` postdates it. + +The legitimate direct-invocation cases for `/ctx-handover` +are `--no-fold` for a mid-session checkpoint, or recovery +when a prior session ended before its wrap-up step. For the +underlying CLI, see +[`ctx handover write`](../cli/handover.md#ctx-handover-write-title). + +## How It Ladders Together + +``` +sources you supply + │ + ▼ +/ctx-kb-ingest (mode-declared, source-coverage advanced) + │ + ├──▶ topic-page ──▶ .context/kb/topics/<slug>/index.md + ├──▶ evidence ──▶ .context/kb/evidence-index.md (EV-###) + ├──▶ side rails ──▶ glossary.md, contradictions.md, + │ outstanding-questions.md, timeline.md, + │ source-map.md, relationship-map.md + └──▶ closeout ──▶ .context/ingest/closeouts/<TS>-...md + │ + ▼ + (next session) + │ + ▼ + /ctx-wrap-up → /ctx-handover folds + → .context/handovers/<TS>-<slug>.md + + archives source closeouts under + .context/archive/closeouts/ + │ + ▼ + /ctx-remember reads handover + postdated + unfolded closeouts as the recall surface +``` + +## What the Editorial Pipeline Is NOT + +- **Not a substitute for `DECISIONS.md`.** Project-level + architectural decisions stay in `.context/DECISIONS.md`. The + kb's `domain-decisions.md` is a *kb-scoped* artifact (different + schema, different write authority, different lifecycle). +- **Not a substitute for `LEARNINGS.md`.** Learnings have author + intent; kb claims have evidence backing. They're different + truth bases; do not cross-feed. +- **Not for casual notes.** Use `/ctx-kb-note` or `ctx kb note + "<text>"` to park a finding for the next ingest pass. + +## Reference + +- Editorial constitution: `.context/ingest/KB-RULES.md` (laid + down by `ctx init`) +- Skills reference: + [`/ctx-kb-ingest`](../reference/skills.md#ctx-kb-ingest), + [`/ctx-kb-ask`](../reference/skills.md#ctx-kb-ask), + [`/ctx-kb-site-review`](../reference/skills.md#ctx-kb-site-review), + [`/ctx-kb-ground`](../reference/skills.md#ctx-kb-ground), + [`/ctx-kb-note`](../reference/skills.md#ctx-kb-note), + [`/ctx-handover`](../reference/skills.md#ctx-handover) +- Related recipes: + [Typical KB Session](typical-kb-session.md), + [Recover an Aborted Session](recover-aborted-session.md) diff --git a/docs/recipes/hub-team.md b/docs/recipes/hub-team.md index 9785a0b17..4ae4a2d2c 100644 --- a/docs/recipes/hub-team.md +++ b/docs/recipes/hub-team.md @@ -33,7 +33,7 @@ without ceremony. the hub. - Each project on each workstation has been activated for the shell with `eval "$(ctx activate)"`. The hub server - (`ctx hub start`, etc.) doesn't need this — but the + (`ctx hub start`, etc.) doesn't need this, but the client side (`ctx connection ...`, `ctx add --share`) lives in a project and does. If you skip activation, those client commands fail with `Error: no context @@ -214,7 +214,7 @@ incident. "Share fast, edit later" is the rule. **Delete noisy entries, don't tolerate them.** The hub is append-only, but the `.context/hub/` mirror on each -client is just markdown. If a shared learning turns out +client is just Markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate `entries.jsonl` (see [Hub operations](../operations/hub.md)). Noisy diff --git a/docs/recipes/index.md b/docs/recipes/index.md index 818c25167..c742ade0a 100644 --- a/docs/recipes/index.md +++ b/docs/recipes/index.md @@ -51,6 +51,46 @@ with private context or **multi-repo** setups. --- +## Knowledge Base (Phase KB) + +### [Build a Knowledge Base](build-a-knowledge-base.md) + +Stand up the editorial pipeline for knowledge-shaped work +(research projects, vendor-spec analysis, post-incident reviews). +Covers the pass-mode contract, source-coverage state-machine +ledger, topic-adjacency pre-flight, cold-reader rubric, +closeout/fold mechanism, and folder-shaped topic pages. + +**Uses**: `ctx init`, `ctx kb topic new`, `ctx kb note`, +`ctx kb reindex`, `ctx handover write`, +`/ctx-kb-ingest`, `/ctx-kb-ask`, `/ctx-kb-site-review`, +`/ctx-kb-ground`, `/ctx-kb-note`, `/ctx-handover` + +--- + +### [Typical KB Session](typical-kb-session.md) + +The everyday flow once the pipeline is set up: session start +recall, ingest a transcript, ask grounded questions, park +findings, wrap up via the mandatory handover. + +**Uses**: `/ctx-remember`, `/ctx-kb-ingest`, `/ctx-kb-ask`, +`/ctx-kb-note`, `/ctx-wrap-up`, `/ctx-handover` + +--- + +### [Recover an Aborted KB Session](recover-aborted-session.md) + +What to do when the session ends after one or more editorial +passes but before `/ctx-handover`. Closeouts survive the abort; +the next session's `/ctx-remember` reads them as unfolded +postdated artifacts; the next `/ctx-handover` folds them +retroactively. + +**Uses**: `/ctx-remember`, `/ctx-handover` + +--- + ## Sessions ### [The Complete Session](session-lifecycle.md) diff --git a/docs/recipes/multi-tool-setup.md b/docs/recipes/multi-tool-setup.md index 32620b9e0..bcd0f92b6 100644 --- a/docs/recipes/multi-tool-setup.md +++ b/docs/recipes/multi-tool-setup.md @@ -183,7 +183,7 @@ registers the `ctx` MCP server globally. See !!! tip "OpenCode Is a First-Class Citizen" With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, - and persists at session end — no manual steps needed. + and persists at session end, with no manual steps needed. #### VS Code diff --git a/docs/recipes/publishing.md b/docs/recipes/publishing.md index ac3135af2..9b661acef 100644 --- a/docs/recipes/publishing.md +++ b/docs/recipes/publishing.md @@ -44,7 +44,7 @@ Read on for details on each stage. | Tool | Type | Purpose | |---------------------------|----------|------------------------------------------------------------------| -| `ctx journal import` | Command | Import session JSONL to editable markdown | +| `ctx journal import` | Command | Import session JSONL to editable Markdown | | `ctx journal site` | Command | Generate a static site from journal entries | | `ctx journal obsidian` | Command | Generate an Obsidian vault from journal entries | | `ctx serve` | Command | Serve any zensical directory (default: journal) | @@ -60,7 +60,7 @@ Read on for details on each stage. ### Step 1: Import Sessions to Markdown Raw session data lives as JSONL files in Claude Code's internal storage. The -first step is converting these into readable, editable markdown. +first step is converting these into readable, editable Markdown. ```bash # Import all sessions from the current project diff --git a/docs/recipes/recover-aborted-session.md b/docs/recipes/recover-aborted-session.md new file mode 100644 index 000000000..8b15b8432 --- /dev/null +++ b/docs/recipes/recover-aborted-session.md @@ -0,0 +1,159 @@ +--- +title: "Recover an Aborted KB Session" +icon: lucide/life-buoy +--- + +![ctx](../images/ctx-banner.png) + +## The Problem + +You ran one or more `/ctx-kb-ingest` passes, then the session +ended before `/ctx-wrap-up`. Maybe you closed the laptop, +the connection dropped, or you just forgot the wrap-up step. + +You come back the next day and ask "do you remember?" and +the agent picks up the previous handover, but the editorial +work since the last handover seems to be missing from the +readback. + +**It isn't missing. It's unfolded.** Here's how the pipeline +handles it and how to close the loop manually. + +## TL;DR + +```text +/ctx-remember # picks up the unfolded + # closeouts automatically +/ctx-handover "recovery: fold the orphan closeouts" # direct invocation is + # appropriate for recovery +``` + +The recovery path is the one legitimate place to invoke +`/ctx-handover` directly. Normally `/ctx-wrap-up` owns +session-end and delegates to the handover step; the abort +broke that path, so a hand-rolled handover invocation is how +you close the loop without re-running the full wrap-up +ceremony. + +## How the Fold Mechanism Survives an Abort + +Two artifacts make abort-recovery work without any cleanup: + +1. **Closeouts are immutable once written.** Every editorial + pass writes a closeout under + `.context/ingest/closeouts/<TS>-<mode>-closeout.md` *before* + the pass reports `done`. If the session dies, the closeout + is already on disk. + +2. **`/ctx-remember` folds unfolded closeouts into the + readback.** The skill always reads the latest handover. + When `.context/kb/` exists, it additionally reads any + closeouts whose `generated-at` postdates the handover. + The `## What changed` and `## Source-coverage updates` + sections from each unfolded closeout are surfaced in + recall. + +So an aborted session never loses editorial work; it just +delays the handover fold by one session. + +## Step 1: Confirm the Orphan Closeouts + +```bash +ls -la .context/ingest/closeouts/ +``` + +Files there with `generated-at` postdating your latest +handover are the unfolded ones. You can read any closeout +directly to see what it claims about its pass: + +```bash +cat .context/ingest/closeouts/<TS>-ingest-closeout.md +``` + +Look at: + +- The **Pass-mode** body block (`Declared / Reason / + Definition of done / Result`): what the pass committed to + and whether it claimed success or `deferred`. +- The **Source-coverage updates** section: what state + transitions hit the ledger. +- The **Next pass hint**: the exact resumption invocation + the closeout recommends, if the pass deferred. + +## Step 2: Run `/ctx-remember` + +```text +/ctx-remember +``` + +The readback will include the editorial-state summary as part +of the standard readback shape. If everything looks +consistent, proceed to Step 3. + +If the readback surfaces something surprising (a closeout +claiming `topic-page: produced` for a slug whose file is +missing, a `comprehensive` ledger advance against a source +whose page is `speculative`, etc.), fix the underlying +inconsistency before folding. (Doctor advisories for these +shapes are on the Phase-7 backlog.) + +## Step 3: Write the Recovery Handover + +This step is the one legitimate direct invocation of +`/ctx-handover`. In normal session-end the call goes through +`/ctx-wrap-up`; here the prior session aborted, so you reach +for the handover step directly to retire the orphan +closeouts: + +```text +/ctx-handover "recovery: fold orphan closeouts from yesterday" +``` + +Or via the CLI: + +```bash +ctx handover write "recovery: fold orphan closeouts from yesterday" \ + --summary "Folded N orphan closeouts from the aborted session." \ + --next "Resume <topic> per the closeout's Next pass hint." +``` + +The handover: + +- Reads the latest handover cursor. +- Finds all closeouts whose `generated-at` is after the cursor. +- Folds their summaries into a `## Folded closeouts` section. +- **Archives the source closeout files** under + `.context/archive/closeouts/` (closeouts are + append-never-rewrite; archival moves bytes but does not + modify them). + +After the handover lands, the orphan closeouts are now durably +tied to a session boundary; the next `/ctx-remember` reads +*just* the new handover (and any closeouts postdating *it*), +without re-folding the recovered ones. + +## Edge Cases + +| Case | Behavior | +|---|---| +| Closeout has malformed frontmatter | Handover fold **skips it with a warning** to stderr. Hand-edit the malformed file (typically a missing `generated-at`) and re-run `ctx handover write` to fold it next time. | +| Closeout's `generated-at` is *before* the last handover but was never folded | Treated as already-folded (silently skipped; the cursor is the source of truth). If you genuinely want to re-fold it, hand-edit the closeout's `generated-at` forward. | +| You aborted *during* an ingest pass, before its closeout was written | No closeout exists; the pass left no recall residue. Treat the source(s) as un-ingested and re-run `/ctx-kb-ingest`. The source-coverage ledger row may show stale residue from a prior pass; the next ingest will advance it correctly. | +| Multiple sessions piled up unfolded closeouts | One handover run folds them all in a single shot. The fold is cursor-driven, not session-driven. | +| You want recall without consuming closeouts | `ctx handover write ... --no-fold` writes a handover with frontmatter but leaves the closeouts in place. The next handover (without `--no-fold`) folds everything postdating the latest handover cursor. | + +## When This Matters + +- After a network drop / laptop close mid-session. +- When you ran `/ctx-kb-ingest` from a sub-agent that + finished without calling `/ctx-handover`. +- After porting work from another environment (e.g. you + rsynced `.context/ingest/closeouts/` from a different + machine) and want to integrate the work into the destination + project's recall thread. + +## Reference + +- Recipe: [Build a Knowledge Base](build-a-knowledge-base.md) +- Recipe: [Typical KB Session](typical-kb-session.md) +- Editorial constitution: `.context/ingest/KB-RULES.md` diff --git a/docs/recipes/session-lifecycle.md b/docs/recipes/session-lifecycle.md index 816ab6af6..245453707 100644 --- a/docs/recipes/session-lifecycle.md +++ b/docs/recipes/session-lifecycle.md @@ -317,6 +317,17 @@ conversation, then proposes structured candidates for your approval. After you select which to keep, it persists them via `ctx add` and offers `/ctx-commit` if uncommitted changes remain. +As its **final step**, `/ctx-wrap-up` writes a **handover** +under `.context/handovers/<TS>-<slug>.md` — a +former-agent-to-next-agent note with a past-tense summary and +a future-tense "first action for the next session". The +filename is timestamped so concurrent agent runs never +overwrite each other. The next `/ctx-remember` reads this +file as the authoritative recall surface; skipping +`/ctx-wrap-up` means the next session has no handover to +read and recall degrades to probabilistic reconstruction +from canonical files plus journal. + Session transcripts are automatically captured by Claude Code and can be browsed later with `ctx journal source` and `ctx journal source --show`. diff --git a/docs/recipes/steering.md b/docs/recipes/steering.md index 8165a7789..19e778ade 100644 --- a/docs/recipes/steering.md +++ b/docs/recipes/steering.md @@ -43,7 +43,7 @@ you the first time you initialize a project: Each file opens with an **inline HTML comment** that explains the three inclusion modes, what `priority` means, and the `tools` scope. The comment is invisible in -rendered markdown but visible when you edit the file. +rendered Markdown but visible when you edit the file. Delete it once the file is yours. All four default to `inclusion: always` and `priority: 10`, diff --git a/docs/recipes/typical-kb-session.md b/docs/recipes/typical-kb-session.md new file mode 100644 index 000000000..41d1a6aef --- /dev/null +++ b/docs/recipes/typical-kb-session.md @@ -0,0 +1,161 @@ +--- +title: "Typical KB Session" +icon: lucide/notebook-pen +--- + +![ctx](../images/ctx-banner.png) + +## The Problem + +You set the editorial pipeline up +([Build a Knowledge Base](build-a-knowledge-base.md)). Now you +sit down for a real research session: a transcript to ingest, a +question to answer against existing evidence, a finding to +capture for later. What's the actual flow? + +## TL;DR + +```text +/ctx-remember # session-start recall +/ctx-kb-ingest ./inputs/transcript.md "topic" # editorial pass +/ctx-kb-ask "does the kb say X?" # grounded Q&A +/ctx-kb-note "follow-up: chase the v1.1 link" # park a finding +/ctx-wrap-up # ceremony → /ctx-handover +``` + +## Commands and Skills Used + +| Tool | Type | Purpose | +|-------------------|---------|---------------------------------------------------| +| `/ctx-remember` | Skill | Session-start recall (folds KB state when present) | +| `/ctx-kb-ingest` | Skill | Mode-aware editorial pass | +| `/ctx-kb-ask` | Skill | Q&A grounded in the kb | +| `/ctx-kb-note` | Skill | Park a finding for the next ingest | +| `/ctx-wrap-up` | Skill | End-of-session ceremony; delegates to the handover step | +| `/ctx-handover` | Skill | Writes the per-session handover; called by `/ctx-wrap-up` | + +## Step 1: Session Start (Recall) + +```text +/ctx-remember +``` + +`/ctx-remember` reads the latest handover under +`.context/handovers/` (timestamped `<TS>-<slug>.md` so +concurrent agent runs never overwrite); its `## Summary` and +`## Next session` are the authoritative recall surface. The +five canonical files (`TASKS`, `DECISIONS`, etc.) are read as +usual. + +When `.context/kb/` exists, `/ctx-remember` additionally folds +editorial state into the readback: any closeouts whose +`generated-at` postdates the handover are read for their +`## What changed` sections (these are unfolded passes the +last handover did not yet consume). + +`SESSION_LOG.md` is **not** read at session start; it is +mid-flight working memory, not a recall surface. + +## Step 2: Ingest the Sources You Brought + +```text +/ctx-kb-ingest ./inputs/2026-05-15-call.md "cursor hooks" +``` + +The skill declares its mode up front (most often +`topic-page`), resolves sources, scans the +**source-coverage ledger** for adjacent incomplete topics, +and synthesizes prose into the topic page section by section. +Every cited claim mints an `EV-###` row in +`evidence-index.md` with the source short-name + locator + +optional `sha:` pin for in-repo files. + +The pass ends with a **circuit-breaker check** (file exists, +cites ≥ 1 `EV-###`, site builds clean, cold-reader rubric at +`pass`) and writes a closeout. + +If the skill reports `topic-page: deferred` instead of +`produced`, look at the closeout's `Next pass hint`. It +names the exact resumption invocation. + +## Step 3: Ask Grounded Questions + +```text +/ctx-kb-ask "does the kb say hooks block until they exit?" +``` + +`/ctx-kb-ask` reads the kb's prose and answers with `EV-###` +citations. If the kb cannot answer, it opens a `Q-###` row +in `outstanding-questions.md` and reports the gap rather than +inventing. + +## Step 4: Park Findings for Later + +```text +/ctx-kb-note "check whether SIGTERM behavior changed in v1.2" +``` + +`/ctx-kb-note` appends one-liners to +`.context/ingest/findings.md`, a lightweight surface for +parking ideas that don't earn a full ingest pass right now. +The next `/ctx-kb-ingest` can choose to absorb them. + +## Step 5: Wrap Up + +```text +/ctx-wrap-up "Cursor Hooks: lifecycle deep dive" +``` + +`/ctx-wrap-up` runs the standard capture checklist (learnings, +decisions, conventions, tasks) and delegates to +`/ctx-handover` as its final step. In a KB session it +additionally: + +- Surfaces pending closeouts under + `.context/ingest/closeouts/`. +- Counts `open` rows in `outstanding-questions.md`. + +The handover artifact lands at +`.context/handovers/<TS>-<slug>.md` (timestamped so concurrent +agent runs never overwrite). The handover folds postdated +closeouts into a `## Folded closeouts` section and archives +them under `.context/archive/closeouts/`. Editorial work that +was incomplete at wrap-up (open `Q-###` rows, `topic-page: +deferred` passes) is surfaced as recall on the next session +start. + +## Common Shapes + +### Multiple Topics in One Session + +Run `/ctx-kb-ingest` once per topic. Each pass writes its own +closeout; the handover folds all of them at the end. + +### Mid-Session Checkpoint + +```bash +ctx handover write "Mid-day checkpoint" \ + --summary "..." --next "..." --no-fold +``` + +`--no-fold` writes the handover without consuming closeouts, +useful when you want a recall anchor mid-session without +ending the editorial chunking. + +### Aborted Session + +If you close the laptop after an ingest pass but before +`/ctx-wrap-up`, the closeouts stay in place. The next +session's `/ctx-remember` reads them as unfolded postdated +closeouts; the next wrap-up's handover step folds them +normally. See +[Recover an Aborted Session](recover-aborted-session.md) for +the failure-mode detail. + +## Reference + +- Recipe: [Build a Knowledge Base](build-a-knowledge-base.md) +- Recipe: [Recover an Aborted Session](recover-aborted-session.md) +- Skill: [`/ctx-kb-ingest`](../reference/skills.md#ctx-kb-ingest) +- Skill: [`/ctx-handover`](../reference/skills.md#ctx-handover) +- Editorial constitution: `.context/ingest/KB-RULES.md` diff --git a/docs/reference/comparison.md b/docs/reference/comparison.md index 8c45bcdb9..993db7c01 100644 --- a/docs/reference/comparison.md +++ b/docs/reference/comparison.md @@ -208,7 +208,7 @@ These tools are designed for: * **local-first**: context lives next to your code, not behind a service boundary. -* **file-based**: everything important is a markdown +* **file-based**: everything important is a Markdown file you can read, diff, grep, and version-control. * **single-binary core**: the context persistence path (`init`, `add`, `agent`, `status`, `drift`, `load`, diff --git a/docs/reference/skills.md b/docs/reference/skills.md index db01b916e..49034e760 100644 --- a/docs/reference/skills.md +++ b/docs/reference/skills.md @@ -77,6 +77,12 @@ opinionated behavior on top. | [`/ctx-skill-create`](#ctx-skill-create) | Create, improve, and test skills | user-invocable | | [`/ctx-pause`](#ctx-pause) | Pause context hooks for this session | user-invocable | | [`/ctx-resume`](#ctx-resume) | Resume context hooks after a pause | user-invocable | +| [`/ctx-kb-ingest`](#ctx-kb-ingest) | Editorial KB pass (topic-page / triage / evidence-only) | user-invocable | +| [`/ctx-kb-ask`](#ctx-kb-ask) | Q&A grounded in the KB; refuses to web-jump | user-invocable | +| [`/ctx-kb-site-review`](#ctx-kb-site-review) | Mechanical KB structural audit | user-invocable | +| [`/ctx-kb-ground`](#ctx-kb-ground) | Re-ground the KB against listed external sources | user-invocable | +| [`/ctx-kb-note`](#ctx-kb-note) | Park a finding in `ingest/findings.md` | user-invocable | +| [`/ctx-handover`](#ctx-handover) | Handover step delegated by `/ctx-wrap-up`; folds postdated closeouts | sub-mechanism | --- @@ -171,15 +177,23 @@ End-of-session context persistence ceremony. Gathers signal from git diff, recent commits, and conversation themes. Proposes candidates (learnings, decisions, conventions, tasks) with complete structured fields for user approval, then persists via `ctx add`. -Offers `/ctx-commit` if uncommitted changes remain. +Offers `/ctx-commit` if uncommitted changes remain. **Always +delegates to `/ctx-handover` as its final step**, regardless of +whether `.context/kb/` exists: KB presence only affects what gets +folded into the handover, not whether it is written. **Ceremony skill**: invoke explicitly at session end. +**Trigger phrases**: "let's wrap up", "save context", "save +state", "leave a handover", "before I go", "stepping away", +"end of session" + **Wraps**: `git diff --stat`, `git log`, `ctx learning add`, `ctx decision add`, `ctx convention add`, `ctx task add`, -chains to `/ctx-commit` +chains to `/ctx-commit`, delegates to `/ctx-handover` **See also**: [Session Ceremonies](../recipes/session-ceremonies.md), -[The Complete Session](../recipes/session-lifecycle.md) +[The Complete Session](../recipes/session-lifecycle.md), +[`/ctx-handover`](#ctx-handover) --- @@ -408,7 +422,7 @@ works but with reduced capability. It runs structural checks and notes: ### `/ctx-link-check` -Scan all markdown files under `docs/` for broken links. Three passes: +Scan all Markdown files under `docs/` for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, @@ -493,8 +507,9 @@ The skill enforces this **authority order** when sources disagree: 1. Frozen contracts in `docs/` (release notes, public CLI docs) 2. Recorded decisions in `.context/DECISIONS.md` 3. The brief at `<path>` -4. Agent inference — only when 1–3 are silent, and labeled `TBD` - in the resulting spec so it stands out for review. +4. Agent inference, only when 1 through 3 are silent, and + labeled `TBD` in the resulting spec so it stands out for + review. Light compression for clarity is allowed; new facts are not. Where the brief is silent, the spec writes `TBD` rather than @@ -705,6 +720,127 @@ and ceremony behavior. Silent no-op if not paused. --- +## Knowledge Base (Phase KB) + +Skills for the editorial knowledge-ingestion pipeline. Active when +`.context/kb/` exists (laid down by `ctx init`). The pipeline gives +you evidence-tracked knowledge with confidence bands, folder-shaped +topic pages, a source-coverage state machine, and per-session +handovers that fold postdated closeouts. + +See the +[Build a Knowledge Base recipe](../recipes/build-a-knowledge-base.md) +for the full workflow. The editorial constitution lives at +`.context/ingest/KB-RULES.md`. + +### `/ctx-kb-ingest` + +Mode-aware editorial pass. Declares its pass-mode +(`topic-page` / `triage` / `evidence-only`) up front, scans the +source-coverage ledger for adjacent incomplete topics, synthesizes +prose into `.context/kb/topics/<slug>/index.md`, mints `EV-###` +rows in `evidence-index.md`, runs a four-invariant completion +circuit breaker, and writes a closeout under +`.context/ingest/closeouts/`. Refuses on empty input. + +**Wraps**: `ctx kb ingest`, `ctx kb topic new`, the writer +packages under `internal/write/kb/`. + +**Trigger phrases**: "ingest the transcripts", "pull this into the +kb", "add evidence from" + +**See also**: +[Build a Knowledge Base](../recipes/build-a-knowledge-base.md), +[Typical KB Session](../recipes/typical-kb-session.md), +[`ctx kb` CLI](../cli/kb.md#ctx-kb) + +--- + +### `/ctx-kb-ask` + +Q&A grounded in the KB. Cites `EV-###` rows; refuses to web-jump. +When the KB cannot answer, opens a `Q-###` row in +`outstanding-questions.md` rather than inventing. Refuses on empty +question. + +**Wraps**: `ctx kb ask`, reads `.context/kb/*.md` + +**Trigger phrases**: "does the kb say", "according to evidence" + +**See also**: [`ctx kb` CLI](../cli/kb.md#ctx-kb) + +--- + +### `/ctx-kb-site-review` + +Mechanical structural audit. Coerces malformed Confidence-band +capitalization, flags malformed closeout frontmatter, refuses +judgment calls that require evidence (those go through ingest). + +**Wraps**: `ctx kb site-review` + +**Trigger phrases**: "audit the kb", "check kb for rot" + +**See also**: [`ctx kb` CLI](../cli/kb.md#ctx-kb) + +--- + +### `/ctx-kb-ground` + +External re-grounding pass. Reads +`.context/ingest/grounding-sources.md` and refreshes each listed +source. Refuses cleanly when the file is absent or empty. + +**Wraps**: `ctx kb ground` + +**Trigger phrases**: "re-ground the kb", "check upstream" + +**See also**: [`ctx kb` CLI](../cli/kb.md#ctx-kb) + +--- + +### `/ctx-kb-note` + +Lightweight capture into `.context/ingest/findings.md`. Never +writes to a topic page or `evidence-index.md`. Use for parking +findings the next ingest pass should absorb. + +**Wraps**: `ctx kb note "<text>"` + +**Trigger phrases**: "drop a note", "park this finding" + +**See also**: [`ctx kb` CLI](../cli/kb.md#ctx-kb) + +--- + +### `/ctx-handover` + +Per-session handover artifact writer; the sub-mechanism that +`/ctx-wrap-up` delegates to as its final step. Collects +`--summary` (past tense) and `--next` (future tense, specific) +and calls `ctx handover write`. Writes the handover to +`.context/handovers/<TS>-<slug>.md` (timestamped so concurrent +agent runs never overwrite). Folds postdated closeouts into a +`## Folded closeouts` section and **physically archives** the +source closeouts under `.context/archive/closeouts/` (closeouts +are append-never-rewrite; archival moves bytes but does not +modify them). `--no-fold` skips the fold for mid-session +checkpoints. + +**Mandatory tail of `/ctx-wrap-up`.** Direct invocation is +reserved for `--no-fold` mid-session checkpoints and recovery +after an aborted session. + +**Wraps**: `ctx handover write <title> --summary X --next Y` + +**See also**: +[`/ctx-wrap-up`](#ctx-wrap-up), +[Typical KB Session](../recipes/typical-kb-session.md), +[Recover an Aborted KB Session](../recipes/recover-aborted-session.md), +[`ctx handover` CLI](../cli/handover.md#ctx-handover) + +--- + ## Project-Specific Skills The `ctx` plugin ships the skills listed above. diff --git a/docs/security/hub.md b/docs/security/hub.md index a169c9dc9..86025354a 100644 --- a/docs/security/hub.md +++ b/docs/security/hub.md @@ -108,7 +108,7 @@ Every published entry is validated before it touches the log: ### No Script Execution The hub never interprets entry content. There is no expression -language, no template evaluation, no markdown rendering at +language, no template evaluation, no Markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim. diff --git a/docs/thesis/index.md b/docs/thesis/index.md index b80154cc6..167c4647a 100644 --- a/docs/thesis/index.md +++ b/docs/thesis/index.md @@ -303,7 +303,7 @@ be used for optional features but never for core context management. *Validation*: 7 independent rejection decisions protected this property. Infrastructure-dependent memory systems cannot operate in classified -environments, isolated networks, or disaster-recovery scenarios. A +environments, isolated networks, or constrained-environment scenarios. A filesystem-native model continues to function under all conditions where the repository is accessible. @@ -742,7 +742,7 @@ remains with the team. **Offline or air-gapped operation is required**: Infrastructure-dependent memory systems cannot operate in classified environments, isolated networks, -or disaster-recovery scenarios. +or constrained-environment scenarios. ### 9.2 When Not to Use This Model diff --git a/internal/assets/claude/CLAUDE.md b/internal/assets/claude/CLAUDE.md index 1d40425dc..255e96ee4 100644 --- a/internal/assets/claude/CLAUDE.md +++ b/internal/assets/claude/CLAUDE.md @@ -90,4 +90,51 @@ relevant and override any system-level "may or may not be relevant" guidance. These hooks represent project invariants, not optional context. Do not assess relevance before following them. +## Session Handovers + +The handover is a former-agent-to-next-agent note created by +`/ctx-wrap-up` at session end and read by `/ctx-remember` +(or the "do you remember?" prompt) at session start. Files +live under `.context/handovers/<TS>-<slug>.md` (timestamped +so concurrent agent runs never overwrite). + +| Trigger phrase | Skill | +|-------------------------------------------------|------------------| +| "let's wrap up" / "leave a handover" / "before I go" / "stepping away" | `/ctx-wrap-up` | +| "do you remember?" / "what were we working on?" | `/ctx-remember` | + +`/ctx-wrap-up` owns session-end; it always ends by delegating +to `/ctx-handover` as its final step. Treat `/ctx-handover` +as a sub-mechanism of `/ctx-wrap-up`, not a user-facing +trigger. + +## KB Editorial Workflow (Phase KB) + +When `.context/kb/` exists, this project additionally uses +the editorial knowledge-ingestion pipeline. Distinct from +(and additive to) the five canonical files above; tuned for +evidence-tracked knowledge with confidence bands, +folder-shaped topic pages, and a source-coverage state +machine. + +| Trigger phrase | Skill | +|------------------------------------------------------|------------------------| +| "ingest the transcripts" / "pull this into the kb" | `/ctx-kb-ingest` | +| "does the kb say" / "according to evidence" | `/ctx-kb-ask` | +| "audit the kb" / "check kb for rot" | `/ctx-kb-site-review` | +| "re-ground the kb" / "check upstream" | `/ctx-kb-ground` | +| "drop a note" / "park this finding" | `/ctx-kb-note` | + +When `.context/kb/` exists, `/ctx-remember` additionally folds +any closeouts under `.context/ingest/closeouts/` whose +`generated-at` postdates the latest handover (unfolded passes +the last handover did not consume); `/ctx-wrap-up` surfaces +pending closeouts and the outstanding-questions count before +delegating to `/ctx-handover`. `SESSION_LOG.md` is mid-flight +working memory and is not read at session start. + +Editorial constitution: `.context/ingest/KB-RULES.md` (laid down by +`ctx init`). Recipe: +https://ctx.ist/recipes/build-a-knowledge-base/. + <!-- ctx:end --> diff --git a/internal/assets/claude/skills/ctx-handover/SKILL.md b/internal/assets/claude/skills/ctx-handover/SKILL.md new file mode 100644 index 000000000..9a5e6fa5d --- /dev/null +++ b/internal/assets/claude/skills/ctx-handover/SKILL.md @@ -0,0 +1,284 @@ +--- +name: ctx-handover +description: Per-session handover artifact writer. Wraps `ctx handover write` with `--summary` and `--next` (both required, both validated non-placeholder by the CLI). Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them. +allowed-tools: Bash(ctx:*), Bash(git status), Bash(git log:*), Read +--- + +# Write a Handover + +Capture the session's narrative thread so the next session (a +fresh agent, a different operator, a cold restart the next +morning) can resume without re-deriving context probabilistically +from canonical files plus journal. + +This skill is the **sole authoritative recall artifact** writer +(per `KB-RULES.md` §Four inviolable rules: *"the handover is +the sole authoritative recall artifact"*). `SESSION_LOG.md` +entries, closeouts, and journal entries are mid-flight surfaces; +the handover is what `/ctx-remember` reads on session start. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Four inviolable rules; +`specs/kb-editorial-pipeline.md` §Interface. + +## When to Use + +`/ctx-wrap-up` owns the user-facing trigger for session-end +("let's wrap up", "save state", "leave a handover", "before I +go", "stepping away") and delegates to this skill as its final +step. Do not advertise this skill as a direct user trigger. + +- **Mandatory tail of `/ctx-wrap-up`.** Every `/ctx-wrap-up` + run ends with this skill. +- Mid-session checkpoint when the user wants to pause without + consuming closeouts (use `--no-fold`). This is the one case + where direct invocation is appropriate. + +## When NOT to Use + +- Nothing meaningful happened (only read files, quick lookup); + but check with the user. A no-op session still benefits from + a "nothing changed; next-step is X" handover when the next + session has zero context. +- The user already ran `/ctx-handover` recently in this session + and nothing has changed since. +- The user invokes a capture skill (`/ctx-task-add`, + `/ctx-decision-add`, etc.); those write to canonical files, + not to a handover artifact. + +## Authority Boundary (vs Other Skills) + +- **`/ctx-handover`**: writes + `.context/handovers/<TS>-<slug>.md`; folds postdated + closeouts from `.context/ingest/closeouts/` into the + handover's `## Folded closeouts` section; archives folded + closeouts to `.context/archive/closeouts/`. Single writer of + this artifact. +- **`/ctx-wrap-up`**: owns the user-facing session-end + trigger. Drives the broader capture ceremony (learnings, + decisions, conventions, tasks) and always delegates to + `/ctx-handover` as its final step. +- **`/ctx-remember`**: reads the latest handover plus any + closeouts whose `generated-at` postdates the handover; the + read-side counterpart to this skill's write surface. +- **Capture skills** (`/ctx-task-add`, `/ctx-decision-add`, + `/ctx-learning-add`, `/ctx-convention-add`): write to the + five canonical files. This skill never modifies those files; + the handover narrative *references* them, it does not author + them. + +## Usage Examples + +```text +/ctx-handover "kb editorial pipeline phase KB skills drafted" +/ctx-handover "rev2 spec landed; tomorrow start the writer package" +/ctx-handover "research session on cursor hooks" +/ctx-handover --no-fold "mid-session checkpoint before lunch" +``` + +## Input Contract + +The skill wraps `ctx handover write`, which enforces required +flags via `MarkFlagRequired` and rejects placeholder bodies via +the Phase SK validation pattern. Empty `TBD`, `see chat`, +whitespace-only values are rejected by the CLI, not just by the +skill text. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--summary` | string | (required) | Past tense; what happened this session. | +| `--next` | string | (required) | Future tense; what the next agent should do FIRST. Specific, not vague. | +| `--highlights` | string | "" | Notable artifacts produced this session. | +| `--open-questions` | string | "" | Things that remain undecided. | +| `--no-fold` | bool | false | Skip closeout consumption (mid-session checkpoint). | +| `--commit` | string | (resolved) | Override resolved git HEAD for Provenance line (CI replay). | + +Positional argument: handover title (becomes filename slug). + +## Pre-Write Gates + +Two distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/handovers/` missing → suggest `ctx init --upgrade` + and stop. + +`.context/kb/` is **not** required for handover; the artifact +exists for code-dev sessions as well. KB-state folding is +conditional on the directory's existence (see §Process). + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate + fails. Zero residue on refusal. + +2. **Gather signal silently** (mirror `/ctx-wrap-up` Phase 1 + when invoked standalone): + + ```bash + git status --short + git diff --stat + git log --oneline @{upstream}..HEAD 2>/dev/null || git log --oneline -5 + ``` + + Scan the conversation history for: + - The session's arc: what shifted from start to now. + - Concrete artifacts produced (files, commits, decisions, + spec entries). + - Open questions surfaced but not resolved. + - The specific first action the next session should take. + +3. **Draft `--summary` and `--next`.** Both are required, both + are validated non-placeholder by the CLI: + + - **`--summary`**: past tense. One paragraph. Names what + was done, not what was attempted. Concrete: *"drafted six + Phase KB skill files; reconciled rev2 spec changes; + deferred CLI wiring to next session"*, not *"made + progress on KB stuff"*. + - **`--next`**: future tense. One paragraph. Names the + specific first action the next agent should take. + Concrete: *"start `internal/cli/handover/cmd/write/cmd.go` + using Phase SK validation pattern"*, not *"continue + work" or "look at the kb"*. + + Surface the drafts to the user for confirmation before + running the CLI. The user is the final authority on what + the handover says. + +4. **Run `ctx handover write`** with the confirmed values: + + ```bash + ctx handover write "<title>" \ + --summary "<one-paragraph past tense>" \ + --next "<one-paragraph future tense>" \ + [--highlights "<bullet list>"] \ + [--open-questions "<bullet list>"] \ + [--no-fold] \ + [--commit <sha>] + ``` + + The CLI: + - Validates flags (placeholder rejection per Phase SK). + - Resolves git HEAD via `gitmeta.ResolveHead` (honors + `CTX_TASK_COMMIT` and `GITHUB_SHA` for CI replay). + - Reads `LatestHandoverCursor` to find the postdated + closeout window. + - Lists `UnconsumedCloseouts` (closeouts whose + `generated-at` postdates the cursor). + - For each unconsumed closeout, folds its body into the + handover's `## Folded closeouts` section. Malformed + closeouts (missing `generated-at`, malformed frontmatter) + are skipped with a warning. + - Calls `ArchiveCloseouts` to move folded closeouts to + `.context/archive/closeouts/`. Archived closeouts are + immutable. + - Writes `.context/handovers/<TS>-<slug>.md`. + + When `--no-fold` is set, the fold + archive steps are + skipped; closeouts stay in place. Use for mid-session + checkpoints where the user wants the handover artifact but + intends to keep ingesting before the next session boundary. + +5. **Report the result.** Surface: + - The handover filename written. + - Count of closeouts folded (or *"none postdated the prior + handover"*). + - Count of malformed closeouts skipped (with filenames so + the user can fix or delete; site-review's job to flag, + but the warning here is opportunistic). + - Any CLI validation failures (with the placeholder text + that triggered rejection). + +## Closeout Fold Mechanics + +The fold mechanism is the integration point between the +editorial pipeline (`/ctx-kb-*` closeouts) and session continuity +(handover artifacts). Mechanically: + +- `LatestHandoverCursor` reads `.context/handovers/` and returns + the `generated-at` of the newest handover (or zero time if + none exists). +- `UnconsumedCloseouts` walks `.context/ingest/closeouts/` and + returns every closeout whose `generated-at` postdates the + cursor. +- Each folded closeout's body is embedded under + `## Folded closeouts` in the new handover, in `generated-at` + order. The frontmatter is preserved verbatim so the audit + trail survives the fold. +- After the fold, `ArchiveCloseouts` moves the source files to + `.context/archive/closeouts/`. Archived closeouts are + immutable; subsequent passes never re-fold them. + +A handover with no postdated closeouts to fold writes a +`## Folded closeouts` section with the body *"none"*; never +omit the section, so the audit trail is explicit. + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/handovers/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Empty `--summary` or `--next` | The CLI rejects with the placeholder-rejection message; surface verbatim. | +| Placeholder values (`TBD`, `see chat`, whitespace-only) for `--summary` or `--next` | The CLI rejects; surface verbatim and ask the user to redraft. | +| No postdated closeouts to fold | Write the handover with `## Folded closeouts` body *"none"*. Never omit the section. | +| Postdated closeout has malformed frontmatter | The CLI skips the file with a warning naming it. Report the warning to the user so they can fix or delete. | +| `--no-fold` set | Skip the fold + archive steps; the handover stands alone; closeouts stay in `.context/ingest/closeouts/` for the next invocation. | +| Mid-session re-invocation | Each invocation writes a new handover file. The newest one is what `/ctx-remember` reads next session. Multiple per session are fine. | +| Session aborted before wrap-up | Closeouts stay in place; next session's `/ctx-remember` reads canonical files + the last handover + any postdated unfolded closeouts. Editorial work survives. | +| User runs `/ctx-wrap-up` without `.context/kb/` present | `/ctx-wrap-up` still drives `/ctx-handover` as its final step; kb-presence affects what gets folded, not whether the handover is written. | +| `gitmeta.ResolveHead` returns an error (no git, detached HEAD with no fallback) | The CLI surfaces the typed `MissingGitError`; relay verbatim. Phase RG owns the recovery path; this skill does not invent one. | +| `CTX_TASK_COMMIT` or `GITHUB_SHA` set | Honoured for the Provenance line per `gitmeta.ResolveHead`'s precedence rules; no special handling here. | + +## Anti-Patterns + +- Writing a handover with `--summary "TBD"` or `--next "see + chat"`. The CLI rejects these; do not work around the + rejection by inventing prose that technically passes the + placeholder check but is still vague. +- Skipping the fold to *"keep closeouts available for a future + pass"*. The fold is the integration point; closeouts that + outlive their relevant handover are recall noise. Use + `--no-fold` explicitly when the user wants the checkpoint + behavior; do not infer it. +- Hand-writing a handover file. The CLI is the sole writer. + Hand-edits drift from the schema the read side expects. +- Modifying an archived closeout. Archived closeouts are + immutable per `KB-RULES.md` §Closeout shape. +- Inventing `--highlights` or `--open-questions` content the + session did not actually produce. Light compression for + clarity is allowed; new facts are not. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. + +For successful handover writes, end with this structured +summary: + +- **Handover**: filename on its own line. +- **Folded closeouts**: count + filenames (or *"none + postdated the prior handover"*). +- **Malformed skipped**: count + filenames (or `none`). +- **Provenance**: `sha=<short> branch=<name>` as resolved by + the CLI. +- **Next-session focus**: the `--next` value, verbatim, so the + operator sees what the next agent will read first. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] `--summary` is past tense and concrete (no placeholder). +- [ ] `--next` is future tense and specific (no placeholder). +- [ ] User confirmed the drafts before the CLI ran. +- [ ] Closeouts were folded (or `--no-fold` was explicitly + requested). +- [ ] Folded closeouts were archived to + `.context/archive/closeouts/`. +- [ ] Handover filename + provenance were reported back to the + user. diff --git a/internal/assets/claude/skills/ctx-history/SKILL.md b/internal/assets/claude/skills/ctx-history/SKILL.md index 950186061..3d34c0ee6 100644 --- a/internal/assets/claude/skills/ctx-history/SKILL.md +++ b/internal/assets/claude/skills/ctx-history/SKILL.md @@ -60,7 +60,7 @@ Use `--full` for the complete conversation. ### `ctx journal import` -Import sessions to the journal directory as markdown. +Import sessions to the journal directory as Markdown. | Flag | Default | Purpose | |----------------------|---------|--------------------------------------------------| diff --git a/internal/assets/claude/skills/ctx-implement/SKILL.md b/internal/assets/claude/skills/ctx-implement/SKILL.md index f9331c2cd..fc4aa19a0 100644 --- a/internal/assets/claude/skills/ctx-implement/SKILL.md +++ b/internal/assets/claude/skills/ctx-implement/SKILL.md @@ -106,7 +106,7 @@ After all steps complete: | Templates/embeds | `CGO_ENABLED=0 go build -o /dev/null ./cmd/ctx` | | Makefile | Run the new/changed target | | Skill files | Build (to verify embed) + check live copy matches | -| Docs/markdown only | None required | +| Docs/Markdown only | None required | | Shell scripts | `bash -n script.sh` (syntax check) | ## Handling Failures diff --git a/internal/assets/claude/skills/ctx-kb-ask/SKILL.md b/internal/assets/claude/skills/ctx-kb-ask/SKILL.md new file mode 100644 index 000000000..6eb54fbe2 --- /dev/null +++ b/internal/assets/claude/skills/ctx-kb-ask/SKILL.md @@ -0,0 +1,236 @@ +--- +name: ctx-kb-ask +description: Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail. +allowed-tools: Bash(ctx:*), Bash(ls:*), Read, Edit +--- + +# Ask the KB + +Answer a question using only what `.context/kb/` already contains. +Cite by `EV-###`. Do not web-jump, do not invent prose, do not +modify topic pages. If the kb cannot answer, open a `Q-###` row +in `.context/kb/outstanding-questions.md` and report the gap. + +This is the read side of the editorial pipeline. The write side +is `/ctx-kb-ingest`. Authority for prose synthesis lives there; +this skill is read-only on prose. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary and +§Evidence discipline; `specs/kb-editorial-pipeline.md` §Interface. + +## When to Use + +- The user asks "does the kb say...", "according to evidence...", + "what do we know about <topic>", or invokes the explicit slash + form with the question. +- The user wants a citation-backed answer before deciding whether + to ingest more material. +- The user is auditing what is already known versus what is + asserted elsewhere (DECISIONS.md, LEARNINGS.md, conversation). + +## When NOT to Use + +- The user wants new material extracted (use `/ctx-kb-ingest`). +- The user wants the kb structurally audited (use + `/ctx-kb-site-review`). +- The user wants kb claims re-grounded against external sources + (use `/ctx-kb-ground`). +- The question is about `ctx` itself or the editorial pipeline + contract (answer from `KB-RULES.md` / spec directly). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ask`**: read-only Q&A over `.context/kb/` prose, + `evidence-index.md`, `glossary.md`, `contradictions.md`, + `outstanding-questions.md`, `timeline.md`, `source-map.md`, + `domain-decisions.md`. Writes are limited to opening a + `Q-###` row in `outstanding-questions.md` when the kb cannot + answer, plus the ask closeout. +- **`/ctx-kb-ingest`**: writes prose, evidence, scaffold. Only + ingest may add citations or extend topic pages. +- **`/ctx-kb-ground`**: refreshes external sources via + `grounding-sources.md`; this skill never web-jumps to fill a + gap. If the gap matters, recommend `/ctx-kb-ground` or + `/ctx-kb-ingest`. + +## Usage Examples + +```text +/ctx-kb-ask "what does the kb say about cursor hooks failure modes?" +/ctx-kb-ask "how do we cite a transcript locator?" +/ctx-kb-ask "are there contradictions on backup retention windows?" +``` + +## Input Contract + +A single question, supplied as the slash argument or inline. No +flags. No sources. No URLs. + +## Refuse-on-Empty + +If the invocation supplied no question (empty slash arg, empty +inline body), return exactly: + +> no question provided; pass a question or describe it inline. + +Stop. Do not prompt interactively. The CLI enforces this +independently via `cmd/ask`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue (no +`Q-###` row opened, no closeout): + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- `.context/kb/index.md` exists but `## Scope` is undeclared → + refuse with the scope message (same wording as `/ctx-kb-ingest` + uses) and stop. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Read the question.** Parse for the concept(s) it names. + +3. **Survey the kb.** Read in this order, stopping early when an + answer surfaces with adequate citation coverage: + - `.context/kb/index.md` for scope. + - `.context/kb/topics/<slug>/index.md` and any sibling + sub-pages for any slug that plausibly matches the question. + - `.context/kb/evidence-index.md` for `EV-###` rows whose + claim text matches. + - `.context/kb/glossary.md` for term definitions. + - `.context/kb/contradictions.md` for known disagreements + relevant to the question. + - `.context/kb/outstanding-questions.md` for prior + unanswered questions on the topic. + +4. **Decide answer vs gap.** One of three outcomes: + + - **Answerable with citations.** The kb's prose plus + `EV-###` rows cover the question. Compose a concise answer. + Cite every load-bearing claim by `EV-###`. Name the topic + page(s) where the prose lives. Note the Confidence floor + of the cited rows. + - **Partial answer.** Some of the question is covered; the + rest is not. Answer the covered part with citations. Name + the gap explicitly. Open a `Q-###` row for the gap (see §6). + - **Not answerable.** The kb has no prose and no `EV-###` + coverage. Do not invent. Do not web-jump. Open a `Q-###` + row (see §6) and report the gap. + +5. **Do not jump.** This skill is read-only on prose AND + web-quiet. If the kb cannot answer: + + - Do **not** fetch a URL. + - Do **not** propose synthesized prose without citations. + - Do **not** call MCP search tools. + - Do **not** quote LLM training-data recall as if it were + kb evidence. + + The correct response to a gap is to name the gap, open a + `Q-###` row, and recommend `/ctx-kb-ground` (if external + refresh is the right path) or `/ctx-kb-ingest <sources>` (if + the user has materials to feed in). + +6. **Open a `Q-###` row if there is a gap.** Append a row to + `.context/kb/outstanding-questions.md` per its schema. The + row's question text is the user's question (or a faithful + paraphrase). The row notes what the kb does cover (if + partial) and what evidence would resolve. Do NOT mint + `EV-###` rows from this skill; that is ingest's authority. + +7. **Write the ask closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-ask-closeout.md` with + required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ask + pass-mode: read-only + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Question**: what the user asked, verbatim. + - **Answer**: the answer given, or `none (gap)` if not + answerable. + - **Citations**: `EV-###` IDs cited, with topic-page paths. + - **Gaps**: `Q-###` opened in `outstanding-questions.md`, + with a one-line rationale. + - **Next pass hint**: explicit invocation for the next + pipeline step (e.g. `/ctx-kb-ground` to refresh, + `/ctx-kb-ingest <sources>` to extend). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty question | Refuse with the standard no-question text. No `Q-###` opened, no closeout. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/kb/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message; point at `.context/kb/index.md`. No residue. | +| Multiple topics relevant | Cite each topic page; do not synthesize a new cross-topic claim (that would be ingest work). Surface the seam as a `Q-###` if it merits one. | +| Contradiction surfaces during answer | Answer with the lower-confidence side noted; cite both `EV-###` rows; point at `contradictions.md`. | +| Cited rows are all `speculative` or `low` | Surface the confidence band in the answer. Recommend `/ctx-kb-ground` to corroborate. Do not promote in this pass. | +| Question matches an existing `Q-###` row | Cite the existing row's ID; report status (`open`, `partially-answered`); do not open a duplicate. | +| Question requires external evidence the kb does not have | Open a `Q-###` row; recommend `/ctx-kb-ground` with the gap named; do not fetch the source. | +| Question is meta (about the pipeline itself) | Answer from `KB-RULES.md` / spec directly; this skill is for kb content, not pipeline contract. State that explicitly. | + +## Anti-Patterns + +- Web-jumping when the kb cannot answer. The contract is + read-only on prose AND web-quiet. +- Inventing citations or claims to make the answer look fuller. +- Modifying a topic page to extend an answer mid-pass. Topic-page + authoring is `/ctx-kb-ingest`'s authority. +- Minting `EV-###` rows from this skill. Evidence authoring is + `/ctx-kb-ingest`'s authority. +- Skipping the `Q-###` row when the kb cannot answer. The gap + is the audit trail; silence on a gap is invisible. +- Skipping the closeout once the pre-write gates pass. The + closeout is the residue wrap-up's handover step folds into + the next session's recall. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For passes that clear pre-write gates, end with this structured +summary: + +- **Question**: verbatim or faithful paraphrase. +- **Answer**: concise; cites every load-bearing claim by + `EV-###`. +- **Confidence floor**: lowest band among cited rows + (`high|medium|low|speculative`), or `n/a` if no rows cited. +- **Gaps**: `Q-### opened` (one bullet per opened row), or + `none`. +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocation if a gap + was opened (e.g. `/ctx-kb-ground` or `/ctx-kb-ingest + <sources>`), or `none` if the answer is complete. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every load-bearing claim in the answer cites at least one + `EV-###` row from `evidence-index.md`. +- [ ] If a gap exists, a `Q-###` row was opened (or an existing + row was cited). +- [ ] No URL was fetched, no MCP search was called, no LLM + training-data recall was quoted as kb evidence. +- [ ] No topic page was modified, no `EV-###` row was minted. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/claude/skills/ctx-kb-ground/SKILL.md b/internal/assets/claude/skills/ctx-kb-ground/SKILL.md new file mode 100644 index 000000000..acf1f3821 --- /dev/null +++ b/internal/assets/claude/skills/ctx-kb-ground/SKILL.md @@ -0,0 +1,279 @@ +--- +name: ctx-kb-ground +description: External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip. +allowed-tools: Bash(ctx:*), Bash(ls:*), Read, Edit +--- + +# Ground the KB Against External Sources + +Refresh `.context/kb/` against the external sources the user has +declared in `.context/ingest/grounding-sources.md`. This is the +*"are we still current?"* pass. It does not mint new evidence by +itself, does not author topic pages, does not modify Confidence +bands. It walks the declared external sources, checks each for +drift against what the kb cites, and advances the source-coverage +ledger to reflect what the refresh found. + +If the refresh surfaces material the kb should absorb, this skill +flags it and recommends `/ctx-kb-ingest`. Authoring is ingest's +authority, not this skill's. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary and +§Source-coverage ledger; `specs/kb-editorial-pipeline.md` +§Interface and §Edge Cases. + +## When to Use + +- The user says "re-ground the kb", "check upstream", + "are the docs still current?", or invokes the explicit slash + form. +- Before a release / handover where source freshness matters. +- After an external vendor has shipped a version bump. +- Periodically (per the user's cadence) as kb hygiene. + +## When NOT to Use + +- The user has new materials in hand (use `/ctx-kb-ingest`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The user wants a structural audit (use `/ctx-kb-site-review`). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ground`**: refreshes external sources listed in + `grounding-sources.md`; advances source-coverage ledger rows + for sources where the refresh changed state; writes a ground + closeout. **May not** mint `EV-###` rows, author prose, + modify a topic page, or change a Confidence band. +- **`/ctx-kb-ingest`**: handles anything this skill surfaces + as new material to absorb. +- **`/ctx-kb-ask`**: handles read-only questions about kb + content. +- **`/ctx-kb-site-review`**: handles structural audit (separate + surface from source-freshness audit). + +## Usage Examples + +```text +/ctx-kb-ground +``` + +No arguments. Sources come from +`.context/ingest/grounding-sources.md`. + +## Input Contract + +The file `.context/ingest/grounding-sources.md` is the sole +declaration surface. Each non-empty, non-comment line names a +source (URL, in-tree path, MCP resource identifier) the user +wants this skill to track. A line whose value is the literal +`NONE` is a **per-pass skip**: this invocation does nothing and +re-prompts on the next invocation. Lines beginning with `#` are +comments. + +There is no CLI argument for sources. To configure what this +skill checks, edit `grounding-sources.md`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- Kb scope undeclared → refuse with the scope message and stop. + +## Refuse-on-Empty + +`.context/ingest/grounding-sources.md` may be in three states: + +1. **Missing or empty**: file does not exist, or has only + comments and blank lines. Prompt once: + + > `grounding-sources.md` has no sources. List one source per + > line (URL, in-tree path, MCP resource). `NONE` on a line + > is a per-pass skip and re-prompts next invocation. + + Stop. Do not synthesize a list. Do not invent sources from + the kb's `source-map.md` (that file's authority is ingest; + grounding's declaration surface is separate by design). + +2. **Single line `NONE`**: per-pass skip. Write no closeout; + return exactly: + + > grounding-sources.md is `NONE` for this pass; skipping. + > Edit `.context/ingest/grounding-sources.md` to set actual + > sources, or leave `NONE` to keep skipping. + + The next invocation re-prompts as in (1) above. + +3. **One or more sources listed**: proceed to Process. + +The empty-and-prompt path is the one exception to the +refuse-on-empty pattern other mode skills enforce; the rationale +is that grounding's declaration lives in a file the user owns +(not in a slash argument), so a one-shot prompt is cheaper than +forcing them to remember the filename. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Read `.context/ingest/grounding-sources.md`.** Handle the + three states per §Refuse-on-empty. + +3. **For each declared source**, in order of appearance: + + - **Resolve** the source: fetch the URL, stat the in-tree + path, enumerate the MCP resource. + - **Cross-reference** against `.context/kb/source-map.md` to + find the kb's short-name for this source (if any). If + absent, the source is *new to the kb*; record it as a flag + to surface in the closeout (do not mint a `source-map.md` + row; that is ingest's authority). + - **Check freshness** using the strongest available signal: + - URL: HTTP Last-Modified header, or ETag, or visible + version stamp on the page; compare against the + `source-map.md` row's `dated:` cell (if present). + - In-tree path: file mtime + git SHA; compare against the + `evidence-index.md` rows that cite the source by SHA + (in-repo citations pin to a SHA at extraction time per + `KB-RULES.md` §Evidence discipline). + - MCP resource: whatever freshness primitive the resource + exposes; if none, treat as opaque (record as + *"freshness opaque"* in the closeout). + - **Classify the refresh outcome** as one of: + - **`unchanged`**: source has not drifted since the + kb's last extraction; no ledger update needed. + - **`drifted`**: source has changed; the kb's claims + citing this source may be stale; advance the ledger row + to a state that reflects the staleness: + - If the row was `comprehensive`, advance to a typed + `superseded-pending` annotation in the `Residue` cell + (do not write a new state name; the state machine in + `KB-RULES.md` is closed; `Residue` is the + human-readable annotation surface). + - If the row was anywhere prior to `comprehensive`, + leave the state and add a `drifted` note in `Residue` + + `Next action` set to the explicit + `/ctx-kb-ingest <slug>` resumption. + - **`gone`**: source returns 404, file deleted, MCP + resource removed; flag for the user. The right + resolution may be `superseded` (with a named successor) + or `skipped` (out of scope); that judgment is the + user's, not this skill's. + - **`freshness opaque`**: no freshness signal available; + record in `Residue` cell as *"freshness opaque + (<date checked>)"*; no ledger state change. + - **Advance the ledger row** only for `drifted` and `gone` + cases, and only via `Residue` / `Next action` annotation + (not state change). State transitions out of `comprehensive` + are ingest's authority. + +4. **Write the ground closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-ground-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ground + pass-mode: external-refresh + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Inputs**: declared sources from + `grounding-sources.md`, count + one bullet each. + - **Refresh outcomes**: for each source: `unchanged`, + `drifted`, `gone`, `freshness opaque`, or `new to kb`. + Cite the kb short-name (or *"new to kb"*) and the + evidence used to classify (Last-Modified header, version + stamp, file mtime). + - **Ledger updates**: every `Residue` / `Next action` + change applied to a `source-coverage.md` row, with the + before/after annotation. + - **Flags**: sources the refresh found `gone`, sources + classified `new to kb`, sources with conflicting + freshness signals. Each flag names the source and the + recommended next pipeline step. + - **Next pass hint**: explicit invocations to absorb + drifted / new material (e.g. *"`/ctx-kb-ingest <slug>` to + refresh `cursor/hooks` against the v1.2 docs"*). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `grounding-sources.md` missing or empty (only comments/blank) | Prompt once with the standard text; stop. No closeout. | +| `grounding-sources.md` single line `NONE` | Skip this pass with the standard skip text; stop. No closeout. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message. No residue. | +| Source returns 404 / file deleted / MCP resource removed | Classify `gone`; flag; recommend the user choose `superseded` (with successor) or `skipped` (out of scope). Do not auto-transition. | +| Source unchanged since last extraction | Record `unchanged` in closeout's `Refresh outcomes`; no ledger update. | +| Source drifted since last extraction (URL bumped, file mtime newer than cited SHA) | Record `drifted`; annotate ledger row's `Residue` / `Next action`; recommend `/ctx-kb-ingest <slug>`. Do not modify topic-page prose. | +| Source has no freshness primitive (opaque) | Record `freshness opaque (<date checked>)`; no ledger state change; surface in `Flags` so the user can decide cadence. | +| Source listed in `grounding-sources.md` but not in `source-map.md` (new to kb) | Classify `new to kb`; flag; recommend `/ctx-kb-ingest <source>` to admit. Do not mint a `source-map.md` row from this skill. | +| Source listed in `grounding-sources.md` but URL malformed or path nonexistent | Surface as a per-source error in the closeout's `Flags`; continue with remaining sources; do not abort the pass. | +| Source's `source-map.md` row has `dated:` but `evidence-index.md` rows lack `occurred:` | Flag (temporal-precedence rule needs it); recommend hand-edit. Do not auto-edit. | +| User added a new source to `grounding-sources.md` since the last pass | Treated as a regular declared source; classified per the freshness check; no special path. | +| Mid-pass MCP fetch failure | Record per-source error; continue; do not abort the whole pass. | + +## Anti-Patterns + +- Minting `EV-###` rows from this skill. Evidence authoring is + ingest's authority. +- Authoring topic-page prose from this skill. Page authoring is + ingest's authority. +- Modifying a claim's Confidence band from this skill. Demotion + is evidence work. +- Auto-transitioning a `comprehensive` ledger row out of + `comprehensive`. State changes require ingest judgment; this + skill annotates `Residue` / `Next action` only. +- Synthesising a source list when `grounding-sources.md` is + empty. The declaration surface is the file the user owns. +- Inventing a freshness signal when none exists. *"Freshness + opaque"* is the honest classification. +- Skipping the closeout once pre-write gates pass and at least + one source was processed. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For empty / `NONE` cases, return the matching prompt or skip +text and stop. No closeout in those cases. + +For passes that processed at least one source, end with this +structured summary: + +- **Sources checked**: count + one bullet each, classified + (`unchanged | drifted | gone | freshness opaque | new to kb`). +- **Ledger updates**: count + one-line categories. +- **Flags**: count + categories. +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocations to absorb + drifted / new material (or `none` if every source was + `unchanged`). + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every declared source from `grounding-sources.md` was + checked, classified, and recorded in `Refresh outcomes`. +- [ ] No `EV-###` row was minted, no topic-page prose was + written, no Confidence band was changed, no ledger state + was transitioned (only `Residue` / `Next action` + annotated). +- [ ] Every `drifted` / `gone` / `new to kb` source has an + explicit `Next-recommended-action`. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/claude/skills/ctx-kb-ingest/SKILL.md b/internal/assets/claude/skills/ctx-kb-ingest/SKILL.md new file mode 100644 index 000000000..09edea30e --- /dev/null +++ b/internal/assets/claude/skills/ctx-kb-ingest/SKILL.md @@ -0,0 +1,645 @@ +--- +name: ctx-kb-ingest +description: Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail. +allowed-tools: Bash(ctx:*), Bash(ls:*), Bash(git status), Read, Edit, Write +--- + +# Editorial Ingestion Pass + +This skill is the **single editorial pass** for adding knowledge +to `.context/kb/`. It reads materials the user supplies, decides +which topic page(s) they belong to, finds-or-creates those pages, +writes synthesized prose section by section, mints `EV-###` rows +in the structured layer as it cites them, cross-links neighboring +topics, updates the source-coverage ledger, and writes a closeout +file under `.context/ingest/closeouts/`. + +The split between "extract claims" and "write the topic page" is +mechanical, not editorial. A student reading a book does not +extract a glossary first and synthesize later, they read and write +at the same time. This skill matches that model: the user supplies +*intent and material*; the skill does *judgment and typing*. + +**The topic page is the deliverable. The closeout is the audit +trail. The closeout never substitutes for the page.** Intermediate +artifacts (EV rows, glossary entries, candidate-source registries, +closeouts) are valuable, but they do not validate topic-page work +by themselves; only the topic page does. + +Authoritative background reading lives at +`.context/ingest/KB-RULES.md` and `specs/kb-editorial-pipeline.md`. +This skill encodes the workflow contract; the rules file is the +constitution. Hand-edit `KB-RULES.md` to evolve the contract; do +not paraphrase it here. + +## When to Use + +- The user supplies one or more sources (paths, URLs, MCP + resources, inline natural-language descriptions) and wants them + read into the kb. +- The user says "ingest the transcripts", "pull this into the + kb", "add evidence from <source>", "extract claims from this + call", or invokes the explicit slash form with paths. +- A prior pass left residue (a `topic-page-drafted` ledger row, + a `Next pass hint` in a closeout) and the user is resuming. + +## When NOT to Use + +- The user asked a question about the kb (use `/ctx-kb-ask`). +- The user wants a structural audit of the kb (use + `/ctx-kb-site-review`). +- The user wants to re-ground existing kb claims against + external sources (use `/ctx-kb-ground`). +- The user wants to park a quick finding for the next ingest + (use `/ctx-kb-note`). +- No sources were supplied (refuse-on-empty; see §Refuse-on-empty + below). +- `.context/kb/` does not exist (refuse with the no-pipeline + message in §Pre-write gates). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ingest`**: primary editorial pass. Reads materials + (in-tree paths, out-of-tree paths, URLs, MCP resources, inline + references); writes topic pages + (`.context/kb/topics/<slug>/index.md`, plus optional sibling + sub-pages); mints evidence, glossary, source-map, timeline, + contradictions, outstanding questions; cross-links into existing + kb topology; updates the source-coverage ledger; writes + closeout. **Topic-page file creation is performed only by + `ctx kb topic new`**: this skill MAY invoke that CLI as part + of a topic-page pass, but it MUST NOT synthesize or write a + scaffold directly. This preserves the public editorial workflow + (`/ctx-kb-ingest`) and the actual scaffold authority + (`ctx kb topic new`) as two separate facts. +- **`/ctx-kb-ask`**: Q&A grounded in the kb. Read-only on prose; + refuses to web-jump; flags gaps the kb cannot answer. +- **`/ctx-kb-site-review`**: structural audit; mechanical fixes + only. Defers anything that requires evidence judgment. +- **`/ctx-kb-ground`**: external grounding against + `grounding-sources.md`; advances ledger rows for sources it + refreshes. +- **`/ctx-kb-note`**: lightweight capture into + `.context/ingest/findings.md`; never writes to a topic page or + to `evidence-index.md`. + +This skill writes prose AND evidence rows AND scaffold (via CLI) +AND cross-links AND ledger updates in the same pass; that +combination is unique to ingest. + +## Usage Examples + +```text +/ctx-kb-ingest ./inputs/2026-04-12-call.md "cursor hooks" +/ctx-kb-ingest ./inputs/your-domain/ +/ctx-kb-ingest https://cursor.com/docs/hooks +/ctx-kb-ingest ./a.md ./b.md "incident retros" +/ctx-kb-ingest --inline "the four transcripts under inputs/ \ + and the pool.go file" "connection pooling" +``` + +## Input Contract + +**Sources**, supplied as one or more of: + +- **Paths**: folder to recurse, single file, list of files. +- **URLs**: primary-source web pages. +- **MCP resources**: named resources from connected MCP servers. +- **Inline gestures**: natural-language naming the materials. +- **Open invitation**: *"feel free to search for more"*. The + skill gets web-search and MCP-discovery authority for this + pass; hard cap of 50 total sources. + +**Optional second argument, topic name**, e.g. *"cursor hooks"*. +When omitted, the skill proposes one at §3 of Process and +confirms with the user before any extraction work. Naming the +topic up front skips that round-trip. + +## Refuse-on-Empty + +The skill writes to the kb; refuse-on-empty is the default. If +the invocation supplied no sources and no inline gesture, return +exactly: + +> no sources provided; pass a folder, a URL, an MCP resource, or +> describe the materials inline. + +Stop. Do not prompt for sources interactively, do not invent a +topic, do not propose a triage pass on imagined material. The CLI +enforces this independently via `cmd/ingest`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue (no +`INBOX.md` rewrite, no `SESSION_LOG.md` entry, no claim +extraction, no ledger update, no closeout, no topic-page edits): + +- `.context/` missing entirely → suggest `ctx init` and stop. +- `.context/ingest/` missing (project initialised before this + spec shipped) → suggest `ctx init --upgrade` and stop. +- Kb scope undeclared (`.context/kb/index.md` missing, contains + the `TODO: declare what this kb covers` placeholder, has no + `## Scope` H2, or `## Scope` lacks substantive + non-placeholder prose): + + > kb scope is undeclared. Open `.context/kb/index.md` and + > replace the TODO placeholder with a one-paragraph scope + > statement that names what is in scope and what is out. + > `/ctx-kb-ingest` refuses to ingest until scope is declared. + +## Pass-Mode Contract + +Every invocation MUST classify itself as exactly one of three +modes **before any source extraction begins**. The mode commits +the pass to a specific definition of done; the skill is held to +that definition and may not narrate success on residue belonging +to a different mode. Full mode semantics live in +`.context/ingest/KB-RULES.md` §Pass-mode contract. + +| Mode | Mints prose? | Mints `EV-###`? | Touches topic page? | Default? | +|------------------|--------------|------------------|------------------------|----------| +| `topic-page` | yes | yes | yes (create/extend) | yes | +| `triage` | no | **no** | no | no | +| `evidence-only` | no | yes (tagged) | no | no | + +**Mode selection rules.** Default is `topic-page`. `triage` fires +only when the user supplied multiple disparate sources with no +clear single topic, OR explicitly invoked triage language. +`evidence-only` fires only on explicit user request matching the +valid-trigger criteria (*"just mint EV rows"*, *"backfill +evidence"*); the skill MAY NOT infer it from source size, +ambiguity, time pressure, or operator convenience. + +**Up-front declaration (mandatory).** Before extraction begins +(after pre-write gates pass and **before** topic resolution), emit +a visible pre-work declaration in the response stream: + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific completion criterion>` + +The declaration is a contract, not a label. The skill is bound to +it for the rest of the pass. + +**Mid-pass mode-switching is forbidden.** If the work in flight +no longer fits, abort with a partial closeout citing what was +done, and recommend re-invocation under the correct mode. Silent +mode-drift is a hard anti-pattern. + +## Topic-Page Circuit Breaker + +A pass operating in `topic-page` mode MAY NOT report +`topic-page: produced` or `topic-page: extended` unless ALL of +the following are true at completion: + +1. `.context/kb/topics/<slug>/index.md` (or a sibling sub-page + like `.context/kb/topics/<slug>/<sub>.md`) exists and was + created or extended in this pass. +2. The page cites at least one `EV-###` row that resolves to + `evidence-index.md`. +3. `ctx kb site build` ran clean (or its failure is named in the + closeout's `Next pass hint` AND the pass reports + `topic-page: deferred`). +4. The cold-reader orientation rubric records **`Result: pass`** + in the closeout's `What changed` section. All four rubric + items must be `yes`. + +Any failure → `topic-page: deferred` and the source-coverage +ledger advances to `topic-page-drafted` (not `comprehensive`). +This invariant prevents intermediate residue from being treated +as topic-page success. **Topic-page validation requires the +topic page.** + +## Source-Coverage Ledger + +`.context/kb/source-coverage.md` is a state machine over every +source the kb has touched. Allowed transitions live in +`KB-RULES.md` §Source-coverage ledger; do not paraphrase them +here. Every pass updates the ledger before writing the closeout. +**Lying to the ledger is a hard anti-pattern.** Set the state +honestly even when it means recording incomplete work. + +## Cold-Reader Orientation Rubric + +Four yes/no items recorded in the closeout's `What changed` +section, in `topic-page` mode: + +``` +Cold-reader orientation: +- Concept clear? yes|no: <short note> +- Why this kb cares clear? yes|no: <short note> +- Canonical evidence reachable? yes|no: <short note> +- Boundaries clear? yes|no: <short note> +Result: pass | fail +``` + +`Result: pass` requires all four `yes`. Any `no` → +`Result: fail` → circuit-breaker fails → `topic-page: deferred`. + +## Life-Stage Check + +Count `.context/kb/topics/*/index.md` pages **before** this pass +begins synthesizing: + +- `< 5` topic pages → **bootstrap** mode. Skip reconciliation + ceremony; synthesize topic pages aggressively. Exception: + surface a contradiction even in bootstrap if the new material + plainly contradicts existing kb claims. +- `>= 5` topic pages → **maintenance** mode. Apply full + reconciliation discipline (laddering, demotion, contradiction + detection). + +Document the life-stage call in the closeout's frontmatter +(`life-stage:`) and `What changed` section. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly with the matching + message from §Pre-write gates if `.context/`, + `.context/ingest/`, or kb scope is missing. No residue on + refusal. + +2. **Declare pass-mode and surface the up-front declaration.** + Determine the mode per §Pass-mode contract. Emit the + three-line declaration block in the response stream **before + any further work**. Mid-pass mode-switching is forbidden; + abort and re-invoke if the work no longer fits. + +3. **Resolve the topic.** *(Topic-page mode only; skipped in + `triage` and `evidence-only`.)* + + - **Read `.context/kb/source-coverage.md` in full first.** It + answers *"what does this kb already know about which + sources, and at what completeness?"*: a precondition for + honest topic resolution, not an afterthought. + - **Topic-adjacency pre-flight (mandatory).** Scan the ledger + for rows whose state is **not** in + `{comprehensive, skipped, superseded}` AND whose `Topic` is + plausibly *adjacent*. Heuristics: + - **Shared first segment of a slash- or hyphen-separated + slug**: `cursor/skills` is adjacent to `cursor/hooks`. + - **Shared product / vendor / surface** in the source URL or + description. + - **Explicit cross-references** in the named topic's + existing sub-pages or this pass's source set. + + For each adjacent incomplete topic surfaced, this pass MUST: + 1. Acknowledge it in `## Related concepts in this kb` on the + topic page being authored. + 2. Surface it in the closeout's `Adjacency pre-flight` + block. + 3. Surface it in the response contract's `Adjacent topics + noted` field. + + **Do NOT enumerate `EV-###` IDs by name in the adjacency + block.** Use *count + location* (*"seventeen rows in + `evidence-index.md`"*). Naming an EV row from a + lower-confidence sibling demotes the floor of cited bands. + + Silence is not a clean pre-flight; if zero matches, record + *"no incomplete adjacent topics surfaced"* explicitly. + + - **Named vs unnamed branches.** If the user named a topic, + accept it and map to slug (lowercase + kebab-case). If not, + scan the inputs *just enough* to propose one and confirm: + + > you haven't named a topic; based on the inputs this looks + > like **"<proposed name>"**. Confirm or correct. + + One question. Wait for confirmation. If material spans + multiple topics, ask once for the splits. Do not auto-split. + +4. **Resolve sources (and discover, if invited).** *(All modes.)* + + - Resolve every supplied source: fetch URLs, recurse folders, + enumerate MCP resources. + - If the user invited discovery, do bounded web/MCP search. + - **Hard cap: 50 total sources** (supplied + discovered) per + pass. Quality of synthesis collapses past it. + - **If discovery exceeds 50**, keep the 50 highest-judged + sources for this pass; append the overflow to + `.context/ingest/candidate-sources.md` under a "Pending + (overflow from <date> ingest of `<topic-slug>`)" heading. + - **Update the source-coverage ledger**: every supplied source + moves from absent → `discovered` (if newly seen) → + `admitted` (if scope-conformant) or → `skipped` (if not). + Discovered sources kept for this pass also land at + `admitted`; overflow stays at `discovered` with a pointer. + + Append a `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=resolve status=<done|partial|blocked> note=<<=120 chars> + ``` + +5. **Survey kb topology and determine life-stage.** *(All + modes.)* + + - List `.context/kb/topics/*/index.md`; glance for sibling + sub-pages so the cross-link palette includes them. + - Read `.context/kb/index.md` for the canonical scope. + - Skim recent sections of `evidence-index.md`, `glossary.md`, + `outstanding-questions.md`, `contradictions.md`, + `timeline.md` for prior claims relevant to this pass. + - **Life-stage check**: count `kb/topics/*/index.md`. `< 5` + is bootstrap; `>= 5` is maintenance. Document the call in + the closeout's frontmatter (`life-stage:`). + +6. **Find or create the topic page.** *(Topic-page mode only.)* + + Topic pages are folder-shaped from day one: + `.context/kb/topics/<slug>/index.md`, with optional sibling + sub-pages. + + - **If `.context/kb/topics/<slug>/index.md` exists**, read it + AND enumerate any sibling sub-pages. The pass **extends** + the topic: append/extend prose; reuse existing `EV-###` + rows where possible; preserve human edits; do not reformat + to match a newer template. Choose the right file: + - Lede / "What it is" overview → edit `index.md`. + - Existing sibling sub-page material → edit that sub-page. + - **Sub-page split is lazy.** Do NOT pre-emptively split. + Only split when `index.md` has grown to fail the + cold-reader "boundaries clear?" check; at which point, + propose the split (one question, wait for confirmation; + sub-page topology affects long-term shape). + - **If `.context/kb/topics/<slug>/` does not exist**, scaffold + by invoking `ctx kb topic new "<concept name>"`. The CLI is + the sole writer of the scaffold; do not synthesize it by + hand. The CLI creates the folder, writes `index.md`, AND + registers the new slug in `.context/kb/index.md`'s + `CTX:KB:TOPICS` managed block. + + After revising the page's H1 or Confidence band in §10, run + `ctx kb reindex` so the managed block refreshes. + +7. **Synthesise.** Body depends on declared mode. + + ### `topic-page` mode + + For each template section (Status block, lede, "What it is", + "Why this kb cares", "Sources and further reading", optional + sections): + + - **Read the source(s) carefully**: full pass, not skim. + - **Write paraphrased prose that captures the understanding**, + not a transcription. + - **For each claim needing citation, mint or reuse `EV-###`:** + - Re-read `evidence-index.md` immediately before writing to + find the highest existing `EV-NNN`; append the next + integer. Pad to three digits (`EV-012`, not `EV-12`). + Duplicate IDs are a hard refusal: abort and re-read. + - **If the claim is already pinned** by an existing row, + reuse the ID verbatim. If the existing claim no longer + matches, treat as a contradiction (§8). + - **If the existing row carries the `evidence-only` tag**, + treat as review-required: re-read the source, confirm the + claim, then promote onto the page. Leave the tag in + place; it is audit trail. + - Append the row to `evidence-index.md` per its schema + (claim, source short name + locator, optional `sha:` for + in-repo citations, confidence band, tags, extracted + date). + - If the source is new, append a row to `source-map.md`. + - Cite `EV-###` inline in the prose. + - **Cross-link** to existing kb topics, DECISIONS.md, + LEARNINGS.md, and `docs/` entries when applicable. + - **Mandatory `## Related concepts in this kb` entries** for + adjacent incomplete topics surfaced by §3's pre-flight. The + acknowledgement must read as a forward pointer (state + + count + location), not as trivia. + - **Mark unbacked claims with `TBD-cite`** and open + `outstanding-questions.md` entries for each. + - **Update `glossary.md`** for net-new terms. + - **Update `timeline.md`** if the pass surfaces a dateable + event. + + **Never invent citations.** **Never** promote a claim above + `speculative` without an `evidence-index.md` row backing it. + + ### `triage` mode + + For each admitted source, judge admission/skip against the + scope paragraph and propose topic routing in the closeout. Do + NOT write to any topic page. **Do NOT mint `EV-###` rows.** Do + NOT touch `evidence-index.md`, `glossary.md`, or + `timeline.md`. + + Triage is routing and admission, not extraction. If the user + asks to *"triage and grab obvious facts as you go,"* abort + with a partial closeout and recommend re-invocation under + either `topic-page` or `evidence-only` mode. Triage MAY update + `source-coverage.md` and `candidate-sources.md`. That is the + full write surface for triage. + + ### `evidence-only` mode + + For each admitted source, mint `EV-###` rows + `source-map.md` + rows + `glossary.md` entries for terms encountered. **Do not + touch any topic page.** Do not write prose synthesis. + + Every minted `EV-###` row MUST include the literal tag + `evidence-only` in its tags column. The tag is **additive**; + it does not replace topical tags. + + Append a `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=synthesise status=<done|partial|blocked> note=<topic slug + <=80 chars> + ``` + +8. **Apply life-stage reconciliation discipline.** *(All modes; + behavior depends on life-stage.)* + + **Bootstrap (`< 5` topic pages)**: skip except for the + contradiction exception in §5. Append a `SESSION_LOG.md` line + with `status=skipped-bootstrap`. + + **Maintenance (`>= 5` topic pages)**: for each EV row minted + in §7: + + - **Reinforces an existing claim** → promote per the + laddering rules in `KB-RULES.md` §Confidence bands + (`speculative → low → medium → high`); cross-link the new + row to the prior one. + - **Contradicts an existing claim** → add a row to + `contradictions.md`; demote the older claim per the + demotion policy in `KB-RULES.md` §Demotion policy; open an + `outstanding-questions.md` entry naming both sides and what + evidence would resolve. + +9. **Set the topic page's Confidence floor.** *(Topic-page mode + only.)* Inspect every `EV-###` cited on the page; the page's + Status-block `Confidence` is the **lowest** of those cited + bands. Refuse to set Confidence above the floor. Refuse to + set above `speculative` while any `TBD-cite` remains. + +10. **Update the topic page's Status block.** *(Topic-page mode + only.)* Substitute `Subject:`, `Last verified:`, `Author:` + (`agent-ingested` if untouched by a human in this pass; + `mixed` if a human revised prose; **never** + `hand-authored`), and `Confidence:` per §9. + +11. **Update the source-coverage ledger.** *(All modes.)* For + every source touched, advance its row in + `.context/kb/source-coverage.md` per the state machine. + Update `EV coverage`, `Residue`, `Next action`, `Updated` + columns honestly. Lying to the ledger is a hard + anti-pattern. + +12. **Topic-page circuit breaker check.** *(Topic-page mode + only.)* Verify all four invariants from §Topic-page circuit + breaker. Any failure → `topic-page: deferred` and ledger to + `topic-page-drafted` (NOT `comprehensive`). + +13. **Write the closeout.** *(All modes; mode-aware body.)* + Create + `.context/ingest/closeouts/<TIMESTAMP>-ingest-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ingest + pass-mode: <topic-page|triage|evidence-only> + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections (mode-aware): **Inputs**, **Pass-mode** (block + repeated from §2 declaration so reviewers can compare promise + vs. result), **Topic(s) touched**, **What changed** + (including the Cold-reader rubric in topic-page mode), + **New questions**, **New contradictions**, **Confidence + drift**, **Source-coverage updates**, **Overflow**, + **Adjacency pre-flight**, **Next pass hint**. + + Append a final `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=closeout status=done note=<topic slug + <=80 chars> + ``` + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty input | Refuse with the standard no-sources text. No residue. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message; point at `.context/kb/index.md`. No residue. | +| Source returns nothing usable (404, binary, paywall) | Record in closeout's `Next pass hint` AND the topic page's "Open questions"; advance the ledger row to `skipped` with the failure reason. Do not invent claims. | +| All sources skipped during admission | Write a short closeout with empty `Topic(s) touched` and `What changed`; `Next pass hint` lists every skipped source with scope-citation. | +| Material spans 3+ topics and user can't decide | Ask once in §3; if still unresolved, abort with a partial closeout recommending re-invocation under `triage`. | +| Discovery turns up zero additional sources | Note in closeout's `Inputs` section. Not a failure. | +| Stale Status block on existing page | Flag in closeout's `Next pass hint`; do not silently overwrite the verification cursor unless the source was actually re-verified. | +| Multiple sessions filling the same page | Read existing prose first; do not overwrite human edits; append/extend rather than replace. | +| Page scaffolded long ago with older template | Fill what's there; do not reformat to match a newer template. Open a task if drift is significant. | +| `ctx kb topic new` fails or refuses (slug exists, kb missing) | Resolve the underlying condition and retry; do not hand-write a scaffold. | +| `ctx kb site build` fails during §12 | Report `topic-page: deferred`; name the build failure in `Next pass hint`; ledger to `topic-page-drafted` (NOT `comprehensive`). | +| Cold-reader rubric returns `Result: fail` | Report `topic-page: deferred` AND `validation: deferred (cold-reader orientation failed)`; name failed items in `Next pass hint`; ledger to `topic-page-drafted`. | +| Adjacency pre-flight surfaces zero matches | Record *"no incomplete adjacent topics surfaced"* explicitly in closeout's `Adjacency pre-flight`; response contract reads `none surfaced`. Silence is not allowed. | +| Mid-pass mode-switching tempted | Forbidden. Abort, write a partial closeout citing the mismatch, recommend re-invocation under the correct mode. Never silent-switch. | +| `evidence-only` pass discovers a contradiction | Still mint the contradiction row (truth surface always wins); flag in `Next pass hint` that a topic-page pass is needed to resolve. | +| Inferring `evidence-only` from source size / time pressure | Hard anti-pattern. Refuse to set `evidence-only` without explicit user trigger. | + +## Hard Anti-Patterns + +- Treating closeout existence as topic-page validation. +- Skipping the topic-page circuit breaker in `topic-page` mode. +- Inferring `evidence-only` from source size, complexity, + ambiguity, time pressure, or operator convenience. +- Mid-pass mode-switching (abort and re-invoke instead). +- Hiding incomplete coverage under a comprehensive-looking + closeout (lying to the ledger). +- Skipping the topic-adjacency pre-flight, or running it but + failing to acknowledge surfaced incomplete adjacent topics. +- Claiming `topic-page: produced` when the cold-reader + orientation result is missing. +- Asking the human mid-pass beyond the §3 naming gate, unless + continuing would change durable kb topology, evidence + confidence, source admission, or scope. +- Inventing claims beyond what the source backs. +- Inventing `EV-###` citations to make a page look complete. +- Promoting claims above `speculative` without an + `evidence-index.md` row. +- Promoting a topic page above its weakest cited band. +- Setting `Confidence` above `speculative` while any + `TBD-cite` remains. +- Setting `Author: hand-authored` on agent-ingested prose. +- Re-extracting from a source that already has `EV-###` rows + instead of reusing the IDs. +- Citing an `evidence-only`-tagged row in a topic page without + re-reading the source first. +- Renumbering or deleting `EV-###` rows when reconciling. +- Skipping the closeout once the pass clears pre-write gates. +- Bypassing `ctx kb topic new` when scaffolding a page. +- Running maintenance discipline against a bootstrap-stage kb. +- Hand-editing `INBOX.md`. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No closeout, no residue. + +For passes that clear pre-write gates, **emit the up-front +declaration first** (between §1 and §3): + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific criterion>` + +Then proceed. At completion, end with this structured summary: + +- **Pass-mode**: as declared, with reason if non-default. +- **Topic-page**: `produced [<slug>]`, `extended [<slug>]`, + `deferred (<reason>)`, or `not-applicable` (triage / + evidence-only). +- **Validation**: `passed`, `not-attempted`, or + `deferred (<reason>)`. +- **Coverage**: current state(s) from `source-coverage.md` for + sources touched. +- **EV range minted**: e.g. `EV-035..EV-051`, or `none`. +- **Counts**: glossary entries added, source-map rows added, + cross-links written, contradictions surfaced, questions + opened. +- **Life-stage**: `bootstrap` or `maintenance`, with the + topic-page count it was based on. +- **Closeout**: filename on its own line. +- **Adjacent topics noted** *(topic-page mode only; mandatory)*: + either `none surfaced` or a slug-list with states. Free prose + fails validation; the doctor advisory parses this field. +- **Next-recommended-action**: explicit invocation that would + resume incomplete work. Adjacent topics surfaced by the + pre-flight MUST appear here too (deliberate redundancy). +- **Review-required**: `true` for `evidence-only` passes; + otherwise omit. + +The structured summary, the closeout's body, and the +source-coverage ledger MUST agree. Discrepancies between the +three are a hard anti-pattern; the doctor advisory detects them +and surfaces a non-fatal warning on next `ctx doctor` run. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Pass-mode declaration was emitted in the response stream + before any extraction. +- [ ] Source-coverage ledger advanced honestly for every source + touched. +- [ ] Topic-adjacency pre-flight ran in `topic-page` mode and its + result is in the closeout AND the page AND the response + contract. +- [ ] Cold-reader rubric is recorded in `topic-page` mode. +- [ ] Circuit-breaker check ran in `topic-page` mode; failure + → `topic-page: deferred`, NOT `produced`. +- [ ] Closeout written with all required frontmatter fields + (`sha`, `branch`, `mode`, `pass-mode`, `life-stage`, + `generated-at`). +- [ ] Structured response summary matches the closeout body and + the ledger. diff --git a/internal/assets/claude/skills/ctx-kb-note/SKILL.md b/internal/assets/claude/skills/ctx-kb-note/SKILL.md new file mode 100644 index 000000000..dab1fef28 --- /dev/null +++ b/internal/assets/claude/skills/ctx-kb-note/SKILL.md @@ -0,0 +1,164 @@ +--- +name: ctx-kb-note +description: Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for "park this for the next ingest". +allowed-tools: Bash(ctx:*), Read, Edit +--- + +# Park a Finding for the Next Ingest + +Append a short note to `.context/ingest/findings.md` so a later +`/ctx-kb-ingest` pass can pick it up. This is the pipeline's +escape hatch for *"I want to remember this, but I'm not running +a full ingest right now."* No closeout, no ledger update, no +topic-page edit, no `EV-###` minting. Just typed memory landing +in one well-known file. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary; +`specs/kb-editorial-pipeline.md` §Interface. + +## When to Use + +- The user says "drop a note", "capture this for the next + ingest", "park this finding", or invokes the explicit slash + form with note text. +- A conversation surfaces a fact, link, or observation that + should land in the kb later but does not justify running + `/ctx-kb-ingest` right now. +- Mid-session, a sibling skill (architecture, brainstorm, etc.) + surfaces something kb-shaped and the user wants it parked + cheaply. + +## When NOT to Use + +- The user has sources in hand and wants them ingested (use + `/ctx-kb-ingest`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The note is actually a task / decision / learning / convention + for the code-dev side (use `/ctx-task-add` / + `/ctx-decision-add` / `/ctx-learning-add` / + `/ctx-convention-add`; those write to canonical files, this + one does not). +- The note is empty (refuse-on-empty; see below). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-note`** appends to + `.context/ingest/findings.md` only. Never writes anywhere + else. No closeout. No ledger update. +- **`/ctx-kb-ingest`** reads `findings.md` opportunistically + when scoping its source set; the user controls when notes get + promoted into evidence. +- **Canonical capture skills** (`/ctx-task-add`, + `/ctx-decision-add`, `/ctx-learning-add`, + `/ctx-convention-add`) write to the five canonical + `.context/` files. Strict authority boundary: this skill + never touches them. + +## Usage Examples + +```text +/ctx-kb-note "cursor.com/changelog mentions hook lifecycle bump in v1.2" +/ctx-kb-note "check whether your-domain RTO claim still cites the 2024 audit" +/ctx-kb-note "Volkan said in chat: the 50-source cap was lifted from the upstream design" +``` + +## Input Contract + +A single argument: the note text. Free-form prose. No flags. + +## Refuse-on-Empty + +If the invocation supplied no note text (empty slash arg, empty +inline body, whitespace-only), return exactly: + +> no note text provided; pass the note inline. + +Stop. Do not prompt interactively. The CLI enforces this +independently via `cmd/note`. + +## Pre-Write Gates + +Two distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → refuse: + + > kb not initialized; run `ctx init` first + + Stop. + +Kb scope declaration is **not** required for this skill. Notes +land in `.context/ingest/findings.md`, which is pre-kb-scope +territory; the user may be parking notes precisely because they +have not yet decided the kb's scope. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Append the note** to `.context/ingest/findings.md` as a + single bulleted line. Prefix with the current UTC timestamp + (RFC-3339, date-time precision) and a short SHA + branch + from `gitmeta.ResolveHead` so the note carries minimal + provenance: + + ``` + - 2026-05-16T14:32:11Z sha=88d52870 branch=main + | <note text> + ``` + + If `findings.md` does not yet exist, create it with a brief + header explaining its purpose (one paragraph; the embedded + template ships at `internal/assets/kb/templates/ingest/` + handles this for fresh inits, so this fallback applies only + when the file was deleted by hand). + +3. **No closeout.** Notes are intentionally lightweight; the + audit trail is the file itself. The next `/ctx-kb-ingest` + pass reads `findings.md` opportunistically. + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty note text | Refuse with the standard no-note text. No residue. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse with the not-initialized message. No residue. | +| `findings.md` missing but `.context/ingest/` exists | Create the file with a brief header; append the note. | +| Multi-line note text | Append as a single bullet with embedded line breaks; preserve the user's formatting. | +| Note text contains a URL | Preserve verbatim; do not auto-fetch (this skill does not web-jump). | +| Note text is structurally a claim that should be evidence | Append as a note anyway; mention in the response that `/ctx-kb-ingest` is the next step if the user wants it minted as `EV-###`. | +| User invokes twice in a row with similar text | Append both; deduplication is the user's call, not this skill's. | + +## Output Contract + +For refusals, return only the specified refusal text and stop. + +For successful appends, return: + +- One line confirming the append, with the line number of the + new entry in `findings.md`. +- A pointer to `/ctx-kb-ingest` as the path for promoting the + note into evidence when the user is ready. + +Example: + +``` +appended to .context/ingest/findings.md line 42. +run /ctx-kb-ingest with the source materials when ready to mint EV. +``` + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] The note landed in `.context/ingest/findings.md` and + nowhere else. +- [ ] No `EV-###` row was minted, no topic page was touched, no + ledger row was advanced, no closeout was written. +- [ ] The appended line carries the timestamp + sha + branch + provenance prefix. diff --git a/internal/assets/claude/skills/ctx-kb-site-review/SKILL.md b/internal/assets/claude/skills/ctx-kb-site-review/SKILL.md new file mode 100644 index 000000000..288b8dd54 --- /dev/null +++ b/internal/assets/claude/skills/ctx-kb-site-review/SKILL.md @@ -0,0 +1,259 @@ +--- +name: ctx-kb-site-review +description: Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail. +allowed-tools: Bash(ctx:*), Bash(ls:*), Read, Edit +--- + +# Site-Review Pass + +Walk `.context/kb/` and `.context/ingest/closeouts/` mechanically. +Fix what is unambiguous (capitalization drift, missing frontmatter +fields the CLI knows how to coerce). Flag what is not (claims +that read as broken but require evidence to fix). Never invent +prose. Never mint `EV-###` rows. Never modify a claim's +Confidence band. + +This is a janitor pass, not an editorial pass. Editorial judgment +lives in `/ctx-kb-ingest`. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary; +`specs/kb-editorial-pipeline.md` §Validation Rules. + +## When to Use + +- The user says "audit the kb", "check kb for rot", "run a + site-review", or invokes the explicit slash form. +- Before a release / handover where structural cleanliness + matters. +- After bulk ingest where drift may have accumulated. +- When the doctor advisory has surfaced structural warnings the + user wants triaged. + +## When NOT to Use + +- The user wants new material extracted (use `/ctx-kb-ingest`). +- The user wants kb claims re-grounded against external sources + (use `/ctx-kb-ground`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The user wants to capture a quick finding (use + `/ctx-kb-note`). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-site-review`**: mechanical structural audit. May + coerce capitalization that the spec deems lossless (e.g. + `Confidence: High` → `high`). May flag any other malformation + in the closeout's `What changed` block. **May not** modify a + claim, an `EV-###` row's content, a Confidence band, a topic + page's prose, or a ledger state. Those require evidence + judgment. +- **`/ctx-kb-ingest`**: handles anything this skill flags as + evidence-dependent. +- **`/ctx-kb-ground`**: handles anything this skill flags as + source-staleness. + +## Usage Examples + +```text +/ctx-kb-site-review +``` + +No arguments. The pass walks the kb in full. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- Kb scope undeclared (placeholder in `.context/kb/index.md`) + → refuse with the scope message and stop. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Walk topic pages.** For every + `.context/kb/topics/<slug>/index.md` and every sibling + sub-page: + - **Status block check**: does the page have the + four-field Status block (`Subject`, `Last verified`, + `Author`, `Confidence`)? Missing fields → flag in + `What changed`. Do not synthesize. + - **Author field check**: `Author: hand-authored` is + prohibited per `KB-RULES.md`. Flag (do not auto-coerce; + human intent matters). + - **Confidence band coercion**: `high|medium|low|speculative` + are the only valid values. Coerce capitalization + (`High` → `high`, `MEDIUM` → `medium`) silently and record + in `What changed`. Any other malformation (e.g. + `Confidence: probable`) is flagged for the user. + - **`TBD-cite` markers**: count them per page. The + Confidence floor for any page with `TBD-cite` is + `speculative`. If the page's Confidence is above + `speculative` while `TBD-cite` is present, flag (do not + auto-demote; demotion is evidence work). + - **`EV-###` citation resolution**: every `EV-###` cited on + the page must resolve to a row in + `.context/kb/evidence-index.md`. Unresolved IDs → flag. + - **`## Related concepts in this kb` presence**: if the + page is more than the lede + Status block AND the kb has + plausibly adjacent topics, the section should be present. + Absence is a soft flag (not auto-fixable). + +3. **Walk `evidence-index.md`.** + - **Duplicate `EV-###` IDs**: flag every duplicate; name + both files / line numbers. The LLM cleanup pass (per + spec's P1) handles renumbering, not this skill. + - **Three-digit padding**: `EV-12` should be `EV-012`. Flag + (do not auto-coerce; renumbering cascades to citations on + topic pages, which is ingest work). + - **Confidence band coercion**: same rule as topic pages. + - **`occurred:` field on dated sources**: if the source-map + row for the cited source has a `dated:` field but the + evidence row lacks `occurred:`, flag. The temporal- + precedence rule needs it. + +4. **Walk `source-coverage.md`.** + - **Ledger row mtime check**: for every row, compare the + row's `Updated` cell against the actual file mtime of the + source it points to (when the source is in-tree). Mismatch + → flag (lying-to-the-ledger advisory). Do not auto-edit. + - **Illegal state transitions**: flag any row whose state + does not match an allowed transition from the prior state. + Examples: `comprehensive → highlights-extracted` without + an explicit `superseded` step. Do not auto-correct. + - **Schema integrity**: every row must have the seven + columns (`Source`, `Topic`, `State`, `EV coverage`, + `Residue`, `Next action`, `Updated`). Missing columns → + flag. + +5. **Walk closeouts in `.context/ingest/closeouts/`.** + - **Frontmatter integrity**: every closeout must have + `sha`, `branch`, `mode`, `pass-mode`, `life-stage`, + `generated-at`. Missing fields → flag (the handover-fold + skips malformed closeouts; surface them so the user can + fix or delete). + - **Pass-mode body block**: every ingest closeout must + have a `Pass-mode` body block whose `Declared:` value + matches the frontmatter's `pass-mode:` field. Drift + between the two is exactly the false-finish signal the + redundancy exists to surface. Flag any drift. + - **Adjacency pre-flight block**: every ingest closeout in + `topic-page` mode must have an `Adjacency pre-flight` + block whose value is either `none surfaced` or a + structured slug-list. Free-prose values fail validation; + flag. + - **Cold-reader rubric**: every ingest closeout in + `topic-page` mode must include the four-item rubric in + `What changed`. Missing → flag. + +6. **Walk `.context/kb/index.md`.** + - **`CTX:KB:TOPICS` managed block**: should list every + `.context/kb/topics/<slug>/index.md` currently on disk. + Drift (slug on disk not in the block, or block entry with + no matching folder) → recommend `ctx kb reindex` in the + closeout's `Next pass hint`. Do not run the CLI from this + skill. + +7. **Write the site-review closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-site-review-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: site-review + pass-mode: mechanical + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Inputs**: count of topic pages, evidence rows, ledger + rows, closeouts walked. + - **What changed**: every coercion this pass actually + applied (capitalization fixes); cite the file and the + before/after. Empty if zero coercions. + - **Flags**: every issue this pass detected but did not + fix. Group by category: malformed Status blocks, + unresolved `EV-###`, ledger mismatches, malformed + closeouts, etc. Each flag names file + line + nature. + - **Next pass hint**: explicit invocations to address each + flag category (e.g. *"`/ctx-kb-ingest <slug>` to restore + missing `EV-###` citation on `<page>`"*). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/kb/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message. No residue. | +| Zero topic pages on disk | Walk closeouts and ledger anyway. Note the bootstrap state in the closeout. Not a failure mode. | +| Zero closeouts on disk | Walk topic pages and ledger anyway. Note in the closeout body. Not a failure mode. | +| `Confidence: High` (capitalization drift) | Coerce to `high` silently; record in `What changed`. | +| `Confidence: probable` (unknown band) | Flag for the user; do not coerce. | +| `Author: hand-authored` | Flag for the user; do not coerce (human intent matters). | +| Duplicate `EV-###` ID across files | Flag both files; defer renumbering to the LLM cleanup pass per spec's P1. | +| `EV-12` (missing zero-pad) | Flag for the user; do not auto-pad (cascades to citations). | +| Unresolved `EV-###` on a topic page | Flag; recommend `/ctx-kb-ingest <slug>` in `Next pass hint`. | +| Ledger row `Updated` predates source file mtime | Flag (lying-to-the-ledger advisory). Do not auto-edit. | +| Illegal ledger transition (e.g. `comprehensive → highlights-extracted` without `superseded`) | Flag; recommend the corrective ingest invocation. Do not auto-correct. | +| Closeout missing `pass-mode` frontmatter field | Flag; the handover-fold skips malformed closeouts so the user can fix or delete. | +| Closeout body's `Pass-mode` `Declared:` disagrees with frontmatter `pass-mode:` | Flag (false-finish signal); recommend hand-edit. | +| Closeout's `Adjacency pre-flight` is free prose instead of `none surfaced` or a slug-list | Flag; recommend hand-edit to structured form. | +| `CTX:KB:TOPICS` managed block drift | Recommend `ctx kb reindex` in `Next pass hint`; do not run the CLI from this skill. | +| `TBD-cite` on a page with Confidence above `speculative` | Flag; do not auto-demote (demotion is evidence work for `/ctx-kb-ingest`). | +| Sibling sub-page exists with no link from `index.md` | Flag; recommend hand-edit or `/ctx-kb-ingest <slug>` to extend. | + +## Anti-Patterns + +- Auto-fixing anything that requires evidence judgment + (Confidence promotion/demotion, claim text edits, `EV-###` + renumbering, ledger state changes, prose synthesis). +- Skipping the closeout once pre-write gates pass. +- Hand-editing `INBOX.md` or `SESSION_LOG.md` (other skills' + surfaces; never this one's). +- Coercing `Author: hand-authored` to anything else. The user's + intent matters; flag and wait. +- Auto-renumbering duplicate `EV-###` IDs. The cascade to + citations is ingest work; this skill flags only. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For passes that clear pre-write gates, end with this structured +summary: + +- **Walked**: counts (topic pages, evidence rows, ledger rows, + closeouts). +- **Coercions applied**: count + one-line categories (e.g. + *"3 capitalization fixes on Confidence bands"*). +- **Flags raised**: count + categories (e.g. *"2 unresolved + EV-### citations; 1 ledger mtime mismatch"*). +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocations to address + each flag category (or `none` if the kb is clean). + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every coercion applied is recorded in `What changed` with + file + before/after. +- [ ] Every flag is recorded in `Flags` with file + line + + nature. +- [ ] No topic-page prose was edited, no `EV-###` row was + modified, no Confidence band was promoted/demoted, no + ledger state was changed. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/claude/skills/ctx-link-check/SKILL.md b/internal/assets/claude/skills/ctx-link-check/SKILL.md index 003dd2942..8bb33facd 100644 --- a/internal/assets/claude/skills/ctx-link-check/SKILL.md +++ b/internal/assets/claude/skills/ctx-link-check/SKILL.md @@ -4,7 +4,7 @@ description: "Audit docs for dead links. Use before releases, after restructurin allowed-tools: Bash(curl:*), Read, Grep, Glob --- -Scan markdown files for broken links. Two passes: +Scan Markdown files for broken links. Two passes: internal (file targets) and external (HTTP URLs). ## Scope Discovery @@ -36,7 +36,7 @@ Report which directories are being scanned at the start of output. ### Pass 1: Internal Links -Scan every `.md` file in the discovered scope for markdown links +Scan every `.md` file in the discovered scope for Markdown links pointing to other files: `[text](target.md)`, `[text](../path/file.md)`, `[text](path/file.md#anchor)`. @@ -57,7 +57,7 @@ BROKEN: source-file.md:LINE → target.md (file not found) ### Pass 2: External Links Scan every `.md` file in the discovered scope for `http://` and -`https://` URLs in markdown link syntax. +`https://` URLs in Markdown link syntax. For each URL: diff --git a/internal/assets/claude/skills/ctx-remember/SKILL.md b/internal/assets/claude/skills/ctx-remember/SKILL.md index bcefcddfa..2e8b46639 100644 --- a/internal/assets/claude/skills/ctx-remember/SKILL.md +++ b/internal/assets/claude/skills/ctx-remember/SKILL.md @@ -46,7 +46,23 @@ feel like a file search rather than genuine recall: ```bash ctx journal source --limit 3 ``` -4. **Present the structured readback** (see format below) +4. **Read the latest handover.** Look under + `.context/handovers/`, sort by filename (timestamped + `<TS>-<slug>.md`; the newest is the lexicographically + last), and read its `## Summary` and `## Next Session` + sections as the authoritative recall surface. The + handover is the previous session's note to this one. + Skip only if `.context/handovers/` is empty or absent. +5. **Read postdated closeouts, if any.** When + `.context/ingest/closeouts/` exists, list closeouts whose + `generated-at` postdates the handover's `generated-at` + and read their `## What Changed` sections. These are + per-pass audit notes the previous wrap-up did not get a + chance to fold into a handover. This step is read-only: + `/ctx-remember` does not run any editorial pass. If the + directory does not exist or holds no postdated entries, + skip the step. +6. **Present the structured readback** (see format below) ## Readback Format diff --git a/internal/assets/claude/skills/ctx-skill-audit/references/anthropic-best-practices.md b/internal/assets/claude/skills/ctx-skill-audit/references/anthropic-best-practices.md index 55960f575..6634ec27c 100644 --- a/internal/assets/claude/skills/ctx-skill-audit/references/anthropic-best-practices.md +++ b/internal/assets/claude/skills/ctx-skill-audit/references/anthropic-best-practices.md @@ -37,7 +37,7 @@ context. If they'd be confused, Claude will be too. Tell Claude what to do, not what to avoid. Positive instructions ("write flowing prose paragraphs") outperform negative ones -("don't use markdown"). +("don't use Markdown"). **Why this matters for skills:** a skill full of "don't do X" leaves the agent guessing what *to* do. Positive instructions @@ -45,7 +45,7 @@ give a clear target. Negative guards are fine as supplements, but the primary instruction should describe the desired behavior. <example> -<poor>Do not use markdown in your response.</poor> +<poor>Do not use Markdown in your response.</poor> <good>Write your response as flowing prose paragraphs.</good> </example> @@ -197,7 +197,7 @@ context windows: - **Checkpoint progress**: encourage saving state at natural breakpoints so work isn't lost if context refreshes. -- **Use structured formats for state**: JSON or markdown +- **Use structured formats for state**: JSON or Markdown checklists for tracking progress. - **Use git for persistence**: commits provide both state tracking and rollback capability. diff --git a/internal/assets/claude/skills/ctx-skill-create/references/anthropic-best-practices.md b/internal/assets/claude/skills/ctx-skill-create/references/anthropic-best-practices.md index 55960f575..6634ec27c 100644 --- a/internal/assets/claude/skills/ctx-skill-create/references/anthropic-best-practices.md +++ b/internal/assets/claude/skills/ctx-skill-create/references/anthropic-best-practices.md @@ -37,7 +37,7 @@ context. If they'd be confused, Claude will be too. Tell Claude what to do, not what to avoid. Positive instructions ("write flowing prose paragraphs") outperform negative ones -("don't use markdown"). +("don't use Markdown"). **Why this matters for skills:** a skill full of "don't do X" leaves the agent guessing what *to* do. Positive instructions @@ -45,7 +45,7 @@ give a clear target. Negative guards are fine as supplements, but the primary instruction should describe the desired behavior. <example> -<poor>Do not use markdown in your response.</poor> +<poor>Do not use Markdown in your response.</poor> <good>Write your response as flowing prose paragraphs.</good> </example> @@ -197,7 +197,7 @@ context windows: - **Checkpoint progress**: encourage saving state at natural breakpoints so work isn't lost if context refreshes. -- **Use structured formats for state**: JSON or markdown +- **Use structured formats for state**: JSON or Markdown checklists for tracking progress. - **Use git for persistence**: commits provide both state tracking and rollback capability. diff --git a/internal/assets/claude/skills/ctx-wrap-up/SKILL.md b/internal/assets/claude/skills/ctx-wrap-up/SKILL.md index ab9ebd15b..e782fd497 100644 --- a/internal/assets/claude/skills/ctx-wrap-up/SKILL.md +++ b/internal/assets/claude/skills/ctx-wrap-up/SKILL.md @@ -18,6 +18,39 @@ Check that the context directory exists. If it does not, tell the user: "No context directory found. Run `ctx init` to set up context tracking, then there will be something to wrap up." +## Handover Is the Mandatory Final Step + +`/ctx-wrap-up` owns the user-facing session-end trigger and +**always** delegates to `/ctx-handover` as its final step. +The handover is the former agent's note to the next agent +(or human): what happened, and what should come next. It +writes `.context/handovers/<TS>-<slug>.md` (timestamped so +multiple agent runs never overwrite). Without this final +step, `/ctx-remember` has nothing to read at the start of +the next session and recall degenerates into probabilistic +reconstruction from canonical files plus journal. + +## KB Editorial State (Phase KB, Optional) + +If `.context/kb/` exists, this project additionally uses the +editorial pipeline. After the capture phase but before the +final `/ctx-handover` delegation: + +1. List any closeouts under `.context/ingest/closeouts/`. + These are per-pass audit artifacts from `/ctx-kb-ingest`, + `/ctx-kb-ask`, etc. that have not yet been folded into a + handover. +2. Count unresolved entries in + `.context/kb/outstanding-questions.md` (rows whose Status + is `open`). +3. Surface both counts in the wrap-up summary so the operator + sees what editorial residue is pending; the handover + step's fold pass will consume the closeouts. + +When `.context/kb/` does NOT exist, skip this section +entirely; the wrap-up proceeds with the standard capture +checklist and still ends with `/ctx-handover`. + ## When to Use - At the end of a session, before the user quits @@ -120,7 +153,7 @@ nudges are suppressed for the remainder of the session: ctx system mark-wrapped-up ``` -### Phase 4: Commit (optional) +### Phase 4: Surface Uncommitted Changes After persisting, check for uncommitted changes: @@ -128,12 +161,63 @@ After persisting, check for uncommitted changes: git status --short ``` -If there are uncommitted changes, offer: +When `git status --short` reports any modified or untracked +files, surface them and offer `/ctx-commit`: + +> There are uncommitted changes (`<count>` files). Run +> `/ctx-commit` to commit with context capture? + +Do not auto-commit; the user decides. But always run the +`git status` check and always surface non-empty output. Do +not skip this phase silently when the working tree is dirty. + +### Phase 5: Delegate to `/ctx-handover` (mandatory) + +`/ctx-wrap-up` always ends here. Drafting the handover reuses +the signal gathered in Phase 1 and the candidates approved in +Phase 3: + +1. **Title**: a short noun phrase naming the session arc + (becomes the slug in `<TS>-<slug>.md`). Drawn from the + conversation; confirm with the user. +2. **`--summary`** (required, past tense): one paragraph + naming what was done this session, drawn from the approved + candidates and the git-log scan. Concrete, not vague. +3. **`--next`** (required, future tense): one paragraph + naming the specific first action the next agent should + take. Pull from the highest-priority pending task in + TASKS.md or the open thread the session was on. +4. **`--highlights`**: draft a bullet list of notable + artifacts produced this session (commits, decisions, + specs, files created). Always present a draft. Pass an + empty string only after the user has explicitly said + there is nothing to highlight. +5. **`--open-questions`**: draft a bullet list of things + that remain undecided. Pull from any candidate the user + did not turn into a decision, any deferred ingest pass, + any `TODO` discovered in the session. Always present a + draft. Pass an empty string only after the user has + explicitly confirmed there is nothing open. + +Surface the drafted values to the user for one final +confirmation, then delegate: + +```text +/ctx-handover "<title>" --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] +``` -> There are uncommitted changes. Want me to run `/ctx-commit` -> to commit with context capture? +The `/ctx-handover` skill performs the pre-write gates, +writes `.context/handovers/<TS>-<slug>.md`, and (when +`.context/kb/` exists) folds postdated closeouts into the +`## Folded Closeouts` section and archives them. See +[`/ctx-handover`](#) for the full input contract and CLI +flag reference. -Do not auto-commit. The user decides. +If `/ctx-handover` refuses (missing `.context/handovers/`, +empty placeholder values, etc.), surface the refusal to the +user. Do not declare the wrap-up complete until the handover +landed. ## Candidate Quality Guide @@ -179,3 +263,5 @@ After persisting, verify: - [ ] Each `ctx add` command succeeded - [ ] Uncommitted changes were surfaced (if any) - [ ] User was offered `/ctx-commit` (if applicable) +- [ ] `/ctx-handover` was invoked and the resulting + `.context/handovers/<TS>-<slug>.md` was written diff --git a/internal/assets/commands/commands.yaml b/internal/assets/commands/commands.yaml index 2e7ac7009..bf703a3f7 100644 --- a/internal/assets/commands/commands.yaml +++ b/internal/assets/commands/commands.yaml @@ -1824,3 +1824,133 @@ skill.remove: Examples: ctx skill remove react-patterns short: Remove an installed skill +handover: + long: |- + Manage per-session handover artifacts under .context/handovers/. + + The handover is the former-agent-to-next-agent note created at + session end by /ctx-wrap-up and read at session start by + /ctx-remember. Filenames are timestamped (<TS>-<slug>.md) so + concurrent agent runs never overwrite each other. Subcommands: + write Write a per-session handover artifact + + Examples: + ctx handover write "Cursor Hooks deep dive" \ + --summary "Drafted topic-page; minted EV-018..EV-024." \ + --next "Re-ingest the v1.1 release-notes URL." + short: Per-session handover artifact writer +handover.write: + long: |- + Write a handover under .context/handovers/<TS>-<slug>.md. + + By default, folds closeouts that postdate the latest handover and + archives them under .context/archive/closeouts/. --no-fold skips + the fold (mid-session checkpoint). + + Examples: + ctx handover write "Cursor Hooks deep dive" \ + --summary "Drafted topic-page; minted EV-018..EV-024." \ + --next "Re-ingest the v1.1 release-notes URL." + + ctx handover write "Mid-day checkpoint" \ + --summary "Working through evidence-index reconciliation." \ + --next "Resolve C-007 contradiction next." --no-fold + short: Write a per-session handover artifact +kb: + long: |- + Manage the .context/kb/ knowledge base via the editorial pipeline. + + Ingest sources, ask grounded questions, audit the site, re-ground + external sources, and scaffold new topics. + + Examples: + ctx kb topic new "Cursor Hooks" # scaffold a topic folder + ctx kb note "follow-up: v1.2 SIGTERM" # park a finding + ctx kb reindex # refresh the topic index + ctx kb ingest ./inputs/cursor-hooks.md # editorial pass + ctx kb ask "does the kb say hooks fire async?" + ctx kb site-review # mechanical audit + ctx kb ground # re-ground external sources + short: Knowledge-base editorial pipeline (Phase KB) +kb.ask: + long: |- + Q&A grounded in the kb. The actual pass is performed by the + /ctx-kb-ask skill; this CLI surface validates input (refuses on + empty question) and prints the canonical skill invocation. + + Examples: + ctx kb ask "does the kb say hooks fire async?" + ctx kb ask "what does EV-042 cite?" + short: Q&A grounded in the kb (driven by the /ctx-kb-ask skill) +kb.ground: + long: |- + Refreshes sources listed in .context/ingest/grounding-sources.md. + Refuses cleanly when the file is absent or empty. + + Examples: + ctx kb ground # refresh every source listed in grounding-sources.md + short: External grounding pass (driven by the /ctx-kb-ground skill) +kb.ingest: + long: |- + Runs the editorial-ingestion pipeline against the supplied sources. + + The pass itself is performed by the /ctx-kb-ingest skill; this CLI + surface validates input and prints the canonical invocation. + + Examples: + ctx kb ingest ./inputs/cursor-hooks.md + ctx kb ingest ./inputs/ "cursor hooks" + ctx kb ingest https://example.com/spec.html + short: Mode-aware editorial pass (driven by the /ctx-kb-ingest skill) +kb.note: + long: |- + Appends a one-liner to .context/ingest/findings.md. Never writes + to a topic page or evidence-index. Use this when you want to park + a finding for the next ingest pass to absorb. + + Examples: + ctx kb note "follow-up: chase the v1.2 release-notes for SIGTERM" + ctx kb note "Volkan said in chat: 50-source cap was upstream" + short: Lightweight capture into .context/ingest/findings.md +kb.reindex: + long: |- + Rewrites the managed block bounded by the CTX:KB:TOPICS markers in + .context/kb/index.md so the landing page enumerates current topic + folders. Run after ctx kb topic new. + + Examples: + ctx kb reindex + short: Refresh the CTX:KB:TOPICS managed block in .context/kb/index.md +kb.site-review: + long: |- + Mechanical structural audit of the kb. Coerces malformed + Confidence-band capitalization, flags malformed closeout + frontmatter, refuses judgment calls that require evidence. The + actual audit is performed by the /ctx-kb-site-review skill. + + Examples: + ctx kb site-review + short: Mechanical structural audit (driven by the /ctx-kb-site-review skill) +kb.topic: + long: |- + Manage kb topic folders. Subcommands: + new Scaffold a folder-shaped topic page from the template + + Examples: + ctx kb topic new "Cursor Hooks" + ctx kb topic new "cursor/hooks" # vendor-namespaced slug + short: Manage kb topic folders +kb.topic.new: + long: |- + Creates .context/kb/topics/<slug>/index.md from the embedded topic + template. Refuses when the topic already exists. This is the sole + writer of topic-page scaffolds; skills must invoke this command + rather than synthesise the scaffold by hand. + + Slug is lowercase + kebab-case. Slashes are preserved for + vendor-namespaced topology (e.g. cursor/hooks, cursor/skills). + + Examples: + ctx kb topic new "Cursor Hooks" # → topics/cursor-hooks/index.md + ctx kb topic new "cursor/hooks" # → topics/cursor/hooks/index.md + short: Scaffold a folder-shaped topic page from the template diff --git a/internal/assets/commands/flags.yaml b/internal/assets/commands/flags.yaml index 28876dea2..f491c344f 100644 --- a/internal/assets/commands/flags.yaml +++ b/internal/assets/commands/flags.yaml @@ -283,3 +283,15 @@ watch.dry-run: short: Show updates without applying watch.log: short: 'Log file to watch (default: stdin)' +handover.summary: + short: 'What happened this session (past tense). Required.' +handover.next: + short: 'What the next agent should do FIRST. Required.' +handover.highlights: + short: 'Notable artifacts produced this session.' +handover.open-questions: + short: 'Things that remain undecided.' +handover.commit: + short: 'Override resolved git HEAD for Provenance line (CI replay).' +handover.no-fold: + short: 'Skip closeout consumption (mid-session checkpoint).' diff --git a/internal/assets/commands/text/errors.yaml b/internal/assets/commands/text/errors.yaml index 0dfad72c7..72136eebc 100644 --- a/internal/assets/commands/text/errors.yaml +++ b/internal/assets/commands/text/errors.yaml @@ -608,3 +608,177 @@ err.init-backup-read: short: 'read %s for backup: %w' err.init-backup-write: short: 'write backup %s: %w' +err.handover.latest: + short: 'latest handover: %w' +err.handover.list-closeouts: + short: 'list closeouts for fold: %w' +err.handover.marshal-frontmatter: + short: 'marshal handover frontmatter: %w' +err.handover.mkdir-handovers: + short: 'mkdir handovers dir: %w' +err.handover.write-handover: + short: 'write handover: %w' +err.handover.archive-folded-closeouts: + short: 'archive folded closeouts: %w' +err.handover.read-handover: + short: 'read handover: %w' +err.handover.read-handovers-dir: + short: 'read handovers dir: %w' +err.handover.parse-frontmatter: + short: 'parse handover frontmatter: %w' +err.handover.resolve-head: + short: 'resolve git head for handover: %w' +err.closeout.read-closeout: + short: 'read closeout: %w' +err.closeout.parse-frontmatter: + short: 'parse closeout frontmatter: %w' +err.closeout.marshal-frontmatter: + short: 'marshal closeout frontmatter: %w' +err.closeout.read-closeouts-dir: + short: 'read closeouts dir: %w' +err.closeout.resolve-head: + short: 'resolve git head for closeout: %w' +err.closeout.mkdir-closeouts: + short: 'mkdir closeouts dir: %w' +err.closeout.write-closeout: + short: 'write closeout: %w' +err.closeout.mkdir-archive: + short: 'mkdir closeout archive dir: %w' +err.closeout.archive-move: + short: 'archive closeout %s: %w' +err.closeout.missing-fields: + short: '%w: %s' +err.gitmeta.missing-git-tree: + short: '%w: .git/ not found at %s; run `git init` to fix' +err.gitmeta.missing-git-tree-for-cmd: + short: '%w: ctx %s: .git/ not found at %s; run `git init` to fix' +err.gitmeta.stat-git-dir: + short: 'stat .git at %s: %w' +err.gitmeta.resolve-head: + short: 'resolve git head: %w' +err.initkb.mkdir: + short: 'mkdir %s: %w' +err.initkb.copy-ingest-templates: + short: 'copy ingest templates: %w' +err.initkb.copy-schemas: + short: 'copy schemas: %w' +err.initkb.copy-landing: + short: 'copy kb landing: %w' +err.initkb.read-embed: + short: 'read embed %s: %w' +err.initkb.mkdir-for: + short: 'mkdir for %s: %w' +err.initkb.write-file: + short: 'write %s: %w' +err.initkb.read-dir: + short: 'read %s: %w' +err.kb.evidence.duplicate-id: + short: '%w: %s' +err.kb.evidence.invalid-band: + short: '%w: %q' +err.kb.evidence.read-index: + short: 'read evidence-index: %w' +err.kb.evidence.mkdir-dir: + short: 'mkdir evidence-index parent: %w' +err.kb.evidence.open-index: + short: 'open evidence-index: %w' +err.kb.evidence.write-row: + short: 'write evidence row: %w' +err.kb.evidence.parse-id-number: + short: 'parse EV number %q: %w' +err.kb.sourcecoverage.illegal-transition: + short: '%w: %s → %s for source %s' +err.kb.sourcecoverage.unknown-source: + short: '%w: %s entering at %s' +err.kb.sourcecoverage.read-ledger: + short: 'read ledger: %w' +err.kb.sourcecoverage.mkdir-ledger-dir: + short: 'mkdir ledger parent: %w' +err.kb.sourcecoverage.write-ledger: + short: 'write ledger: %w' +err.kb.glossary.read-file: + short: 'stat glossary: %w' +err.kb.glossary.mkdir-dir: + short: 'mkdir glossary parent: %w' +err.kb.glossary.open-file: + short: 'open glossary: %w' +err.kb.glossary.write-row: + short: 'write glossary row: %w' +err.kb.timeline.read-file: + short: 'stat timeline: %w' +err.kb.timeline.mkdir-dir: + short: 'mkdir timeline parent: %w' +err.kb.timeline.open-file: + short: 'open timeline: %w' +err.kb.timeline.write-row: + short: 'write timeline row: %w' +err.kb.sourcemap.read-file: + short: 'stat source-map: %w' +err.kb.sourcemap.mkdir-dir: + short: 'mkdir source-map parent: %w' +err.kb.sourcemap.open-file: + short: 'open source-map: %w' +err.kb.sourcemap.write-row: + short: 'write source-map row: %w' +err.kb.relationship.read-file: + short: 'stat relationship-map: %w' +err.kb.relationship.mkdir-dir: + short: 'mkdir relationship-map parent: %w' +err.kb.relationship.open-file: + short: 'open relationship-map: %w' +err.kb.relationship.write-row: + short: 'write relationship-map row: %w' +err.kb.contradiction.read-file: + short: 'read contradictions: %w' +err.kb.contradiction.mkdir-dir: + short: 'mkdir contradictions parent: %w' +err.kb.contradiction.open-file: + short: 'open contradictions: %w' +err.kb.contradiction.write-row: + short: 'write contradictions row: %w' +err.kb.contradiction.parse-c-number: + short: 'parse C number %q: %w' +err.kb.decision.read-file: + short: 'read decisions: %w' +err.kb.decision.mkdir-dir: + short: 'mkdir decisions parent: %w' +err.kb.decision.open-file: + short: 'open decisions: %w' +err.kb.decision.write-row: + short: 'write decisions row: %w' +err.kb.decision.parse-dd-number: + short: 'parse DD number %q: %w' +err.kb.question.read-file: + short: 'read questions: %w' +err.kb.question.mkdir-dir: + short: 'mkdir questions parent: %w' +err.kb.question.open-file: + short: 'open questions: %w' +err.kb.question.write-row: + short: 'write questions row: %w' +err.kb.question.parse-q-number: + short: 'parse Q number %q: %w' +err.kb.cli.grounding-missing: + short: 'grounding-sources.md not found at %s; list sources first' +err.kb.cli.grounding-empty: + short: 'grounding-sources.md is empty at %s; list sources first' +err.kb.cli.topic-exists: + short: 'topic %s already exists at %s' +err.kb.cli.mkdir-ingest: + short: 'mkdir ingest: %w' +err.kb.cli.open-findings: + short: 'open findings: %w' +err.kb.cli.write-finding: + short: 'write finding: %w' +err.kb.cli.read-kb-index: + short: 'read kb index: %w' +err.kb.cli.write-kb-index: + short: 'write kb index: %w' +err.kb.cli.read-topics-dir: + short: 'read topics dir: %w' +err.kb.cli.mkdir-topic: + short: 'mkdir topic: %w' +err.kb.cli.read-topic-template: + short: 'read embedded topic template: %w' +err.kb.cli.write-topic-index: + short: 'write topic index: %w' diff --git a/internal/assets/commands/text/ui.yaml b/internal/assets/commands/text/ui.yaml index 1b3a8ca93..fc5c31b9a 100644 --- a/internal/assets/commands/text/ui.yaml +++ b/internal/assets/commands/text/ui.yaml @@ -256,6 +256,8 @@ init.label-plugin-enable: short: Plugin enablement init.label-steering: short: Steering foundation files +init.label-kb: + short: kb editorial pipeline init.confirm-claude: short: Would you like to append ctx context management instructions? events.empty: diff --git a/internal/assets/commands/text/write.yaml b/internal/assets/commands/text/write.yaml index 4b137a900..90acd8868 100644 --- a/internal/assets/commands/text/write.yaml +++ b/internal/assets/commands/text/write.yaml @@ -1061,3 +1061,39 @@ write.steering-body-workflow: - **Commit conventions** (message format, signed-off-by) - **Pre-commit / pre-push checks** - **Review expectations** +write.handover.wrote: + short: "wrote %s\n" +write.handover.folded: + short: "folded %d closeout(s); archived to %s\n" +write.handover.malformed-warning: + short: "warning: %d malformed closeout(s) skipped during fold:\n" +write.handover.malformed-line: + short: " %s\n" +write.kb-cli.finding-line: + short: "- [%s] %s\n" +write.kb-cli.reindexed: + short: "reindexed %d topic(s) into %s\n" +write.kb-cli.scaffolded: + short: "scaffolded %s\n" +write.kb-cli.appended-to: + short: "appended to %s\n" +write.kb-cli.ask-driven-hint: + short: "Q&A is driven by the /ctx-kb-ask skill.\n" +write.kb-cli.ask-invoke-format: + short: "In Claude Code, invoke: /ctx-kb-ask %s\n" +write.kb-cli.ask-contract-pointer: + short: "See .context/ingest/40-ASK.md for the contract.\n" +write.kb-cli.ground-driven-hint: + short: "Grounding is driven by the /ctx-kb-ground skill.\n" +write.kb-cli.ground-contract-pointer: + short: "See .context/ingest/00-GROUND.md for the contract.\n" +write.kb-cli.ingest-driven-hint: + short: "Editorial ingestion is driven by the /ctx-kb-ingest skill.\n" +write.kb-cli.ingest-invoke-format: + short: "In Claude Code, invoke: /ctx-kb-ingest %s\n" +write.kb-cli.ingest-fallback-hint: + short: "Without the skill, follow .context/ingest/PROMPT.md.\n" +write.kb-cli.site-review-driven-hint: + short: "Site-review is driven by the /ctx-kb-site-review skill.\n" +write.kb-cli.site-review-contract-pointer: + short: "See .context/ingest/50-SITE_REVIEW.md for the contract.\n" diff --git a/internal/assets/context/CONSTITUTION.md b/internal/assets/context/CONSTITUTION.md index 260322c94..fd0461272 100644 --- a/internal/assets/context/CONSTITUTION.md +++ b/internal/assets/context/CONSTITUTION.md @@ -102,6 +102,14 @@ Leave the system in a better state than you found it. no exceptions, no "non-trivial" qualifier. Even one-liner fixes need a spec for traceability. Use `/ctx-commit` instead of raw `git commit`. +- [ ] **Git is required.** Every `ctx` project must live in a git + working tree. `ctx init` and every non-administrative + subcommand refuse to operate when `<projectRoot>/.git` is + absent. Rationale: `ctx`'s persistent-memory promise is + dishonest without an undo layer; agent-driven file + operations need `git reflog` as the safety net. The only + opt-outs are help-shaped / diagnostic commands + (`--help`, `--version`, `ctx system bootstrap`). ## TASKS.md Structure Invariants diff --git a/internal/assets/embed.go b/internal/assets/embed.go index 6076bce51..f760fdf91 100644 --- a/internal/assets/embed.go +++ b/internal/assets/embed.go @@ -23,4 +23,6 @@ import ( //go:embed hooks/messages/*/*.txt hooks/messages/registry.yaml hooks/trace/*.sh //go:embed schema/*.json why/*.md //go:embed permissions/*.txt commands/*.yaml commands/text/*.yaml journal/*.css +//go:embed kb/templates/ingest/*.md kb/templates/ingest/schemas/*.md +//go:embed kb/templates/kb/index.md kb/templates/kb/topics/_template/index.md var FS embed.FS diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-check-links/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-check-links/SKILL.md index dac6a0e72..7e8b2d9a2 100644 --- a/internal/assets/integrations/copilot-cli/skills/ctx-check-links/SKILL.md +++ b/internal/assets/integrations/copilot-cli/skills/ctx-check-links/SKILL.md @@ -4,7 +4,7 @@ description: "Audit docs for dead links. Use before releases, after restructurin tools: [bash, read, glob, grep] --- -Scan markdown files for broken links. Two passes: +Scan Markdown files for broken links. Two passes: internal (file targets) and external (HTTP URLs). ## Scope Discovery @@ -36,7 +36,7 @@ Report which directories are being scanned at the start of output. ### Pass 1: Internal Links -Scan every `.md` file in the discovered scope for markdown links +Scan every `.md` file in the discovered scope for Markdown links pointing to other files: `[text](target.md)`, `[text](../path/file.md)`, `[text](path/file.md#anchor)`. @@ -57,7 +57,7 @@ BROKEN: source-file.md:LINE → target.md (file not found) ### Pass 2: External Links Scan every `.md` file in the discovered scope for `http://` and -`https://` URLs in markdown link syntax. +`https://` URLs in Markdown link syntax. For each URL: diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md new file mode 100644 index 000000000..4e6cfe171 --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md @@ -0,0 +1,271 @@ +--- +name: ctx-handover +description: "Per-session handover artifact writer. Wraps `ctx handover write` with `--summary` and `--next` (both required, both validated non-placeholder by the CLI). Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them." +tools: [bash] +--- + +# Write a Handover + +Capture the session's narrative thread so the next session (a +fresh agent, a different operator, a cold restart the next +morning) can resume without re-deriving context probabilistically +from canonical files plus journal. + +This skill is the **sole authoritative recall artifact** writer. +`SESSION_LOG.md` entries, closeouts, and journal entries are +mid-flight surfaces; the handover is what `/ctx-remember` reads +on session start. + +## When to Use + +`/ctx-wrap-up` owns the user-facing trigger for session-end +("let's wrap up", "save state", "leave a handover", "before I +go", "stepping away") and delegates to this skill as its final +step. Do not advertise this skill as a direct user trigger. + +- **Mandatory tail of `/ctx-wrap-up`.** Every `/ctx-wrap-up` + run ends with this skill. +- Mid-session checkpoint when the user wants to pause without + consuming closeouts (use `--no-fold`). This is the one case + where direct invocation is appropriate. + +## When NOT to Use + +- Nothing meaningful happened (only read files, quick lookup); + but check with the user. A no-op session still benefits from + a "nothing changed; next-step is X" handover when the next + session has zero context. +- The user already ran `/ctx-handover` recently in this session + and nothing has changed since. +- The user invokes a capture skill (`/ctx-add-task`, + `/ctx-add-decision`, etc.); those write to canonical files, + not to a handover artifact. + +## Authority Boundary (vs Other Skills) + +- **`/ctx-handover`**: writes + `.context/handovers/<TS>-<slug>.md`; folds postdated + closeouts from `.context/ingest/closeouts/` into the + handover's `## Folded Closeouts` section; archives folded + closeouts to `.context/archive/closeouts/`. Single writer of + this artifact. +- **`/ctx-wrap-up`**: owns the user-facing session-end + trigger. Drives the broader capture ceremony (learnings, + decisions, conventions, tasks) and always delegates to + `/ctx-handover` as its final step. +- **`/ctx-remember`**: reads the latest handover plus any + closeouts whose `generated-at` postdates the handover; the + read-side counterpart to this skill's write surface. +- **Capture skills** (`/ctx-add-task`, `/ctx-add-decision`, + `/ctx-add-learning`, `/ctx-add-convention`): write to the + five canonical files. This skill never modifies those files; + the handover narrative *references* them, it does not author + them. + +## Usage Examples + +```text +/ctx-handover "Cursor Hooks deep dive" +/ctx-handover "rev2 spec landed; tomorrow start the writer package" +/ctx-handover "research session on cursor hooks" +/ctx-handover --no-fold "mid-session checkpoint before lunch" +``` + +## Input Contract + +The skill wraps `ctx handover write`, which enforces required +flags via `MarkFlagRequired` and rejects placeholder bodies. +Empty `TBD`, `see chat`, whitespace-only values are rejected by +the CLI, not just by the skill text. + +| Flag | Type | Default | Description | +|------|------|---------|-------------| +| `--summary` | string | (required) | Past tense; what happened this session. | +| `--next` | string | (required) | Future tense; what the next agent should do FIRST. Specific, not vague. | +| `--highlights` | string | "" | Notable artifacts produced this session. | +| `--open-questions` | string | "" | Things that remain undecided. | +| `--no-fold` | bool | false | Skip closeout consumption (mid-session checkpoint). | +| `--commit` | string | (resolved) | Override resolved git HEAD for Provenance line (CI replay). | + +Positional argument: handover title (becomes filename slug). + +## Pre-Write Gates + +Two distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/handovers/` missing → suggest `ctx init --upgrade` + and stop. + +`.context/kb/` is **not** required for handover; the artifact +exists for code-dev sessions as well. KB-state folding is +conditional on the directory's existence (see §Process). + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate + fails. Zero residue on refusal. + +2. **Gather signal silently** (mirror `/ctx-wrap-up` Phase 1 + when invoked standalone): + + ```bash + git status --short + git diff --stat + git log --oneline @{upstream}..HEAD 2>/dev/null || git log --oneline -5 + ``` + + Scan the conversation history for: + - The session's arc: what shifted from start to now. + - Concrete artifacts produced (files, commits, decisions, + spec entries). + - Open questions surfaced but not resolved. + - The specific first action the next session should take. + +3. **Draft `--summary` and `--next`.** Both are required, both + are validated non-placeholder by the CLI: + + - **`--summary`**: past tense. One paragraph. Names what + was done, not what was attempted. Concrete: *"drafted six + Phase KB skill files; reconciled rev2 spec changes; + deferred CLI wiring to next session"*, not *"made + progress on KB stuff"*. + - **`--next`**: future tense. One paragraph. Names the + specific first action the next agent should take. + Concrete: *"start `internal/cli/handover/cmd/write/cmd.go` + using Phase SK validation pattern"*, not *"continue + work" or "look at the kb"*. + + Surface the drafts to the user for confirmation before + running the CLI. The user is the final authority on what + the handover says. + +4. **Run `ctx handover write`** with the confirmed values: + + ```bash + ctx handover write "<title>" \ + --summary "<one-paragraph past tense>" \ + --next "<one-paragraph future tense>" \ + [--highlights "<bullet list>"] \ + [--open-questions "<bullet list>"] \ + [--no-fold] \ + [--commit <sha>] + ``` + + The CLI: + - Validates flags (rejects placeholder values). + - Resolves git HEAD (honors `CTX_TASK_COMMIT` and + `GITHUB_SHA` for CI replay). + - Reads the latest-handover cursor to find the postdated + closeout window. + - For each unconsumed closeout, folds its body into the + handover's `## Folded Closeouts` section. Malformed + closeouts are skipped with a warning. + - Moves folded closeouts to `.context/archive/closeouts/`. + Archived closeouts are immutable. + - Writes `.context/handovers/<TS>-<slug>.md`. + + When `--no-fold` is set, the fold + archive steps are + skipped; closeouts stay in place. Use for mid-session + checkpoints where the user wants the handover artifact but + intends to keep ingesting before the next session boundary. + +5. **Report the result.** Surface: + - The handover filename written. + - Count of closeouts folded (or *"none postdated the prior + handover"*). + - Count of malformed closeouts skipped (with filenames so + the user can fix or delete). + - Any CLI validation failures (with the placeholder text + that triggered rejection). + +## Closeout Fold Mechanics + +When `.context/kb/` is present, the editorial pipeline +(`/ctx-kb-*`) writes per-pass closeouts under +`.context/ingest/closeouts/`. The handover fold is how those +closeouts are tied back to a session boundary: + +- The latest-handover cursor is the `generated-at` of the + newest handover (or zero time if none exists). +- Every closeout whose `generated-at` postdates the cursor is + folded. +- Each folded closeout's body is embedded under + `## Folded Closeouts` in the new handover, in + `generated-at` order. The frontmatter is preserved + verbatim so the audit trail survives the fold. +- After the fold, source closeouts are moved to + `.context/archive/closeouts/`. Archived closeouts are + immutable; subsequent passes never re-fold them. + +A handover with no postdated closeouts to fold writes a +`## Folded Closeouts` section with the body *"none"*; never +omit the section, so the audit trail is explicit. + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/handovers/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Empty `--summary` or `--next` | The CLI rejects with the placeholder-rejection message; surface verbatim. | +| Placeholder values (`TBD`, `see chat`, whitespace-only) | The CLI rejects; surface verbatim and ask the user to redraft. | +| No postdated closeouts to fold | Write the handover with `## Folded Closeouts` body *"none"*. Never omit the section. | +| Postdated closeout has malformed frontmatter | The CLI skips the file with a warning naming it. Report the warning so the user can fix or delete. | +| `--no-fold` set | Skip the fold + archive steps; the handover stands alone; closeouts stay in `.context/ingest/closeouts/` for the next invocation. | +| Mid-session re-invocation | Each invocation writes a new handover file. The newest one is what `/ctx-remember` reads next session. Multiple per session are fine. | +| Session aborted before wrap-up | Closeouts stay in place; next session's `/ctx-remember` reads canonical files + the last handover + any postdated unfolded closeouts. Editorial work survives. | +| `gitmeta.ResolveHead` returns an error (no git, detached HEAD with no fallback) | The CLI surfaces a typed error; relay verbatim. The recovery path is to add a git repo, not to invent one here. | +| `CTX_TASK_COMMIT` or `GITHUB_SHA` set | Honored for the Provenance line per the resolver's precedence rules; no special handling here. | + +## Anti-Patterns + +- Writing a handover with `--summary "TBD"` or `--next "see + chat"`. The CLI rejects these; do not work around the + rejection by inventing prose that technically passes the + placeholder check but is still vague. +- Skipping the fold to *"keep closeouts available for a future + pass"*. The fold is the integration point; closeouts that + outlive their relevant handover are recall noise. Use + `--no-fold` explicitly when the user wants the checkpoint + behavior; do not infer it. +- Hand-writing a handover file. The CLI is the sole writer. + Hand-edits drift from the schema the read side expects. +- Modifying an archived closeout. Archived closeouts are + immutable. +- Inventing `--highlights` or `--open-questions` content the + session did not actually produce. Light compression for + clarity is allowed; new facts are not. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. + +For successful handover writes, end with this structured +summary: + +- **Handover**: filename on its own line. +- **Folded closeouts**: count + filenames (or *"none + postdated the prior handover"*). +- **Malformed skipped**: count + filenames (or `none`). +- **Provenance**: `sha=<short> branch=<name>` as resolved by + the CLI. +- **Next-session focus**: the `--next` value, verbatim, so the + operator sees what the next agent will read first. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] `--summary` is past tense and concrete (no placeholder). +- [ ] `--next` is future tense and specific (no placeholder). +- [ ] User confirmed the drafts before the CLI ran. +- [ ] Closeouts were folded (or `--no-fold` was explicitly + requested). +- [ ] Folded closeouts were archived to + `.context/archive/closeouts/`. +- [ ] Handover filename + provenance were reported back to the + user. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-implement/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-implement/SKILL.md index f9331c2cd..fc4aa19a0 100644 --- a/internal/assets/integrations/copilot-cli/skills/ctx-implement/SKILL.md +++ b/internal/assets/integrations/copilot-cli/skills/ctx-implement/SKILL.md @@ -106,7 +106,7 @@ After all steps complete: | Templates/embeds | `CGO_ENABLED=0 go build -o /dev/null ./cmd/ctx` | | Makefile | Run the new/changed target | | Skill files | Build (to verify embed) + check live copy matches | -| Docs/markdown only | None required | +| Docs/Markdown only | None required | | Shell scripts | `bash -n script.sh` (syntax check) | ## Handling Failures diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-journal-normalize/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-journal-normalize/SKILL.md index 298f5dc28..b7b65e80e 100644 --- a/internal/assets/integrations/copilot-cli/skills/ctx-journal-normalize/SKILL.md +++ b/internal/assets/integrations/copilot-cli/skills/ctx-journal-normalize/SKILL.md @@ -4,20 +4,20 @@ description: "Normalize journal source markdown for clean rendering. Use after j tools: [bash, read, write, edit] --- -Reconstruct journal entries as clean markdown from stripped plain text. +Reconstruct journal entries as clean Markdown from stripped plain text. ## When to Use - After `ctx journal site` shows rendering issues - When journal entries have fence nesting problems - When metadata blocks render as raw `**Key**: value` -- Before running `ctx-journal-enrich` (clean markdown improves extraction) +- Before running `ctx-journal-enrich` (clean Markdown improves extraction) ## When NOT to Use - On entries already normalized (check `.state.json`) - When the site renders correctly -- On non-journal markdown files +- On non-journal Markdown files ## Output Rules diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md new file mode 100644 index 000000000..ce28465f0 --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md @@ -0,0 +1,236 @@ +--- +name: ctx-kb-ask +description: "Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail." +tools: [bash] +--- + +# Ask the KB + +Answer a question using only what `.context/kb/` already contains. +Cite by `EV-###`. Do not web-jump, do not invent prose, do not +modify topic pages. If the kb cannot answer, open a `Q-###` row +in `.context/kb/outstanding-questions.md` and report the gap. + +This is the read side of the editorial pipeline. The write side +is `/ctx-kb-ingest`. Authority for prose synthesis lives there; +this skill is read-only on prose. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary and +§Evidence discipline. + +## When to Use + +- The user asks "does the kb say...", "according to evidence...", + "what do we know about <topic>", or invokes the explicit slash + form with the question. +- The user wants a citation-backed answer before deciding whether + to ingest more material. +- The user is auditing what is already known versus what is + asserted elsewhere (DECISIONS.md, LEARNINGS.md, conversation). + +## When NOT to Use + +- The user wants new material extracted (use `/ctx-kb-ingest`). +- The user wants the kb structurally audited (use + `/ctx-kb-site-review`). +- The user wants kb claims re-grounded against external sources + (use `/ctx-kb-ground`). +- The question is about `ctx` itself or the editorial pipeline + contract (answer from `KB-RULES.md` / spec directly). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ask`**: read-only Q&A over `.context/kb/` prose, + `evidence-index.md`, `glossary.md`, `contradictions.md`, + `outstanding-questions.md`, `timeline.md`, `source-map.md`, + `domain-decisions.md`. Writes are limited to opening a + `Q-###` row in `outstanding-questions.md` when the kb cannot + answer, plus the ask closeout. +- **`/ctx-kb-ingest`**: writes prose, evidence, scaffold. Only + ingest may add citations or extend topic pages. +- **`/ctx-kb-ground`**: refreshes external sources via + `grounding-sources.md`; this skill never web-jumps to fill a + gap. If the gap matters, recommend `/ctx-kb-ground` or + `/ctx-kb-ingest`. + +## Usage Examples + +```text +/ctx-kb-ask "what does the kb say about cursor hooks failure modes?" +/ctx-kb-ask "how do we cite a transcript locator?" +/ctx-kb-ask "are there contradictions on backup retention windows?" +``` + +## Input Contract + +A single question, supplied as the slash argument or inline. No +flags. No sources. No URLs. + +## Refuse-on-Empty + +If the invocation supplied no question (empty slash arg, empty +inline body), return exactly: + +> no question provided; pass a question or describe it inline. + +Stop. Do not prompt interactively. The CLI enforces this +independently via `cmd/ask`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue (no +`Q-###` row opened, no closeout): + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- `.context/kb/index.md` exists but `## Scope` is undeclared → + refuse with the scope message (same wording as `/ctx-kb-ingest` + uses) and stop. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Read the question.** Parse for the concept(s) it names. + +3. **Survey the kb.** Read in this order, stopping early when an + answer surfaces with adequate citation coverage: + - `.context/kb/index.md` for scope. + - `.context/kb/topics/<slug>/index.md` and any sibling + sub-pages for any slug that plausibly matches the question. + - `.context/kb/evidence-index.md` for `EV-###` rows whose + claim text matches. + - `.context/kb/glossary.md` for term definitions. + - `.context/kb/contradictions.md` for known disagreements + relevant to the question. + - `.context/kb/outstanding-questions.md` for prior + unanswered questions on the topic. + +4. **Decide answer vs gap.** One of three outcomes: + + - **Answerable with citations.** The kb's prose plus + `EV-###` rows cover the question. Compose a concise answer. + Cite every load-bearing claim by `EV-###`. Name the topic + page(s) where the prose lives. Note the Confidence floor + of the cited rows. + - **Partial answer.** Some of the question is covered; the + rest is not. Answer the covered part with citations. Name + the gap explicitly. Open a `Q-###` row for the gap (see §6). + - **Not answerable.** The kb has no prose and no `EV-###` + coverage. Do not invent. Do not web-jump. Open a `Q-###` + row (see §6) and report the gap. + +5. **Do not jump.** This skill is read-only on prose AND + web-quiet. If the kb cannot answer: + + - Do **not** fetch a URL. + - Do **not** propose synthesized prose without citations. + - Do **not** call MCP search tools. + - Do **not** quote LLM training-data recall as if it were + kb evidence. + + The correct response to a gap is to name the gap, open a + `Q-###` row, and recommend `/ctx-kb-ground` (if external + refresh is the right path) or `/ctx-kb-ingest <sources>` (if + the user has materials to feed in). + +6. **Open a `Q-###` row if there is a gap.** Append a row to + `.context/kb/outstanding-questions.md` per its schema. The + row's question text is the user's question (or a faithful + paraphrase). The row notes what the kb does cover (if + partial) and what evidence would resolve. Do NOT mint + `EV-###` rows from this skill; that is ingest's authority. + +7. **Write the ask closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-ask-closeout.md` with + required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ask + pass-mode: read-only + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Question**: what the user asked, verbatim. + - **Answer**: the answer given, or `none (gap)` if not + answerable. + - **Citations**: `EV-###` IDs cited, with topic-page paths. + - **Gaps**: `Q-###` opened in `outstanding-questions.md`, + with a one-line rationale. + - **Next pass hint**: explicit invocation for the next + pipeline step (e.g. `/ctx-kb-ground` to refresh, + `/ctx-kb-ingest <sources>` to extend). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty question | Refuse with the standard no-question text. No `Q-###` opened, no closeout. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/kb/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message; point at `.context/kb/index.md`. No residue. | +| Multiple topics relevant | Cite each topic page; do not synthesize a new cross-topic claim (that would be ingest work). Surface the seam as a `Q-###` if it merits one. | +| Contradiction surfaces during answer | Answer with the lower-confidence side noted; cite both `EV-###` rows; point at `contradictions.md`. | +| Cited rows are all `speculative` or `low` | Surface the confidence band in the answer. Recommend `/ctx-kb-ground` to corroborate. Do not promote in this pass. | +| Question matches an existing `Q-###` row | Cite the existing row's ID; report status (`open`, `partially-answered`); do not open a duplicate. | +| Question requires external evidence the kb does not have | Open a `Q-###` row; recommend `/ctx-kb-ground` with the gap named; do not fetch the source. | +| Question is meta (about the pipeline itself) | Answer from `KB-RULES.md` / spec directly; this skill is for kb content, not pipeline contract. State that explicitly. | + +## Anti-Patterns + +- Web-jumping when the kb cannot answer. The contract is + read-only on prose AND web-quiet. +- Inventing citations or claims to make the answer look fuller. +- Modifying a topic page to extend an answer mid-pass. Topic-page + authoring is `/ctx-kb-ingest`'s authority. +- Minting `EV-###` rows from this skill. Evidence authoring is + `/ctx-kb-ingest`'s authority. +- Skipping the `Q-###` row when the kb cannot answer. The gap + is the audit trail; silence on a gap is invisible. +- Skipping the closeout once the pre-write gates pass. The + closeout is the residue wrap-up's handover step folds into + the next session's recall. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For passes that clear pre-write gates, end with this structured +summary: + +- **Question**: verbatim or faithful paraphrase. +- **Answer**: concise; cites every load-bearing claim by + `EV-###`. +- **Confidence floor**: lowest band among cited rows + (`high|medium|low|speculative`), or `n/a` if no rows cited. +- **Gaps**: `Q-### opened` (one bullet per opened row), or + `none`. +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocation if a gap + was opened (e.g. `/ctx-kb-ground` or `/ctx-kb-ingest + <sources>`), or `none` if the answer is complete. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every load-bearing claim in the answer cites at least one + `EV-###` row from `evidence-index.md`. +- [ ] If a gap exists, a `Q-###` row was opened (or an existing + row was cited). +- [ ] No URL was fetched, no MCP search was called, no LLM + training-data recall was quoted as kb evidence. +- [ ] No topic page was modified, no `EV-###` row was minted. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md new file mode 100644 index 000000000..eb6987175 --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md @@ -0,0 +1,278 @@ +--- +name: ctx-kb-ground +description: "External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip." +tools: [bash] +--- + +# Ground the KB Against External Sources + +Refresh `.context/kb/` against the external sources the user has +declared in `.context/ingest/grounding-sources.md`. This is the +*"are we still current?"* pass. It does not mint new evidence by +itself, does not author topic pages, does not modify Confidence +bands. It walks the declared external sources, checks each for +drift against what the kb cites, and advances the source-coverage +ledger to reflect what the refresh found. + +If the refresh surfaces material the kb should absorb, this skill +flags it and recommends `/ctx-kb-ingest`. Authoring is ingest's +authority, not this skill's. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary and +§Source-coverage ledger. + +## When to Use + +- The user says "re-ground the kb", "check upstream", + "are the docs still current?", or invokes the explicit slash + form. +- Before a release / handover where source freshness matters. +- After an external vendor has shipped a version bump. +- Periodically (per the user's cadence) as kb hygiene. + +## When NOT to Use + +- The user has new materials in hand (use `/ctx-kb-ingest`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The user wants a structural audit (use `/ctx-kb-site-review`). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ground`**: refreshes external sources listed in + `grounding-sources.md`; advances source-coverage ledger rows + for sources where the refresh changed state; writes a ground + closeout. **May not** mint `EV-###` rows, author prose, + modify a topic page, or change a Confidence band. +- **`/ctx-kb-ingest`**: handles anything this skill surfaces + as new material to absorb. +- **`/ctx-kb-ask`**: handles read-only questions about kb + content. +- **`/ctx-kb-site-review`**: handles structural audit (separate + surface from source-freshness audit). + +## Usage Examples + +```text +/ctx-kb-ground +``` + +No arguments. Sources come from +`.context/ingest/grounding-sources.md`. + +## Input Contract + +The file `.context/ingest/grounding-sources.md` is the sole +declaration surface. Each non-empty, non-comment line names a +source (URL, in-tree path, MCP resource identifier) the user +wants this skill to track. A line whose value is the literal +`NONE` is a **per-pass skip**: this invocation does nothing and +re-prompts on the next invocation. Lines beginning with `#` are +comments. + +There is no CLI argument for sources. To configure what this +skill checks, edit `grounding-sources.md`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- Kb scope undeclared → refuse with the scope message and stop. + +## Refuse-on-Empty + +`.context/ingest/grounding-sources.md` may be in three states: + +1. **Missing or empty**: file does not exist, or has only + comments and blank lines. Prompt once: + + > `grounding-sources.md` has no sources. List one source per + > line (URL, in-tree path, MCP resource). `NONE` on a line + > is a per-pass skip and re-prompts next invocation. + + Stop. Do not synthesize a list. Do not invent sources from + the kb's `source-map.md` (that file's authority is ingest; + grounding's declaration surface is separate by design). + +2. **Single line `NONE`**: per-pass skip. Write no closeout; + return exactly: + + > grounding-sources.md is `NONE` for this pass; skipping. + > Edit `.context/ingest/grounding-sources.md` to set actual + > sources, or leave `NONE` to keep skipping. + + The next invocation re-prompts as in (1) above. + +3. **One or more sources listed**: proceed to Process. + +The empty-and-prompt path is the one exception to the +refuse-on-empty pattern other mode skills enforce; the rationale +is that grounding's declaration lives in a file the user owns +(not in a slash argument), so a one-shot prompt is cheaper than +forcing them to remember the filename. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Read `.context/ingest/grounding-sources.md`.** Handle the + three states per §Refuse-on-empty. + +3. **For each declared source**, in order of appearance: + + - **Resolve** the source: fetch the URL, stat the in-tree + path, enumerate the MCP resource. + - **Cross-reference** against `.context/kb/source-map.md` to + find the kb's short-name for this source (if any). If + absent, the source is *new to the kb*; record it as a flag + to surface in the closeout (do not mint a `source-map.md` + row; that is ingest's authority). + - **Check freshness** using the strongest available signal: + - URL: HTTP Last-Modified header, or ETag, or visible + version stamp on the page; compare against the + `source-map.md` row's `dated:` cell (if present). + - In-tree path: file mtime + git SHA; compare against the + `evidence-index.md` rows that cite the source by SHA + (in-repo citations pin to a SHA at extraction time per + `KB-RULES.md` §Evidence discipline). + - MCP resource: whatever freshness primitive the resource + exposes; if none, treat as opaque (record as + *"freshness opaque"* in the closeout). + - **Classify the refresh outcome** as one of: + - **`unchanged`**: source has not drifted since the + kb's last extraction; no ledger update needed. + - **`drifted`**: source has changed; the kb's claims + citing this source may be stale; advance the ledger row + to a state that reflects the staleness: + - If the row was `comprehensive`, advance to a typed + `superseded-pending` annotation in the `Residue` cell + (do not write a new state name; the state machine in + `KB-RULES.md` is closed; `Residue` is the + human-readable annotation surface). + - If the row was anywhere prior to `comprehensive`, + leave the state and add a `drifted` note in `Residue` + + `Next action` set to the explicit + `/ctx-kb-ingest <slug>` resumption. + - **`gone`**: source returns 404, file deleted, MCP + resource removed; flag for the user. The right + resolution may be `superseded` (with a named successor) + or `skipped` (out of scope); that judgment is the + user's, not this skill's. + - **`freshness opaque`**: no freshness signal available; + record in `Residue` cell as *"freshness opaque + (<date checked>)"*; no ledger state change. + - **Advance the ledger row** only for `drifted` and `gone` + cases, and only via `Residue` / `Next action` annotation + (not state change). State transitions out of `comprehensive` + are ingest's authority. + +4. **Write the ground closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-ground-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ground + pass-mode: external-refresh + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Inputs**: declared sources from + `grounding-sources.md`, count + one bullet each. + - **Refresh outcomes**: for each source: `unchanged`, + `drifted`, `gone`, `freshness opaque`, or `new to kb`. + Cite the kb short-name (or *"new to kb"*) and the + evidence used to classify (Last-Modified header, version + stamp, file mtime). + - **Ledger updates**: every `Residue` / `Next action` + change applied to a `source-coverage.md` row, with the + before/after annotation. + - **Flags**: sources the refresh found `gone`, sources + classified `new to kb`, sources with conflicting + freshness signals. Each flag names the source and the + recommended next pipeline step. + - **Next pass hint**: explicit invocations to absorb + drifted / new material (e.g. *"`/ctx-kb-ingest <slug>` to + refresh `cursor/hooks` against the v1.2 docs"*). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `grounding-sources.md` missing or empty (only comments/blank) | Prompt once with the standard text; stop. No closeout. | +| `grounding-sources.md` single line `NONE` | Skip this pass with the standard skip text; stop. No closeout. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message. No residue. | +| Source returns 404 / file deleted / MCP resource removed | Classify `gone`; flag; recommend the user choose `superseded` (with successor) or `skipped` (out of scope). Do not auto-transition. | +| Source unchanged since last extraction | Record `unchanged` in closeout's `Refresh outcomes`; no ledger update. | +| Source drifted since last extraction (URL bumped, file mtime newer than cited SHA) | Record `drifted`; annotate ledger row's `Residue` / `Next action`; recommend `/ctx-kb-ingest <slug>`. Do not modify topic-page prose. | +| Source has no freshness primitive (opaque) | Record `freshness opaque (<date checked>)`; no ledger state change; surface in `Flags` so the user can decide cadence. | +| Source listed in `grounding-sources.md` but not in `source-map.md` (new to kb) | Classify `new to kb`; flag; recommend `/ctx-kb-ingest <source>` to admit. Do not mint a `source-map.md` row from this skill. | +| Source listed in `grounding-sources.md` but URL malformed or path nonexistent | Surface as a per-source error in the closeout's `Flags`; continue with remaining sources; do not abort the pass. | +| Source's `source-map.md` row has `dated:` but `evidence-index.md` rows lack `occurred:` | Flag (temporal-precedence rule needs it); recommend hand-edit. Do not auto-edit. | +| User added a new source to `grounding-sources.md` since the last pass | Treated as a regular declared source; classified per the freshness check; no special path. | +| Mid-pass MCP fetch failure | Record per-source error; continue; do not abort the whole pass. | + +## Anti-Patterns + +- Minting `EV-###` rows from this skill. Evidence authoring is + ingest's authority. +- Authoring topic-page prose from this skill. Page authoring is + ingest's authority. +- Modifying a claim's Confidence band from this skill. Demotion + is evidence work. +- Auto-transitioning a `comprehensive` ledger row out of + `comprehensive`. State changes require ingest judgment; this + skill annotates `Residue` / `Next action` only. +- Synthesising a source list when `grounding-sources.md` is + empty. The declaration surface is the file the user owns. +- Inventing a freshness signal when none exists. *"Freshness + opaque"* is the honest classification. +- Skipping the closeout once pre-write gates pass and at least + one source was processed. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For empty / `NONE` cases, return the matching prompt or skip +text and stop. No closeout in those cases. + +For passes that processed at least one source, end with this +structured summary: + +- **Sources checked**: count + one bullet each, classified + (`unchanged | drifted | gone | freshness opaque | new to kb`). +- **Ledger updates**: count + one-line categories. +- **Flags**: count + categories. +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocations to absorb + drifted / new material (or `none` if every source was + `unchanged`). + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every declared source from `grounding-sources.md` was + checked, classified, and recorded in `Refresh outcomes`. +- [ ] No `EV-###` row was minted, no topic-page prose was + written, no Confidence band was changed, no ledger state + was transitioned (only `Residue` / `Next action` + annotated). +- [ ] Every `drifted` / `gone` / `new to kb` source has an + explicit `Next-recommended-action`. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md new file mode 100644 index 000000000..1f22f8c0c --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md @@ -0,0 +1,644 @@ +--- +name: ctx-kb-ingest +description: "Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail." +tools: [bash] +--- + +# Editorial Ingestion Pass + +This skill is the **single editorial pass** for adding knowledge +to `.context/kb/`. It reads materials the user supplies, decides +which topic page(s) they belong to, finds-or-creates those pages, +writes synthesized prose section by section, mints `EV-###` rows +in the structured layer as it cites them, cross-links neighboring +topics, updates the source-coverage ledger, and writes a closeout +file under `.context/ingest/closeouts/`. + +The split between "extract claims" and "write the topic page" is +mechanical, not editorial. A student reading a book does not +extract a glossary first and synthesize later, they read and write +at the same time. This skill matches that model: the user supplies +*intent and material*; the skill does *judgment and typing*. + +**The topic page is the deliverable. The closeout is the audit +trail. The closeout never substitutes for the page.** Intermediate +artifacts (EV rows, glossary entries, candidate-source registries, +closeouts) are valuable, but they do not validate topic-page work +by themselves; only the topic page does. + +Authoritative background reading lives at +`.context/ingest/KB-RULES.md`. This skill encodes the workflow +contract; the rules file is the constitution. Hand-edit +`KB-RULES.md` to evolve the contract; do not paraphrase it here. + +## When to Use + +- The user supplies one or more sources (paths, URLs, MCP + resources, inline natural-language descriptions) and wants them + read into the kb. +- The user says "ingest the transcripts", "pull this into the + kb", "add evidence from <source>", "extract claims from this + call", or invokes the explicit slash form with paths. +- A prior pass left residue (a `topic-page-drafted` ledger row, + a `Next pass hint` in a closeout) and the user is resuming. + +## When NOT to Use + +- The user asked a question about the kb (use `/ctx-kb-ask`). +- The user wants a structural audit of the kb (use + `/ctx-kb-site-review`). +- The user wants to re-ground existing kb claims against + external sources (use `/ctx-kb-ground`). +- The user wants to park a quick finding for the next ingest + (use `/ctx-kb-note`). +- No sources were supplied (refuse-on-empty; see §Refuse-on-empty + below). +- `.context/kb/` does not exist (refuse with the no-pipeline + message in §Pre-write gates). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-ingest`**: primary editorial pass. Reads materials + (in-tree paths, out-of-tree paths, URLs, MCP resources, inline + references); writes topic pages + (`.context/kb/topics/<slug>/index.md`, plus optional sibling + sub-pages); mints evidence, glossary, source-map, timeline, + contradictions, outstanding questions; cross-links into existing + kb topology; updates the source-coverage ledger; writes + closeout. **Topic-page file creation is performed only by + `ctx kb topic new`**: this skill MAY invoke that CLI as part + of a topic-page pass, but it MUST NOT synthesize or write a + scaffold directly. This preserves the public editorial workflow + (`/ctx-kb-ingest`) and the actual scaffold authority + (`ctx kb topic new`) as two separate facts. +- **`/ctx-kb-ask`**: Q&A grounded in the kb. Read-only on prose; + refuses to web-jump; flags gaps the kb cannot answer. +- **`/ctx-kb-site-review`**: structural audit; mechanical fixes + only. Defers anything that requires evidence judgment. +- **`/ctx-kb-ground`**: external grounding against + `grounding-sources.md`; advances ledger rows for sources it + refreshes. +- **`/ctx-kb-note`**: lightweight capture into + `.context/ingest/findings.md`; never writes to a topic page or + to `evidence-index.md`. + +This skill writes prose AND evidence rows AND scaffold (via CLI) +AND cross-links AND ledger updates in the same pass; that +combination is unique to ingest. + +## Usage Examples + +```text +/ctx-kb-ingest ./inputs/2026-04-12-call.md "cursor hooks" +/ctx-kb-ingest ./inputs/your-domain/ +/ctx-kb-ingest https://cursor.com/docs/hooks +/ctx-kb-ingest ./a.md ./b.md "incident retros" +/ctx-kb-ingest --inline "the four transcripts under inputs/ \ + and the pool.go file" "connection pooling" +``` + +## Input Contract + +**Sources**, supplied as one or more of: + +- **Paths**: folder to recurse, single file, list of files. +- **URLs**: primary-source web pages. +- **MCP resources**: named resources from connected MCP servers. +- **Inline gestures**: natural-language naming the materials. +- **Open invitation**: *"feel free to search for more"*. The + skill gets web-search and MCP-discovery authority for this + pass; hard cap of 50 total sources. + +**Optional second argument, topic name**, e.g. *"cursor hooks"*. +When omitted, the skill proposes one at §3 of Process and +confirms with the user before any extraction work. Naming the +topic up front skips that round-trip. + +## Refuse-on-Empty + +The skill writes to the kb; refuse-on-empty is the default. If +the invocation supplied no sources and no inline gesture, return +exactly: + +> no sources provided; pass a folder, a URL, an MCP resource, or +> describe the materials inline. + +Stop. Do not prompt for sources interactively, do not invent a +topic, do not propose a triage pass on imagined material. The CLI +enforces this independently via `cmd/ingest`. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue (no +`INBOX.md` rewrite, no `SESSION_LOG.md` entry, no claim +extraction, no ledger update, no closeout, no topic-page edits): + +- `.context/` missing entirely → suggest `ctx init` and stop. +- `.context/ingest/` missing (project initialised before this + spec shipped) → suggest `ctx init --upgrade` and stop. +- Kb scope undeclared (`.context/kb/index.md` missing, contains + the `TODO: declare what this kb covers` placeholder, has no + `## Scope` H2, or `## Scope` lacks substantive + non-placeholder prose): + + > kb scope is undeclared. Open `.context/kb/index.md` and + > replace the TODO placeholder with a one-paragraph scope + > statement that names what is in scope and what is out. + > `/ctx-kb-ingest` refuses to ingest until scope is declared. + +## Pass-Mode Contract + +Every invocation MUST classify itself as exactly one of three +modes **before any source extraction begins**. The mode commits +the pass to a specific definition of done; the skill is held to +that definition and may not narrate success on residue belonging +to a different mode. Full mode semantics live in +`.context/ingest/KB-RULES.md` §Pass-mode contract. + +| Mode | Mints prose? | Mints `EV-###`? | Touches topic page? | Default? | +|------------------|--------------|------------------|------------------------|----------| +| `topic-page` | yes | yes | yes (create/extend) | yes | +| `triage` | no | **no** | no | no | +| `evidence-only` | no | yes (tagged) | no | no | + +**Mode selection rules.** Default is `topic-page`. `triage` fires +only when the user supplied multiple disparate sources with no +clear single topic, OR explicitly invoked triage language. +`evidence-only` fires only on explicit user request matching the +valid-trigger criteria (*"just mint EV rows"*, *"backfill +evidence"*); the skill MAY NOT infer it from source size, +ambiguity, time pressure, or operator convenience. + +**Up-front declaration (mandatory).** Before extraction begins +(after pre-write gates pass and **before** topic resolution), emit +a visible pre-work declaration in the response stream: + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific completion criterion>` + +The declaration is a contract, not a label. The skill is bound to +it for the rest of the pass. + +**Mid-pass mode-switching is forbidden.** If the work in flight +no longer fits, abort with a partial closeout citing what was +done, and recommend re-invocation under the correct mode. Silent +mode-drift is a hard anti-pattern. + +## Topic-Page Circuit Breaker + +A pass operating in `topic-page` mode MAY NOT report +`topic-page: produced` or `topic-page: extended` unless ALL of +the following are true at completion: + +1. `.context/kb/topics/<slug>/index.md` (or a sibling sub-page + like `.context/kb/topics/<slug>/<sub>.md`) exists and was + created or extended in this pass. +2. The page cites at least one `EV-###` row that resolves to + `evidence-index.md`. +3. `ctx kb site build` ran clean (or its failure is named in the + closeout's `Next pass hint` AND the pass reports + `topic-page: deferred`). +4. The cold-reader orientation rubric records **`Result: pass`** + in the closeout's `What changed` section. All four rubric + items must be `yes`. + +Any failure → `topic-page: deferred` and the source-coverage +ledger advances to `topic-page-drafted` (not `comprehensive`). +This invariant prevents intermediate residue from being treated +as topic-page success. **Topic-page validation requires the +topic page.** + +## Source-Coverage Ledger + +`.context/kb/source-coverage.md` is a state machine over every +source the kb has touched. Allowed transitions live in +`KB-RULES.md` §Source-coverage ledger; do not paraphrase them +here. Every pass updates the ledger before writing the closeout. +**Lying to the ledger is a hard anti-pattern.** Set the state +honestly even when it means recording incomplete work. + +## Cold-Reader Orientation Rubric + +Four yes/no items recorded in the closeout's `What changed` +section, in `topic-page` mode: + +``` +Cold-reader orientation: +- Concept clear? yes|no: <short note> +- Why this kb cares clear? yes|no: <short note> +- Canonical evidence reachable? yes|no: <short note> +- Boundaries clear? yes|no: <short note> +Result: pass | fail +``` + +`Result: pass` requires all four `yes`. Any `no` → +`Result: fail` → circuit-breaker fails → `topic-page: deferred`. + +## Life-Stage Check + +Count `.context/kb/topics/*/index.md` pages **before** this pass +begins synthesizing: + +- `< 5` topic pages → **bootstrap** mode. Skip reconciliation + ceremony; synthesize topic pages aggressively. Exception: + surface a contradiction even in bootstrap if the new material + plainly contradicts existing kb claims. +- `>= 5` topic pages → **maintenance** mode. Apply full + reconciliation discipline (laddering, demotion, contradiction + detection). + +Document the life-stage call in the closeout's frontmatter +(`life-stage:`) and `What changed` section. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly with the matching + message from §Pre-write gates if `.context/`, + `.context/ingest/`, or kb scope is missing. No residue on + refusal. + +2. **Declare pass-mode and surface the up-front declaration.** + Determine the mode per §Pass-mode contract. Emit the + three-line declaration block in the response stream **before + any further work**. Mid-pass mode-switching is forbidden; + abort and re-invoke if the work no longer fits. + +3. **Resolve the topic.** *(Topic-page mode only; skipped in + `triage` and `evidence-only`.)* + + - **Read `.context/kb/source-coverage.md` in full first.** It + answers *"what does this kb already know about which + sources, and at what completeness?"*: a precondition for + honest topic resolution, not an afterthought. + - **Topic-adjacency pre-flight (mandatory).** Scan the ledger + for rows whose state is **not** in + `{comprehensive, skipped, superseded}` AND whose `Topic` is + plausibly *adjacent*. Heuristics: + - **Shared first segment of a slash- or hyphen-separated + slug**: `cursor/skills` is adjacent to `cursor/hooks`. + - **Shared product / vendor / surface** in the source URL or + description. + - **Explicit cross-references** in the named topic's + existing sub-pages or this pass's source set. + + For each adjacent incomplete topic surfaced, this pass MUST: + 1. Acknowledge it in `## Related concepts in this kb` on the + topic page being authored. + 2. Surface it in the closeout's `Adjacency pre-flight` + block. + 3. Surface it in the response contract's `Adjacent topics + noted` field. + + **Do NOT enumerate `EV-###` IDs by name in the adjacency + block.** Use *count + location* (*"seventeen rows in + `evidence-index.md`"*). Naming an EV row from a + lower-confidence sibling demotes the floor of cited bands. + + Silence is not a clean pre-flight; if zero matches, record + *"no incomplete adjacent topics surfaced"* explicitly. + + - **Named vs unnamed branches.** If the user named a topic, + accept it and map to slug (lowercase + kebab-case). If not, + scan the inputs *just enough* to propose one and confirm: + + > you haven't named a topic; based on the inputs this looks + > like **"<proposed name>"**. Confirm or correct. + + One question. Wait for confirmation. If material spans + multiple topics, ask once for the splits. Do not auto-split. + +4. **Resolve sources (and discover, if invited).** *(All modes.)* + + - Resolve every supplied source: fetch URLs, recurse folders, + enumerate MCP resources. + - If the user invited discovery, do bounded web/MCP search. + - **Hard cap: 50 total sources** (supplied + discovered) per + pass. Quality of synthesis collapses past it. + - **If discovery exceeds 50**, keep the 50 highest-judged + sources for this pass; append the overflow to + `.context/ingest/candidate-sources.md` under a "Pending + (overflow from <date> ingest of `<topic-slug>`)" heading. + - **Update the source-coverage ledger**: every supplied source + moves from absent → `discovered` (if newly seen) → + `admitted` (if scope-conformant) or → `skipped` (if not). + Discovered sources kept for this pass also land at + `admitted`; overflow stays at `discovered` with a pointer. + + Append a `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=resolve status=<done|partial|blocked> note=<<=120 chars> + ``` + +5. **Survey kb topology and determine life-stage.** *(All + modes.)* + + - List `.context/kb/topics/*/index.md`; glance for sibling + sub-pages so the cross-link palette includes them. + - Read `.context/kb/index.md` for the canonical scope. + - Skim recent sections of `evidence-index.md`, `glossary.md`, + `outstanding-questions.md`, `contradictions.md`, + `timeline.md` for prior claims relevant to this pass. + - **Life-stage check**: count `kb/topics/*/index.md`. `< 5` + is bootstrap; `>= 5` is maintenance. Document the call in + the closeout's frontmatter (`life-stage:`). + +6. **Find or create the topic page.** *(Topic-page mode only.)* + + Topic pages are folder-shaped from day one: + `.context/kb/topics/<slug>/index.md`, with optional sibling + sub-pages. + + - **If `.context/kb/topics/<slug>/index.md` exists**, read it + AND enumerate any sibling sub-pages. The pass **extends** + the topic: append/extend prose; reuse existing `EV-###` + rows where possible; preserve human edits; do not reformat + to match a newer template. Choose the right file: + - Lede / "What it is" overview → edit `index.md`. + - Existing sibling sub-page material → edit that sub-page. + - **Sub-page split is lazy.** Do NOT pre-emptively split. + Only split when `index.md` has grown to fail the + cold-reader "boundaries clear?" check; at which point, + propose the split (one question, wait for confirmation; + sub-page topology affects long-term shape). + - **If `.context/kb/topics/<slug>/` does not exist**, scaffold + by invoking `ctx kb topic new "<concept name>"`. The CLI is + the sole writer of the scaffold; do not synthesize it by + hand. The CLI creates the folder, writes `index.md`, AND + registers the new slug in `.context/kb/index.md`'s + `CTX:KB:TOPICS` managed block. + + After revising the page's H1 or Confidence band in §10, run + `ctx kb reindex` so the managed block refreshes. + +7. **Synthesise.** Body depends on declared mode. + + ### `topic-page` mode + + For each template section (Status block, lede, "What it is", + "Why this kb cares", "Sources and further reading", optional + sections): + + - **Read the source(s) carefully**: full pass, not skim. + - **Write paraphrased prose that captures the understanding**, + not a transcription. + - **For each claim needing citation, mint or reuse `EV-###`:** + - Re-read `evidence-index.md` immediately before writing to + find the highest existing `EV-NNN`; append the next + integer. Pad to three digits (`EV-012`, not `EV-12`). + Duplicate IDs are a hard refusal: abort and re-read. + - **If the claim is already pinned** by an existing row, + reuse the ID verbatim. If the existing claim no longer + matches, treat as a contradiction (§8). + - **If the existing row carries the `evidence-only` tag**, + treat as review-required: re-read the source, confirm the + claim, then promote onto the page. Leave the tag in + place; it is audit trail. + - Append the row to `evidence-index.md` per its schema + (claim, source short name + locator, optional `sha:` for + in-repo citations, confidence band, tags, extracted + date). + - If the source is new, append a row to `source-map.md`. + - Cite `EV-###` inline in the prose. + - **Cross-link** to existing kb topics, DECISIONS.md, + LEARNINGS.md, and `docs/` entries when applicable. + - **Mandatory `## Related concepts in this kb` entries** for + adjacent incomplete topics surfaced by §3's pre-flight. The + acknowledgement must read as a forward pointer (state + + count + location), not as trivia. + - **Mark unbacked claims with `TBD-cite`** and open + `outstanding-questions.md` entries for each. + - **Update `glossary.md`** for net-new terms. + - **Update `timeline.md`** if the pass surfaces a dateable + event. + + **Never invent citations.** **Never** promote a claim above + `speculative` without an `evidence-index.md` row backing it. + + ### `triage` mode + + For each admitted source, judge admission/skip against the + scope paragraph and propose topic routing in the closeout. Do + NOT write to any topic page. **Do NOT mint `EV-###` rows.** Do + NOT touch `evidence-index.md`, `glossary.md`, or + `timeline.md`. + + Triage is routing and admission, not extraction. If the user + asks to *"triage and grab obvious facts as you go,"* abort + with a partial closeout and recommend re-invocation under + either `topic-page` or `evidence-only` mode. Triage MAY update + `source-coverage.md` and `candidate-sources.md`. That is the + full write surface for triage. + + ### `evidence-only` mode + + For each admitted source, mint `EV-###` rows + `source-map.md` + rows + `glossary.md` entries for terms encountered. **Do not + touch any topic page.** Do not write prose synthesis. + + Every minted `EV-###` row MUST include the literal tag + `evidence-only` in its tags column. The tag is **additive**; + it does not replace topical tags. + + Append a `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=synthesise status=<done|partial|blocked> note=<topic slug + <=80 chars> + ``` + +8. **Apply life-stage reconciliation discipline.** *(All modes; + behavior depends on life-stage.)* + + **Bootstrap (`< 5` topic pages)**: skip except for the + contradiction exception in §5. Append a `SESSION_LOG.md` line + with `status=skipped-bootstrap`. + + **Maintenance (`>= 5` topic pages)**: for each EV row minted + in §7: + + - **Reinforces an existing claim** → promote per the + laddering rules in `KB-RULES.md` §Confidence bands + (`speculative → low → medium → high`); cross-link the new + row to the prior one. + - **Contradicts an existing claim** → add a row to + `contradictions.md`; demote the older claim per the + demotion policy in `KB-RULES.md` §Demotion policy; open an + `outstanding-questions.md` entry naming both sides and what + evidence would resolve. + +9. **Set the topic page's Confidence floor.** *(Topic-page mode + only.)* Inspect every `EV-###` cited on the page; the page's + Status-block `Confidence` is the **lowest** of those cited + bands. Refuse to set Confidence above the floor. Refuse to + set above `speculative` while any `TBD-cite` remains. + +10. **Update the topic page's Status block.** *(Topic-page mode + only.)* Substitute `Subject:`, `Last verified:`, `Author:` + (`agent-ingested` if untouched by a human in this pass; + `mixed` if a human revised prose; **never** + `hand-authored`), and `Confidence:` per §9. + +11. **Update the source-coverage ledger.** *(All modes.)* For + every source touched, advance its row in + `.context/kb/source-coverage.md` per the state machine. + Update `EV coverage`, `Residue`, `Next action`, `Updated` + columns honestly. Lying to the ledger is a hard + anti-pattern. + +12. **Topic-page circuit breaker check.** *(Topic-page mode + only.)* Verify all four invariants from §Topic-page circuit + breaker. Any failure → `topic-page: deferred` and ledger to + `topic-page-drafted` (NOT `comprehensive`). + +13. **Write the closeout.** *(All modes; mode-aware body.)* + Create + `.context/ingest/closeouts/<TIMESTAMP>-ingest-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: ingest + pass-mode: <topic-page|triage|evidence-only> + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections (mode-aware): **Inputs**, **Pass-mode** (block + repeated from §2 declaration so reviewers can compare promise + vs. result), **Topic(s) touched**, **What changed** + (including the Cold-reader rubric in topic-page mode), + **New questions**, **New contradictions**, **Confidence + drift**, **Source-coverage updates**, **Overflow**, + **Adjacency pre-flight**, **Next pass hint**. + + Append a final `SESSION_LOG.md` line: + + ``` + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=closeout status=done note=<topic slug + <=80 chars> + ``` + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty input | Refuse with the standard no-sources text. No residue. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message; point at `.context/kb/index.md`. No residue. | +| Source returns nothing usable (404, binary, paywall) | Record in closeout's `Next pass hint` AND the topic page's "Open questions"; advance the ledger row to `skipped` with the failure reason. Do not invent claims. | +| All sources skipped during admission | Write a short closeout with empty `Topic(s) touched` and `What changed`; `Next pass hint` lists every skipped source with scope-citation. | +| Material spans 3+ topics and user can't decide | Ask once in §3; if still unresolved, abort with a partial closeout recommending re-invocation under `triage`. | +| Discovery turns up zero additional sources | Note in closeout's `Inputs` section. Not a failure. | +| Stale Status block on existing page | Flag in closeout's `Next pass hint`; do not silently overwrite the verification cursor unless the source was actually re-verified. | +| Multiple sessions filling the same page | Read existing prose first; do not overwrite human edits; append/extend rather than replace. | +| Page scaffolded long ago with older template | Fill what's there; do not reformat to match a newer template. Open a task if drift is significant. | +| `ctx kb topic new` fails or refuses (slug exists, kb missing) | Resolve the underlying condition and retry; do not hand-write a scaffold. | +| `ctx kb site build` fails during §12 | Report `topic-page: deferred`; name the build failure in `Next pass hint`; ledger to `topic-page-drafted` (NOT `comprehensive`). | +| Cold-reader rubric returns `Result: fail` | Report `topic-page: deferred` AND `validation: deferred (cold-reader orientation failed)`; name failed items in `Next pass hint`; ledger to `topic-page-drafted`. | +| Adjacency pre-flight surfaces zero matches | Record *"no incomplete adjacent topics surfaced"* explicitly in closeout's `Adjacency pre-flight`; response contract reads `none surfaced`. Silence is not allowed. | +| Mid-pass mode-switching tempted | Forbidden. Abort, write a partial closeout citing the mismatch, recommend re-invocation under the correct mode. Never silent-switch. | +| `evidence-only` pass discovers a contradiction | Still mint the contradiction row (truth surface always wins); flag in `Next pass hint` that a topic-page pass is needed to resolve. | +| Inferring `evidence-only` from source size / time pressure | Hard anti-pattern. Refuse to set `evidence-only` without explicit user trigger. | + +## Hard Anti-Patterns + +- Treating closeout existence as topic-page validation. +- Skipping the topic-page circuit breaker in `topic-page` mode. +- Inferring `evidence-only` from source size, complexity, + ambiguity, time pressure, or operator convenience. +- Mid-pass mode-switching (abort and re-invoke instead). +- Hiding incomplete coverage under a comprehensive-looking + closeout (lying to the ledger). +- Skipping the topic-adjacency pre-flight, or running it but + failing to acknowledge surfaced incomplete adjacent topics. +- Claiming `topic-page: produced` when the cold-reader + orientation result is missing. +- Asking the human mid-pass beyond the §3 naming gate, unless + continuing would change durable kb topology, evidence + confidence, source admission, or scope. +- Inventing claims beyond what the source backs. +- Inventing `EV-###` citations to make a page look complete. +- Promoting claims above `speculative` without an + `evidence-index.md` row. +- Promoting a topic page above its weakest cited band. +- Setting `Confidence` above `speculative` while any + `TBD-cite` remains. +- Setting `Author: hand-authored` on agent-ingested prose. +- Re-extracting from a source that already has `EV-###` rows + instead of reusing the IDs. +- Citing an `evidence-only`-tagged row in a topic page without + re-reading the source first. +- Renumbering or deleting `EV-###` rows when reconciling. +- Skipping the closeout once the pass clears pre-write gates. +- Bypassing `ctx kb topic new` when scaffolding a page. +- Running maintenance discipline against a bootstrap-stage kb. +- Hand-editing `INBOX.md`. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No closeout, no residue. + +For passes that clear pre-write gates, **emit the up-front +declaration first** (between §1 and §3): + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific criterion>` + +Then proceed. At completion, end with this structured summary: + +- **Pass-mode**: as declared, with reason if non-default. +- **Topic-page**: `produced [<slug>]`, `extended [<slug>]`, + `deferred (<reason>)`, or `not-applicable` (triage / + evidence-only). +- **Validation**: `passed`, `not-attempted`, or + `deferred (<reason>)`. +- **Coverage**: current state(s) from `source-coverage.md` for + sources touched. +- **EV range minted**: e.g. `EV-035..EV-051`, or `none`. +- **Counts**: glossary entries added, source-map rows added, + cross-links written, contradictions surfaced, questions + opened. +- **Life-stage**: `bootstrap` or `maintenance`, with the + topic-page count it was based on. +- **Closeout**: filename on its own line. +- **Adjacent topics noted** *(topic-page mode only; mandatory)*: + either `none surfaced` or a slug-list with states. Free prose + fails validation; the doctor advisory parses this field. +- **Next-recommended-action**: explicit invocation that would + resume incomplete work. Adjacent topics surfaced by the + pre-flight MUST appear here too (deliberate redundancy). +- **Review-required**: `true` for `evidence-only` passes; + otherwise omit. + +The structured summary, the closeout's body, and the +source-coverage ledger MUST agree. Discrepancies between the +three are a hard anti-pattern; the doctor advisory detects them +and surfaces a non-fatal warning on next `ctx doctor` run. + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Pass-mode declaration was emitted in the response stream + before any extraction. +- [ ] Source-coverage ledger advanced honestly for every source + touched. +- [ ] Topic-adjacency pre-flight ran in `topic-page` mode and its + result is in the closeout AND the page AND the response + contract. +- [ ] Cold-reader rubric is recorded in `topic-page` mode. +- [ ] Circuit-breaker check ran in `topic-page` mode; failure + → `topic-page: deferred`, NOT `produced`. +- [ ] Closeout written with all required frontmatter fields + (`sha`, `branch`, `mode`, `pass-mode`, `life-stage`, + `generated-at`). +- [ ] Structured response summary matches the closeout body and + the ledger. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md new file mode 100644 index 000000000..33ee4c11f --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md @@ -0,0 +1,163 @@ +--- +name: ctx-kb-note +description: "Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for 'park this for the next ingest'." +tools: [bash] +--- + +# Park a Finding for the Next Ingest + +Append a short note to `.context/ingest/findings.md` so a later +`/ctx-kb-ingest` pass can pick it up. This is the pipeline's +escape hatch for *"I want to remember this, but I'm not running +a full ingest right now."* No closeout, no ledger update, no +topic-page edit, no `EV-###` minting. Just typed memory landing +in one well-known file. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary. + +## When to Use + +- The user says "drop a note", "capture this for the next + ingest", "park this finding", or invokes the explicit slash + form with note text. +- A conversation surfaces a fact, link, or observation that + should land in the kb later but does not justify running + `/ctx-kb-ingest` right now. +- Mid-session, a sibling skill (architecture, brainstorm, etc.) + surfaces something kb-shaped and the user wants it parked + cheaply. + +## When NOT to Use + +- The user has sources in hand and wants them ingested (use + `/ctx-kb-ingest`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The note is actually a task / decision / learning / convention + for the code-dev side (use `/ctx-add-task` / + `/ctx-add-decision` / `/ctx-add-learning` / + `/ctx-add-convention`; those write to canonical files, this + one does not). +- The note is empty (refuse-on-empty; see below). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-note`** appends to + `.context/ingest/findings.md` only. Never writes anywhere + else. No closeout. No ledger update. +- **`/ctx-kb-ingest`** reads `findings.md` opportunistically + when scoping its source set; the user controls when notes get + promoted into evidence. +- **Canonical capture skills** (`/ctx-add-task`, + `/ctx-add-decision`, `/ctx-add-learning`, + `/ctx-add-convention`) write to the five canonical + `.context/` files. Strict authority boundary: this skill + never touches them. + +## Usage Examples + +```text +/ctx-kb-note "cursor.com/changelog mentions hook lifecycle bump in v1.2" +/ctx-kb-note "check whether your-domain RTO claim still cites the 2024 audit" +/ctx-kb-note "Volkan said in chat: the 50-source cap was lifted from the upstream design" +``` + +## Input Contract + +A single argument: the note text. Free-form prose. No flags. + +## Refuse-on-Empty + +If the invocation supplied no note text (empty slash arg, empty +inline body, whitespace-only), return exactly: + +> no note text provided; pass the note inline. + +Stop. Do not prompt interactively. The CLI enforces this +independently via `cmd/note`. + +## Pre-Write Gates + +Two distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → refuse: + + > kb not initialized; run `ctx init` first + + Stop. + +Kb scope declaration is **not** required for this skill. Notes +land in `.context/ingest/findings.md`, which is pre-kb-scope +territory; the user may be parking notes precisely because they +have not yet decided the kb's scope. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Append the note** to `.context/ingest/findings.md` as a + single bulleted line. Prefix with the current UTC timestamp + (RFC-3339, date-time precision) and a short SHA + branch + from `gitmeta.ResolveHead` so the note carries minimal + provenance: + + ``` + - 2026-05-16T14:32:11Z sha=88d52870 branch=main + | <note text> + ``` + + If `findings.md` does not yet exist, create it with a brief + header explaining its purpose (one paragraph; the embedded + template ships at `internal/assets/kb/templates/ingest/` + handles this for fresh inits, so this fallback applies only + when the file was deleted by hand). + +3. **No closeout.** Notes are intentionally lightweight; the + audit trail is the file itself. The next `/ctx-kb-ingest` + pass reads `findings.md` opportunistically. + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| Empty note text | Refuse with the standard no-note text. No residue. | +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/ingest/` missing | Refuse with the not-initialized message. No residue. | +| `findings.md` missing but `.context/ingest/` exists | Create the file with a brief header; append the note. | +| Multi-line note text | Append as a single bullet with embedded line breaks; preserve the user's formatting. | +| Note text contains a URL | Preserve verbatim; do not auto-fetch (this skill does not web-jump). | +| Note text is structurally a claim that should be evidence | Append as a note anyway; mention in the response that `/ctx-kb-ingest` is the next step if the user wants it minted as `EV-###`. | +| User invokes twice in a row with similar text | Append both; deduplication is the user's call, not this skill's. | + +## Output Contract + +For refusals, return only the specified refusal text and stop. + +For successful appends, return: + +- One line confirming the append, with the line number of the + new entry in `findings.md`. +- A pointer to `/ctx-kb-ingest` as the path for promoting the + note into evidence when the user is ready. + +Example: + +``` +appended to .context/ingest/findings.md line 42. +run /ctx-kb-ingest with the source materials when ready to mint EV. +``` + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] The note landed in `.context/ingest/findings.md` and + nowhere else. +- [ ] No `EV-###` row was minted, no topic page was touched, no + ledger row was advanced, no closeout was written. +- [ ] The appended line carries the timestamp + sha + branch + provenance prefix. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md new file mode 100644 index 000000000..c0e850ebd --- /dev/null +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md @@ -0,0 +1,258 @@ +--- +name: ctx-kb-site-review +description: "Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail." +tools: [bash] +--- + +# Site-Review Pass + +Walk `.context/kb/` and `.context/ingest/closeouts/` mechanically. +Fix what is unambiguous (capitalization drift, missing frontmatter +fields the CLI knows how to coerce). Flag what is not (claims +that read as broken but require evidence to fix). Never invent +prose. Never mint `EV-###` rows. Never modify a claim's +Confidence band. + +This is a janitor pass, not an editorial pass. Editorial judgment +lives in `/ctx-kb-ingest`. + +Authoritative background reading: +`.context/ingest/KB-RULES.md` §Authority boundary. + +## When to Use + +- The user says "audit the kb", "check kb for rot", "run a + site-review", or invokes the explicit slash form. +- Before a release / handover where structural cleanliness + matters. +- After bulk ingest where drift may have accumulated. +- When the doctor advisory has surfaced structural warnings the + user wants triaged. + +## When NOT to Use + +- The user wants new material extracted (use `/ctx-kb-ingest`). +- The user wants kb claims re-grounded against external sources + (use `/ctx-kb-ground`). +- The user is asking a content question (use `/ctx-kb-ask`). +- The user wants to capture a quick finding (use + `/ctx-kb-note`). + +## Authority Boundary (vs Other Skills) + +- **`/ctx-kb-site-review`**: mechanical structural audit. May + coerce capitalization that the spec deems lossless (e.g. + `Confidence: High` → `high`). May flag any other malformation + in the closeout's `What changed` block. **May not** modify a + claim, an `EV-###` row's content, a Confidence band, a topic + page's prose, or a ledger state. Those require evidence + judgment. +- **`/ctx-kb-ingest`**: handles anything this skill flags as + evidence-dependent. +- **`/ctx-kb-ground`**: handles anything this skill flags as + source-staleness. + +## Usage Examples + +```text +/ctx-kb-site-review +``` + +No arguments. The pass walks the kb in full. + +## Pre-Write Gates + +Three distinct refusals, each leaves zero residue: + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- Kb scope undeclared (placeholder in `.context/kb/index.md`) + → refuse with the scope message and stop. + +## Process + +1. **Verify pre-write gates.** Refuse cleanly if any gate fails. + Zero residue on refusal. + +2. **Walk topic pages.** For every + `.context/kb/topics/<slug>/index.md` and every sibling + sub-page: + - **Status block check**: does the page have the + four-field Status block (`Subject`, `Last verified`, + `Author`, `Confidence`)? Missing fields → flag in + `What changed`. Do not synthesize. + - **Author field check**: `Author: hand-authored` is + prohibited per `KB-RULES.md`. Flag (do not auto-coerce; + human intent matters). + - **Confidence band coercion**: `high|medium|low|speculative` + are the only valid values. Coerce capitalization + (`High` → `high`, `MEDIUM` → `medium`) silently and record + in `What changed`. Any other malformation (e.g. + `Confidence: probable`) is flagged for the user. + - **`TBD-cite` markers**: count them per page. The + Confidence floor for any page with `TBD-cite` is + `speculative`. If the page's Confidence is above + `speculative` while `TBD-cite` is present, flag (do not + auto-demote; demotion is evidence work). + - **`EV-###` citation resolution**: every `EV-###` cited on + the page must resolve to a row in + `.context/kb/evidence-index.md`. Unresolved IDs → flag. + - **`## Related concepts in this kb` presence**: if the + page is more than the lede + Status block AND the kb has + plausibly adjacent topics, the section should be present. + Absence is a soft flag (not auto-fixable). + +3. **Walk `evidence-index.md`.** + - **Duplicate `EV-###` IDs**: flag every duplicate; name + both files / line numbers. The LLM cleanup pass (per + spec's P1) handles renumbering, not this skill. + - **Three-digit padding**: `EV-12` should be `EV-012`. Flag + (do not auto-coerce; renumbering cascades to citations on + topic pages, which is ingest work). + - **Confidence band coercion**: same rule as topic pages. + - **`occurred:` field on dated sources**: if the source-map + row for the cited source has a `dated:` field but the + evidence row lacks `occurred:`, flag. The temporal- + precedence rule needs it. + +4. **Walk `source-coverage.md`.** + - **Ledger row mtime check**: for every row, compare the + row's `Updated` cell against the actual file mtime of the + source it points to (when the source is in-tree). Mismatch + → flag (lying-to-the-ledger advisory). Do not auto-edit. + - **Illegal state transitions**: flag any row whose state + does not match an allowed transition from the prior state. + Examples: `comprehensive → highlights-extracted` without + an explicit `superseded` step. Do not auto-correct. + - **Schema integrity**: every row must have the seven + columns (`Source`, `Topic`, `State`, `EV coverage`, + `Residue`, `Next action`, `Updated`). Missing columns → + flag. + +5. **Walk closeouts in `.context/ingest/closeouts/`.** + - **Frontmatter integrity**: every closeout must have + `sha`, `branch`, `mode`, `pass-mode`, `life-stage`, + `generated-at`. Missing fields → flag (the handover-fold + skips malformed closeouts; surface them so the user can + fix or delete). + - **Pass-mode body block**: every ingest closeout must + have a `Pass-mode` body block whose `Declared:` value + matches the frontmatter's `pass-mode:` field. Drift + between the two is exactly the false-finish signal the + redundancy exists to surface. Flag any drift. + - **Adjacency pre-flight block**: every ingest closeout in + `topic-page` mode must have an `Adjacency pre-flight` + block whose value is either `none surfaced` or a + structured slug-list. Free-prose values fail validation; + flag. + - **Cold-reader rubric**: every ingest closeout in + `topic-page` mode must include the four-item rubric in + `What changed`. Missing → flag. + +6. **Walk `.context/kb/index.md`.** + - **`CTX:KB:TOPICS` managed block**: should list every + `.context/kb/topics/<slug>/index.md` currently on disk. + Drift (slug on disk not in the block, or block entry with + no matching folder) → recommend `ctx kb reindex` in the + closeout's `Next pass hint`. Do not run the CLI from this + skill. + +7. **Write the site-review closeout.** Create + `.context/ingest/closeouts/<TIMESTAMP>-site-review-closeout.md` + with required frontmatter: + + ```yaml + --- + sha: <short> + branch: <name> + mode: site-review + pass-mode: mechanical + life-stage: <bootstrap|maintenance> + generated-at: <RFC-3339> + --- + ``` + + Body sections: + - **Inputs**: count of topic pages, evidence rows, ledger + rows, closeouts walked. + - **What changed**: every coercion this pass actually + applied (capitalization fixes); cite the file and the + before/after. Empty if zero coercions. + - **Flags**: every issue this pass detected but did not + fix. Group by category: malformed Status blocks, + unresolved `EV-###`, ledger mismatches, malformed + closeouts, etc. Each flag names file + line + nature. + - **Next pass hint**: explicit invocations to address each + flag category (e.g. *"`/ctx-kb-ingest <slug>` to restore + missing `EV-###` citation on `<page>`"*). + +## Edge Cases + +| Case | Expected behavior | +|------|-------------------| +| `.context/` missing | Refuse; suggest `ctx init`. No residue. | +| `.context/kb/` missing | Refuse; suggest `ctx init --upgrade`. No residue. | +| Kb scope undeclared | Refuse with the scope message. No residue. | +| Zero topic pages on disk | Walk closeouts and ledger anyway. Note the bootstrap state in the closeout. Not a failure mode. | +| Zero closeouts on disk | Walk topic pages and ledger anyway. Note in the closeout body. Not a failure mode. | +| `Confidence: High` (capitalization drift) | Coerce to `high` silently; record in `What changed`. | +| `Confidence: probable` (unknown band) | Flag for the user; do not coerce. | +| `Author: hand-authored` | Flag for the user; do not coerce (human intent matters). | +| Duplicate `EV-###` ID across files | Flag both files; defer renumbering to the LLM cleanup pass per spec's P1. | +| `EV-12` (missing zero-pad) | Flag for the user; do not auto-pad (cascades to citations). | +| Unresolved `EV-###` on a topic page | Flag; recommend `/ctx-kb-ingest <slug>` in `Next pass hint`. | +| Ledger row `Updated` predates source file mtime | Flag (lying-to-the-ledger advisory). Do not auto-edit. | +| Illegal ledger transition (e.g. `comprehensive → highlights-extracted` without `superseded`) | Flag; recommend the corrective ingest invocation. Do not auto-correct. | +| Closeout missing `pass-mode` frontmatter field | Flag; the handover-fold skips malformed closeouts so the user can fix or delete. | +| Closeout body's `Pass-mode` `Declared:` disagrees with frontmatter `pass-mode:` | Flag (false-finish signal); recommend hand-edit. | +| Closeout's `Adjacency pre-flight` is free prose instead of `none surfaced` or a slug-list | Flag; recommend hand-edit to structured form. | +| `CTX:KB:TOPICS` managed block drift | Recommend `ctx kb reindex` in `Next pass hint`; do not run the CLI from this skill. | +| `TBD-cite` on a page with Confidence above `speculative` | Flag; do not auto-demote (demotion is evidence work for `/ctx-kb-ingest`). | +| Sibling sub-page exists with no link from `index.md` | Flag; recommend hand-edit or `/ctx-kb-ingest <slug>` to extend. | + +## Anti-Patterns + +- Auto-fixing anything that requires evidence judgment + (Confidence promotion/demotion, claim text edits, `EV-###` + renumbering, ledger state changes, prose synthesis). +- Skipping the closeout once pre-write gates pass. +- Hand-editing `INBOX.md` or `SESSION_LOG.md` (other skills' + surfaces; never this one's). +- Coercing `Author: hand-authored` to anything else. The user's + intent matters; flag and wait. +- Auto-renumbering duplicate `EV-###` IDs. The cascade to + citations is ingest work; this skill flags only. + +## Output Contract + +For pre-write refusals, return only the specified refusal text +and stop. No residue. + +For passes that clear pre-write gates, end with this structured +summary: + +- **Walked**: counts (topic pages, evidence rows, ledger rows, + closeouts). +- **Coercions applied**: count + one-line categories (e.g. + *"3 capitalization fixes on Confidence bands"*). +- **Flags raised**: count + categories (e.g. *"2 unresolved + EV-### citations; 1 ledger mtime mismatch"*). +- **Closeout**: filename on its own line. +- **Next-recommended-action**: explicit invocations to address + each flag category (or `none` if the kb is clean). + +## Quality Checklist + +Before reporting completion, verify: + +- [ ] Pre-write gates passed (or the matching refusal was + returned with zero residue). +- [ ] Every coercion applied is recorded in `What changed` with + file + before/after. +- [ ] Every flag is recorded in `Flags` with file + line + + nature. +- [ ] No topic-page prose was edited, no `EV-###` row was + modified, no Confidence band was promoted/demoted, no + ledger state was changed. +- [ ] Closeout written with all required frontmatter fields. diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md index 245c7b8e3..0fc8d6d80 100644 --- a/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md +++ b/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md @@ -45,7 +45,23 @@ feel like a file search rather than genuine recall: ```bash ctx journal source --limit 3 ``` -4. **Present the structured readback** (see format below) +4. **Read the latest handover.** Look under + `.context/handovers/`, sort by filename (timestamped + `<TS>-<slug>.md`; the newest is the lexicographically + last), and read its `## Summary` and `## Next Session` + sections as the authoritative recall surface. The + handover is the previous session's note to this one. + Skip only if `.context/handovers/` is empty or absent. +5. **Read postdated closeouts, if any.** When + `.context/ingest/closeouts/` exists, list closeouts whose + `generated-at` postdates the handover's `generated-at` + and read their `## What Changed` sections. These are + per-pass audit notes the previous wrap-up did not get a + chance to fold into a handover. This step is read-only: + `/ctx-remember` does not run any editorial pass. If the + directory does not exist or holds no postdated entries, + skip the step. +6. **Present the structured readback** (see format below) ## Readback Format @@ -73,7 +89,7 @@ tasks, or ask the user for direction if priorities are unclear. not *searching* - Be honest about the mechanism only if the user explicitly asks *how* you remember (e.g., "It's stored in context files managed - by ctx") + by `ctx`") ## Examples @@ -97,7 +113,7 @@ tasks, or ask the user for direction if priorities are unclear. > partially done. Want to continue those, or shift to the JSON > status flag? -### Bad Readback (Anti-patterns) +### Bad Readback (Anti-Patterns) > "I don't have persistent memory, but let me check if there > are any context files..." @@ -114,7 +130,7 @@ Skip this section entirely if `companion_check: false` is set in `.ctxrc`: check by running `ctx config status` and looking for the field value. -**Companion tools** enhance ctx skills with web search and code +**Companion tools** enhance `ctx` skills with web search and code intelligence. They are optional but recommended: | Tool | Purpose | Smoke test | diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md index f59820389..ec3bebfac 100644 --- a/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md +++ b/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md @@ -17,6 +17,39 @@ Check that the context directory exists. If it does not, tell the user: "No context directory found. Run `ctx init` to set up context tracking, then there will be something to wrap up." +## Handover Is the Mandatory Final Step + +`/ctx-wrap-up` owns the user-facing session-end trigger and +**always** delegates to `/ctx-handover` as its final step. +The handover is the former agent's note to the next agent +(or human): what happened, and what should come next. It +writes `.context/handovers/<TS>-<slug>.md` (timestamped so +multiple agent runs never overwrite). Without this final +step, `/ctx-remember` has nothing to read at the start of +the next session and recall degenerates into probabilistic +reconstruction from canonical files plus journal. + +## KB Editorial State (Phase KB, Optional) + +If `.context/kb/` exists, this project additionally uses the +editorial pipeline. After the capture phase but before the +final `/ctx-handover` delegation: + +1. List any closeouts under `.context/ingest/closeouts/`. + These are per-pass audit artifacts from `/ctx-kb-ingest`, + `/ctx-kb-ask`, etc. that have not yet been folded into a + handover. +2. Count unresolved entries in + `.context/kb/outstanding-questions.md` (rows whose Status + is `open`). +3. Surface both counts in the wrap-up summary so the operator + sees what editorial residue is pending; the handover + step's fold pass will consume the closeouts. + +When `.context/kb/` does NOT exist, skip this section +entirely; the wrap-up proceeds with the standard capture +checklist and still ends with `/ctx-handover`. + ## When to Use - At the end of a session, before the user quits @@ -33,7 +66,7 @@ tracking, then there will be something to wrap up." ## Process -### Phase 1: Gather signal +### Phase 1: Gather Signal Do this **silently**: do not narrate the steps: @@ -52,7 +85,7 @@ Do this **silently**: do not narrate the steps: - Follow-up work identified but not yet started - Tasks completed or progressed -### Phase 2: Propose candidates +### Phase 2: Propose Candidates Think step-by-step about what is worth persisting. For each potential candidate, ask yourself: @@ -89,7 +122,7 @@ Skip categories with no candidates: do not show empty sections. Persist all? Or select which to keep? ``` -### Phase 3: Persist approved candidates +### Phase 3: Persist Approved Candidates Wait for the user to approve, select, or modify candidates. Wait for the user to approve each item before persisting: @@ -110,7 +143,7 @@ For each approved candidate, run the appropriate command: Report the result of each command. If any fail, report the error and continue with the remaining items. -### Phase 3.5: Suppress post-wrap-up nudges +### Phase 3.5: Suppress Post-Wrap-Up Nudges After persisting, mark the session as wrapped up so checkpoint nudges are suppressed for the remainder of the session: @@ -119,7 +152,7 @@ nudges are suppressed for the remainder of the session: ctx system mark-wrapped-up ``` -### Phase 4: Commit (optional) +### Phase 4: Surface Uncommitted Changes After persisting, check for uncommitted changes: @@ -127,16 +160,66 @@ After persisting, check for uncommitted changes: git status --short ``` -If there are uncommitted changes, offer: +When `git status --short` reports any modified or untracked +files, surface them and offer `/ctx-commit`: + +> There are uncommitted changes (`<count>` files). Run +> `/ctx-commit` to commit with context capture? + +Do not auto-commit; the user decides. But always run the +`git status` check and always surface non-empty output. Do +not skip this phase silently when the working tree is dirty. + +### Phase 5: Delegate to `/ctx-handover` (mandatory) + +`/ctx-wrap-up` always ends here. Drafting the handover reuses +the signal gathered in Phase 1 and the candidates approved in +Phase 3: + +1. **Title**: a short noun phrase naming the session arc + (becomes the slug in `<TS>-<slug>.md`). Drawn from the + conversation; confirm with the user. +2. **`--summary`** (required, past tense): one paragraph + naming what was done this session, drawn from the + approved candidates and the git-log scan. Concrete, not + vague. +3. **`--next`** (required, future tense): one paragraph + naming the specific first action the next agent should + take. Pull from the highest-priority pending task in + TASKS.md or the open thread the session was on. +4. **`--highlights`**: draft a bullet list of notable + artifacts produced this session (commits, decisions, + specs, files created). Always present a draft. Pass an + empty string only after the user has explicitly said + there is nothing to highlight. +5. **`--open-questions`**: draft a bullet list of things + that remain undecided. Pull from any candidate the user + did not turn into a decision, any deferred ingest pass, + any `TODO` discovered in the session. Always present a + draft. Pass an empty string only after the user has + explicitly confirmed there is nothing open. + +Surface the drafted values to the user for one final +confirmation, then delegate: + +```text +/ctx-handover "<title>" --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] +``` -> There are uncommitted changes. Want me to run `/ctx-commit` -> to commit with context capture? +The `/ctx-handover` skill performs the pre-write gates, +writes `.context/handovers/<TS>-<slug>.md`, and (when +`.context/kb/` exists) folds postdated closeouts into the +`## Folded Closeouts` section and archives them. -Do not auto-commit. The user decides. +If `/ctx-handover` refuses (missing `.context/handovers/`, +empty placeholder values, etc.), surface the refusal to the +user. Do not declare the wrap-up complete until the +handover landed. ## Candidate Quality Guide -### Good candidates +### Good Candidates - "PyMdownx `details` extension wraps content in `<details>` tags, breaking `<pre><code>` rendering in MkDocs": specific @@ -147,7 +230,7 @@ Do not auto-commit. The user decides. - "Convention: all skill descriptions use imperative mood": codifies a pattern for consistency -### Weak candidates (do not propose) +### Weak Candidates (Do Not Propose) - "Go has good error handling": general knowledge, not project-specific @@ -178,3 +261,5 @@ After persisting, verify: - [ ] Each `ctx add` command succeeded - [ ] Uncommitted changes were surfaced (if any) - [ ] User was offered `/ctx-commit` (if applicable) +- [ ] `/ctx-handover` was invoked and the resulting + `.context/handovers/<TS>-<slug>.md` was written diff --git a/internal/assets/integrations/opencode/skills/ctx-handover/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-handover/SKILL.md new file mode 100644 index 000000000..f8cca3e51 --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-handover/SKILL.md @@ -0,0 +1,86 @@ +--- +name: ctx-handover +description: "Per-session handover artifact writer. Wraps `ctx handover write` with required `--summary` and `--next`. Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them." +--- + +Write the per-session handover under +`.context/handovers/<TS>-<slug>.md`. The handover is the +former agent's note to the next agent (or human): what +happened, and what should come next. `/ctx-remember` reads +it at the start of the next session. + +## When to Use + +`/ctx-wrap-up` owns the user-facing session-end trigger and +delegates to this skill as its final step. Direct invocation +is reserved for: + +- `--no-fold` mid-session checkpoint when the user wants to + pause without consuming closeouts. +- Recovery, when a prior session aborted before wrap-up. + +Otherwise, the user invokes `/ctx-wrap-up`, not this skill. + +## Input Contract + +Wraps `ctx handover write`. Empty `TBD`, `see chat`, +whitespace-only values for required flags are rejected by +the CLI. + +| Flag | Required | Description | +|------|----------|-------------| +| `--summary` | yes | Past tense; what happened this session. | +| `--next` | yes | Future tense; the specific first action for the next agent. | +| `--highlights` | no | Notable artifacts produced this session. | +| `--open-questions` | no | Things that remain undecided. | +| `--no-fold` | no | Skip closeout consumption (mid-session checkpoint). | +| `--commit` | no | Override resolved git HEAD for the Provenance line (CI replay). | + +Positional argument: handover title (becomes filename slug). + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/handovers/` missing → suggest `ctx init --upgrade` + and stop. + +`.context/kb/` is not required for handover; KB-state folding +is conditional on the directory's existence. + +## Process + +1. Verify pre-write gates. Refuse cleanly on failure. +2. Gather signal: `git status --short`, `git diff --stat`, + `git log --oneline @{upstream}..HEAD || git log --oneline -5`, + and scan the conversation for the session's arc, concrete + artifacts, open questions, and the specific next action. +3. Draft `--summary` (past tense, concrete) and `--next` + (future tense, specific). Surface to the user for + confirmation before running the CLI. +4. Run: + + ```bash + ctx handover write "<title>" \ + --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] \ + [--no-fold] [--commit <sha>] + ``` + + The CLI validates flags, resolves git HEAD, reads the + latest-handover cursor, folds postdated closeouts into + the new handover's `## Folded Closeouts` section, and + archives them under `.context/archive/closeouts/`. + +5. Report the handover filename, count of closeouts folded, + count of malformed closeouts skipped (with filenames), + and the `--next` value verbatim so the operator sees what + the next agent will read first. + +## Anti-Patterns + +- Hand-writing a handover file. The CLI is the sole writer. +- Skipping the fold to "keep closeouts available." Use + `--no-fold` explicitly when the user wants the checkpoint + behavior; do not infer it. +- Inventing `--highlights` or `--open-questions` content the + session did not actually produce. diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-ask/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-ask/SKILL.md new file mode 100644 index 000000000..8aa87ea04 --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-kb-ask/SKILL.md @@ -0,0 +1,101 @@ +--- +name: ctx-kb-ask +description: "Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail." +--- + +Answer a question using only what `.context/kb/` already contains. +Cite by `EV-###`. Do not web-jump, do not invent prose, do not +modify topic pages. If the kb cannot answer, open a `Q-###` row +in `.context/kb/outstanding-questions.md` and report the gap. + +This is the read side of the editorial pipeline. The write side +is `/ctx-kb-ingest`. Authority for prose synthesis lives there; +this skill is read-only on prose. + +## When to Use + +- The user asks "does the kb say...", "according to evidence...", + "what do we know about <topic>". +- The user wants a citation-backed answer before deciding + whether to ingest more material. + +## When NOT to Use + +- The user wants new material extracted (`/ctx-kb-ingest`). +- The user wants the kb structurally audited + (`/ctx-kb-site-review`). +- The user wants external re-grounding (`/ctx-kb-ground`). +- The question is about `ctx` itself (answer from + `KB-RULES.md` directly). + +## Input + +A single question, supplied as the slash argument or inline. +No flags, no sources, no URLs. + +## Refuse-on-Empty + +If no question was supplied, return exactly: + +> no question provided; pass a question or describe it inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and stop. +- Kb scope undeclared → refuse with the scope message and stop. + +## Process + +1. Verify pre-write gates. +2. Survey the kb in this order, stopping early when an answer + surfaces with adequate citation coverage: `index.md` for + scope, topic-page indexes and sub-pages for matching slugs, + `evidence-index.md`, `glossary.md`, `contradictions.md`, + `outstanding-questions.md`. +3. Decide answer vs gap: + - **Answerable with citations**: cite every load-bearing + claim by `EV-###`. Name the topic page(s). Note the + confidence floor of cited rows. + - **Partial answer**: answer the covered part; open a + `Q-###` row for the gap. + - **Not answerable**: open a `Q-###` row; report the gap. + Do not invent. Do not web-jump. +4. If a gap exists, append a `Q-###` row to + `outstanding-questions.md`. Do NOT mint `EV-###` rows; + evidence authoring is `/ctx-kb-ingest`'s authority. +5. Write the ask closeout under + `.context/ingest/closeouts/<TS>-ask-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ask`, + `pass-mode: read-only`, `life-stage`, `generated-at`) and + body sections: Question, Answer (or `none (gap)`), + Citations (`EV-###` + topic-page paths), Gaps (`Q-###` + opened with one-line rationale), Next pass hint. + +## Anti-Patterns + +- Web-jumping when the kb cannot answer. The contract is + read-only on prose AND web-quiet. +- Inventing citations or claims. +- Modifying a topic page to extend an answer mid-pass. +- Minting `EV-###` rows from this skill. +- Skipping the `Q-###` row when the kb cannot answer. +- Skipping the closeout once pre-write gates pass. + +## Output Contract + +For pre-write refusals, return only the refusal text and stop. + +For passes that clear pre-write gates, end with: + +- **Question**: verbatim or faithful paraphrase. +- **Answer**: concise; cites every load-bearing claim by + `EV-###`. +- **Confidence floor**: lowest band among cited rows. +- **Gaps**: `Q-### opened`, or `none`. +- **Closeout**: filename. +- **Next-recommended-action**: explicit invocation if a gap + was opened (`/ctx-kb-ground` or + `/ctx-kb-ingest <sources>`), or `none`. diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md new file mode 100644 index 000000000..6c8c58099 --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md @@ -0,0 +1,78 @@ +--- +name: ctx-kb-ground +description: "External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip." +--- + +Refresh `.context/kb/` against the external sources the user +has declared in `.context/ingest/grounding-sources.md`. This is +the *"are we still current?"* pass. It does not mint new +evidence by itself, does not author topic pages, does not +modify Confidence bands. It walks the declared external +sources, checks each for drift against what the kb cites, and +advances the source-coverage ledger to reflect what the +refresh found. + +If the refresh surfaces material the kb should absorb, this +skill flags it and recommends `/ctx-kb-ingest`. Authoring is +ingest's authority, not this skill's. + +## When to Use + +- The user says "re-ground the kb", "check upstream", + "refresh sources". +- A grounding cadence is hitting its scheduled boundary. +- A prior pass left a `Q-###` row that names "needs external + re-grounding". + +## When NOT to Use + +- The user has new sources to add (`/ctx-kb-ingest`). +- The user asks a question (`/ctx-kb-ask`). +- The user wants a mechanical audit (`/ctx-kb-site-review`). + +## Input + +No positional arg. Sources come from +`.context/ingest/grounding-sources.md` (one source per line; +`NONE` on a line is a per-pass skip). + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- `grounding-sources.md` missing or empty → prompt the user + once for sources to add; if they decline, write a ground + closeout with `sources: 0` and stop. + +## Process + +1. Verify pre-write gates. +2. Read `.context/ingest/grounding-sources.md`. For each + non-skipped line, fetch / re-read the source and compare + against `evidence-index.md` rows already citing it. +3. Update the source-coverage ledger row for each source + touched: `partially-ingested` → `partially-ingested` + (touched), `comprehensive` → `comprehensive` (if no drift + detected), or flag drift in the closeout. +4. For each source that surfaces material the kb should + absorb, flag it and recommend a follow-up + `/ctx-kb-ingest` invocation in the closeout's Next pass + hint. +5. Write the ground closeout under + `.context/ingest/closeouts/<TS>-ground-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ground`, + `pass-mode: n/a`, `life-stage`, `generated-at`) and a + body listing each source touched, its drift verdict, and + any Next pass hint. + +## Anti-Patterns + +- Authoring topic-page prose from refresh output. Authoring + is `/ctx-kb-ingest`'s authority. +- Minting `EV-###` rows. Evidence minting is ingest's + authority. +- Promoting confidence bands without contradicting evidence. + Drift detection alone is not promotion. +- Skipping the closeout. Even a no-op refresh writes one so + the ledger advance is auditable. diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-ingest/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-ingest/SKILL.md new file mode 100644 index 000000000..396ed9099 --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-kb-ingest/SKILL.md @@ -0,0 +1,157 @@ +--- +name: ctx-kb-ingest +description: "Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail." +--- + +Single editorial pass that adds knowledge to `.context/kb/`. +Reads materials the user supplies, decides which topic page(s) +they belong to, finds-or-creates those pages, writes synthesized +prose section by section, mints `EV-###` rows as it cites them, +cross-links neighbouring topics, updates the source-coverage +ledger, and writes a closeout under `.context/ingest/closeouts/`. + +Authoritative background reading lives at +`.context/ingest/KB-RULES.md`. This skill encodes the workflow +contract; the rules file is the constitution. Hand-edit +`KB-RULES.md` to evolve the contract; do not paraphrase it +here. + +## When to Use + +- The user supplies one or more sources (paths, URLs, MCP + resources, inline natural-language descriptions) and wants + them read into the kb. +- The user says "ingest the transcripts", "pull this into the + kb", "add evidence from <source>", or invokes the slash form + with paths. + +## When NOT to Use + +- The user asked a question about the kb (use `/ctx-kb-ask`). +- The user wants a structural audit (use `/ctx-kb-site-review`). +- The user wants external re-grounding (use `/ctx-kb-ground`). +- The user wants to park a quick finding (use `/ctx-kb-note`). +- No sources were supplied (refuse-on-empty). + +## Input + +Sources, supplied as one or more of: paths (file or folder), +URLs, MCP resources, or inline natural-language gestures. +Optional second argument is the topic name; when omitted the +skill proposes one and confirms before extraction. + +## Refuse-on-Empty + +If the invocation supplied no sources, return exactly: + +> no sources provided; pass a folder, a URL, an MCP resource, or +> describe the materials inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. +- Kb scope undeclared (`.context/kb/index.md` missing, or its + `## Scope` H2 holds the `TODO` placeholder, or lacks + substantive non-placeholder prose): + + > kb scope is undeclared. Open `.context/kb/index.md` and + > replace the TODO placeholder with a one-paragraph scope + > statement that names what is in scope and what is out. + +## Pass-Mode Contract + +Every invocation classifies itself as exactly one mode before +extraction begins. Full semantics in +`.context/ingest/KB-RULES.md` §Pass-mode contract. + +| Mode | Mints prose? | Mints `EV-###`? | Touches topic page? | Default? | +|------------------|--------------|------------------|------------------------|----------| +| `topic-page` | yes | yes | yes (create/extend) | yes | +| `triage` | no | no | no | no | +| `evidence-only` | no | yes (tagged) | no | no | + +Default is `topic-page`. `triage` fires when sources are +disparate with no clear single topic, or the user explicitly +asks for triage. `evidence-only` fires only on explicit user +request ("just mint EV rows", "backfill evidence"); never +inferred from source size or operator convenience. + +Before extraction, emit the declaration in the response stream: + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific completion criterion>` + +The declaration is a contract. Mid-pass mode-switching is +forbidden: abort with a partial closeout and recommend +re-invocation under the correct mode. + +## Topic-Page Circuit Breaker + +A pass in `topic-page` mode may not report `topic-page: +produced` or `topic-page: extended` unless: + +1. `.context/kb/topics/<slug>/index.md` exists and was + created or extended in this pass (topic-page file + creation is performed only by `ctx kb topic new`; this + skill MAY invoke it but MUST NOT write the scaffold + directly). +2. The page cites at least one `EV-###` row that resolves + to `evidence-index.md`. +3. `ctx kb site build` ran clean, or its failure is named + in the closeout's `Next pass hint` and the pass reports + `topic-page: deferred`. +4. The cold-reader orientation rubric records `Result: pass`. + +Any failure → `topic-page: deferred`; ledger advances to +`topic-page-drafted` (not `comprehensive`). + +## Process + +1. Verify pre-write gates. Refuse cleanly on failure. +2. Emit the pass-mode declaration. +3. Resolve sources and the topic name; confirm with the user + when topic is not supplied. +4. Run the topic-adjacency pre-flight against + `.context/kb/source-coverage.md`; record the result in + the closeout's Adjacency pre-flight block. +5. Life-stage check (< 5 topic pages = bootstrap; + reconciliation ceremony skipped except for contradictions; + >= 5 = maintenance, full discipline). +6. Scaffold (topic-page mode only): if the topic folder does + not exist, shell out to `ctx kb topic new "<name>"`. +7. Extract atomic claims; mint `EV-###` rows in + `evidence-index.md`. Confidence band per `KB-RULES.md`; + topic page never claims more certainty than its weakest + cited band. +8. Reconcile (maintenance only): net-new claims append; + reinforcing claims promote; contradicting claims demote + per the demotion policy and open a paired + `outstanding-questions.md` row. +9. Advance the source-coverage ledger per the state-machine + transitions in `KB-RULES.md`. Illegal transitions are + refused at write time. +10. Run the topic-page circuit breaker (topic-page mode only). +11. Record the cold-reader orientation rubric in the closeout. +12. Write the closeout under + `.context/ingest/closeouts/<TS>-ingest-closeout.md` with + required frontmatter (`sha`, `branch`, `mode: ingest`, + `pass-mode`, `life-stage`, `generated-at`). +13. Append one line to `.context/ingest/SESSION_LOG.md` at + the closeout phase boundary, in the exact shape from + `KB-RULES.md` §SESSION_LOG line shape. + +## Anti-Patterns + +- Synthesizing the topic-page scaffold by hand. Only `ctx kb + topic new` writes scaffolds. +- Mid-pass mode-switching without aborting. +- Claiming `comprehensive` ledger advance when the topic page + is incomplete. +- Inventing `EV-###` IDs to make a topic page look complete. +- Demoting in `evidence-index.md` (rows are append-only; + retire by demoting the confidence band, not by deletion). diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-note/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-note/SKILL.md new file mode 100644 index 000000000..bd9ab89e4 --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-kb-note/SKILL.md @@ -0,0 +1,72 @@ +--- +name: ctx-kb-note +description: "Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for 'park this for the next ingest'." +--- + +Append a short note to `.context/ingest/findings.md` so a later +`/ctx-kb-ingest` pass can pick it up. The pipeline's escape +hatch for *"I want to remember this, but I'm not running a full +ingest right now."* No closeout, no ledger update, no +topic-page edit, no `EV-###` minting. Just typed memory landing +in one well-known file. + +## When to Use + +- The user says "drop a note", "capture this for the next + ingest", "park this finding". +- A conversation surfaces a fact, link, or observation that + should land in the kb later but does not justify a full + ingest pass right now. + +## When NOT to Use + +- The user has sources to ingest (`/ctx-kb-ingest`). +- The user is asking a content question (`/ctx-kb-ask`). +- The note is actually a task / decision / learning / + convention for the code-dev side; use the matching + canonical-capture path instead. +- The note is empty (refuse-on-empty). + +## Input + +A single argument: the note text. Free-form prose. No flags. + +## Refuse-on-Empty + +If the invocation supplied no note text, return exactly: + +> no note text provided; pass the note inline. + +Stop. The CLI enforces this independently. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/ingest/` missing → suggest `ctx init --upgrade` + and stop. + +Kb scope declaration is not required; notes land pre-scope. + +## Process + +1. Verify pre-write gates. +2. Append the note to `.context/ingest/findings.md` as a + single bulleted line, prefixed with the UTC timestamp + (RFC-3339), short SHA, and branch: + + ``` + - 2026-05-16T14:32:11Z sha=88d52870 branch=main + | <note text> + ``` + + If `findings.md` does not exist, create it with a brief + header explaining its purpose. +3. No closeout. Notes are intentionally lightweight; the + audit trail is the file itself. + +## Anti-Patterns + +- Writing a topic page or minting `EV-###` from this skill. +- Skipping the timestamp + provenance prefix; the audit + trail depends on it. +- Hand-editing prior entries to "consolidate" them. Append-only. diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-site-review/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-site-review/SKILL.md new file mode 100644 index 000000000..1e1c0000f --- /dev/null +++ b/internal/assets/integrations/opencode/skills/ctx-kb-site-review/SKILL.md @@ -0,0 +1,72 @@ +--- +name: ctx-kb-site-review +description: "Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail." +--- + +Walk `.context/kb/` and `.context/ingest/closeouts/` +mechanically. Fix what is unambiguous (capitalization drift, +missing frontmatter fields the CLI knows how to coerce). Flag +what is not (claims that read as broken but require evidence +to fix). Never invent prose. Never mint `EV-###` rows. Never +modify a claim's Confidence band. + +This is a janitor pass, not an editorial pass. Editorial +judgment lives in `/ctx-kb-ingest`. + +## When to Use + +- The user says "audit the kb", "check kb for rot", + "run a site-review". +- Before a release or at the end of a long editorial run, + to catch drift. + +## When NOT to Use + +- The user has new sources to ingest (`/ctx-kb-ingest`). +- The user wants Q&A (`/ctx-kb-ask`). +- The user wants external re-grounding (`/ctx-kb-ground`). + +## Authority Boundary + +- Coerces formatting drift the CLI can fix unambiguously. +- Flags structural problems that require evidence to resolve + (open `Q-###` rows; never invent answers). +- Never writes prose, mints `EV-###`, or changes confidence + bands. + +## Pre-Write Gates + +- `.context/` missing → suggest `ctx init` and stop. +- `.context/kb/` missing → suggest `ctx init --upgrade` and + stop. +- Kb scope undeclared → refuse with the scope message and + stop. + +## Process + +1. Verify pre-write gates. +2. Walk `.context/kb/` and `.context/ingest/closeouts/`: + coerce capitalization drift in Confidence bands; fix + missing frontmatter fields the CLI can supply + deterministically; flag malformed closeouts (missing + `generated-at`, malformed YAML). +3. For each structural problem that requires evidence to + resolve, open a `Q-###` row in + `outstanding-questions.md` naming the file and the + shape of evidence that would close it. +4. Write the site-review closeout under + `.context/ingest/closeouts/<TS>-site-review-closeout.md` + with required frontmatter (`sha`, `branch`, + `mode: site-review`, `pass-mode: n/a`, `life-stage`, + `generated-at`) and a body listing what was coerced, + what was flagged, and any `Q-###` rows opened. + +## Anti-Patterns + +- Inventing prose or claims. +- Minting `EV-###` rows. +- Modifying Confidence bands. +- Hand-editing closeouts post-write (closeouts are + append-never-rewrite). +- Skipping the `Q-###` row when a structural flag would + otherwise vanish without trace. diff --git a/internal/assets/integrations/opencode/skills/ctx-remember/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-remember/SKILL.md index 50d74e7ae..442f39ff8 100644 --- a/internal/assets/integrations/opencode/skills/ctx-remember/SKILL.md +++ b/internal/assets/integrations/opencode/skills/ctx-remember/SKILL.md @@ -15,8 +15,18 @@ Recall project context and present a structured readback. **Do this FIRST (silently):** 1. Read TASKS.md, DECISIONS.md, and LEARNINGS.md from the context directory -2. Check recent session history (for example via `.context/sessions/` or `ctx journal source --limit 5` when available) -3. Run `ctx agent` for the full context packet +2. Read the latest handover under `.context/handovers/` if present. + Filenames are timestamped `<TS>-<slug>.md`; the lexicographically + last is the newest. Its `## Summary` and `## Next Session` sections + are the authoritative recall surface — this is the previous + session's note to this one. +3. If `.context/ingest/closeouts/` exists, list closeouts whose + `generated-at` postdates the handover's `generated-at` and read + their `## What Changed` sections. These are per-pass audit notes + the previous wrap-up did not get a chance to fold. Read-only; + `/ctx-remember` does not run any editorial pass. +4. Check recent session history (for example via `.context/sessions/` or `ctx journal source --limit 5` when available) +5. Run `ctx agent` for the full context packet **Then respond with a structured readback:** diff --git a/internal/assets/integrations/opencode/skills/ctx-wrap-up/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-wrap-up/SKILL.md index 4d7f16493..b33112964 100644 --- a/internal/assets/integrations/opencode/skills/ctx-wrap-up/SKILL.md +++ b/internal/assets/integrations/opencode/skills/ctx-wrap-up/SKILL.md @@ -20,8 +20,42 @@ Run the end-of-session context persistence ceremony. 4. Capture any new conventions to `.context/CONVENTIONS.md` 5. Update task status in `.context/TASKS.md` 6. Save a session summary to `.context/sessions/` +7. If `.context/kb/` exists, list pending closeouts under + `.context/ingest/closeouts/` and count `open` rows in + `.context/kb/outstanding-questions.md`; surface both + counts so the operator sees what editorial residue is + pending. The handover step's fold pass consumes the + closeouts. Skip this step entirely when `.context/kb/` + does not exist. +8. **Mandatory final step:** delegate to `/ctx-handover` so + the next session has something to read. Draft: + - **Title**: short noun phrase naming the session arc + (becomes the slug in `<TS>-<slug>.md`). + - **`--summary`** (past tense, one paragraph): what was + done this session. Concrete, not vague. + - **`--next`** (future tense, one paragraph): the + specific first action the next agent should take. + - **`--highlights`**: bullet list of notable artifacts. + Always draft; pass empty only after the user + explicitly says there is nothing to highlight. + - **`--open-questions`**: bullet list of things that + remain undecided. Always draft; pass empty only after + explicit user confirmation. + + Confirm the draft with the user, then invoke: + + ```text + /ctx-handover "<title>" --summary "<...>" --next "<...>" \ + [--highlights "<...>"] [--open-questions "<...>"] + ``` + + Do not declare the wrap-up complete until + `.context/handovers/<TS>-<slug>.md` has been written. ## Self-Check Ask: "If this session ended right now, would the next session -know what happened?" If no, persist more context before ending. +know what happened?" If no, persist more context before +ending. The handover step is the floor: without it, recall +degenerates to probabilistic reconstruction from canonical +files plus journal. diff --git a/internal/assets/kb/templates/ingest/00-GROUND.md b/internal/assets/kb/templates/ingest/00-GROUND.md new file mode 100644 index 000000000..0bad0a807 --- /dev/null +++ b/internal/assets/kb/templates/ingest/00-GROUND.md @@ -0,0 +1,92 @@ +# Ground Mode + +External-grounding pass. This mode reaches **outside** the +project to pull authoritative facts that the kb depends on. +Internal grounding (reading the project's own code, specs, +transcripts) belongs in `ctx kb ingest`, not here. + +Read `KB-RULES.md` first; it carries the editorial contract, +the demotion policy, evidence discipline, and the closeout +shape. This file describes the per-mode procedure only. + +--- + +## Inputs + +The skill reads `grounding-sources.md`. Each non-comment, +non-blank line is one of: + +- `mcp:<server>:<resource>` — an MCP-reachable resource. +- `https://...` — a URL the skill should fetch (prefer a + grounded-search MCP when available; web fetch is fallback). +- `./relative/path` or `/abs/path` — a local folder or file + **outside** `.context/`. +- `symlink:<target>` — a stable symlink the project uses for + cross-repo grounding. +- `NONE` — explicit no-op for this pass; re-prompts on next + invocation. **Not** a project-wide opt-out. + +Empty file (no non-comment lines) ⇒ the skill prompts once +for sources before doing any work and exits. It does **not** +fabricate sources, and it does **not** scan the project for +"likely" external references. + +--- + +## Pre-Write Gates + +Before the first fetch, confirm: + +1. `.context/` and `.context/kb/` exist. +2. `.context/kb/index.md` has a non-placeholder `## Scope` + section (no `<!-- TODO: ... -->` marker present). +3. `grounding-sources.md` has at least one resolvable line. + +If any gate fails, refuse cleanly with the recovery hint +named in `KB-RULES.md` and `OPERATOR.md`. + +--- + +## Constraints + +- Ground does **not** rewrite kb prose by itself. It produces + `evidence-index.md` rows; the human (or a follow-up + `ctx kb ingest` pass) weaves new claims into glossary, + domain-decisions, contradictions, timeline. +- A `low`-confidence claim grounded externally promotes one + band **only** when the external source is independent of + the original. Two transcripts of the same meeting do not + count as independent. +- Out-of-repo citations omit the `sha:` field on their + evidence rows; in-repo grounding is not this mode's job. +- If a source returns nothing (fetch failed, MCP unreachable, + page 404), name the failure in the closeout's + `Next pass hint` and continue with the remaining sources. + +--- + +## Procedure + +1. **Resolve sources.** Read `grounding-sources.md`; expand + each non-comment line to a concrete fetchable target. +2. **Fetch.** Call the appropriate retrieval mechanism per + source kind. Prefer a grounded-search MCP for URLs. +3. **Extract.** Pull the smallest set of atomic claims that + bear on the kb's declared scope. Cite each by source + short name (defined in `source-map.md`) plus a locator. +4. **Advance the ledger.** Update `source-coverage.md` for + each source touched in this pass per the state-machine + rules in `KB-RULES.md`. Honest state only — refusing to + record incomplete coverage is the anti-pattern. +5. **Reconcile.** Compare new claims against `glossary.md`, + `domain-decisions.md`, `contradictions.md`. Where external + evidence diverges from internal claims, add a + `contradictions.md` row and demote per the demotion + policy. +6. **Append evidence.** Add new rows to `evidence-index.md`. + Never renumber existing rows. +7. **Write the closeout** under + `.context/ingest/closeouts/<TS>-ground-closeout.md` with + the frontmatter shape from `KB-RULES.md` (`mode: ground`). +8. **Append a `SESSION_LOG.md` line** at the closeout phase + boundary in the exact shape described in `KB-RULES.md`. diff --git a/internal/assets/kb/templates/ingest/30-INGEST.md b/internal/assets/kb/templates/ingest/30-INGEST.md new file mode 100644 index 000000000..319c7ca98 --- /dev/null +++ b/internal/assets/kb/templates/ingest/30-INGEST.md @@ -0,0 +1,260 @@ +# Ingest Mode + +The primary editorial pass. Turns semi-structured inputs +(notes, transcripts, source code, diagrams, upstream docs) +into evidence-backed Markdown kb content with explicit +confidence bands. + +Read `KB-RULES.md` first; it carries the inviolable rules, +the pass-mode contract, the topic-page circuit breaker, the +source-coverage state machine, the topic-adjacency pre-flight, +the cold-reader rubric, evidence discipline, confidence +bands, the demotion policy, and the closeout shape. This +file describes **how a single ingest pass executes**. It does +not duplicate `KB-RULES.md`; it cites it. + +The `/ctx-kb-ingest` skill reads this file before doing +anything else. + +--- + +## Inputs + +The skill takes either a folder (recurse), a list of paths, +a URL, an MCP resource, or inline natural-language context +naming the materials. **No input ⇒ refuse cleanly:** + +> no sources provided; pass a folder, a URL, an MCP resource, +> or describe the materials inline. + +Refuse-on-empty is the contract; do not prompt the user for +sources mid-skill. Exit non-zero so the operator re-invokes +with arguments. + +--- + +## Pre-Write Gates (All Must Pass Before Any Extraction) + +1. `.context/` and `.context/kb/` exist. +2. `.context/kb/index.md` has a non-placeholder `## Scope` + section (no `<!-- TODO: ... -->` marker present). +3. `.context/ingest/KB-RULES.md` exists and was read this + pass. +4. Source paths supplied on the command line all resolve. A + missing path is a refuse condition, not a partial-extract + condition. + +Any gate failure ⇒ refuse with the recovery hint named in +`KB-RULES.md`'s error table. Do not partially execute. + +--- + +## Up-Front Pass-Mode Declaration (MANDATORY) + +After pre-write gates pass and **before** topic resolution, +emit a visible three-line block: + +> **Pass-mode:** `<topic-page|triage|evidence-only>` +> **Reason:** `<one sentence; required for non-default modes>` +> **Definition of done:** `<mode-specific completion criterion>` + +The declaration is a contract, not a label. The closeout's +body block restates these three lines verbatim, and the +closeout's frontmatter records `pass-mode:` to match. Doctor +advisory flags mismatches between the body and frontmatter. + +**Mode selection (see `KB-RULES.md` "Pass-mode contract"):** + +- Default is `topic-page`. +- `triage` fires when the user supplied multiple disparate + sources with no clear single topic, OR explicitly invoked + triage language ("triage these"; "just classify"). +- `evidence-only` fires **only** on explicit user request + matching valid-trigger criteria ("just mint EV rows"; + "backfill evidence"). Size, ambiguity, time pressure, and + operator convenience are NOT valid triggers. Inferring + `evidence-only` to dodge topic-page validation is a hard + anti-pattern. + +**Mid-pass mode-switching is forbidden.** If the work in +flight no longer fits, abort with a partial closeout citing +what was done so far and recommend re-invocation under the +correct mode. Never silent-switch. + +--- + +## Procedure + +### 1. Resolve Topic and Run Adjacency Pre-Flight + +Identify the slug. In `topic-page` mode, before scaffolding +anything: + +1. Read `.context/kb/source-coverage.md`. +2. Scan for rows whose state is **not** in + `{comprehensive, skipped, superseded}` and whose `Topic` + is plausibly adjacent (shared first slug segment, shared + vendor / surface, explicit cross-reference). +3. Record the result in the closeout's `Adjacency pre-flight` + block. Use count + location (*"seventeen rows in + `evidence-index.md`"*); do **not** name individual + `EV-###` IDs (naming a lower-confidence sibling's row + demotes the floor of cited bands on this page). +4. If zero matches, record explicit + *"no incomplete adjacent topics surfaced"*. Silence is + not allowed. + +Each surfaced incomplete adjacent topic MUST be acknowledged +in `## Related concepts in this kb` on the page being +authored. + +### 2. Life-Stage Check + +Count `.context/kb/topics/*/index.md` before synthesizing: + +- `< 5` topic pages → **bootstrap**. Skip reconciliation + ceremony; synthesize aggressively. Exception: surface a + contradiction even in bootstrap if the new material + plainly contradicts existing kb claims. +- `>= 5` topic pages → **maintenance**. Apply full + reconciliation discipline (laddering, demotion, + contradictions). + +Record the life-stage call in the closeout frontmatter +(`life-stage:`) and `What changed` section. + +### 3. Scaffold (Topic-Page Mode Only) + +If `.context/kb/topics/<slug>/index.md` does not exist, +shell out to `ctx kb topic new "<name>"`. **Topic-page file +creation is performed only by `ctx kb topic new`.** Skills +do not synthesize the scaffold by hand. + +If the slug already exists, append/extend the existing +`index.md` rather than creating a new one. + +### 4. Extract and Synthesise + +For each input, extract atomic claims. One claim = one row +in `evidence-index.md`. Each row carries: claim text (single +sentence, declarative, present tense), source short name + +locator, `sha:` for in-repo citations only, confidence band +per `KB-RULES.md`. + +Mode-specific output: + +- `topic-page`: mint prose AND `EV-###` rows AND topic + scaffold AND cross-links AND ledger updates. +- `triage`: classify sources into the inbox, advance ledger + rows to `discovered` or `admitted`. **No EV rows minted.** + No topic page touched. No prose written. +- `evidence-only`: mint `EV-###` rows tagged with the source. + No topic page. No prose. No glossary / domain-decisions + edits. + +### 5. Reconcile (Maintenance Life-Stage Only) + +For each new claim, decide: + +- **Net new** ⇒ append to the appropriate kb file + (`glossary.md` for terms; `domain-decisions.md` for + editorial calls; `timeline.md` for events; the relevant + topic-page prose for narrative context). +- **Reinforces existing** ⇒ promote the existing claim's + confidence band per `KB-RULES.md`; cross-link the new + evidence row. +- **Contradicts existing** ⇒ add a `contradictions.md` row; + demote per the demotion policy; open an + `outstanding-questions.md` entry. + +In bootstrap, the reconciliation ceremony is skipped except +for the contradiction exception. + +### 6. Advance the Source-Coverage Ledger + +Update `.context/kb/source-coverage.md` for every source +touched in this pass, per the state-machine transitions in +`KB-RULES.md`. Illegal transitions are refused at write time +by `AdvanceLedger`. **Lying to the ledger is a hard +anti-pattern**: record `topic-page-drafted` honestly when +the page is incomplete; do not claim `comprehensive` to make +the closeout look clean. + +### 7. Topic-Page Circuit Breaker (Topic-Page Mode Only) + +Before claiming `topic-page: produced` or +`topic-page: extended`, verify ALL FOUR invariants from +`KB-RULES.md` "Topic-page circuit breaker": + +1. The page exists and was created/extended this pass. +2. The page cites at least one `EV-###` row that resolves to + `evidence-index.md`. +3. `ctx kb site build` ran clean (or its failure is named in + `Next pass hint` AND the pass reports `topic-page: + deferred`). +4. The cold-reader orientation rubric records + `Result: pass`. + +Any failure ⇒ `topic-page: deferred`; ledger advances to +`topic-page-drafted` (not `comprehensive`). + +### 8. Cold-Reader Orientation Rubric (Topic-Page Mode Only) + +Record in the closeout's `What changed` section: + +``` +Cold-reader orientation: +- Concept clear? yes|no: <short note> +- Why this kb cares clear? yes|no: <short note> +- Canonical evidence reachable? yes|no: <short note> +- Boundaries clear? yes|no: <short note> +Result: pass | fail +``` + +All four `yes` ⇒ `Result: pass`. Any `no` ⇒ `Result: fail` +⇒ circuit-breaker fails ⇒ `topic-page: deferred`. Name +which items returned `no` in the closeout body. + +If `Boundaries clear?` is `no` because `index.md` has +outgrown a single page, **propose** a sub-page split (e.g. +`.context/kb/topics/<slug>/security.md`) and wait for +operator confirmation. Never auto-split. + +### 9. Write the Closeout + +Path: +`.context/ingest/closeouts/<TS>-ingest-closeout.md`. + +Required frontmatter (see `KB-RULES.md` "Closeout shape"): +`sha`, `branch`, `mode: ingest`, `pass-mode`, `life-stage`, +`generated-at`. + +Body sections (mode-aware): Inputs, Pass-mode block, Topic(s) +touched, What changed (including the Cold-reader rubric in +topic-page mode), New questions, New contradictions, +Confidence drift, Source-coverage updates, Overflow, +Adjacency pre-flight, Next pass hint. + +Closeouts are append-never-rewrite. Archived closeouts are +immutable. + +### 10. Append the SESSION_LOG Line + +At the closeout phase boundary, append a line to +`.context/ingest/SESSION_LOG.md` in the exact shape defined +by `KB-RULES.md` "SESSION_LOG line shape". + +--- + +## What This Mode Does NOT Do + +- Does not web-fetch new external sources; that is + `ctx kb ground`'s job. Inputs are project-internal or + user-supplied URLs. +- Does not mechanically audit existing kb structure; that + is `ctx kb site-review`. +- Does not answer questions from the kb; that is + `ctx kb ask`. +- Does not write to canonical files (`TASKS.md`, + `DECISIONS.md`, `LEARNINGS.md`, `CONVENTIONS.md`). The + authority boundary in `KB-RULES.md` is strict. diff --git a/internal/assets/kb/templates/ingest/40-ASK.md b/internal/assets/kb/templates/ingest/40-ASK.md new file mode 100644 index 000000000..04482b5f1 --- /dev/null +++ b/internal/assets/kb/templates/ingest/40-ASK.md @@ -0,0 +1,104 @@ +# Ask Mode + +Q&A pass grounded in the kb. The operator has a question; +the skill answers from `.context/kb/` content with explicit +confidence bands and citation chains. **Read-only on kb +prose.** The only kb-side write allowed is a new row in +`outstanding-questions.md` when the answer surfaces a gap. + +Read `KB-RULES.md` first; it carries the authority boundary, +evidence discipline, confidence bands, and the closeout +shape. This file describes the per-mode procedure only. + +--- + +## Inputs + +The skill takes a question as its single argument, or inline +natural-language context with the question embedded. **No +question ⇒ refuse cleanly:** + +> no question provided; pass a question or describe it inline. + +Refuse-on-empty is the contract; the skill does not prompt +mid-flight. + +--- + +## Pre-Write Gates + +1. `.context/` and `.context/kb/` exist. +2. `.context/kb/index.md` has a non-placeholder `## Scope` + section. + +If either gate fails, refuse with the recovery hint from +`KB-RULES.md`. + +--- + +## Procedure + +1. **Restate** the question in one sentence. If the operator + gave a multi-part question, split it into sub-questions and + answer each in turn. Record the decomposition in the + closeout's `Inputs` section. +2. **Search** `.context/kb/` and `evidence-index.md` for + relevant claims. Use the source short names from + `source-map.md` to track citation chains. Walk topic + `index.md` files first; descend into sub-pages only when + the lead claim is in one. +3. **Compose** the answer in this shape: + + - **Direct answer** in 1-3 sentences. + - **Confidence** band (`high` / `medium` / `low` / + `speculative`), determined by the floor of the cited + `EV-###` bands per `KB-RULES.md` "Confidence bands". + - **Sources**: bullet list of evidence rows that ground + the answer (cite by `EV-###` plus the source short + name). + - **Caveats**: any contradictions or open questions that + bear on the answer. + +4. **Detect gaps.** If the answer is `speculative` or relies + on `low`-confidence claims, OR if no evidence row backs + the answer at all: + + - Append a `Q-###` row to `outstanding-questions.md` with + the question text and a one-line summary of why current + evidence is insufficient. + - Suggest a concrete next pass in the closeout's + `Next pass hint` (usually `ctx kb ground` for external + evidence or `ctx kb ingest <source>` for internal). + +5. **Write the closeout** under + `.context/ingest/closeouts/<TS>-ask-closeout.md` with + `mode: ask` in the frontmatter and the body shape from + `KB-RULES.md`. + +6. **Append a SESSION_LOG line** at the closeout phase + boundary. + +--- + +## Constraints + +- **Read-only on kb prose.** This mode does not append + paragraphs to `glossary.md`, `domain-decisions.md`, + `timeline.md`, or any topic-page prose. The single + kb-side write allowed is a new row in + `outstanding-questions.md`. +- **No web-jumping.** If the answer is not in the kb, + surface that fact and recommend `ctx kb ground` as the + next pass. Do not fetch external sources to fabricate an + answer. +- **Never invent citations.** If a claim is in working + memory but not in `evidence-index.md`, mark the answer + `speculative` and open a `Q-###` row rather than inflating + the confidence band with a fictional row. +- Multi-part questions get a single closeout, not one per + sub-question. The `Inputs` section captures the + decomposition. +- The `pass-mode:` frontmatter field is required by the + closeout shape, but ask mode does not carry a pass-mode + contract; set `pass-mode: n/a`. Doctor advisory tolerates + `n/a` for non-ingest modes. diff --git a/internal/assets/kb/templates/ingest/50-SITE_REVIEW.md b/internal/assets/kb/templates/ingest/50-SITE_REVIEW.md new file mode 100644 index 000000000..7056a7000 --- /dev/null +++ b/internal/assets/kb/templates/ingest/50-SITE_REVIEW.md @@ -0,0 +1,123 @@ +# Site-Review Mode + +Mechanical structural audit. Walks `.context/kb/` and (if it +exists) the rendered site under `.context/site/kb/` looking +for structural issues that can be fixed **without making +editorial judgment calls**. Defers anything requiring evidence +weighing to a follow-up `ctx kb ingest` or `ctx kb ground` +pass. + +Read `KB-RULES.md` first; it carries the authority boundary, +the closeout shape, the validation rules for confidence +bands, and the demotion policy this mode refuses to apply. +This file describes the per-mode procedure only. + +--- + +## Inputs + +No arguments required. The skill walks `.context/kb/` +automatically. The operator may pass a scope hint inline +(e.g. *"focus on glossary.md"*) which the skill records in +the closeout's `Inputs` section. + +--- + +## Pre-Write Gates + +1. `.context/` and `.context/kb/` exist. +2. `.context/kb/index.md` has a non-placeholder `## Scope` + section. + +--- + +## What Site-Review Fixes (No Truth-Content Needed) + +Mechanical issues. The skill resolves them in place and +records the fix in the closeout's `What changed`: + +- **Broken internal links** — relative paths under + `.context/kb/` that no longer resolve. Retarget to the + renamed file when the target is unambiguous; otherwise + remove the link and open an `outstanding-questions.md` + row. +- **Orphaned source short names** — referenced in + `evidence-index.md` but missing from `source-map.md`. + Add the source-map entry when the source identifier is + obvious from context; flag otherwise. +- **Confidence bands missing or malformed** — must be one of + `high|medium|low|speculative`. Coerce malformed + capitalization in place (`High` → `high`, `MEDIUM` → + `medium`). Flag any other malformation (typos, unknown + bands) without changing the band. +- **Missing closeout frontmatter fields** in + `.context/ingest/closeouts/`. Required fields per + `KB-RULES.md` "Closeout shape": `sha`, `branch`, `mode`, + `pass-mode`, `life-stage`, `generated-at`. Generate the + missing field where derivable from filename + git context; + flag where not derivable. +- **Dangling `outstanding-questions.md` entries** — + questions whose linked contradictions or claims no longer + exist after a rename. Update the link or close the row + with a `(superseded)` annotation citing the rename. +- **Source-coverage ledger inconsistencies** that are purely + mechanical (a row's `Updated` predates the file's mtime, a + row references a source not in `source-map.md`). Refresh + the `Updated` cell; flag the source-map mismatch. + +--- + +## What Site-Review Defers (Requires Evidence Judgment) + +These are recorded in `outstanding-questions.md` and as +`Next pass hint`s in the closeout, but **not** resolved in +this pass: + +- Confidence-band changes that would require new evidence. +- Promotion or demotion of any claim based on + stale-vs-fresh judgment. +- Contradictions that surface during the walk but lack a + clear resolution path. +- Glossary entries with wording the reviewer "doesn't like" + but that aren't factually wrong. +- Illegal source-coverage ledger state transitions (e.g. + `comprehensive → highlights-extracted` without an explicit + `superseded` step). Flag for `ctx kb ingest`; do not + rewrite the state. +- Sub-page splits suggested by oversized `index.md` files. + Site-review surfaces the candidate but never auto-splits. + +--- + +## Procedure + +1. **Plan** the walk: list the files the skill will touch in + the closeout's `Inputs` section. +2. **Walk** each file in order. Per file: detect, fix what's + mechanical, record what's deferred. +3. **Aggregate** into the closeout's `What changed` + (mechanical fixes) and `New questions` (deferred items). +4. **Write the closeout** under + `.context/ingest/closeouts/<TS>-site-review-closeout.md` + with `mode: site-review` in the frontmatter. Set + `pass-mode: n/a` — site-review does not carry a pass-mode + contract. +5. **Append a SESSION_LOG line** at the closeout phase + boundary. + +--- + +## Constraints + +- Mechanical fixes are batchable; do **not** write one + closeout per file. One pass = one closeout. +- Do not touch the rendered site under `.context/site/kb/`. + The renderer (`zensical`) owns that directory; site-review + only reads it for cross-referencing. +- If a "fix" turns out to need judgment mid-walk (e.g. a + broken link could be retargeted to one of two files), + defer it rather than guessing. The bias in this mode is + toward refusing to act, not toward acting hastily. +- Site-review never demotes or promotes claims. It never + edits `evidence-index.md` rows except to fix malformed + confidence-band capitalization in place. diff --git a/internal/assets/kb/templates/ingest/KB-RULES.md b/internal/assets/kb/templates/ingest/KB-RULES.md new file mode 100644 index 000000000..7513b15b4 --- /dev/null +++ b/internal/assets/kb/templates/ingest/KB-RULES.md @@ -0,0 +1,354 @@ +# KB Rules + +Stable workflow contract for the editorial knowledge-ingestion +pipeline shipped by `ctx`. These rules govern the +**`.context/ingest/`** working directory and the mode skills +(`/ctx-kb-ingest`, `/ctx-kb-ask`, `/ctx-kb-site-review`, +`/ctx-kb-ground`, `/ctx-kb-note`). + +This file is project memory. Hand-edit it the same way you +hand-edit the five canonical `.context/` files. Round-trips +through git like any Markdown file. + +The filename is `KB-RULES.md`, not `CONSTITUTION.md`, to avoid +collision with `.context/CONSTITUTION.md`. Per DECISIONS.md +"Editorial constitution at `.context/ingest/KB-RULES.md`, not +`CONSTITUTION.md`" (2026-05-10). + +--- + +## Four Inviolable Rules + +These four rules are load-bearing for the entire workflow. +Breaking any one of them silently corrupts the knowledge base. + +1. **Skills are the sole writers of `INBOX.md`.** The inbox is + an audit trail of the last skill run, never a hand-edit + form. Args or inline natural-language context drive what the + skill writes; cold invocation with no input causes the skill + to refuse cleanly. The pipeline takes no hand-edited config + file; sources are supplied at invocation time. To configure + external grounding inputs, edit `grounding-sources.md`. + +2. **The handover is the sole authoritative recall artifact.** + `/ctx-wrap-up` writes a handover at the end of every session + (always delegating to `/ctx-handover` as its final step, + regardless of whether `.context/kb/` exists). Handovers live + at `.context/handovers/<TS>-<slug>.md` (timestamped so + concurrent agent runs never overwrite). `Do you remember?` + reads the latest handover unconditionally; when + `.context/kb/` exists, it additionally folds any closeouts + whose `generated-at` postdates the handover. + `SESSION_LOG.md` is mid-flight working memory; it is **not** + read on session start. + +3. **Provenance lives on operational artifacts, not knowledge + artifacts.** `SESSION_LOG.md` entries, closeouts, and + handovers carry `sha=<short>` and `branch=<name>`. KB prose, + glossary, domain-decisions, contradictions, + outstanding-questions, timeline, schemas, and the rendered + site stay SHA-free. Single exception: `evidence-index.md` + entries pointing at in-repo files pin to a SHA at extraction + time so cited bytes are recoverable via `git show`. + +4. **Schemas ship with shape, not content.** Each + `schemas/*.md` lists fields and one worked example; no + domain entries. The drift-prevention property the schemas + folder exists for is preserved without prescribing values + per project. + +--- + +## Pass-Mode Contract + +Every `ctx kb ingest` invocation declares its mode **before any +source extraction begins**: + +| Mode | Mints prose? | Mints EV-### ? | Touches topic page? | Default? | +|------------------|--------------|----------------|---------------------|----------| +| `topic-page` | yes | yes | yes (create/extend) | yes | +| `triage` | no | **no** | no | no | +| `evidence-only` | no | yes (tagged) | no | no | + +The declaration is a contract, not a label. The skill emits a +three-line block in the response stream: + +> **Pass-mode:** `<mode>` +> **Reason:** `<one sentence; required when non-default>` +> **Definition of done:** `<mode-specific completion criterion>` + +**Mid-pass mode-switching is forbidden.** If the work in flight +no longer fits, abort with a partial closeout citing what was +done, and recommend re-invocation under the correct mode. + +**Inferring `evidence-only` to avoid topic-page validation is a +hard anti-pattern.** Mode is explicit-request-only; size, +ambiguity, time pressure, and operator convenience are NOT +valid triggers. + +--- + +## Topic-Page Circuit Breaker + +A pass in `topic-page` mode MAY NOT report +`topic-page: produced` or `topic-page: extended` unless ALL of +the following are true at completion: + +1. `.context/kb/topics/<slug>/index.md` (or a sibling sub-page) + exists and was created or extended in this pass. +2. The page cites at least one `EV-###` row that resolves to + `evidence-index.md`. +3. `ctx kb site build` ran clean (or its failure is named in + the closeout's `Next pass hint` AND the pass reports + `topic-page: deferred`). +4. The cold-reader orientation rubric records `Result: pass` + in the closeout (all four items at `yes`). + +Any failure → `topic-page: deferred` and the source-coverage +ledger advances to `topic-page-drafted` (not `comprehensive`). + +--- + +## Source-Coverage Ledger (State Machine) + +`.context/kb/source-coverage.md` is a state machine over every +source the kb has touched. Allowed transitions: + +| state | next states | +|-------------------------|-------------| +| `discovered` | `admitted`, `skipped` | +| `admitted` | `highlights-extracted`, `partially-ingested`, `topic-page-drafted`, `comprehensive` | +| `highlights-extracted` | `partially-ingested`, `topic-page-drafted`, `comprehensive` | +| `partially-ingested` | `topic-page-drafted`, `comprehensive` | +| `topic-page-drafted` | `comprehensive` | +| `comprehensive` | terminal until source updates | +| `superseded` | terminal | +| `skipped` | terminal until scope changes | + +Every pass updates the ledger before writing the closeout. +**Lying to the ledger is a hard anti-pattern.** Set the state +honestly even when it means recording incomplete work. + +--- + +## Topic-Adjacency Pre-Flight + +Before resolving the topic in `topic-page` mode, scan the +ledger for rows whose state is **not** in +`{comprehensive, skipped, superseded}` AND whose `Topic` is +plausibly adjacent (shared first segment of a slug, +shared vendor / surface, explicit cross-references). + +For each adjacent incomplete topic surfaced, the pass MUST: + +1. Acknowledge it in `## Related concepts in this kb` on the + topic page being authored. +2. Surface it in the closeout's `Adjacency pre-flight` block. +3. Surface it in the response contract's + `Adjacent topics noted` field. + +**Do NOT enumerate `EV-###` IDs by name in the adjacency +block.** Use count + location (*"seventeen rows in +`evidence-index.md`"*) instead; naming an EV row from a +lower-confidence sibling demotes the floor of cited bands. + +Silence is not the same as a clean pre-flight; when zero +matches, explicitly record *"no incomplete adjacent topics +surfaced"*. + +--- + +## Cold-Reader Orientation Rubric + +Four yes/no items recorded in the closeout's `What changed` +section, in `topic-page` mode: + +``` +Cold-reader orientation: +- Concept clear? yes|no: <short note> +- Why this kb cares clear? yes|no: <short note> +- Canonical evidence reachable? yes|no: <short note> +- Boundaries clear? yes|no: <short note> +Result: pass | fail +``` + +`Result: pass` requires all four `yes`. Any `no` → +`Result: fail` → circuit-breaker fails → `topic-page: +deferred`. + +--- + +## Life-Stage Discipline + +Count `.context/kb/topics/*/index.md` before the pass begins +synthesizing: + +- `< 5` topic pages → **bootstrap** mode. Skip reconciliation + ceremony; synthesize topic pages aggressively. Exception: + surface a contradiction even in bootstrap if the new + material plainly contradicts existing kb claims. +- `>= 5` topic pages → **maintenance** mode. Apply full + reconciliation: claim laddering, demotion, contradiction + detection. + +Document the life-stage call in the closeout's frontmatter +(`life-stage:`). + +--- + +## Evidence Discipline + +- Every claim in `.context/kb/` carries at least one citation + in `evidence-index.md`. Claims without citations stay in + `outstanding-questions.md` until grounded. +- Citations name the source by **short name** (defined in + `source-map.md`) plus a locator: line range for files, + timestamp for transcripts, anchor for URLs, symbol for code. +- In-repo citations include a `sha:` field set to the short + SHA at extraction time. Out-of-repo citations omit `sha:`. +- A claim with **only** verbal-source backing (transcript, + meeting note) is `confidence: low` until reinforced by a + written source. + +--- + +## Confidence Bands + +Every claim and definition in `.context/kb/` carries an +explicit confidence band: + +- **`high`**: corroborated by ≥ 2 independent sources, or by + one source plus a working code-level check (compiles, runs, + matches). +- **`medium`**: single authoritative source, no independent + corroboration; or two sources that agree on the conclusion + but not the mechanism. +- **`low`** — single source only; or sources disagree but the + claim is the best current synthesis. +- **`speculative`** — no source backing; an inference the + human or agent flagged for follow-up. + +`speculative` content does **not** ship in the rendered site. +`low`-confidence content ships only when paired with the +matching `outstanding-questions.md` entry. + +**Floor-of-cited-bands rule:** a topic page's Confidence is +the *lowest* band cited on the page. Refuse to set Confidence +above the floor. Refuse to set above `speculative` while any +`TBD-cite` remains. + +--- + +## Demotion Policy + +When new evidence contradicts an existing claim: + +1. Add a row to `contradictions.md` naming both EV-### IDs and + one-line summary of the disagreement. +2. Demote the older claim one band + (`high → medium → low → speculative`), citing the new EV + row as the cause. +3. Open an `outstanding-questions.md` entry naming both sides + and what evidence would resolve. + +**Never renumber or delete `EV-###` rows.** Demote in place. + +--- + +## Authority Boundary + +- `/ctx-kb-ingest` writes prose AND evidence rows AND topic + scaffold AND cross-links AND ledger updates in the same + pass. That combination is unique to ingest. +- `/ctx-kb-ask` is Q&A grounded in the kb; read-only on prose; + refuses to web-jump; flags gaps the kb cannot answer. +- `/ctx-kb-site-review` is a structural audit; mechanical + fixes only. Defers anything that requires evidence judgment. +- `/ctx-kb-ground` re-grounds against external sources listed + in `grounding-sources.md`. +- `/ctx-kb-note` is a lightweight capture into + `ingest/findings.md`; never writes to a topic page or to + evidence-index. + +**Topic-page file creation is performed only by `ctx kb topic +new`.** Skills invoke that CLI; they do not synthesize a +scaffold by hand. + +--- + +## Closeout Shape + +Every pass that clears pre-write gates writes a closeout under +`.context/ingest/closeouts/<TIMESTAMP>-<mode>-closeout.md` +with required frontmatter: + +```yaml +--- +sha: <short> +branch: <name> +mode: <ingest|ask|site-review|ground|note> +pass-mode: <topic-page|triage|evidence-only> +life-stage: <bootstrap|maintenance> +generated-at: <RFC-3339> +--- +``` + +Body sections (mode-aware): Inputs, Pass-mode block, Topic(s) +touched, What changed (including the Cold-reader rubric in +topic-page mode), New questions, New contradictions, +Confidence drift, Source-coverage updates, Overflow, Adjacency +pre-flight, Next pass hint. + +Closeouts are append-never-rewrite. Archived closeouts are +immutable. + +--- + +## SESSION_LOG Line Shape + +Each phase-boundary line: + +``` +[YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=<name> status=<done|partial|blocked> note=<≤120 chars> +``` + +`<short>` is the 7-char git short SHA. `<name>` is the current +symbolic ref or `detached` (HEAD is not on a branch). +`<phase>` is one of `resolve`, `synthesise`, `reconcile`, +`closeout`. + +--- + +## Hard Anti-Patterns + +- Treating closeout existence as topic-page validation. +- Skipping the topic-page circuit breaker in topic-page mode. +- Inferring `evidence-only` mode from source size, ambiguity, + time pressure, or operator convenience. +- Mid-pass mode-switching (abort and re-invoke instead). +- Hiding incomplete coverage under a comprehensive-looking + closeout (lying to the ledger). +- Skipping the topic-adjacency pre-flight or running it but + failing to acknowledge surfaced incomplete adjacent topics. +- Inventing claims beyond what the source backs. +- Inventing `EV-###` citations to make a page look complete. +- Promoting claims above `speculative` without an + `evidence-index.md` row. +- Promoting a topic page above its weakest cited band. +- Setting `Confidence` above `speculative` while `TBD-cite` + remains. +- Renumbering or deleting `EV-###` rows when reconciling. +- Skipping the closeout once the pass clears pre-write gates. +- Bypassing `ctx kb topic new` when scaffolding a page. +- Running maintenance discipline against a bootstrap-stage kb. +- Hand-editing `INBOX.md`. + +--- + +## See Also + +- `specs/kb-editorial-pipeline.md` — full spec, including + failure analysis and open questions. +- `OPERATOR.md` — human-facing framing. +- `PROMPT.md` — fallback auto-router when no skill is + installed. diff --git a/internal/assets/kb/templates/ingest/OPERATOR.md b/internal/assets/kb/templates/ingest/OPERATOR.md new file mode 100644 index 000000000..3891c2157 --- /dev/null +++ b/internal/assets/kb/templates/ingest/OPERATOR.md @@ -0,0 +1,153 @@ +# Operator Manual + +How a human drives the kb editorial pipeline. If you are the +operator, read this once before running any of the mode +skills. + +--- + +## The Shape of the Work + +The pipeline lifts knowledge-shaped work (research projects, +domain modeling, post-incident reviews, vendor-spec analysis) +into a discipline that's separate from but adjacent to `ctx`'s +five canonical files. A kb has its own truth basis (cited +evidence, confidence bands) which doesn't fit cleanly into +`DECISIONS.md` or `LEARNINGS.md`. + +You do not *decide* in a kb; you *learn*, and your confidence +in claims increases. A claim with confidence >0.9 is a +fact-by-contract. There is no decide-skill on the kb side; +ad-hoc capture flows through `ctx kb note` (lightweight) or +hand-edit (escape hatch). + +--- + +## Day-Zero Workflow + +1. Run `git init && ctx init` in the project root. The init + lays down the five canonical files **plus** the kb + pipeline scaffolding under `.context/ingest/` and + `.context/kb/`. +2. Open `.context/kb/index.md` and replace the + `<!-- TODO: ... -->` placeholder in `## Scope` with a + one-paragraph statement of what this kb covers and what it + deliberately doesn't. **Mode skills refuse to operate + until you do.** +3. Run `ctx setup` to deploy the kb skills (`/ctx-kb-ingest`, + `/ctx-kb-ask`, `/ctx-kb-site-review`, `/ctx-kb-ground`, + `/ctx-kb-note`, plus the session-wide `/ctx-handover`). +4. Drop your raw materials anywhere. The ingest skill takes + a path or inline reference (folders, files, URLs, MCP + resources). +5. Invoke `/ctx-kb-ingest <folder-or-paths-or-urls>`. +6. When the pass completes, read the closeout under + `.context/ingest/closeouts/<TS>-ingest-closeout.md`. + +--- + +## Steady-State Workflow + +| Trigger | Command | +|---------|---------| +| New material arrives | `/ctx-kb-ingest <folder-or-paths-or-urls>` | +| Question the kb should answer | `/ctx-kb-ask "<question>"` | +| Suspect kb has structural rot | `/ctx-kb-site-review` | +| Need fresh external grounding | `/ctx-kb-ground` | +| Quick capture for the next pass | `/ctx-kb-note "..."` | +| Stepping away from the session | `/ctx-wrap-up` (always delegates to `/ctx-handover` as its tail; KB presence affects what gets folded, not whether the handover is written) | + +`/ctx-wrap-up` **always** delegates to `/ctx-handover` as its +final step, in every ctx project, regardless of whether +`.context/kb/` exists. When `.context/kb/` does exist, the +wrap-up additionally surfaces pending closeouts plus the +outstanding-questions count before delegating, and the +handover step folds postdated closeouts and archives them. +The handover artifact lands at +`.context/handovers/<TS>-<slug>.md` (timestamped so concurrent +agent runs never overwrite). Treat `/ctx-handover` as +wrap-up's handover step, not as a user-facing trigger; the +legitimate direct-invocation cases are `--no-fold` mid-session +checkpoints and recovery after an aborted session. + +--- + +## Reading the KB + +- **`.context/kb/index.md`**: top-level entry and scope + declaration. The `<!-- CTX:KB:TOPICS ... -->` managed block + in this file is regenerated by `ctx kb reindex`; do not + hand-edit between the markers. +- **`.context/kb/topics/<slug>/index.md`**: one folder per + topic; sub-pages (`security.md`, `multi-surface.md`) split + off lazily when an `index.md` outgrows the cold-reader + rubric's *"boundaries clear?"* check. +- **`.context/kb/glossary.md`**: terms with definitions and + confidence bands. +- **`.context/kb/domain-decisions.md`**: editorial calls + about how the domain is described. Separate from + `.context/DECISIONS.md` (different schema, different + authority). +- **`.context/kb/contradictions.md`**: unresolved or + historical contradictions, both sides cited. +- **`.context/kb/outstanding-questions.md`**: open gaps; + what to ground next. +- **`.context/kb/evidence-index.md`**: claim -> source map; + the spine of the workflow. +- **`.context/kb/source-coverage.md`**: state machine over + every source the kb has touched. +- **`.context/kb/timeline.md`**: events the domain cares + about. + +--- + +## The Inbox Is an Audit Trail, Not a Form + +`.context/ingest/INBOX.md` is rewritten by the mode skills +on every run. **Do not hand-edit it.** Edits are silently +discarded the next time a skill runs, with no archive. + +To configure external grounding inputs, edit +`.context/ingest/grounding-sources.md`. That is the only +hand-editable surface that drives mode-skill behavior. + +--- + +## Recovering an Aborted Session + +If a session ends before `/ctx-wrap-up` runs, closeouts +accumulate in `.context/ingest/closeouts/`. The next time +you ask *"do you remember?"*, the agent reads the latest +handover plus any closeout whose `generated-at` postdates +it. So you do not lose the residue; you just don't get it +folded into a clean handover until the next wrap-up's +handover step runs. + +To recover explicitly: invoke `/ctx-wrap-up`, which always +delegates to `/ctx-handover` as its final step. The handover +step folds the postdated closeouts into the new handover and +archives the source files to `.context/archive/closeouts/`. +You may also invoke `/ctx-handover` directly for recovery +(the one legitimate direct-invocation case outside mid-session +checkpoints). + +For mid-session checkpoint without consuming closeouts, use +`ctx handover write --no-fold ...`. + +--- + +## Things This Pipeline Does NOT Do + +- It does **not** invoke the static-site renderer itself. + `ctx kb site build` and `ctx kb site serve` shell out to + `zensical`; missing-binary case fails with a one-line + install hint and non-zero exit. +- It does **not** lint schemas. Schemas under + `.context/ingest/schemas/` are guidance; field drift is + caught by reviewers, not by tools. +- It does **not** programmatically enforce confidence bands. + Discipline is rule-driven via `KB-RULES.md`. +- It does **not** write to the five canonical files + (`TASKS.md`, `DECISIONS.md`, `LEARNINGS.md`, + `CONVENTIONS.md`, `CONSTITUTION.md`). Authority is + enforced by path constants in the CLI. diff --git a/internal/assets/kb/templates/ingest/PROMPT.md b/internal/assets/kb/templates/ingest/PROMPT.md new file mode 100644 index 000000000..a4d834a45 --- /dev/null +++ b/internal/assets/kb/templates/ingest/PROMPT.md @@ -0,0 +1,95 @@ +# Auto-Router Prompt + +Paste this prompt into Claude Code (or any agent IDE) to +enter editorial mode by hand. The agent reads `KB-RULES.md` +first, then routes to the matching mode prompt based on what +you describe next. + +This prompt exists so the workflow is drivable **by hand** +even without the mode skills installed. If `/ctx-kb-ingest`, +`/ctx-kb-ask`, `/ctx-kb-site-review`, `/ctx-kb-ground` are +deployed (run `ctx setup` to deploy them); prefer those. +They are wired with `MarkFlagRequired`-equivalent argument +discipline and refuse-on-empty contracts. + +--- + +``` +You are operating the editorial knowledge-ingestion pipeline +for this project. Read these files before doing anything else: + + .context/ingest/KB-RULES.md (the contract; read in full) + .context/ingest/OPERATOR.md (operator framing) + +Then route to one of the four modes based on what I tell you +next: + + - "ingest <folder-or-paths-or-urls>" + → read .context/ingest/30-INGEST.md and run. + - "ask <question>" + → read .context/ingest/40-ASK.md and run. + - "site review" + → read .context/ingest/50-SITE_REVIEW.md and run. + - "ground" + → read .context/ingest/00-GROUND.md and run + (sources from grounding-sources.md; + prompt me once if the file is empty). + +You are the sole writer of .context/ingest/INBOX.md. Rewrite +it at the start of every pass; never preserve hand-edits. + +If we are in ingest mode, emit the up-front three-line +pass-mode declaration BEFORE topic resolution, per KB-RULES.md +"Pass-mode contract": + + **Pass-mode:** <topic-page|triage|evidence-only> + **Reason:** <one sentence; required for non-default modes> + **Definition of done:** <mode-specific completion criterion> + +Mid-pass mode-switching is forbidden. If the work no longer +fits the declared mode, abort with a partial closeout and +recommend re-invocation under the correct mode. + +Append one line to .context/ingest/SESSION_LOG.md at every +phase boundary, in the exact shape from KB-RULES.md +"SESSION_LOG line shape": + + [YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=<name> status=<done|partial|blocked> note=<≤120 chars> + +End every pass by writing a closeout under +.context/ingest/closeouts/<TS>-<mode>-closeout.md (where +<mode> is one of ingest|ask|site-review|ground|note) with +the required frontmatter: + + --- + sha: <short> + branch: <name> + mode: <ingest|ask|site-review|ground|note> + pass-mode: <topic-page|triage|evidence-only|n/a> + life-stage: <bootstrap|maintenance|n/a> + generated-at: <RFC-3339 with timezone> + --- + +In topic-page mode, the four-invariant circuit breaker and +the cold-reader rubric apply per KB-RULES.md. Do not claim +`topic-page: produced` or `topic-page: extended` unless ALL +four invariants hold. + +Do not invent claims. Do not promote confidence bands without +independent corroboration. Do not delete kb content; demote +per the demotion policy. Do not lie to the source-coverage +ledger. + +Now: which mode? +``` + +--- + +## When to Update This Prompt + +If `KB-RULES.md` changes substantively, update the routing +list above so the auto-router stays aligned with the +contract. Do **not** quote `KB-RULES.md` content here +verbatim. That is drift waiting to happen. Reference it by +path; let the agent read the latest authoritative copy at +run time. diff --git a/internal/assets/kb/templates/ingest/schemas/contradictions.md b/internal/assets/kb/templates/ingest/schemas/contradictions.md new file mode 100644 index 000000000..4d09a4315 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/contradictions.md @@ -0,0 +1,45 @@ +# Contradictions Schema + +Shape for `.context/kb/contradictions.md`. Each entry records +two or more `EV-###` rows that disagree, the demotion applied +under the demotion policy, and a resolution status. Resolved +contradictions stay in the file as audit trail. + +## Fields + +| Field | Description | +|---------------------|-------------------------------------------------------------------| +| `id` | Stable `C-###` identifier; zero-padded; never renumbered. | +| `opened` | ISO date the contradiction was filed. | +| `evidence` | Two or more `EV-###` IDs that disagree. | +| `summary` | One-line statement of what the rows disagree about. | +| `demotion-applied` | Which claim was demoted to which band, with reason. | +| `resolution-status` | `open` or `resolved`; resolved entries name the winning claim. | + +## Example + +```markdown +## C-007: Widget Scope (Per-Folder vs Per-File) + +- **Opened:** 2026-05-16. +- **Evidence:** EV-042, EV-051. +- **Summary.** EV-042 says a widget is a folder; EV-051 says a + widget is a single `.md` file with frontmatter. +- **Demotion applied.** EV-051 demoted from `medium` to `low`; + EV-042 corroborated by EV-043 holds at `high`. +- **Resolution status:** resolved; folder-shaped (2026-05-16). +``` + +## Rules + +- Every contradiction applies the demotion policy at filing + time; recording the disagreement without demoting an older + claim is a hard anti-pattern. +- Cite at least two `EV-###` IDs; a contradiction is between + evidence rows, not between prose paragraphs. +- Never renumber or delete `EV-###` rows when reconciling; + demote in place and let the row stand as audit trail. +- Resolved entries are kept; deletion erases kb history. +- Open contradictions surface in `/ctx-kb-site-review` and + drive a paired `outstanding-questions.md` row naming both + sides and what evidence would resolve. diff --git a/internal/assets/kb/templates/ingest/schemas/domain-decisions.md b/internal/assets/kb/templates/ingest/schemas/domain-decisions.md new file mode 100644 index 000000000..833939a2f --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/domain-decisions.md @@ -0,0 +1,58 @@ +# Domain Decisions Schema + +Shape for `.context/kb/domain-decisions.md`. Kb-scoped +decisions about the subject matter under study, distinct +from project-level `.context/DECISIONS.md`, which records +decisions about how this project is built. + +The two files have different schemas, different write +authority, and different lifecycles. `domain-decisions.md` +is written by `/ctx-kb-ingest`; `DECISIONS.md` is written by +`/ctx-decision-add`. + +## Fields + +| Field | Description | +|-----------------|-------------------------------------------------------------------| +| `id` | Stable `DD-###` identifier; zero-padded; never renumbered. | +| `date` | ISO date the decision was recorded in the kb. | +| `context` | What in the domain prompted the decision; observable facts only. | +| `decision` | The position taken, in one sentence. | +| `rationale` | Why this position over the alternatives that were on the table. | +| `consequence` | What now changes for topic pages, glossary, or downstream claims. | +| `supporting-ev` | Comma-separated `EV-###` references that ground the decision. | + +## Example + +```markdown +## DD-004: Treat Widget Bundles as Opaque to the Consumer + +- **Date:** 2026-05-16. +- **Context.** Two upstream sources disagreed on whether a + widget consumer should inspect bundle internals (EV-042, + EV-051); the disagreement was resolved in `C-007`. +- **Decision.** The kb treats widget bundles as opaque from + the consumer's perspective; inspection is an implementation + concern of the producer. +- **Rationale.** Opacity preserves the producer's freedom to + evolve internals without breaking downstream call sites. +- **Consequence.** The `widget-composition` topic page is + rewritten to lead with opacity; the glossary's `widget` + entry gains an `opacity` cross-reference. +- **Supporting EV:** EV-042, EV-043. +``` + +## Rules + +- Do not confuse this with `.context/DECISIONS.md`; that file + records project-build decisions and is owned by a different + CLI surface. +- A domain decision is a kb-scoped position on the subject + matter; it cites `EV-###` rows, not commit SHAs. +- Every entry cites at least one supporting `EV-###`; a + domain decision without evidence belongs in + `outstanding-questions.md` instead. +- Decisions are append-only and never renumbered; supersede + in place by recording a new `DD-###` that cites the prior. +- Do not write to `.context/DECISIONS.md` from + `/ctx-kb-ingest`; the authority boundary is strict. diff --git a/internal/assets/kb/templates/ingest/schemas/evidence-index.md b/internal/assets/kb/templates/ingest/schemas/evidence-index.md new file mode 100644 index 000000000..276925ff8 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/evidence-index.md @@ -0,0 +1,45 @@ +# Evidence Index Schema + +Shape for `.context/kb/evidence-index.md`. Each row is one +atomic claim, minted by `/ctx-kb-ingest` and never renumbered. +The evidence index is the spine of the kb: glossary entries, +contradictions, domain-decisions, timeline events, and topic +pages all cite back to specific `EV-###` IDs here. + +Rows are append-only. To retire a stale claim, demote its +`confidence` band per the demotion policy in `KB-RULES.md`. +Never delete the row. + +## Fields + +| Column | Description | +|--------------|------------------------------------------------------------| +| `id` | Stable `EV-###` identifier; zero-padded; never renumbered. | +| `claim` | One-sentence atomic claim; declarative, source-backed. | +| `source` | Short name from `source-map.md` (e.g. `CURSOR-HOOKS`). | +| `locator` | Line range, timestamp, anchor, or symbol within source. | +| `sha` | Optional short SHA; required only for in-repo citations. | +| `confidence` | One of `high`, `medium`, `low`, `speculative`. | +| `tags` | Comma-separated; `evidence-only` is additive (mode tag). | +| `occurred` | Optional ISO date the underlying event occurred. | +| `extracted` | ISO date the row was extracted into the kb. | + +## Example + +| id | claim | source | locator | sha | confidence | tags | occurred | extracted | +|--------|--------------------------------------------------------------------------------------|--------------|-----------|---------|------------|-----------------------|------------|------------| +| EV-042 | The widget contract pins its schema version in the first frontmatter line of `SKILL.md`. | WIDGET-SPEC | §Schema | | high | widget, contract | | 2026-05-16 | + +## Rules + +- Append-only. Renumbering or deleting an `EV-###` row is a + hard anti-pattern; demote in place per the demotion policy. +- Rows tagged `evidence-only` come from `evidence-only`-mode + passes and have no topic-page prose backing them yet; + citing one requires re-reading the underlying source. +- In-repo citations include `sha:` set to the short SHA at + extraction time. Out-of-repo citations omit `sha:`. +- A claim whose only backing is a transcript or meeting note + is `confidence: low` until a written source reinforces it. +- Never invent an `EV-###` to make a topic page look complete; + every cited ID must resolve to a row in this file. diff --git a/internal/assets/kb/templates/ingest/schemas/glossary.md b/internal/assets/kb/templates/ingest/schemas/glossary.md new file mode 100644 index 000000000..95e193f7d --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/glossary.md @@ -0,0 +1,54 @@ +# Glossary Schema + +Shape for `.context/kb/glossary.md`. Canonical terms scoped to +this kb's `## Scope` statement. Each entry is a declarative +definition with a confidence band and at least one +`evidence-index.md` row backing it. + +Glossary confidence reflects definition quality ("do we agree +on what this term means?"), independent of whether the cited +evidence rows themselves carry `high` or `low` bands. + +## Fields + +| Field | Description | +|-----------------|--------------------------------------------------------------| +| `term` | Canonical term as a level-2 heading; lowercase preferred. | +| `aliases` | Optional comma-separated list of synonyms or short forms. | +| `definition` | One paragraph; declarative; no hedging beyond the band. | +| `confidence` | One of `high`, `medium`, `low`, `speculative`. | +| `evidence` | Comma-separated `EV-###` references; at least one required. | +| `related-terms` | Optional links to other glossary entries in this file. | + +## Example + +```markdown +## widget + +**Aliases:** widget-the-primitive, the unit. + +**Definition.** A widget is the smallest packaging boundary in +the system: a folder containing `SKILL.md` plus optional +`scripts/`, `references/`, and `assets/` subdirectories. + +**Confidence:** high. + +**Evidence:** EV-042, EV-043. + +**See also:** [widget contract](#widget-contract). +``` + +## Rules + +- Do not define a term outside the kb's declared `## Scope` in + `.context/kb/index.md`; out-of-scope terms belong elsewhere. +- Every entry cites at least one `EV-###`; ungrounded terms + open an `outstanding-questions.md` row instead. +- Aliases are additive, not renamings; the canonical term is + the heading and never changes once published. +- Glossary confidence tracks definitional consensus, not the + bands of the cited evidence rows; do not mechanically + downgrade a definition just because its evidence was demoted. +- Cross-reference related terms within the file; do not link + to topic pages from the definition body (use the topic page's + `## Related concepts in this kb` block for that direction). diff --git a/internal/assets/kb/templates/ingest/schemas/outstanding-questions.md b/internal/assets/kb/templates/ingest/schemas/outstanding-questions.md new file mode 100644 index 000000000..c38ca4589 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/outstanding-questions.md @@ -0,0 +1,51 @@ +# Outstanding Questions Schema + +Shape for `.context/kb/outstanding-questions.md`. Each entry +is an open gap the kb has not yet resolved. Stable `Q-###` +IDs; newest first; never renumber. + +Questions block the promotion of any claim that depends on +them: a topic page with an open `Q-###` cited in its +`## Open questions` section cannot ship at `confidence: high`. + +## Fields + +| Field | Description | +|----------------------------|------------------------------------------------------------| +| `id` | Stable `Q-###` identifier; zero-padded; never renumbered. | +| `opened` | ISO date the question was opened. | +| `question` | One-sentence open question, phrased as a question. | +| `why-it-matters` | Why answering this changes a topic page or a band. | +| `what-evidence-would-resolve` | The shape of evidence that would close this entry. | +| `related-ev` | Optional comma-separated `EV-###` references. | + +## Example + +```markdown +## Q-009: Does the Widget Contract Permit Nested Skills? + +- **Opened:** 2026-05-16. +- **Question.** Does the widget folder shape allow a nested + `SKILL.md` inside a subfolder, or is one skill per folder + the hard limit? +- **Why it matters.** The `widget-composition` topic page + cannot promote above `low` until this is answered; the page + currently hedges with both readings. +- **What evidence would resolve.** A statement in the upstream + spec, or a worked example from the vendor's own repo. +- **Related EV:** EV-042, EV-043. +``` + +## Rules + +- Opening a question without stating what evidence would + resolve it is a hard anti-pattern; the field is required. +- Phrase the question as an interrogative; declarative + statements belong in `evidence-index.md` with a band. +- `low`-confidence topic-page content ships only when paired + with a matching `outstanding-questions.md` entry; the entry + is the license to ship the hedge. +- Closed questions are not deleted; they record what was once + open and how it resolved. +- Do not reuse a retired `Q-###` ID for a new question; the + ID space is monotonic. diff --git a/internal/assets/kb/templates/ingest/schemas/relationship-map.md b/internal/assets/kb/templates/ingest/schemas/relationship-map.md new file mode 100644 index 000000000..475f823b5 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/relationship-map.md @@ -0,0 +1,41 @@ +# Relationship Map Schema + +Shape for `.context/kb/relationship-map.md`. Cross-topic and +cross-source relationships recorded as rows keyed on topic +slug or `EV-###` ID. The relationship map is the kb's edge +list; nodes are topic pages and evidence rows. + +Relationships key on *slug + EV-### ID*, never on file path, +so folder reorganisations and lazy sub-page splits do not +invalidate the graph. + +## Fields + +| Column | Description | +|-----------|--------------------------------------------------------------------------| +| `from` | Originating topic slug or `EV-###` ID. | +| `to` | Destination topic slug or `EV-###` ID. | +| `kind` | Relationship kind (`depends-on`, `refines`, `contradicts`, `supersedes`).| +| `summary` | One-line description of the relationship. | + +## Example + +| from | to | kind | summary | +|-------------|-----------|-------------|----------------------------------------------------------| +| widgets | EV-042 | depends-on | Widget topic page leans on EV-042 for its scope claim. | + +## Rules + +- Key on slug and `EV-###`, never on file path; lazy + sub-page splits and folder moves must not break edges. +- `kind` is drawn from a controlled vocabulary; introducing + a new kind requires a `domain-decisions.md` entry naming + the rationale. +- `contradicts` edges pair with a row in + `contradictions.md`; the relationship map is the index, + the contradictions file is the body. +- `supersedes` edges pair with the superseded row's demotion + in `evidence-index.md`; never delete the superseded row. +- Self-edges (a slug pointing to itself, or an `EV-###` + pointing to itself) are not meaningful and are rejected + on write. diff --git a/internal/assets/kb/templates/ingest/schemas/session-log.md b/internal/assets/kb/templates/ingest/schemas/session-log.md new file mode 100644 index 000000000..cfd78b143 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/session-log.md @@ -0,0 +1,52 @@ +# Session Log Schema + +Shape for `.context/ingest/SESSION_LOG.md`. One line per +phase boundary during a `/ctx-kb-ingest` pass. The session +log is mid-flight working memory: it records the agent's +movement through the pass so a partial closeout can name +exactly where the work stopped. + +The session log is not the recall artifact. The handover +(`.context/handovers/<TS>-<slug>.md`) is the sole +authoritative source for "what happened in the last +session"; the session log is operational telemetry for the +pass currently in flight. + +## Fields + +The single-line format encodes every field positionally: + +``` +[YYYY-MM-DD HH:MM:SS sha=<short> branch=<name>] phase=<name> status=<state> note=<≤120 chars> +``` + +| Field | Description | +|------------|-------------------------------------------------------------------| +| timestamp | `YYYY-MM-DD HH:MM:SS` local time at the phase boundary. | +| `sha` | 7-char git short SHA of HEAD at the boundary. | +| `branch` | Current symbolic ref, or `detached` if HEAD is not on a branch. | +| `phase` | One of `resolve`, `synthesise`, `reconcile`, `closeout`. | +| `status` | One of `done`, `partial`, `blocked`. | +| `note` | Free text, at most 120 characters; no newlines. | + +## Example + +``` +[2026-05-16 14:32:08 sha=88d5287 branch=main] phase=synthesise status=done note=widget topic page extended; cited EV-042..EV-046 +``` + +## Rules + +- Do not read `SESSION_LOG.md` on session start; it is + mid-flight working memory, not a recall artifact. The + handover is the recall surface. +- Append-only; never edit a past line. Corrections go on a + new line with a `note=` explaining the prior entry. +- The four phase tokens are the closed set; introducing a + new phase requires a `domain-decisions.md` entry and a + spec update. +- Keep notes under the 120-character budget; longer + observations belong in the closeout body, not here. +- Lines without `sha=` and `branch=` are malformed; doctor + advisory flags them. Provenance is non-optional on this + operational artifact. diff --git a/internal/assets/kb/templates/ingest/schemas/source-coverage.md b/internal/assets/kb/templates/ingest/schemas/source-coverage.md new file mode 100644 index 000000000..9ce5d8261 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/source-coverage.md @@ -0,0 +1,65 @@ +# Source Coverage Schema + +Shape for `.context/kb/source-coverage.md`. Per-source +completeness ledger maintained by `/ctx-kb-ingest`. Each row +tracks **coverage state**, not just existence: a source can be +known to the kb at any of several stages between discovery and +comprehensive understanding. + +This file is the canonical answer to *"what is incomplete?"* +Distinct from `source-map.md` (which records WHAT a source +is); the ledger records HOW COMPLETE the work against it is. + +## Fields + +| Column | Description | +|---------------|------------------------------------------------------------------------| +| `Source` | Short name from `source-map.md`. | +| `Topic` | Kb topic slug, or `n/a` if no topic page applies (e.g. `triage` runs). | +| `State` | One of the state-machine values listed in the transitions table. | +| `EV coverage` | Range like `EV-018..EV-034`, comma list, or `none`. | +| `Residue` | One-line free text describing what was deliberately left out. | +| `Next action` | Exact resumption invocation (e.g. `/ctx-kb-ingest <slug> (resume ...)`). | +| `Updated` | ISO date the row was last written. | + +## Allowed Transitions + +| state | next states | +|-------------------------|----------------------------------------------------------------------------------------------| +| `discovered` | `admitted`, `skipped` | +| `admitted` | `highlights-extracted`, `partially-ingested`, `topic-page-drafted`, `comprehensive` | +| `highlights-extracted` | `partially-ingested`, `topic-page-drafted`, `comprehensive` | +| `partially-ingested` | `topic-page-drafted`, `comprehensive` | +| `topic-page-drafted` | `comprehensive` | +| `comprehensive` | terminal until source updates | +| `superseded` | terminal | +| `skipped` | terminal until scope changes | + +Backward steps (e.g. `comprehensive → highlights-extracted`) +require an explicit `superseded` step first; otherwise the +transition is illegal and `AdvanceLedger` refuses it. + +## Example + +| Source | Topic | State | EV coverage | Residue | Next action | Updated | +|--------------|---------|------------------------|-----------------|------------------------------------|------------------------------------------|------------| +| WIDGET-SPEC | widgets | highlights-extracted | EV-042..EV-051 | composition examples, error cases | /ctx-kb-ingest widgets (resume topic-page) | 2026-05-16 | + +## Rules + +- Lying to the ledger is a hard anti-pattern; record the + state honestly even when it means writing + `topic-page-drafted` instead of `comprehensive`. +- Illegal transitions are refused at write time; backing out + of `comprehensive` requires an explicit `superseded` step + first, naming the superseder. +- `comprehensive` requires cited bands ≥ `medium`, no + `TBD-cite` markers, and a passing cold-reader rubric; + topic-page-drafted is the right state when any of those + three conditions fail. +- `Next action` is the exact resumption invocation a future + agent should run; free prose like "finish later" defeats + the ledger's purpose. +- Every pass that touches a source updates its row *before* + writing the closeout; doctor advisory flags rows whose + `Updated` predates the file's last-modified time. diff --git a/internal/assets/kb/templates/ingest/schemas/source-map.md b/internal/assets/kb/templates/ingest/schemas/source-map.md new file mode 100644 index 000000000..41ce65cfe --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/source-map.md @@ -0,0 +1,45 @@ +# Source Map Schema + +Shape for `.context/kb/source-map.md`. One row per source +cited from `evidence-index.md`. Records what a source *is* +and whether it was admitted against the kb's scope. + +Distinct from `source-coverage.md`: this file records WHAT a +source is (identity, locator, admission); the coverage ledger +records HOW COMPLETE the kb's work against it is (state +machine over discovered → comprehensive). + +## Fields + +| Column | Description | +|------------------------|--------------------------------------------------------------| +| `short-name` | Stable identifier used in `evidence-index.md` cites. | +| `kind` | One of `transcript`, `code`, `doc`, `url`, `mcp`, `kb`. | +| `locator` | URL, repo path, MCP resource ID, or kb pointer. | +| `admission-status` | `admitted`, `rejected`, or `pending`. | +| `admission-rationale` | One-sentence reason the source was admitted or rejected. | +| `dated` | Optional ISO date the source itself is dated. | + +## Example + +| short-name | kind | locator | admission-status | admission-rationale | dated | +|--------------|------|----------------------------------------|------------------|--------------------------------------------------|------------| +| WIDGET-SPEC | url | `https://example.org/widget/v2/spec` | admitted | Canonical upstream spec; in scope per index.md. | 2026-05-16 | + +## Rules + +- Short names are stable; rename via alias-add, not by + rewriting in place. Existing `EV-###` rows pin to the + original short name. +- Admission is against the kb's declared `## Scope` in + `.context/kb/index.md`; rejected sources stay in the map + with their rationale as audit trail. +- `kind: kb` marks federation into another kb (per the + KB-of-KBs organizing principle); the locator points at + another `.context/kb/` directory. +- A source can appear here at `admission-status: pending` + before it is admitted; the coverage ledger picks up only + admitted sources. +- Do not conflate this file with `source-coverage.md`; the + identity-vs-progress split is load-bearing for the + state-machine ledger. diff --git a/internal/assets/kb/templates/ingest/schemas/timeline.md b/internal/assets/kb/templates/ingest/schemas/timeline.md new file mode 100644 index 000000000..bc46d7c82 --- /dev/null +++ b/internal/assets/kb/templates/ingest/schemas/timeline.md @@ -0,0 +1,49 @@ +# Timeline Schema + +Shape for `.context/kb/timeline.md`. Dated events worth +recording in the kb, oldest first. Each entry pins a real +occurrence in the domain to `EV-###` rows so the timeline +can be cross-checked against its sources. + +The timeline is not a changelog of kb activity; that lives +in `SESSION_LOG.md` and the closeouts under +`.context/ingest/closeouts/`. The timeline records events +the kb is studying, not events the kb performed. + +## Fields + +| Field | Description | +|------------------|----------------------------------------------------------------| +| `date` | ISO date the event occurred (not the date it was ingested). | +| `event` | One-paragraph description of what happened. | +| `source-ev` | Comma-separated `EV-###` references that ground the event. | +| `related-topics` | Optional topic slugs touched by the event. | + +## Example + +```markdown +## 2026-05-16: Widget Contract v2 Published + +- **Event.** The upstream maintainer published v2 of the + widget contract, adding the `compatibility` frontmatter + field and tightening the `description` length budget. +- **Source EV:** EV-042, EV-051. +- **Related topics:** widgets, widget-composition. +``` + +## Rules + +- Pin the event to its `occurred:` date, not the date it was + ingested; the `extracted:` field on the evidence row + records the latter. +- Cite at least one `EV-###`; an entry without source + backing is speculation, not a timeline event. +- Speculation about future events belongs in + `outstanding-questions.md`, not here; filing a dated event + for something the kb merely expects to happen is a hard + anti-pattern. +- Newer events are appended below older ones; do not + reorder once published. +- Cross-reference related topic slugs so the timeline can be + scanned for blast radius when a single event touches + multiple pages. diff --git a/internal/assets/kb/templates/kb/index.md b/internal/assets/kb/templates/kb/index.md new file mode 100644 index 000000000..0c54372cb --- /dev/null +++ b/internal/assets/kb/templates/kb/index.md @@ -0,0 +1,77 @@ +# Knowledge Base + +The kb is the project's evidence-tracked knowledge surface. +It lives at `.context/kb/` and is governed by the editorial +contract in `../ingest/KB-RULES.md`. Topic pages live under +`topics/<slug>/index.md`. Cross-cutting artifacts +(`glossary.md`, `domain-decisions.md`, `contradictions.md`, +`outstanding-questions.md`, `evidence-index.md`, +`source-coverage.md`, `timeline.md`) live alongside this +file. + +--- + +## Scope + +<!-- +TODO: declare what this kb covers, in one paragraph. + +`ctx kb ingest`, `ctx kb ask`, `ctx kb ground`, and +`ctx kb site-review` all refuse to run while this placeholder +remains in place. Replace the entire HTML comment with a +prose paragraph that: + + - names the domain the kb covers; + - names what the kb deliberately does NOT cover; + - names the operator audience (the human and any agent + skills that read this). + +Keep it to one paragraph. Specificity matters more than +length. +--> + +--- + +## Topics + +<!-- CTX:KB:TOPICS START --> +<!-- +This block is managed by `ctx kb reindex`. Do not hand-edit +between the START and END markers. Content here is +regenerated from `.context/kb/topics/*/index.md` and any +manual edits are overwritten on the next reindex. + +Each topic page registered by `ctx kb topic new` appears here +as a bullet linking to `topics/<slug>/index.md` with the +topic's lede sentence. + +Until a topic exists, this block stays empty. +--> +<!-- CTX:KB:TOPICS END --> + +--- + +## Conventions + +This kb is governed by: + +- **`../ingest/KB-RULES.md`** is the editorial contract: + pass-mode discipline, topic-page circuit breaker, + source-coverage state machine, topic-adjacency pre-flight, + cold-reader rubric, life-stage check, evidence discipline, + confidence bands, demotion policy, closeout shape. +- **`../ingest/schemas/`** holds field-level shape for each + cross-cutting artifact (`evidence-index.md`, `glossary.md`, + `contradictions.md`, `outstanding-questions.md`, + `domain-decisions.md`, `timeline.md`, `source-map.md`, + `source-coverage.md`, `relationship-map.md`). Each schema + ships shape, not content. +- **`../../specs/kb-editorial-pipeline.md`** is the full spec, + including the failure-analysis section and the v1 + non-goals. Read this when you need to understand *why* a + rule exists, not just *what* it says. + +The mode skills (`/ctx-kb-ingest`, `/ctx-kb-ask`, +`/ctx-kb-site-review`, `/ctx-kb-ground`, `/ctx-kb-note`) are +the canonical writers. Hand-edits to kb files are an escape +hatch, not the primary path. diff --git a/internal/assets/kb/templates/kb/topics/_template/index.md b/internal/assets/kb/templates/kb/topics/_template/index.md new file mode 100644 index 000000000..db841277d --- /dev/null +++ b/internal/assets/kb/templates/kb/topics/_template/index.md @@ -0,0 +1,89 @@ +<!-- +Topic-page template. `ctx kb topic new "<name>"` copies this +file into `.context/kb/topics/<slug>/index.md` and +substitutes the slug + display name. Sub-pages +(`security.md`, `multi-surface.md`, ...) split off lazily +from this index when the cold-reader rubric's "boundaries +clear?" check fails. + +The `topic-page` pass of `ctx kb ingest` is expected to fill +`TBD` placeholders with cited prose. Do not hand-edit this +template unless you are changing the topic-page shape for +the whole project. +--> + +--- +Subject: TBD; short subject line, max 80 chars +Last verified: TBD-YYYY-MM-DD +Author: TBD; agent or human name +Confidence: TBD; high | medium | low | speculative +sha: TBD; short SHA at last verification (in-repo context) +branch: TBD; branch at last verification +--- + +# <Topic name> + +TBD: one-paragraph lede. State what this topic is, in plain +language, in 2-4 sentences. The cold-reader rubric's +"concept clear?" item is judged against this paragraph. Cite +at least one `EV-###` row from `evidence-index.md` here. + +--- + +## What It Is + +TBD: definition prose. Describe the thing this page is +about: shape, mechanism, surface area, key behaviors. Cite +evidence rows inline as `[EV-###]`. Confidence band on each +substantive claim, demoted to the floor of cited bands per +`../../../ingest/KB-RULES.md` "Confidence bands". + +If this section grows past the cold-reader rubric's +"boundaries clear?" threshold, propose a sub-page split +(e.g. `mechanics.md`) on the next ingest pass; do not +auto-split. + +--- + +## Why This KB Cares + +TBD: relevance statement. Why does this kb track this +topic? What downstream questions does it answer? What +decisions or claims elsewhere in the kb lean on it? The +cold-reader rubric's "why this kb cares clear?" item is +judged against this section. + +--- + +## Sources and Further Reading + +TBD: bullet list of the canonical sources this page draws +from, by source short name as defined in +`../../source-map.md`. Each bullet pairs a source short name +with a one-line description. + +- `<SOURCE-SHORTNAME>`: TBD one-line description; locator + hint. + +For deep evidence, the cold-reader rubric's "canonical +evidence reachable?" item requires that walking from this +page to `../../evidence-index.md` to the original source +takes one or two clicks. If reachability degrades, this +section needs revision. + +--- + +## Related Concepts in This KB + +TBD: bullet list of adjacent topic pages, surfaced by the +topic-adjacency pre-flight on the most recent ingest pass. +Each bullet pairs a topic slug with a one-line description +of the relationship. + +- `<adjacent-slug>`: TBD one-line description of the + relationship. + +When the adjacency pre-flight finds no incomplete adjacent +topics, this section records `none surfaced` explicitly. +Silence is not the same as a clean pre-flight; see +`../../../ingest/KB-RULES.md` "Topic-adjacency pre-flight". diff --git a/internal/assets/why/about.md b/internal/assets/why/about.md index 44b21e31d..d52798ff7 100644 --- a/internal/assets/why/about.md +++ b/internal/assets/why/about.md @@ -67,7 +67,7 @@ Without persistent memory, every session starts at zero. `ctx` gives your AI a memory that persists across sessions: -=== "Without ctx" +=== "Without `ctx`" ```text Session 12: Monday morning @@ -86,7 +86,7 @@ Without persistent memory, every session starts at zero. 20 minutes spent restoring decisions that already exist. ``` -=== "With ctx" +=== "With `ctx`" ```text Session 12: Monday morning diff --git a/internal/audit/desckey_namespace_test.go b/internal/audit/desckey_namespace_test.go index 9dcdbf095..c3bf57da9 100644 --- a/internal/audit/desckey_namespace_test.go +++ b/internal/audit/desckey_namespace_test.go @@ -119,7 +119,7 @@ func TestDescKeyOnlyInLookupCalls(t *testing.T) { if strings.Contains( pkg.PkgPath, "config/embed/", ) || strings.Contains( - pkg.PkgPath, "flagbind", + pkg.PkgPath, "flag_bind", ) { continue } diff --git a/internal/audit/flagbind_test.go b/internal/audit/flagbind_test.go index f0979909c..9a7659e9c 100644 --- a/internal/audit/flagbind_test.go +++ b/internal/audit/flagbind_test.go @@ -25,7 +25,7 @@ import ( ) // flagMethods lists cobra flag registration methods -// that must go through internal/flagbind/. +// that must go through internal/flag_bind/. var flagMethods = map[string]bool{ "StringVar": true, "StringVarP": true, @@ -43,7 +43,7 @@ var flagMethods = map[string]bool{ // TestNoFlagBindOutsideFlagbind ensures direct cobra // flag registration (.Flags().StringVar, etc.) only -// appears in internal/flagbind/. All other packages +// appears in internal/flag_bind/. All other packages // must use the flagbind helpers. // // Test files are exempt. @@ -54,7 +54,7 @@ func TestNoFlagBindOutsideFlagbind(t *testing.T) { var violations []string for _, pkg := range pkgs { - if strings.Contains(pkg.PkgPath, "flagbind") { + if strings.Contains(pkg.PkgPath, "flag_bind") { continue } diff --git a/internal/bootstrap/bootstrap_test.go b/internal/bootstrap/bootstrap_test.go index 0e509eba0..a09db12bc 100644 --- a/internal/bootstrap/bootstrap_test.go +++ b/internal/bootstrap/bootstrap_test.go @@ -323,6 +323,15 @@ func TestInitGuard_AllowsInitializedCommand(t *testing.T) { if mkErr := os.MkdirAll(ctxDir, 0o700); mkErr != nil { t.Fatal(mkErr) } + // Phase RG: the root PersistentPreRunE requires a .git/ + // directory under the project root. Without it, every + // subcommand short-circuits with "git working tree + // required". Create an empty .git/ marker to satisfy the + // precondition (no real repo needed for this guard). + gitDir := filepath.Join(tmp, ".git") + if mkGitErr := os.MkdirAll(gitDir, 0o700); mkGitErr != nil { + t.Fatal(mkGitErr) + } // Create required context files so Initialized() returns true. for _, f := range ctx.FilesRequired { diff --git a/internal/bootstrap/cmd.go b/internal/bootstrap/cmd.go index d673fd8e0..377315506 100644 --- a/internal/bootstrap/cmd.go +++ b/internal/bootstrap/cmd.go @@ -9,7 +9,9 @@ package bootstrap import ( + "errors" "os" + "path/filepath" "github.com/spf13/cobra" @@ -20,7 +22,9 @@ import ( embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag" "github.com/ActiveMemory/ctx/internal/config/flag" ctxContext "github.com/ActiveMemory/ctx/internal/context/validate" + errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta" errInit "github.com/ActiveMemory/ctx/internal/err/initialize" + "github.com/ActiveMemory/ctx/internal/git_meta" "github.com/ActiveMemory/ctx/internal/rc" writeBootstrap "github.com/ActiveMemory/ctx/internal/write/bootstrap" ) @@ -102,6 +106,21 @@ func RootCmd() *cobra.Command { return errInit.NotInitialized() } + // Phase RG: require git as architectural precondition. + // The project root is the parent of the declared + // .context/ directory. RequireGitTree refuses when + // <projectRoot>/.git is absent. + projectRoot := filepath.Dir(ctxDir) + if gitErr := gitmeta.RequireGitTree(projectRoot); gitErr != nil { + cmd.SilenceUsage = true + if errors.Is(gitErr, errGitmeta.ErrMissingGitTree) { + return errGitmeta.MissingGitTreeForCmd( + cmd.Name(), projectRoot, + ) + } + return gitErr + } + return nil }, } diff --git a/internal/bootstrap/group.go b/internal/bootstrap/group.go index 395f1571d..298c23810 100644 --- a/internal/bootstrap/group.go +++ b/internal/bootstrap/group.go @@ -20,10 +20,12 @@ import ( "github.com/ActiveMemory/ctx/internal/cli/drift" ctxFmt "github.com/ActiveMemory/ctx/internal/cli/fmt" "github.com/ActiveMemory/ctx/internal/cli/guide" + "github.com/ActiveMemory/ctx/internal/cli/handover" "github.com/ActiveMemory/ctx/internal/cli/hook" cliHub "github.com/ActiveMemory/ctx/internal/cli/hub" "github.com/ActiveMemory/ctx/internal/cli/initialize" "github.com/ActiveMemory/ctx/internal/cli/journal" + "github.com/ActiveMemory/ctx/internal/cli/kb" "github.com/ActiveMemory/ctx/internal/cli/learning" "github.com/ActiveMemory/ctx/internal/cli/load" "github.com/ActiveMemory/ctx/internal/cli/loop" @@ -108,6 +110,8 @@ func artifacts() []registration { {task.Cmd, embedCmd.GroupArtifacts}, {convention.Cmd, embedCmd.GroupArtifacts}, {reindex.Cmd, embedCmd.GroupArtifacts}, + {kb.Cmd, embedCmd.GroupArtifacts}, + {handover.Cmd, embedCmd.GroupArtifacts}, } } diff --git a/internal/cli/add/core/build/build.go b/internal/cli/add/core/build/build.go index 137e92df5..b093fcdc0 100644 --- a/internal/cli/add/core/build/build.go +++ b/internal/cli/add/core/build/build.go @@ -15,7 +15,7 @@ import ( cfgEntry "github.com/ActiveMemory/ctx/internal/config/entry" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/entity" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd builds a cobra add subcommand for the given noun. diff --git a/internal/cli/agent/cmd/root/cmd.go b/internal/cli/agent/cmd/root/cmd.go index 44099c2c5..cd6177561 100644 --- a/internal/cli/agent/cmd/root/cmd.go +++ b/internal/cli/agent/cmd/root/cmd.go @@ -19,7 +19,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/fmt" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/rc" ) diff --git a/internal/cli/agent/core/cooldown/cooldown.go b/internal/cli/agent/core/cooldown/cooldown.go index c8912819a..b80e5e252 100644 --- a/internal/cli/agent/core/cooldown/cooldown.go +++ b/internal/cli/agent/core/cooldown/cooldown.go @@ -88,8 +88,8 @@ func TouchTombstone(session string) error { // - error: non-nil when the context directory is not declared, // the project is not initialized, or the state directory cannot // be created. Delegates to [state.Dir] so the -// [errCtx.ErrNotInitialized] gate applies here too — see -// specs/state-dir-no-mkdir-when-uninitialized.md. Without this, +// [errCtx.ErrNotInitialized] gate applies here too (see +// specs/state-dir-no-mkdir-when-uninitialized.md). Without this, // a hook-driven `ctx agent` invocation in a non-ctx project // (the cross-IDE Cursor leak path) would mkdir `.context/state/` // directly here, bypassing [state.Dir]'s gate. diff --git a/internal/cli/change/cmd/root/cmd.go b/internal/cli/change/cmd/root/cmd.go index f028949e5..4460246de 100644 --- a/internal/cli/change/cmd/root/cmd.go +++ b/internal/cli/change/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the change command. diff --git a/internal/cli/cli_test.go b/internal/cli/cli_test.go index e9db11ffe..04ea819ce 100644 --- a/internal/cli/cli_test.go +++ b/internal/cli/cli_test.go @@ -64,6 +64,13 @@ func TestBinaryIntegration(t *testing.T) { if err := os.Mkdir(testDir, 0750); err != nil { t.Fatalf("failed to create test dir: %v", err) } + // Phase RG: the root PersistentPreRunE refuses every + // command when `<projectRoot>/.git` is absent. The CLI + // integration test simulates a real working tree, so + // stub an empty `.git/` directory to satisfy the guard. + if err := os.Mkdir(filepath.Join(testDir, ".git"), 0750); err != nil { + t.Fatalf("failed to create .git dir: %v", err) + } // Under the explicit-context-dir model each subprocess invocation // must declare CTX_DIR. t.Setenv mutates the current process env diff --git a/internal/cli/compact/cmd/root/cmd.go b/internal/cli/compact/cmd/root/cmd.go index bfd562737..b9db3c530 100644 --- a/internal/cli/compact/cmd/root/cmd.go +++ b/internal/cli/compact/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx compact" command for cleaning up context files. diff --git a/internal/cli/connection/cmd/register/cmd.go b/internal/cli/connection/cmd/register/cmd.go index bc0bbd19d..f6e6a0e07 100644 --- a/internal/cli/connection/cmd/register/cmd.go +++ b/internal/cli/connection/cmd/register/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the connect register subcommand. diff --git a/internal/cli/doctor/cmd/root/cmd.go b/internal/cli/doctor/cmd/root/cmd.go index a2658b247..11d70178b 100644 --- a/internal/cli/doctor/cmd/root/cmd.go +++ b/internal/cli/doctor/cmd/root/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx doctor" command. diff --git a/internal/cli/drift/cmd/root/cmd.go b/internal/cli/drift/cmd/root/cmd.go index 6b402534c..53457202f 100644 --- a/internal/cli/drift/cmd/root/cmd.go +++ b/internal/cli/drift/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx drift" command for detecting stale context. diff --git a/internal/cli/event/cmd.go b/internal/cli/event/cmd.go index 230f98787..1c431e9bb 100644 --- a/internal/cli/event/cmd.go +++ b/internal/cli/event/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" eventCfg "github.com/ActiveMemory/ctx/internal/config/event" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx hook event" command. diff --git a/internal/cli/fmt/cmd/root/cmd.go b/internal/cli/fmt/cmd/root/cmd.go index ac8aa6272..86b90fabe 100644 --- a/internal/cli/fmt/cmd/root/cmd.go +++ b/internal/cli/fmt/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( embedCmd "github.com/ActiveMemory/ctx/internal/config/embed/cmd" embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/wrap" ) diff --git a/internal/cli/guide/cmd/root/cmd.go b/internal/cli/guide/cmd/root/cmd.go index 4c792e03f..15210f17d 100644 --- a/internal/cli/guide/cmd/root/cmd.go +++ b/internal/cli/guide/cmd/root/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx guide" cobra command. diff --git a/internal/cli/handover/cmd.go b/internal/cli/handover/cmd.go new file mode 100644 index 000000000..86806391b --- /dev/null +++ b/internal/cli/handover/cmd.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + writeCmd "github.com/ActiveMemory/ctx/internal/cli/handover/cmd/write" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx handover` parent command. +// +// Returns: +// - *cobra.Command: parent with `write` subcommand registered. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyHandover) + c := &cobra.Command{ + Use: cmd.UseHandover, + Short: short, + Long: long, + } + c.AddCommand(writeCmd.Cmd()) + return c +} diff --git a/internal/cli/handover/cmd/write/cmd.go b/internal/cli/handover/cmd/write/cmd.go new file mode 100644 index 000000000..65031f36d --- /dev/null +++ b/internal/cli/handover/cmd/write/cmd.go @@ -0,0 +1,71 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package write + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" + "github.com/ActiveMemory/ctx/internal/config/embed/flag" + cFlag "github.com/ActiveMemory/ctx/internal/config/flag" + "github.com/ActiveMemory/ctx/internal/flag_bind" +) + +// Cmd returns the `ctx handover write <title>` command. +// +// Returns: +// - *cobra.Command: configured cobra command with required +// --summary and --next flags and placeholder rejection. +func Cmd() *cobra.Command { + var ( + summary string + next string + highlights string + openQuestions string + commit string + noFold bool + ) + + short, long := desc.Command(cmd.DescKeyHandoverWrite) + c := &cobra.Command{ + Use: cmd.UseHandoverWrite, + Short: short, + Long: long, + Args: cobra.ExactArgs(1), + RunE: func(cobraCmd *cobra.Command, args []string) error { + return Run(cobraCmd, args[0], summary, next, + highlights, openQuestions, commit, noFold) + }, + } + + flagbind.StringFlag( + c, &summary, cFlag.Summary, flag.DescKeyHandoverSummary, + ) + flagbind.StringFlag( + c, &next, cFlag.Next, flag.DescKeyHandoverNext, + ) + flagbind.StringFlag( + c, &highlights, cFlag.Highlights, + flag.DescKeyHandoverHighlights, + ) + flagbind.StringFlag( + c, &openQuestions, cFlag.OpenQuestions, + flag.DescKeyHandoverOpenQuestions, + ) + flagbind.StringFlag( + c, &commit, cFlag.Commit, flag.DescKeyHandoverCommit, + ) + flagbind.BoolFlag( + c, &noFold, cFlag.NoFold, flag.DescKeyHandoverNoFold, + ) + + _ = c.MarkFlagRequired(cFlag.Summary) + _ = c.MarkFlagRequired(cFlag.Next) + + return c +} diff --git a/internal/cli/handover/cmd/write/doc.go b/internal/cli/handover/cmd/write/doc.go new file mode 100644 index 000000000..8140953e1 --- /dev/null +++ b/internal/cli/handover/cmd/write/doc.go @@ -0,0 +1,13 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package write implements `ctx handover write <title>`. +// +// Required flags: --summary, --next (placeholder values +// rejected at the CLI layer per Phase SK). +// Optional flags: --highlights, --open-questions, --commit, +// --no-fold. +package write diff --git a/internal/cli/handover/cmd/write/run.go b/internal/cli/handover/cmd/write/run.go new file mode 100644 index 000000000..5c027e930 --- /dev/null +++ b/internal/cli/handover/cmd/write/run.go @@ -0,0 +1,120 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package write + +import ( + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + handoverPath "github.com/ActiveMemory/ctx/internal/cli/handover/core/path" + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cFlag "github.com/ActiveMemory/ctx/internal/config/flag" + "github.com/ActiveMemory/ctx/internal/io" + "github.com/ActiveMemory/ctx/internal/rc" + "github.com/ActiveMemory/ctx/internal/validate" + "github.com/ActiveMemory/ctx/internal/write/handover" +) + +// Run executes the handover write command. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - title: handover title (passed as positional arg). +// - summary: past-tense summary; validated non-placeholder. +// - next: next-session first action; validated +// non-placeholder. +// - highlights: notable artifacts. +// - openQuestions: undecided items. +// - commit: optional commit-override. +// - noFold: when true, skip closeout fold + archive. +// +// Returns: +// - error: rejection or wrapped writer errors. +func Run( + cobraCmd *cobra.Command, + title, summary, next, highlights, openQuestions, commit string, + noFold bool, +) error { + if rejectErr := validate.RejectPlaceholder( + cFlag.Summary, summary, + ); rejectErr != nil { + cobraCmd.SilenceUsage = true + return rejectErr + } + if rejectErr := validate.RejectPlaceholder( + cFlag.Next, next, + ); rejectErr != nil { + cobraCmd.SilenceUsage = true + return rejectErr + } + + handoversDir, hoDirErr := handoverPath.Dir() + if hoDirErr != nil { + return hoDirErr + } + closeoutsDir, coDirErr := kbPath.CloseoutsDir() + if coDirErr != nil { + return coDirErr + } + archiveDir, archiveErr := kbPath.ArchiveCloseoutsDir() + if archiveErr != nil { + return archiveErr + } + ctxDir, ctxErr := rc.ContextDir() + if ctxErr != nil { + return ctxErr + } + projectRoot := filepath.Dir(ctxDir) + + res, writeErr := handover.Write( + handoversDir, closeoutsDir, archiveDir, projectRoot, + handover.Entry{ + Title: title, + Summary: summary, + Next: next, + Highlights: highlights, + OpenQuestions: openQuestions, + CommitOverride: commit, + NoFold: noFold, + }, + ) + if writeErr != nil { + cobraCmd.SilenceUsage = true + return writeErr + } + + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteHandoverWrote), + res.File.Path, + ) + if len(res.FoldedCloseouts) > 0 { + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteHandoverFolded), + len(res.FoldedCloseouts), archiveDir, + ) + } + if len(res.MalformedCloseouts) > 0 { + io.SafeFprintf( + cobraCmd.ErrOrStderr(), + desc.Text(text.DescKeyWriteHandoverMalformedWarning), + len(res.MalformedCloseouts), + ) + for _, p := range res.MalformedCloseouts { + io.SafeFprintf( + cobraCmd.ErrOrStderr(), + desc.Text(text.DescKeyWriteHandoverMalformedLine), + p, + ) + } + } + return nil +} diff --git a/internal/cli/handover/core/path/doc.go b/internal/cli/handover/core/path/doc.go new file mode 100644 index 000000000..947c8726e --- /dev/null +++ b/internal/cli/handover/core/path/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package path resolves on-disk locations for the handover +// mechanism. Handover is the session-to-session glue created +// by `/ctx-wrap-up` and read by `/ctx-remember`; it is +// universal to every ctx project and does not depend on the +// editorial pipeline. +// +// # Public Surface +// +// - [Dir]: returns the `.context/handovers/` directory. +// +// The closeout-archive directory used by the optional fold +// pass lives under +// [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// (closeouts themselves are editorial-pipeline artifacts; the +// handover writer happens to be their consumer when KB is +// present). +package path diff --git a/internal/cli/handover/core/path/path.go b/internal/cli/handover/core/path/path.go new file mode 100644 index 000000000..392303810 --- /dev/null +++ b/internal/cli/handover/core/path/path.go @@ -0,0 +1,27 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path + +import ( + "path/filepath" + + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" + "github.com/ActiveMemory/ctx/internal/rc" +) + +// Dir returns the `.context/handovers/` directory. +// +// Returns: +// - string: full path to .context/handovers/. +// - error: non-nil when the context directory is not declared. +func Dir() (string, error) { + ctxDir, err := rc.ContextDir() + if err != nil { + return "", err + } + return filepath.Join(ctxDir, cfgHandover.Subdir), nil +} diff --git a/internal/cli/handover/core/path/path_test.go b/internal/cli/handover/core/path/path_test.go new file mode 100644 index 000000000..07f0256ba --- /dev/null +++ b/internal/cli/handover/core/path/path_test.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path_test + +import ( + "os" + "path/filepath" + "testing" + + handoverPath "github.com/ActiveMemory/ctx/internal/cli/handover/core/path" + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" +) + +// canonicalCtxDir builds a CTX_DIR honoring the rc-required +// `.context` basename and points the env var at it. +func canonicalCtxDir(t *testing.T) string { + t.Helper() + root := t.TempDir() + ctxDir := filepath.Join(root, ".context") + if err := os.MkdirAll(ctxDir, 0o755); err != nil { + t.Fatalf("setup: %v", err) + } + t.Setenv("CTX_DIR", ctxDir) + return ctxDir +} + +func TestDir(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := handoverPath.Dir() + if err != nil { + t.Fatalf("Dir: %v", err) + } + want := filepath.Join(ctxDir, cfgHandover.Subdir) + if got != want { + t.Errorf("Dir: want %q; got %q", want, got) + } +} diff --git a/internal/cli/handover/core/path/testmain_test.go b/internal/cli/handover/core/path/testmain_test.go new file mode 100644 index 000000000..2f3cfd019 --- /dev/null +++ b/internal/cli/handover/core/path/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/cli/handover/doc.go b/internal/cli/handover/doc.go new file mode 100644 index 000000000..459e42641 --- /dev/null +++ b/internal/cli/handover/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package handover wires the `ctx handover` parent command +// and its `write` subcommand. +// +// `ctx handover write` is the per-session handover writer for +// the ctx knowledge-base editorial pipeline (Phase KB). It +// folds postdated closeouts into the new handover by default +// and archives the source closeouts; `--no-fold` skips the +// fold for mid-session checkpoints. +// +// See [github.com/ActiveMemory/ctx/internal/write/handover] +// for the writer implementation. +package handover diff --git a/internal/cli/hub/cmd/start/cmd.go b/internal/cli/hub/cmd/start/cmd.go index 7d1ba48e7..934af5d40 100644 --- a/internal/cli/hub/cmd/start/cmd.go +++ b/internal/cli/hub/cmd/start/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the hub start subcommand. diff --git a/internal/cli/hub/cmd/stop/cmd.go b/internal/cli/hub/cmd/stop/cmd.go index 529b0be55..771d4695d 100644 --- a/internal/cli/hub/cmd/stop/cmd.go +++ b/internal/cli/hub/cmd/stop/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the hub stop subcommand. diff --git a/internal/cli/initialize/cmd/root/cmd.go b/internal/cli/initialize/cmd/root/cmd.go index 27b013abf..c4897a9a9 100644 --- a/internal/cli/initialize/cmd/root/cmd.go +++ b/internal/cli/initialize/cmd/root/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx init" command for initializing a .context/ directory. diff --git a/internal/cli/initialize/cmd/root/run.go b/internal/cli/initialize/cmd/root/run.go index 94d391f2e..e321774ed 100644 --- a/internal/cli/initialize/cmd/root/run.go +++ b/internal/cli/initialize/cmd/root/run.go @@ -22,6 +22,7 @@ import ( coreClaude "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude" coreCC "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude_check" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/entry" + coreKB "github.com/ActiveMemory/ctx/internal/cli/initialize/core/kb" coreMerge "github.com/ActiveMemory/ctx/internal/cli/initialize/core/merge" corePad "github.com/ActiveMemory/ctx/internal/cli/initialize/core/pad" "github.com/ActiveMemory/ctx/internal/cli/initialize/core/plugin" @@ -251,6 +252,17 @@ func Run( initialize.InfoWarnNonFatal(cmd, label, padErr) } + // Phase KB: scaffold the editorial-pipeline directories + // and copy embedded templates (KB-RULES, mode prompts, + // schemas, kb landing). Per-file existence is preserved. + if kbErr := coreKB.Scaffold(contextDir); kbErr != nil { + // Non-fatal: warn but continue. The KB surfaces are + // opt-in for users who run /ctx-kb-ingest et al; a + // scaffold failure should not block init. + kbLabel := desc.Text(text.DescKeyInitLabelKB) + initialize.InfoWarnNonFatal(cmd, kbLabel, kbErr) + } + // Create project root files initialize.InfoCreatingRootFiles(cmd) diff --git a/internal/cli/initialize/core/kb/copy.go b/internal/cli/initialize/core/kb/copy.go new file mode 100644 index 000000000..fe1c2d692 --- /dev/null +++ b/internal/cli/initialize/core/kb/copy.go @@ -0,0 +1,75 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +import ( + "io/fs" + "path/filepath" + + "github.com/ActiveMemory/ctx/internal/assets" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + "github.com/ActiveMemory/ctx/internal/config/token" + errInitKB "github.com/ActiveMemory/ctx/internal/err/initialize/kb" + "github.com/ActiveMemory/ctx/internal/io" +) + +// CopyEmbedTree copies every regular file at the top level of +// the embedded directory at assetPath into dstDir. Subdirs are +// not recursed (callers pass leaf directories). +// +// Parameters: +// - assetPath: directory under assets.FS (no leading slash). +// - dstDir: destination directory on disk. +// +// Returns: +// - error: wrapped errors on read / write failure. +func CopyEmbedTree(assetPath, dstDir string) error { + entries, readErr := fs.ReadDir(assets.FS, assetPath) + if readErr != nil { + return errInitKB.ReadEmbed(assetPath, readErr) + } + for _, e := range entries { + if e.IsDir() { + continue + } + src := assetPath + token.Slash + e.Name() + dst := filepath.Join(dstDir, e.Name()) + if copyErr := CopyEmbedFile(src, dst); copyErr != nil { + return copyErr + } + } + return nil +} + +// CopyEmbedFile copies a single embedded file to dst. Skips +// silently when dst already exists (init preserves curated +// content). +// +// Parameters: +// - assetPath: file under assets.FS. +// - dst: destination path. +// +// Returns: +// - error: wrapped errors on read / write failure. +func CopyEmbedFile(assetPath, dst string) error { + if _, statErr := io.SafeStat(dst); statErr == nil { + return nil + } + raw, readErr := fs.ReadFile(assets.FS, assetPath) + if readErr != nil { + return errInitKB.ReadEmbed(assetPath, readErr) + } + mkErr := io.SafeMkdirAll(filepath.Dir(dst), cfgFs.PermExec) + if mkErr != nil { + return errInitKB.MkdirFor(dst, mkErr) + } + writeErr := io.SafeWriteFile(dst, raw, cfgFs.PermSecret) + if writeErr != nil { + return errInitKB.WriteFile(dst, writeErr) + } + return nil +} diff --git a/internal/cli/initialize/core/kb/doc.go b/internal/cli/initialize/core/kb/doc.go new file mode 100644 index 000000000..048f25a21 --- /dev/null +++ b/internal/cli/initialize/core/kb/doc.go @@ -0,0 +1,31 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kb extracts the embedded KB editorial-pipeline +// templates from [github.com/ActiveMemory/ctx/internal/assets] +// into a freshly-initialized .context/ directory. +// +// Layout written (relative to contextDir): +// +// kb/ +// ├── index.md (from kb/templates/kb/index.md) +// └── topics/.gitkeep +// ingest/ +// ├── KB-RULES.md +// ├── 00-GROUND.md +// ├── 30-INGEST.md +// ├── 40-ASK.md +// ├── 50-SITE_REVIEW.md +// ├── OPERATOR.md +// ├── PROMPT.md +// ├── closeouts/.gitkeep +// └── schemas/ +// └── *.md (10 files) +// handovers/.gitkeep +// +// Existing files are left untouched (init never overwrites +// curated content). +package kb diff --git a/internal/cli/initialize/core/kb/gitkeep.go b/internal/cli/initialize/core/kb/gitkeep.go new file mode 100644 index 000000000..580c6b7ec --- /dev/null +++ b/internal/cli/initialize/core/kb/gitkeep.go @@ -0,0 +1,53 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +import ( + "os" + "path/filepath" + "strings" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgInitKB "github.com/ActiveMemory/ctx/internal/config/initialize/kb" + "github.com/ActiveMemory/ctx/internal/config/token" + errInitKB "github.com/ActiveMemory/ctx/internal/err/initialize/kb" + "github.com/ActiveMemory/ctx/internal/io" +) + +// PlaceGitkeep adds a .gitkeep stub to dir when the directory +// is empty (so an empty kb-state subdirectory survives git +// add). +// +// Parameters: +// - dir: directory to stub. +// +// Returns: +// - error: wrapped error on read / write failure. +func PlaceGitkeep(dir string) error { + entries, readErr := os.ReadDir(dir) + if readErr != nil { + return errInitKB.ReadDir(dir, readErr) + } + for _, e := range entries { + if !strings.HasPrefix(e.Name(), token.Dot) { + return nil + } + if e.Name() != cfgInitKB.GitkeepName && !e.IsDir() { + return nil + } + } + path := filepath.Join(dir, cfgInitKB.GitkeepName) + if _, statErr := io.SafeStat(path); statErr == nil { + return nil + } + if writeErr := io.SafeWriteFile( + path, nil, cfgFs.PermSecret, + ); writeErr != nil { + return errInitKB.WriteFile(path, writeErr) + } + return nil +} diff --git a/internal/cli/initialize/core/kb/scaffold.go b/internal/cli/initialize/core/kb/scaffold.go new file mode 100644 index 000000000..7e2f34706 --- /dev/null +++ b/internal/cli/initialize/core/kb/scaffold.go @@ -0,0 +1,97 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +import ( + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errInitKB "github.com/ActiveMemory/ctx/internal/err/initialize/kb" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Scaffold extracts the embedded KB / ingest / handover +// templates into contextDir. Existing files are skipped +// (preserves curated content). +// +// Parameters: +// - contextDir: absolute path to .context/. +// +// Returns: +// - error: wrapped errors for directory create / file copy +// failures. Per-file existence is non-fatal (skipped with +// a noop). +func Scaffold(contextDir string) error { + dirs := []string{ + filepath.Join(contextDir, cfgKB.KBSubdir), + filepath.Join(contextDir, cfgKB.KBSubdir, cfgKB.TopicsSubdir), + filepath.Join(contextDir, cfgKB.IngestSubdir), + filepath.Join( + contextDir, cfgKB.IngestSubdir, cfgKB.CloseoutsSubdir, + ), + filepath.Join( + contextDir, cfgKB.IngestSubdir, cfgKB.SchemasSubdir, + ), + filepath.Join(contextDir, cfgHandover.Subdir), + } + for _, d := range dirs { + if mkErr := io.SafeMkdirAll(d, cfgFs.PermExec); mkErr != nil { + return errInitKB.Mkdir(d, mkErr) + } + } + + // Place .gitkeep stubs so the empty dirs round-trip + // through git. Skipped silently when any file in the dir + // already exists. + gitkeepDirs := []string{ + filepath.Join(contextDir, cfgKB.KBSubdir, cfgKB.TopicsSubdir), + filepath.Join( + contextDir, cfgKB.IngestSubdir, cfgKB.CloseoutsSubdir, + ), + filepath.Join(contextDir, cfgHandover.Subdir), + } + for _, d := range gitkeepDirs { + if gkErr := PlaceGitkeep(d); gkErr != nil { + return gkErr + } + } + + // Copy embedded ingest-side templates. + ingestErr := CopyEmbedTree( + cfgKB.AssetTemplatesIngest, + filepath.Join(contextDir, cfgKB.IngestSubdir), + ) + if ingestErr != nil { + return errInitKB.CopyIngestTemplates(ingestErr) + } + + // Copy embedded schemas. + schemasErr := CopyEmbedTree( + cfgKB.AssetTemplatesSchemas, + filepath.Join( + contextDir, cfgKB.IngestSubdir, cfgKB.SchemasSubdir, + ), + ) + if schemasErr != nil { + return errInitKB.CopySchemas(schemasErr) + } + + // Copy embedded kb landing. + landingErr := CopyEmbedFile( + cfgKB.AssetTemplateKBIndex, + filepath.Join( + contextDir, cfgKB.KBSubdir, cfgKB.KBIndex, + ), + ) + if landingErr != nil { + return errInitKB.CopyLanding(landingErr) + } + + return nil +} diff --git a/internal/cli/journal/cmd/importer/cmd.go b/internal/cli/journal/cmd/importer/cmd.go index 825da6f60..66c1fd26e 100644 --- a/internal/cli/journal/cmd/importer/cmd.go +++ b/internal/cli/journal/cmd/importer/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/entity" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the journal import subcommand. diff --git a/internal/cli/journal/cmd/lock/cmd.go b/internal/cli/journal/cmd/lock/cmd.go index e0e8a1220..800a3557a 100644 --- a/internal/cli/journal/cmd/lock/cmd.go +++ b/internal/cli/journal/cmd/lock/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx journal lock" subcommand. diff --git a/internal/cli/journal/cmd/obsidian/cmd.go b/internal/cli/journal/cmd/obsidian/cmd.go index cd9f4979d..be4e33dc7 100644 --- a/internal/cli/journal/cmd/obsidian/cmd.go +++ b/internal/cli/journal/cmd/obsidian/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the journal obsidian subcommand. diff --git a/internal/cli/journal/cmd/schema/check/cmd.go b/internal/cli/journal/cmd/schema/check/cmd.go index c8eb90f5a..0bc2624fb 100644 --- a/internal/cli/journal/cmd/schema/check/cmd.go +++ b/internal/cli/journal/cmd/schema/check/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the journal schema check subcommand. diff --git a/internal/cli/journal/cmd/site/cmd.go b/internal/cli/journal/cmd/site/cmd.go index 045421334..2a8e4e67a 100644 --- a/internal/cli/journal/cmd/site/cmd.go +++ b/internal/cli/journal/cmd/site/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the journal site subcommand. diff --git a/internal/cli/journal/cmd/source/cmd.go b/internal/cli/journal/cmd/source/cmd.go index dce783c3d..5c4deffdc 100644 --- a/internal/cli/journal/cmd/source/cmd.go +++ b/internal/cli/journal/cmd/source/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/journal" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the journal source subcommand. diff --git a/internal/cli/journal/cmd/unlock/cmd.go b/internal/cli/journal/cmd/unlock/cmd.go index 979546633..022d20941 100644 --- a/internal/cli/journal/cmd/unlock/cmd.go +++ b/internal/cli/journal/cmd/unlock/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx journal unlock" subcommand. diff --git a/internal/cli/journal/core/plan/plan.go b/internal/cli/journal/core/plan/plan.go index cc34b9390..bd8ad1770 100644 --- a/internal/cli/journal/core/plan/plan.go +++ b/internal/cli/journal/core/plan/plan.go @@ -14,7 +14,6 @@ import ( "github.com/ActiveMemory/ctx/internal/assets/tpl" "github.com/ActiveMemory/ctx/internal/cli/journal/core/index" "github.com/ActiveMemory/ctx/internal/cli/journal/core/lock" - "github.com/ActiveMemory/ctx/internal/cli/journal/core/slug" srcFmt "github.com/ActiveMemory/ctx/internal/cli/journal/core/source/format" "github.com/ActiveMemory/ctx/internal/cli/journal/core/validate" "github.com/ActiveMemory/ctx/internal/config/file" @@ -23,6 +22,7 @@ import ( "github.com/ActiveMemory/ctx/internal/entity" "github.com/ActiveMemory/ctx/internal/io" "github.com/ActiveMemory/ctx/internal/journal/state" + "github.com/ActiveMemory/ctx/internal/slug" ) // Import builds an ImportPlan without writing any files. diff --git a/internal/cli/journal/core/slug/doc.go b/internal/cli/journal/core/slug/doc.go deleted file mode 100644 index c3a7e4340..000000000 --- a/internal/cli/journal/core/slug/doc.go +++ /dev/null @@ -1,47 +0,0 @@ -// / ctx: https://ctx.ist -// ,'`./ do you remember? -// `.,'\ -// \ Copyright 2026-present Context contributors. -// SPDX-License-Identifier: Apache-2.0 - -// Package slug generates **URL-safe, filesystem-safe -// identifiers** from session titles and handles the -// deduplication logic that keeps two sessions with the -// same title from colliding on disk. -// -// Slugs are how journal entries are addressed throughout -// ctx: filenames are `YYYY-MM-DD-<slug>.md`, links use -// the slug as the path component, and `ctx journal source -// --show <slug>` looks up by slug. -// -// # Public Surface -// -// - **[FromTitle](title)**: converts a title to a -// lowercase, hyphenated, alphanumeric slug. -// Strips punctuation, collapses runs of separators, -// trims leading/trailing hyphens. Idempotent. -// - **[CleanTitle](title)**: strips non-alphanumeric -// characters from a display title (kept for the -// filename's human-readable suffix when one is -// wanted in addition to the slug). -// - **[ForTitle](title, existing)**: the dedup-aware -// wrapper: produces both a slug and a cleaned -// title, appending `-2`, `-3`, etc. when the -// base slug already exists in `existing`. Used by -// the importer when two sessions share a topic -// summary. -// -// # Stability Contract -// -// The slug for a given (title, dedup-context) pair is -// **deterministic**: re-running the importer against -// the same source produces the same slug. This is -// what makes the importer idempotent and what lets -// `git diff` show a meaningful patch when an entry -// is re-enriched. -// -// # Concurrency -// -// All functions are pure. Concurrent callers never -// race. -package slug diff --git a/internal/cli/kb/cmd/ask/cmd.go b/internal/cli/kb/cmd/ask/cmd.go new file mode 100644 index 000000000..d1b235c74 --- /dev/null +++ b/internal/cli/kb/cmd/ask/cmd.go @@ -0,0 +1,35 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ask + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// Cmd returns the `ctx kb ask` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBAsk) + return &cobra.Command{ + Use: cmd.UseKBAsk, + Short: short, + Long: long, + Args: cobra.ArbitraryArgs, + RunE: func(cobraCmd *cobra.Command, args []string) error { + q := strings.TrimSpace(strings.Join(args, token.Space)) + return Run(cobraCmd, q, args) + }, + } +} diff --git a/internal/cli/kb/cmd/ask/doc.go b/internal/cli/kb/cmd/ask/doc.go new file mode 100644 index 000000000..70aa3a672 --- /dev/null +++ b/internal/cli/kb/cmd/ask/doc.go @@ -0,0 +1,24 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package ask implements `ctx kb ask "<question>"`. +// +// The Q&A pass itself is performed by the /ctx-kb-ask skill; +// this CLI surface validates input and prints the canonical +// invocation so non-Claude users see the workflow as well. +// +// # Refusal contract +// +// - Empty question (no positional args or whitespace-only): +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrAskNoQuestion]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the hint and refusal strings. +// - [github.com/ActiveMemory/ctx/internal/err/kb/cli] supplies the +// sentinel constructors. +package ask diff --git a/internal/cli/kb/cmd/ask/run.go b/internal/cli/kb/cmd/ask/run.go new file mode 100644 index 000000000..c445518d3 --- /dev/null +++ b/internal/cli/kb/cmd/ask/run.go @@ -0,0 +1,52 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ask + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + "github.com/ActiveMemory/ctx/internal/config/token" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run validates a non-empty question and prints the canonical +// `/ctx-kb-ask` skill invocation. The CLI surface itself does +// not perform the Q&A pass; the skill does. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - question: trimmed question text (empty is rejected). +// - args: raw positional args, joined for the printed +// skill invocation. +// +// Returns: +// - error: refusal on empty question. +func Run(cobraCmd *cobra.Command, question string, args []string) error { + if question == "" { + cobraCmd.SilenceUsage = true + return errKbCli.ErrAskNoQuestion + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliAskDrivenHint), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteKbCliAskInvokeFormat), + strings.Join(args, token.Space), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliAskContractPointer), + ) + return nil +} diff --git a/internal/cli/kb/cmd/ground/cmd.go b/internal/cli/kb/cmd/ground/cmd.go new file mode 100644 index 000000000..fb681c278 --- /dev/null +++ b/internal/cli/kb/cmd/ground/cmd.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ground + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb ground` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBGround) + return &cobra.Command{ + Use: cmd.UseKBGround, + Short: short, + Long: long, + RunE: func(cobraCmd *cobra.Command, _ []string) error { + return Run(cobraCmd) + }, + } +} diff --git a/internal/cli/kb/cmd/ground/doc.go b/internal/cli/kb/cmd/ground/doc.go new file mode 100644 index 000000000..49e2d67b5 --- /dev/null +++ b/internal/cli/kb/cmd/ground/doc.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package ground implements `ctx kb ground`. +// +// Refreshes sources listed in .context/ingest/grounding-sources.md. +// The pass itself is performed by the /ctx-kb-ground skill; this +// CLI surface verifies that the grounding sources file exists and +// is non-empty before printing the canonical skill invocation. +// +// # Refusal contract +// +// - Missing grounding-sources.md: [GroundingMissing] wraps an +// errors.Is-friendly refusal carrying the resolved path. +// - Empty grounding-sources.md: [GroundingEmpty] wraps the same. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the hint and refusal strings. +// - [github.com/ActiveMemory/ctx/internal/err/kb/cli] supplies the +// refusal constructors. +package ground diff --git a/internal/cli/kb/cmd/ground/run.go b/internal/cli/kb/cmd/ground/run.go new file mode 100644 index 000000000..0e10f35e5 --- /dev/null +++ b/internal/cli/kb/cmd/ground/run.go @@ -0,0 +1,63 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ground + +import ( + "errors" + "os" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/config/token" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run validates the grounding-sources file and prints the +// canonical `/ctx-kb-ground` skill invocation. The CLI +// surface itself does not perform the re-grounding pass; +// the skill does. +// +// Parameters: +// - cobraCmd: cobra command for output. +// +// Returns: +// - error: refusal when `grounding-sources.md` is missing +// or empty. +func Run(cobraCmd *cobra.Command) error { + gPath, pathErr := kbPath.IngestArtifactFile( + cfgKB.GroundingSources, + ) + if pathErr != nil { + return pathErr + } + info, statErr := io.SafeStat(gPath) + if errors.Is(statErr, os.ErrNotExist) { + cobraCmd.SilenceUsage = true + return errKbCli.GroundingMissing(gPath) + } + if statErr != nil { + return statErr + } + if info.Size() == 0 { + cobraCmd.SilenceUsage = true + return errKbCli.GroundingEmpty(gPath) + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliGroundDrivenHint), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliGroundContractPointer), + ) + return nil +} diff --git a/internal/cli/kb/cmd/ingest/cmd.go b/internal/cli/kb/cmd/ingest/cmd.go new file mode 100644 index 000000000..886cdc8e0 --- /dev/null +++ b/internal/cli/kb/cmd/ingest/cmd.go @@ -0,0 +1,29 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ingest + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb ingest` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBIngest) + return &cobra.Command{ + Use: cmd.UseKBIngest, + Short: short, + Long: long, + Args: cobra.ArbitraryArgs, + RunE: Run, + } +} diff --git a/internal/cli/kb/cmd/ingest/doc.go b/internal/cli/kb/cmd/ingest/doc.go new file mode 100644 index 000000000..1d0b559f8 --- /dev/null +++ b/internal/cli/kb/cmd/ingest/doc.go @@ -0,0 +1,25 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package ingest implements `ctx kb ingest`. +// +// The editorial pass itself is performed by the /ctx-kb-ingest +// skill (the agent reads .context/ingest/30-INGEST.md and follows +// the pass-mode + circuit-breaker contract). This CLI surface +// surfaces the workflow to non-Claude users (printing the +// canonical invocation), and refuses cleanly on empty input so +// the contract is the same in both surfaces. +// +// # Refusal contract +// +// - No source argument: +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrIngestNoSources]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the hint strings. +package ingest diff --git a/internal/cli/kb/cmd/ingest/run.go b/internal/cli/kb/cmd/ingest/run.go new file mode 100644 index 000000000..30c30fec6 --- /dev/null +++ b/internal/cli/kb/cmd/ingest/run.go @@ -0,0 +1,52 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package ingest + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + "github.com/ActiveMemory/ctx/internal/config/token" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run validates non-empty source args and prints the +// canonical `/ctx-kb-ingest` skill invocation. The CLI +// surface itself does not perform the editorial pass; the +// skill does. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - args: positional source arguments (paths, URLs, or +// inline gestures). Empty is rejected. +// +// Returns: +// - error: refusal when no sources were supplied. +func Run(cobraCmd *cobra.Command, args []string) error { + if len(args) == 0 { + cobraCmd.SilenceUsage = true + return errKbCli.ErrIngestNoSources + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliIngestDrivenHint), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteKbCliIngestInvokeFormat), + strings.Join(args, token.Space), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliIngestFallbackHint), + ) + return nil +} diff --git a/internal/cli/kb/cmd/note/cmd.go b/internal/cli/kb/cmd/note/cmd.go new file mode 100644 index 000000000..d61d9cda3 --- /dev/null +++ b/internal/cli/kb/cmd/note/cmd.go @@ -0,0 +1,35 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package note + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// Cmd returns the `ctx kb note` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBNote) + return &cobra.Command{ + Use: cmd.UseKBNote, + Short: short, + Long: long, + Args: cobra.ArbitraryArgs, + RunE: func(cobraCmd *cobra.Command, args []string) error { + text := strings.TrimSpace(strings.Join(args, token.Space)) + return Run(cobraCmd, text) + }, + } +} diff --git a/internal/cli/kb/cmd/note/doc.go b/internal/cli/kb/cmd/note/doc.go new file mode 100644 index 000000000..22064ad28 --- /dev/null +++ b/internal/cli/kb/cmd/note/doc.go @@ -0,0 +1,22 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package note implements `ctx kb note "<text>"`. +// +// Appends a one-liner to .context/ingest/findings.md. Never +// writes to a topic page or evidence-index. Use this when you +// want to park a finding for the next ingest pass to absorb. +// +// # Refusal contract +// +// - Empty text: +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrNoteNoText]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the format and refusal strings. +package note diff --git a/internal/cli/kb/cmd/note/run.go b/internal/cli/kb/cmd/note/run.go new file mode 100644 index 000000000..21f5269e1 --- /dev/null +++ b/internal/cli/kb/cmd/note/run.go @@ -0,0 +1,67 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package note + +import ( + "fmt" + "path/filepath" + "time" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run appends a timestamped one-liner to +// `.context/ingest/findings.md`. Refuses on empty input. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - noteText: free-text note body (trimmed; empty rejected). +// +// Returns: +// - error: refusal on empty input, or wrapped I/O failure. +func Run(cobraCmd *cobra.Command, noteText string) error { + if noteText == "" { + cobraCmd.SilenceUsage = true + return errKbCli.ErrNoteNoText + } + ingestDir, dirErr := kbPath.IngestDir() + if dirErr != nil { + return dirErr + } + findings := filepath.Join(ingestDir, cfgKB.Findings) + mkErr := io.SafeMkdirAll(ingestDir, cfgFs.PermExec) + if mkErr != nil { + return errKbCli.MkdirIngest(mkErr) + } + f, openErr := io.SafeAppendFile(findings, cfgFs.PermSecret) + if openErr != nil { + return errKbCli.OpenFindings(openErr) + } + defer func() { _ = f.Close() }() + + stamp := time.Now().UTC().Format(time.RFC3339) + line := fmt.Sprintf( + desc.Text(text.DescKeyWriteKbCliFindingLine), stamp, noteText, + ) + if _, writeErr := f.WriteString(line); writeErr != nil { + return errKbCli.WriteFinding(writeErr) + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteKbCliAppendedTo), + findings, + ) + return nil +} diff --git a/internal/cli/kb/cmd/reindex/cmd.go b/internal/cli/kb/cmd/reindex/cmd.go new file mode 100644 index 000000000..c30da1038 --- /dev/null +++ b/internal/cli/kb/cmd/reindex/cmd.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package reindex + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb reindex` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBReindex) + return &cobra.Command{ + Use: cmd.UseKBReindex, + Short: short, + Long: long, + RunE: func(cobraCmd *cobra.Command, _ []string) error { + return Run(cobraCmd) + }, + } +} diff --git a/internal/cli/kb/cmd/reindex/doc.go b/internal/cli/kb/cmd/reindex/doc.go new file mode 100644 index 000000000..3fd5249e6 --- /dev/null +++ b/internal/cli/kb/cmd/reindex/doc.go @@ -0,0 +1,25 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package reindex implements `ctx kb reindex`. The command +// refreshes the CTX:KB:TOPICS managed block inside +// `.context/kb/index.md` so the kb landing page enumerates +// current topic folders. +// +// # Refusal contract +// +// - Landing page missing the CTX:KB:TOPICS block: +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrReindexMissingBlock]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/reindex] +// hosts [ListTopics] and [RenderBlock]. +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the marker and format strings. +// - [github.com/ActiveMemory/ctx/internal/config/regex] hosts the +// greedy [regex.ManagedKBTopics] matcher. +package reindex diff --git a/internal/cli/kb/cmd/reindex/run.go b/internal/cli/kb/cmd/reindex/run.go new file mode 100644 index 000000000..d89607da9 --- /dev/null +++ b/internal/cli/kb/cmd/reindex/run.go @@ -0,0 +1,72 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package reindex + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + reindexCore "github.com/ActiveMemory/ctx/internal/cli/kb/core/reindex" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + "github.com/ActiveMemory/ctx/internal/config/regex" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run rebuilds the managed block from the current topic +// folders. +// +// Parameters: +// - cobraCmd: cobra command for output. +// +// Returns: +// - error: I/O or parse failure. +func Run(cobraCmd *cobra.Command) error { + indexPath, pathErr := kbPath.KBIndexFile() + if pathErr != nil { + return pathErr + } + topicsDir, topicsErr := kbPath.KBTopicsDir() + if topicsErr != nil { + return topicsErr + } + + slugs, listErr := reindexCore.ListTopics(topicsDir) + if listErr != nil { + return listErr + } + + raw, readErr := io.SafeReadUserFile(indexPath) + if readErr != nil { + return errKbCli.ReadKBIndex(readErr) + } + if !regex.ManagedKBTopics.MatchString(string(raw)) { + cobraCmd.SilenceUsage = true + return errKbCli.ErrReindexMissingBlock + } + block := reindexCore.RenderBlock(slugs) + updated := regex.ManagedKBTopics.ReplaceAllString( + string(raw), block, + ) + writeErr := io.SafeWriteFile( + indexPath, + []byte(updated), + cfgFs.PermSecret, + ) + if writeErr != nil { + return errKbCli.WriteKBIndex(writeErr) + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteKbCliReindexed), + len(slugs), + indexPath, + ) + return nil +} diff --git a/internal/cli/kb/cmd/sitereview/cmd.go b/internal/cli/kb/cmd/sitereview/cmd.go new file mode 100644 index 000000000..4c5aaa392 --- /dev/null +++ b/internal/cli/kb/cmd/sitereview/cmd.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sitereview + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb site-review` command. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBSiteReview) + return &cobra.Command{ + Use: cmd.UseKBSiteReview, + Short: short, + Long: long, + RunE: func(cobraCmd *cobra.Command, _ []string) error { + return Run(cobraCmd) + }, + } +} diff --git a/internal/cli/kb/cmd/sitereview/doc.go b/internal/cli/kb/cmd/sitereview/doc.go new file mode 100644 index 000000000..36a7032b5 --- /dev/null +++ b/internal/cli/kb/cmd/sitereview/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sitereview implements `ctx kb site-review`. +// +// The mechanical structural audit itself is performed by the +// /ctx-kb-site-review skill; this CLI surface prints the +// canonical invocation and points operators at the contract. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the hint strings. +package sitereview diff --git a/internal/cli/kb/cmd/sitereview/run.go b/internal/cli/kb/cmd/sitereview/run.go new file mode 100644 index 000000000..873c76a34 --- /dev/null +++ b/internal/cli/kb/cmd/sitereview/run.go @@ -0,0 +1,38 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sitereview + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + "github.com/ActiveMemory/ctx/internal/config/token" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Run prints the canonical `/ctx-kb-site-review` skill +// invocation. The CLI surface itself does not perform the +// structural audit; the skill does. +// +// Parameters: +// - cobraCmd: cobra command for output. +// +// Returns: +// - error: always nil (the pointer is informational; the +// skill carries the refusal contract). +func Run(cobraCmd *cobra.Command) error { + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliSiteReviewDrivenHint), + ) + io.SafeFprintf( + cobraCmd.OutOrStdout(), token.FormatString, + desc.Text(text.DescKeyWriteKbCliSiteReviewContractPointer), + ) + return nil +} diff --git a/internal/cli/kb/cmd/topic/cmd.go b/internal/cli/kb/cmd/topic/cmd.go new file mode 100644 index 000000000..3381402c2 --- /dev/null +++ b/internal/cli/kb/cmd/topic/cmd.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package topic + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/topic/cmd/newcmd" + "github.com/ActiveMemory/ctx/internal/cli/parent" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb topic` parent command with the +// `new` subcommand registered. +// +// Returns: +// - *cobra.Command: topic parent with `new` registered. +func Cmd() *cobra.Command { + return parent.Cmd(cmd.DescKeyKBTopic, cmd.UseKBTopic, + newcmd.Cmd(), + ) +} diff --git a/internal/cli/kb/cmd/topic/cmd/newcmd/cmd.go b/internal/cli/kb/cmd/topic/cmd/newcmd/cmd.go new file mode 100644 index 000000000..8ccf0e65e --- /dev/null +++ b/internal/cli/kb/cmd/topic/cmd/newcmd/cmd.go @@ -0,0 +1,35 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package newcmd + +import ( + "strings" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// Cmd returns the `new` subcommand under `ctx kb topic`. +// +// Returns: +// - *cobra.Command: configured command. +func Cmd() *cobra.Command { + short, long := desc.Command(cmd.DescKeyKBTopicNew) + return &cobra.Command{ + Use: cmd.UseKBTopicNew, + Short: short, + Long: long, + Args: cobra.MinimumNArgs(1), + RunE: func(cobraCmd *cobra.Command, args []string) error { + name := strings.Join(args, token.Space) + return Run(cobraCmd, name) + }, + } +} diff --git a/internal/cli/kb/cmd/topic/cmd/newcmd/doc.go b/internal/cli/kb/cmd/topic/cmd/newcmd/doc.go new file mode 100644 index 000000000..4861c6167 --- /dev/null +++ b/internal/cli/kb/cmd/topic/cmd/newcmd/doc.go @@ -0,0 +1,24 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package newcmd implements the `new` leaf under +// `ctx kb topic`. Calls into the kb/core/topic helpers to +// scaffold a topic-page folder from the embedded template. +// +// # Refusal contract +// +// - Empty / placeholder name reducing to an empty slug: +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrTopicEmptyName]. +// - Topic already exists: +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.TopicExists]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/topic] +// hosts [topic.Scaffold] and [topic.Substitute]. +// - [github.com/ActiveMemory/ctx/internal/slug] supplies the +// slug-derivation primitive ([slug.Path]). +package newcmd diff --git a/internal/cli/kb/cmd/topic/cmd/newcmd/run.go b/internal/cli/kb/cmd/topic/cmd/newcmd/run.go new file mode 100644 index 000000000..7b560a3eb --- /dev/null +++ b/internal/cli/kb/cmd/topic/cmd/newcmd/run.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package newcmd + +import ( + "github.com/spf13/cobra" + + topicCore "github.com/ActiveMemory/ctx/internal/cli/kb/core/topic" +) + +// Run scaffolds a new topic-page folder from the embedded +// template via [topicCore.Scaffold]. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - name: free-text topic name (slugified to kebab-case). +// +// Returns: +// - error: scaffolding failure or refusal when topic exists. +func Run(cobraCmd *cobra.Command, name string) error { + return topicCore.Scaffold(cobraCmd, name) +} diff --git a/internal/cli/kb/cmd/topic/doc.go b/internal/cli/kb/cmd/topic/doc.go new file mode 100644 index 000000000..b81c26225 --- /dev/null +++ b/internal/cli/kb/cmd/topic/doc.go @@ -0,0 +1,32 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package topic implements the `ctx kb topic` parent +// command. `new` is the sole writer of folder-shaped +// topic-page scaffolds under `.context/kb/topics/<slug>/`. +// +// # Subcommands +// +// - newcmd: scaffolds a new topic-page folder. +// +// # Refusal contract (from newcmd) +// +// An empty placeholder name (one that slugifies to the empty +// string) returns +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.ErrTopicEmptyName]. +// A name colliding with an existing topic folder returns +// [github.com/ActiveMemory/ctx/internal/err/kb/cli.TopicExists] +// wrapping the slug and target index path. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/topic] +// hosts [Scaffold] and [Substitute]. +// - [github.com/ActiveMemory/ctx/internal/slug] supplies the +// slug-derivation primitive ([slug.Path]). +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the substitution tokens and format strings. +package topic diff --git a/internal/cli/kb/core/path/doc.go b/internal/cli/kb/core/path/doc.go new file mode 100644 index 000000000..a8b3a0618 --- /dev/null +++ b/internal/cli/kb/core/path/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package path resolves full filesystem paths for every artifact +// the ctx knowledge-base editorial pipeline reads or writes. +// +// All functions compose [rc.ContextDir] with the directory and +// filename constants from +// [github.com/ActiveMemory/ctx/internal/config/kb]; nothing here +// invents new strings. Tests verify the joins are stdlib-clean +// (no string concatenation, per CONSTITUTION.md Quality +// Invariants). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] is the +// name source. +// - [github.com/ActiveMemory/ctx/internal/rc] resolves the +// declared .context/ directory. +package path diff --git a/internal/cli/kb/core/path/path.go b/internal/cli/kb/core/path/path.go new file mode 100644 index 000000000..59dad9415 --- /dev/null +++ b/internal/cli/kb/core/path/path.go @@ -0,0 +1,173 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path + +import ( + "path/filepath" + + "github.com/ActiveMemory/ctx/internal/config/dir" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/rc" +) + +// KBDir returns the .context/kb/ directory. +// +// Returns: +// - string: full path to .context/kb/ +// - error: non-nil when the context directory is not declared +func KBDir() (string, error) { + ctxDir, err := rc.ContextDir() + if err != nil { + return "", err + } + return filepath.Join(ctxDir, cfgKB.KBSubdir), nil +} + +// KBIndexFile returns the kb landing-page path. +// +// Returns: +// - string: full path to .context/kb/index.md +// - error: non-nil when the context directory is not declared +func KBIndexFile() (string, error) { + kb, err := KBDir() + if err != nil { + return "", err + } + return filepath.Join(kb, cfgKB.KBIndex), nil +} + +// KBTopicsDir returns the .context/kb/topics/ directory. +// +// Returns: +// - string: full path to .context/kb/topics/ +// - error: non-nil when the context directory is not declared +func KBTopicsDir() (string, error) { + kb, err := KBDir() + if err != nil { + return "", err + } + return filepath.Join(kb, cfgKB.TopicsSubdir), nil +} + +// KBTopicDir returns the .context/kb/topics/<slug>/ directory +// for a given topic slug. +// +// Parameters: +// - slug: kebab-case topic slug (e.g. "cursor-hooks"). +// +// Returns: +// - string: full path to .context/kb/topics/<slug>/ +// - error: non-nil when the context directory is not declared +func KBTopicDir(slug string) (string, error) { + topics, err := KBTopicsDir() + if err != nil { + return "", err + } + return filepath.Join(topics, slug), nil +} + +// KBTopicIndexFile returns the index.md path inside a topic +// folder. +// +// Parameters: +// - slug: kebab-case topic slug. +// +// Returns: +// - string: full path to .context/kb/topics/<slug>/index.md +// - error: non-nil when the context directory is not declared +func KBTopicIndexFile(slug string) (string, error) { + topic, err := KBTopicDir(slug) + if err != nil { + return "", err + } + return filepath.Join(topic, cfgKB.TopicIndex), nil +} + +// IngestDir returns the .context/ingest/ directory. +// +// Returns: +// - string: full path to .context/ingest/ +// - error: non-nil when the context directory is not declared +func IngestDir() (string, error) { + ctxDir, err := rc.ContextDir() + if err != nil { + return "", err + } + return filepath.Join(ctxDir, cfgKB.IngestSubdir), nil +} + +// IngestArtifactFile returns a path under .context/ingest/ for +// a named artifact (e.g. Rules, ModeIngest, SessionLog). +// +// Parameters: +// - name: filename constant from internal/config/kb. +// +// Returns: +// - string: full path to .context/ingest/<name> +// - error: non-nil when the context directory is not declared +func IngestArtifactFile(name string) (string, error) { + ingest, err := IngestDir() + if err != nil { + return "", err + } + return filepath.Join(ingest, name), nil +} + +// CloseoutsDir returns the .context/ingest/closeouts/ directory. +// +// Returns: +// - string: full path to .context/ingest/closeouts/ +// - error: non-nil when the context directory is not declared +func CloseoutsDir() (string, error) { + ingest, err := IngestDir() + if err != nil { + return "", err + } + return filepath.Join(ingest, cfgKB.CloseoutsSubdir), nil +} + +// ArchiveCloseoutsDir returns the .context/archive/closeouts/ +// directory where folded closeouts are moved by the handover +// fold mechanism. +// +// Returns: +// - string: full path to .context/archive/closeouts/ +// - error: non-nil when the context directory is not declared +func ArchiveCloseoutsDir() (string, error) { + ctxDir, err := rc.ContextDir() + if err != nil { + return "", err + } + return filepath.Join(ctxDir, dir.Archive, cfgKB.ArchiveCloseoutsSubdir), nil +} + +// SiteDir returns the .context/site/ root (gitignored). +// +// Returns: +// - string: full path to .context/site/ +// - error: non-nil when the context directory is not declared +func SiteDir() (string, error) { + ctxDir, err := rc.ContextDir() + if err != nil { + return "", err + } + return filepath.Join(ctxDir, cfgKB.SiteSubdir), nil +} + +// SiteKBDir returns the .context/site/kb/ render-output +// directory. +// +// Returns: +// - string: full path to .context/site/kb/ +// - error: non-nil when the context directory is not declared +func SiteKBDir() (string, error) { + site, err := SiteDir() + if err != nil { + return "", err + } + return filepath.Join(site, cfgKB.SiteKBSubdir), nil +} diff --git a/internal/cli/kb/core/path/path_test.go b/internal/cli/kb/core/path/path_test.go new file mode 100644 index 000000000..bbc5de7d0 --- /dev/null +++ b/internal/cli/kb/core/path/path_test.go @@ -0,0 +1,119 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" +) + +// canonicalCtxDir builds a CTX_DIR honoring the rc-required +// `.context` basename and points the env var at it. +func canonicalCtxDir(t *testing.T) string { + t.Helper() + root := t.TempDir() + ctxDir := filepath.Join(root, ".context") + if err := os.MkdirAll(ctxDir, 0o755); err != nil { + t.Fatalf("setup: %v", err) + } + t.Setenv("CTX_DIR", ctxDir) + return ctxDir +} + +func TestKBDir(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := kbPath.KBDir() + if err != nil { + t.Fatalf("KBDir: %v", err) + } + want := filepath.Join(ctxDir, cfgKB.KBSubdir) + if got != want { + t.Errorf("KBDir: want %q; got %q", want, got) + } +} + +func TestKBTopicIndexFile(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := kbPath.KBTopicIndexFile("cursor-hooks") + if err != nil { + t.Fatalf("KBTopicIndexFile: %v", err) + } + want := filepath.Join( + ctxDir, cfgKB.KBSubdir, cfgKB.TopicsSubdir, + "cursor-hooks", cfgKB.TopicIndex, + ) + if got != want { + t.Errorf("KBTopicIndexFile: want %q; got %q", want, got) + } + // Sanity: index.md must end the path; topic slug must be + // the parent. + if !strings.HasSuffix(got, "/cursor-hooks/index.md") { + t.Errorf("path suffix unexpected: %s", got) + } +} + +func TestIngestArtifactFile(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := kbPath.IngestArtifactFile(cfgKB.Rules) + if err != nil { + t.Fatalf("IngestArtifactFile: %v", err) + } + want := filepath.Join(ctxDir, cfgKB.IngestSubdir, cfgKB.Rules) + if got != want { + t.Errorf("IngestArtifactFile: want %q; got %q", want, got) + } +} + +func TestCloseoutsDir(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := kbPath.CloseoutsDir() + if err != nil { + t.Fatalf("CloseoutsDir: %v", err) + } + want := filepath.Join( + ctxDir, cfgKB.IngestSubdir, cfgKB.CloseoutsSubdir, + ) + if got != want { + t.Errorf("CloseoutsDir: want %q; got %q", want, got) + } +} + +func TestArchiveCloseoutsDir(t *testing.T) { + _ = canonicalCtxDir(t) + + got, err := kbPath.ArchiveCloseoutsDir() + if err != nil { + t.Fatalf("ArchiveCloseoutsDir: %v", err) + } + if !strings.HasSuffix(got, "/archive/closeouts") { + t.Errorf("path suffix unexpected: %s", got) + } +} + +func TestSiteKBDir(t *testing.T) { + ctxDir := canonicalCtxDir(t) + + got, err := kbPath.SiteKBDir() + if err != nil { + t.Fatalf("SiteKBDir: %v", err) + } + want := filepath.Join( + ctxDir, cfgKB.SiteSubdir, cfgKB.SiteKBSubdir, + ) + if got != want { + t.Errorf("SiteKBDir: want %q; got %q", want, got) + } +} diff --git a/internal/cli/kb/core/path/testmain_test.go b/internal/cli/kb/core/path/testmain_test.go new file mode 100644 index 000000000..2f3cfd019 --- /dev/null +++ b/internal/cli/kb/core/path/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package path_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/cli/kb/core/reindex/block.go b/internal/cli/kb/core/reindex/block.go new file mode 100644 index 000000000..929fb0a1d --- /dev/null +++ b/internal/cli/kb/core/reindex/block.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package reindex + +import ( + "strings" + + cfgKbCli "github.com/ActiveMemory/ctx/internal/config/kb/cli" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// RenderBlock builds the managed block content that the +// reindex command splices between the CTX:KB:TOPICS START and +// END markers in `.context/kb/index.md`. +// +// Parameters: +// - slugs: sorted topic slugs. +// +// Returns: +// - string: managed block with delimiters. +func RenderBlock(slugs []string) string { + var sb strings.Builder + sb.WriteString(cfgKbCli.ManagedKBTopicsStart) + sb.WriteString(token.NewlineLF) + if len(slugs) == 0 { + sb.WriteString(cfgKbCli.ManagedKBTopicsEmpty) + } + for _, s := range slugs { + sb.WriteString(cfgKbCli.TopicEntryPrefix) + sb.WriteString(s) + sb.WriteString(cfgKbCli.TopicEntryMiddle) + sb.WriteString(s) + sb.WriteString(cfgKbCli.TopicEntrySuffix) + } + sb.WriteString(cfgKbCli.ManagedKBTopicsEnd) + return sb.String() +} diff --git a/internal/cli/kb/core/reindex/doc.go b/internal/cli/kb/core/reindex/doc.go new file mode 100644 index 000000000..da645104c --- /dev/null +++ b/internal/cli/kb/core/reindex/doc.go @@ -0,0 +1,25 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package reindex carries the topic-enumeration and managed- +// block-rendering helpers used by `ctx kb reindex` to refresh +// the CTX:KB:TOPICS section of `.context/kb/index.md`. +// +// # Files +// +// - topics.go: directory scan that returns topic slugs whose +// index.md exists. +// - block.go: rendering of the managed block contents. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/cmd/reindex] +// is the CLI surface that drives this core. +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the marker strings. +// - [github.com/ActiveMemory/ctx/internal/config/regex.ManagedKBTopics] +// is the matcher. +package reindex diff --git a/internal/cli/kb/core/reindex/topic.go b/internal/cli/kb/core/reindex/topic.go new file mode 100644 index 000000000..8d5775236 --- /dev/null +++ b/internal/cli/kb/core/reindex/topic.go @@ -0,0 +1,51 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package reindex + +import ( + "errors" + "os" + "path/filepath" + "sort" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" +) + +// ListTopics returns every subdirectory of topicsDir that +// contains an index.md file. Slugs are returned sorted. +// +// Parameters: +// - topicsDir: absolute path to .context/kb/topics/. +// +// Returns: +// - []string: sorted topic slugs (slashes preserved for +// vendor-namespaced topology). +// - error: wrapped enumeration failure. +func ListTopics(topicsDir string) ([]string, error) { + entries, readErr := os.ReadDir(topicsDir) + if readErr != nil { + if errors.Is(readErr, os.ErrNotExist) { + return nil, nil + } + return nil, errKbCli.ReadTopicsDir(readErr) + } + var slugs []string + for _, e := range entries { + if !e.IsDir() { + continue + } + // Test if <topicsDir>/<name>/index.md exists. + idx := filepath.Join(topicsDir, e.Name(), cfgKB.TopicIndex) + if _, statErr := io.SafeStat(idx); statErr == nil { + slugs = append(slugs, e.Name()) + } + } + sort.Strings(slugs) + return slugs, nil +} diff --git a/internal/cli/kb/core/topic/doc.go b/internal/cli/kb/core/topic/doc.go new file mode 100644 index 000000000..14c3c41ed --- /dev/null +++ b/internal/cli/kb/core/topic/doc.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package topic carries the topic-page scaffolding helpers +// used by `ctx kb topic new`: slug normalisation, template +// substitution, and the disk-writing scaffolder. +// +// # Files +// +// - scaffold.go: [Scaffold] orchestrates the topic-folder +// create, embedded-template copy, and substitution pass. +// Slug derivation delegates to +// [github.com/ActiveMemory/ctx/internal/slug.Path]. +// - template.go: [Substitute] applies <NAME> / <SLUG> token +// replacement on the rendered topic-page body. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/cmd/topic] +// is the CLI surface that drives this core. +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] supplies +// the substitution tokens and format strings. +package topic diff --git a/internal/cli/kb/core/topic/scaffold.go b/internal/cli/kb/core/topic/scaffold.go new file mode 100644 index 000000000..283ebd814 --- /dev/null +++ b/internal/cli/kb/core/topic/scaffold.go @@ -0,0 +1,76 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package topic + +import ( + "io/fs" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/assets" + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + kbPath "github.com/ActiveMemory/ctx/internal/cli/kb/core/path" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbCli "github.com/ActiveMemory/ctx/internal/err/kb/cli" + "github.com/ActiveMemory/ctx/internal/io" + "github.com/ActiveMemory/ctx/internal/slug" +) + +// Scaffold creates the topic folder and writes the index.md +// rendered from the embedded template. +// +// Parameters: +// - cobraCmd: cobra command for output. +// - name: free-text topic name (slugified to kebab-case). +// +// Returns: +// - error: scaffolding failure or refusal when topic exists. +func Scaffold(cobraCmd *cobra.Command, name string) error { + topicSlug := slug.Path(name) + if topicSlug == "" { + cobraCmd.SilenceUsage = true + return errKbCli.ErrTopicEmptyName + } + topicDir, dirErr := kbPath.KBTopicDir(topicSlug) + if dirErr != nil { + return dirErr + } + indexPath := filepath.Join(topicDir, cfgKB.TopicIndex) + if _, statErr := io.SafeStat(indexPath); statErr == nil { + cobraCmd.SilenceUsage = true + return errKbCli.TopicExists(topicSlug, indexPath) + } + + mkErr := io.SafeMkdirAll(topicDir, cfgFs.PermExec) + if mkErr != nil { + return errKbCli.MkdirTopic(mkErr) + } + raw, readErr := fs.ReadFile( + assets.FS, cfgKB.AssetTemplateTopicIndex, + ) + if readErr != nil { + return errKbCli.ReadTopicTemplate(readErr) + } + rendered := Substitute(string(raw), name, topicSlug) + writeErr := io.SafeWriteFile( + indexPath, + []byte(rendered), + cfgFs.PermSecret, + ) + if writeErr != nil { + return errKbCli.WriteTopicIndex(writeErr) + } + io.SafeFprintf( + cobraCmd.OutOrStdout(), + desc.Text(text.DescKeyWriteKbCliScaffolded), + indexPath, + ) + return nil +} diff --git a/internal/cli/kb/core/topic/template.go b/internal/cli/kb/core/topic/template.go new file mode 100644 index 000000000..f7bb022fb --- /dev/null +++ b/internal/cli/kb/core/topic/template.go @@ -0,0 +1,32 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package topic + +import ( + "strings" + + cfgKbCli "github.com/ActiveMemory/ctx/internal/config/kb/cli" +) + +// Substitute applies <NAME> / <SLUG> token replacement to the +// template body so the new file is human-meaningful from the +// first read. +// +// Parameters: +// - body: raw template content. +// - name: human-readable topic name. +// - slug: kebab-case slug. +// +// Returns: +// - string: substituted body. +func Substitute(body, name, slug string) string { + body = strings.ReplaceAll(body, cfgKbCli.TokenTopicName, name) + body = strings.ReplaceAll(body, cfgKbCli.TokenTopicSlug, slug) + body = strings.ReplaceAll(body, cfgKbCli.TokenName, name) + body = strings.ReplaceAll(body, cfgKbCli.TokenSlug, slug) + return body +} diff --git a/internal/cli/kb/doc.go b/internal/cli/kb/doc.go new file mode 100644 index 000000000..f1f8e9093 --- /dev/null +++ b/internal/cli/kb/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kb wires the `ctx kb` parent command and its +// subcommand tree (Phase KB). +// +// Subcommands: +// +// - ctx kb topic new <name>: scaffold a folder-shaped topic. +// - ctx kb note <text>: lightweight capture into findings.md. +// - ctx kb ingest: invoke the editorial pass (skill-driven). +// - ctx kb ask: Q&A grounded in the kb (skill-driven). +// - ctx kb site-review: mechanical audit (skill-driven). +// - ctx kb ground: re-grounding pass (skill-driven). +// - ctx kb reindex: refresh the CTX:KB:TOPICS managed block. +// - ctx kb site build/serve/customize: render via zensical. +// +// See specs/kb-editorial-pipeline.md for the editorial +// pipeline contract. +package kb diff --git a/internal/cli/kb/kb.go b/internal/cli/kb/kb.go new file mode 100644 index 000000000..2b2b60e97 --- /dev/null +++ b/internal/cli/kb/kb.go @@ -0,0 +1,39 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +import ( + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/ask" + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/ground" + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/ingest" + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/note" + kbReindex "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/reindex" + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/sitereview" + "github.com/ActiveMemory/ctx/internal/cli/kb/cmd/topic" + "github.com/ActiveMemory/ctx/internal/cli/parent" + "github.com/ActiveMemory/ctx/internal/config/embed/cmd" +) + +// Cmd returns the `ctx kb` parent command with the editorial +// pipeline subcommands registered. +// +// Returns: +// - *cobra.Command: kb parent with topic, ingest, ask, +// site-review, ground, note, and reindex. +func Cmd() *cobra.Command { + return parent.Cmd(cmd.DescKeyKB, cmd.UseKB, + topic.Cmd(), + ingest.Cmd(), + ask.Cmd(), + sitereview.Cmd(), + ground.Cmd(), + note.Cmd(), + kbReindex.Cmd(), + ) +} diff --git a/internal/cli/load/cmd/root/cmd.go b/internal/cli/load/cmd/root/cmd.go index 83421d5b8..9e4b788c2 100644 --- a/internal/cli/load/cmd/root/cmd.go +++ b/internal/cli/load/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/rc" ) diff --git a/internal/cli/loop/cmd/root/cmd.go b/internal/cli/loop/cmd/root/cmd.go index a7a500a99..bc78c22b7 100644 --- a/internal/cli/loop/cmd/root/cmd.go +++ b/internal/cli/loop/cmd/root/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/loop" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx loop" command for generating Ralph loop scripts. diff --git a/internal/cli/memory/cmd/importer/cmd.go b/internal/cli/memory/cmd/importer/cmd.go index 5e56429b9..0cbfcfa72 100644 --- a/internal/cli/memory/cmd/importer/cmd.go +++ b/internal/cli/memory/cmd/importer/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the memory import subcommand. diff --git a/internal/cli/memory/cmd/publish/cmd.go b/internal/cli/memory/cmd/publish/cmd.go index 345f5eefb..998fba772 100644 --- a/internal/cli/memory/cmd/publish/cmd.go +++ b/internal/cli/memory/cmd/publish/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/memory" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the memory publish subcommand. diff --git a/internal/cli/memory/cmd/sync/cmd.go b/internal/cli/memory/cmd/sync/cmd.go index 1ad142405..45522858a 100644 --- a/internal/cli/memory/cmd/sync/cmd.go +++ b/internal/cli/memory/cmd/sync/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the memory sync subcommand. diff --git a/internal/cli/notify/notify.go b/internal/cli/notify/notify.go index 53dc326f2..c196c750e 100644 --- a/internal/cli/notify/notify.go +++ b/internal/cli/notify/notify.go @@ -20,7 +20,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/token" "github.com/ActiveMemory/ctx/internal/entity" errCli "github.com/ActiveMemory/ctx/internal/err/cli" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" iNotify "github.com/ActiveMemory/ctx/internal/notify" ) diff --git a/internal/cli/pad/cmd/add/cmd.go b/internal/cli/pad/cmd/add/cmd.go index 3f31368ea..625553422 100644 --- a/internal/cli/pad/cmd/add/cmd.go +++ b/internal/cli/pad/cmd/add/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad add subcommand. diff --git a/internal/cli/pad/cmd/edit/cmd.go b/internal/cli/pad/cmd/edit/cmd.go index 356a5abc7..456241bae 100644 --- a/internal/cli/pad/cmd/edit/cmd.go +++ b/internal/cli/pad/cmd/edit/cmd.go @@ -18,7 +18,7 @@ import ( cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/pad" errPad "github.com/ActiveMemory/ctx/internal/err/pad" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad edit subcommand. diff --git a/internal/cli/pad/cmd/export/cmd.go b/internal/cli/pad/cmd/export/cmd.go index 29639f064..c418a083d 100644 --- a/internal/cli/pad/cmd/export/cmd.go +++ b/internal/cli/pad/cmd/export/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/token" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad export subcommand. diff --git a/internal/cli/pad/cmd/merge/cmd.go b/internal/cli/pad/cmd/merge/cmd.go index 397b187d9..9280c83a0 100644 --- a/internal/cli/pad/cmd/merge/cmd.go +++ b/internal/cli/pad/cmd/merge/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad merge subcommand. diff --git a/internal/cli/pad/cmd/root/cmd.go b/internal/cli/pad/cmd/root/cmd.go index 4f412489c..906f88f23 100644 --- a/internal/cli/pad/cmd/root/cmd.go +++ b/internal/cli/pad/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad import subcommand. diff --git a/internal/cli/pad/cmd/show/cmd.go b/internal/cli/pad/cmd/show/cmd.go index 2eea8dbcc..b1fe87d13 100644 --- a/internal/cli/pad/cmd/show/cmd.go +++ b/internal/cli/pad/cmd/show/cmd.go @@ -16,7 +16,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" errPad "github.com/ActiveMemory/ctx/internal/err/pad" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad show subcommand. diff --git a/internal/cli/pad/cmd/tag/cmd.go b/internal/cli/pad/cmd/tag/cmd.go index e2163561c..ab20ff3ad 100644 --- a/internal/cli/pad/cmd/tag/cmd.go +++ b/internal/cli/pad/cmd/tag/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the pad tag subcommand. diff --git a/internal/cli/pad/pad.go b/internal/cli/pad/pad.go index d770d57c3..b38410f13 100644 --- a/internal/cli/pad/pad.go +++ b/internal/cli/pad/pad.go @@ -30,7 +30,7 @@ import ( embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag" "github.com/ActiveMemory/ctx/internal/config/embed/text" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/write/pad" ) diff --git a/internal/cli/prune/cmd.go b/internal/cli/prune/cmd.go index 70d13ba16..4e6463bc3 100644 --- a/internal/cli/prune/cmd.go +++ b/internal/cli/prune/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/runtime" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx prune" top-level command. diff --git a/internal/cli/remind/cmd/add/cmd.go b/internal/cli/remind/cmd/add/cmd.go index 02cd44ad1..47561e236 100644 --- a/internal/cli/remind/cmd/add/cmd.go +++ b/internal/cli/remind/cmd/add/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the remind add subcommand. diff --git a/internal/cli/remind/cmd/dismiss/cmd.go b/internal/cli/remind/cmd/dismiss/cmd.go index 322ab6a9e..690332d59 100644 --- a/internal/cli/remind/cmd/dismiss/cmd.go +++ b/internal/cli/remind/cmd/dismiss/cmd.go @@ -15,7 +15,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" errReminder "github.com/ActiveMemory/ctx/internal/err/reminder" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the remind dismiss subcommand. diff --git a/internal/cli/remind/remind.go b/internal/cli/remind/remind.go index c1eda6bc8..de59887ef 100644 --- a/internal/cli/remind/remind.go +++ b/internal/cli/remind/remind.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the remind command with subcommands. diff --git a/internal/cli/setup/cmd/root/cmd.go b/internal/cli/setup/cmd/root/cmd.go index 56fd2de1f..08c94b717 100644 --- a/internal/cli/setup/cmd/root/cmd.go +++ b/internal/cli/setup/cmd/root/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx setup" command for generating AI tool integrations. diff --git a/internal/cli/setup/core/copilot_cli/agent.go b/internal/cli/setup/core/copilot_cli/agent.go deleted file mode 100644 index 245382659..000000000 --- a/internal/cli/setup/core/copilot_cli/agent.go +++ /dev/null @@ -1,52 +0,0 @@ -// / ctx: https://ctx.ist -// ,'`./ do you remember? -// `.,'\ -// \ Copyright 2026-present Context contributors. -// SPDX-License-Identifier: Apache-2.0 - -package copilotcli - -import ( - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/ActiveMemory/ctx/internal/assets/read/agent" - "github.com/ActiveMemory/ctx/internal/config/fs" - cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" - ctxIo "github.com/ActiveMemory/ctx/internal/io" - writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" -) - -// deployAgent creates .github/agents/ctx.md for Copilot CLI custom -// agent delegation. Skips if the file already exists. -// -// Parameters: -// - cmd: Cobra command for output messages -// -// Returns: -// - error: Non-nil if directory creation or file write fails -func deployAgent(cmd *cobra.Command) error { - agentsDir := filepath.Join(cfgHook.DirGitHub, cfgHook.DirGitHubAgents) - target := filepath.Join(agentsDir, cfgHook.FileAgentsCtxMd) - - if _, statErr := os.Stat(target); statErr == nil { - writeSetup.InfoCopilotCLISkipped(cmd, target) - return nil - } - - if mkErr := ctxIo.SafeMkdirAll(agentsDir, fs.PermExec); mkErr != nil { - return mkErr - } - - content, readErr := agent.AgentsCtxMd() - if readErr != nil { - return readErr - } - if wErr := ctxIo.SafeWriteFile(target, content, fs.PermFile); wErr != nil { - return wErr - } - writeSetup.InfoCopilotCLICreated(cmd, target) - return nil -} diff --git a/internal/cli/setup/core/copilot_cli/copilot_cli.go b/internal/cli/setup/core/copilot_cli/copilot_cli.go index f403c99d3..8f69b17cf 100644 --- a/internal/cli/setup/core/copilot_cli/copilot_cli.go +++ b/internal/cli/setup/core/copilot_cli/copilot_cli.go @@ -76,12 +76,20 @@ func Deploy(cmd *cobra.Command) error { } // Write .github/agents/ctx.md - if agentErr := deployAgent(cmd); agentErr != nil { + if agentErr := deployGithubAsset( + cmd, + cfgHook.DirGitHubAgents, cfgHook.FileAgentsCtxMd, + agent.AgentsCtxMd, + ); agentErr != nil { writeErr.WarnFile(cmd, cfgHook.DirGitHubAgents, agentErr) } // Write .github/instructions/context.instructions.md - if instrErr := deployInstructions(cmd); instrErr != nil { + if instrErr := deployGithubAsset( + cmd, + cfgHook.DirGitHubInstructions, cfgHook.FileInstructionsCtxMd, + agent.InstructionsCtxMd, + ); instrErr != nil { writeErr.WarnFile( cmd, cfgHook.DirGitHubInstructions, instrErr, ) diff --git a/internal/cli/setup/core/copilot_cli/github_asset.go b/internal/cli/setup/core/copilot_cli/github_asset.go new file mode 100644 index 000000000..af11ee4e8 --- /dev/null +++ b/internal/cli/setup/core/copilot_cli/github_asset.go @@ -0,0 +1,65 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package copilotcli + +import ( + "os" + "path/filepath" + + "github.com/spf13/cobra" + + "github.com/ActiveMemory/ctx/internal/config/fs" + cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" + ctxIo "github.com/ActiveMemory/ctx/internal/io" + writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" +) + +// deployGithubAsset writes a single Copilot CLI markdown asset +// under `.github/<subDir>/<fileName>`, idempotently. If the +// target already exists it logs a skip notice and returns nil; +// otherwise it creates the directory, fetches the embedded +// content via readContent, writes the file, and logs a created +// notice. +// +// Parameters: +// - cmd: cobra command for output messages. +// - subDir: subdirectory under `.github/` (e.g. +// [cfgHook.DirGitHubAgents]). +// - fileName: target filename inside subDir. +// - readContent: embedded-asset accessor that returns the +// file body. +// +// Returns: +// - error: non-nil on directory creation, embedded read, or +// file write failure. +func deployGithubAsset( + cmd *cobra.Command, + subDir, fileName string, + readContent func() ([]byte, error), +) error { + assetDir := filepath.Join(cfgHook.DirGitHub, subDir) + target := filepath.Join(assetDir, fileName) + + if _, statErr := os.Stat(target); statErr == nil { + writeSetup.InfoCopilotCLISkipped(cmd, target) + return nil + } + + if mkErr := ctxIo.SafeMkdirAll(assetDir, fs.PermExec); mkErr != nil { + return mkErr + } + + content, readErr := readContent() + if readErr != nil { + return readErr + } + if wErr := ctxIo.SafeWriteFile(target, content, fs.PermFile); wErr != nil { + return wErr + } + writeSetup.InfoCopilotCLICreated(cmd, target) + return nil +} diff --git a/internal/cli/setup/core/copilot_cli/instruction.go b/internal/cli/setup/core/copilot_cli/instruction.go deleted file mode 100644 index 2cbf85639..000000000 --- a/internal/cli/setup/core/copilot_cli/instruction.go +++ /dev/null @@ -1,53 +0,0 @@ -// / ctx: https://ctx.ist -// ,'`./ do you remember? -// `.,'\ -// \ Copyright 2026-present Context contributors. -// SPDX-License-Identifier: Apache-2.0 - -package copilotcli - -import ( - "os" - "path/filepath" - - "github.com/spf13/cobra" - - "github.com/ActiveMemory/ctx/internal/assets/read/agent" - "github.com/ActiveMemory/ctx/internal/config/fs" - cfgHook "github.com/ActiveMemory/ctx/internal/config/hook" - ctxIo "github.com/ActiveMemory/ctx/internal/io" - writeSetup "github.com/ActiveMemory/ctx/internal/write/setup" -) - -// deployInstructions creates -// .github/instructions/context.instructions.md for path-specific -// context file instructions. Skips if the file already exists. -// -// Parameters: -// - cmd: Cobra command for output messages -// -// Returns: -// - error: Non-nil if directory creation or file write fails -func deployInstructions(cmd *cobra.Command) error { - instrDir := filepath.Join(cfgHook.DirGitHub, cfgHook.DirGitHubInstructions) - target := filepath.Join(instrDir, cfgHook.FileInstructionsCtxMd) - - if _, statErr := os.Stat(target); statErr == nil { - writeSetup.InfoCopilotCLISkipped(cmd, target) - return nil - } - - if mkErr := ctxIo.SafeMkdirAll(instrDir, fs.PermExec); mkErr != nil { - return mkErr - } - - content, readErr := agent.InstructionsCtxMd() - if readErr != nil { - return readErr - } - if wErr := ctxIo.SafeWriteFile(target, content, fs.PermFile); wErr != nil { - return wErr - } - writeSetup.InfoCopilotCLICreated(cmd, target) - return nil -} diff --git a/internal/cli/site/cmd/feed/cmd.go b/internal/cli/site/cmd/feed/cmd.go index 5b9d574ad..023c799b8 100644 --- a/internal/cli/site/cmd/feed/cmd.go +++ b/internal/cli/site/cmd/feed/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/rss" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx site feed" subcommand. diff --git a/internal/cli/status/cmd/root/cmd.go b/internal/cli/status/cmd/root/cmd.go index 32276d5e4..712a81591 100644 --- a/internal/cli/status/cmd/root/cmd.go +++ b/internal/cli/status/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the status command. diff --git a/internal/cli/steering/cmd/synccmd/cmd.go b/internal/cli/steering/cmd/synccmd/cmd.go index 2487973f6..edd26f73b 100644 --- a/internal/cli/steering/cmd/synccmd/cmd.go +++ b/internal/cli/steering/cmd/synccmd/cmd.go @@ -17,7 +17,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/flag" "github.com/ActiveMemory/ctx/internal/config/token" errSteering "github.com/ActiveMemory/ctx/internal/err/steering" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/steering" ) diff --git a/internal/cli/sync/cmd/root/cmd.go b/internal/cli/sync/cmd/root/cmd.go index d2f3369c3..e7865b264 100644 --- a/internal/cli/sync/cmd/root/cmd.go +++ b/internal/cli/sync/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx sync" command for reconciling context with the codebase. diff --git a/internal/cli/system/cmd/bootstrap/cmd.go b/internal/cli/system/cmd/bootstrap/cmd.go index fb6853bb0..149752eb4 100644 --- a/internal/cli/system/cmd/bootstrap/cmd.go +++ b/internal/cli/system/cmd/bootstrap/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx system bootstrap" hidden command. diff --git a/internal/cli/system/cmd/session_event/cmd.go b/internal/cli/system/cmd/session_event/cmd.go index 6e9bed79f..4beccba15 100644 --- a/internal/cli/system/cmd/session_event/cmd.go +++ b/internal/cli/system/cmd/session_event/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" embFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx system session-event" subcommand. diff --git a/internal/cli/system/core/session/session_token.go b/internal/cli/system/core/session/session_token.go index a8101a18e..788977ecd 100644 --- a/internal/cli/system/core/session/session_token.go +++ b/internal/cli/system/core/session/session_token.go @@ -67,8 +67,8 @@ func ReadTokenInfo(sessionID string) (entity.TokenInfo, error) { // from many entry points (including provenance.Emit, which is // intentionally unconditional) and we must not materialize // .context/state/ as a side effect of glob caching in projects that -// have never run ctx init. Returns ("", nil) — caller treats as -// "no token data" and proceeds. +// have never run ctx init. Returns ("", nil); the caller treats +// that as "no token data" and proceeds. // // Parameters: // - sessionID: The Claude Code session ID diff --git a/internal/cli/task/cmd/archive/cmd.go b/internal/cli/task/cmd/archive/cmd.go index ba53f207c..88c523c2b 100644 --- a/internal/cli/task/cmd/archive/cmd.go +++ b/internal/cli/task/cmd/archive/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the `task archive` subcommand. diff --git a/internal/cli/trace/cmd/collect/cmd.go b/internal/cli/trace/cmd/collect/cmd.go index 06fb1b48a..7540716c9 100644 --- a/internal/cli/trace/cmd/collect/cmd.go +++ b/internal/cli/trace/cmd/collect/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the trace collect subcommand. diff --git a/internal/cli/trace/cmd/file/cmd.go b/internal/cli/trace/cmd/file/cmd.go index 1ede2443d..812b08116 100644 --- a/internal/cli/trace/cmd/file/cmd.go +++ b/internal/cli/trace/cmd/file/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" cfgTrace "github.com/ActiveMemory/ctx/internal/config/trace" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the trace file subcommand. diff --git a/internal/cli/trace/cmd/show/cmd.go b/internal/cli/trace/cmd/show/cmd.go index 3a14677bb..bb354d5f1 100644 --- a/internal/cli/trace/cmd/show/cmd.go +++ b/internal/cli/trace/cmd/show/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the trace command. diff --git a/internal/cli/trace/cmd/tag/cmd.go b/internal/cli/trace/cmd/tag/cmd.go index f74c2de26..c68b8c241 100644 --- a/internal/cli/trace/cmd/tag/cmd.go +++ b/internal/cli/trace/cmd/tag/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the trace tag subcommand. diff --git a/internal/cli/trigger/cmd/test/cmd.go b/internal/cli/trigger/cmd/test/cmd.go index d031279ea..abae8ff1f 100644 --- a/internal/cli/trigger/cmd/test/cmd.go +++ b/internal/cli/trigger/cmd/test/cmd.go @@ -20,7 +20,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/token" cfgTrigger "github.com/ActiveMemory/ctx/internal/config/trigger" errTrigger "github.com/ActiveMemory/ctx/internal/err/trigger" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" "github.com/ActiveMemory/ctx/internal/rc" "github.com/ActiveMemory/ctx/internal/trigger" writeTrigger "github.com/ActiveMemory/ctx/internal/write/trigger" diff --git a/internal/cli/usage/cmd.go b/internal/cli/usage/cmd.go index 876c73024..0f01b4d58 100644 --- a/internal/cli/usage/cmd.go +++ b/internal/cli/usage/cmd.go @@ -14,7 +14,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" cfgStats "github.com/ActiveMemory/ctx/internal/config/stats" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the "ctx usage" top-level command. diff --git a/internal/cli/watch/cmd/root/cmd.go b/internal/cli/watch/cmd/root/cmd.go index 254af31f6..aa385965a 100644 --- a/internal/cli/watch/cmd/root/cmd.go +++ b/internal/cli/watch/cmd/root/cmd.go @@ -13,7 +13,7 @@ import ( "github.com/ActiveMemory/ctx/internal/config/embed/cmd" "github.com/ActiveMemory/ctx/internal/config/embed/flag" cFlag "github.com/ActiveMemory/ctx/internal/config/flag" - "github.com/ActiveMemory/ctx/internal/flagbind" + "github.com/ActiveMemory/ctx/internal/flag_bind" ) // Cmd returns the watch command. diff --git a/internal/config/closeout/closeout.go b/internal/config/closeout/closeout.go new file mode 100644 index 000000000..74a9892aa --- /dev/null +++ b/internal/config/closeout/closeout.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +// Sentinel error-message constants. These back `errors.New` +// values in `internal/err/closeout/` and are matched via +// `errors.Is` at the call site. They cannot use desc.Text +// because the sentinels are package-level vars evaluated +// before the embedded YAML lookup is populated; wrapping +// format strings live in commands/text/errors.yaml instead. +const ( + // ErrMsgMissingFrontmatter signals a closeout file missing + // the `---` open delimiter on line 1. + ErrMsgMissingFrontmatter = "closeout missing frontmatter" + // ErrMsgMissingFields signals a closeout frontmatter + // missing one of the required fields (sha, branch, mode, + // generated-at). + ErrMsgMissingFields = "closeout frontmatter missing required fields" + // ErrMsgModeRequired signals an empty Mode supplied to + // Write. Every closeout must declare a mode. + ErrMsgModeRequired = "closeout mode is required" +) + +// YAML field-name constants used by the closeout frontmatter +// parser to report missing fields. These mirror the YAML tags +// on [entity.CloseoutFrontmatter]; they are structural +// identifiers, not localizable prose. +const ( + // FieldSHA is the YAML key for the short git SHA. + FieldSHA = "sha" + // FieldBranch is the YAML key for the git branch name. + FieldBranch = "branch" + // FieldMode is the YAML key for the closeout mode. + FieldMode = "mode" + // FieldGeneratedAt is the YAML key for the write timestamp. + FieldGeneratedAt = "generated-at" +) diff --git a/internal/config/closeout/doc.go b/internal/config/closeout/doc.go new file mode 100644 index 000000000..27308e6f2 --- /dev/null +++ b/internal/config/closeout/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package closeout supplies the sentinel-error message strings +// and wrapping-format strings used by +// [github.com/ActiveMemory/ctx/internal/err/closeout]. Holding +// them here honours the project-wide rule that magic strings +// belong under `internal/config/`; the err package consumes +// these constants when constructing user-facing errors for the +// closeout writer (Phase KB). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/closeout] +// declares the sentinel error vars + constructors that +// consume these strings. +// - [github.com/ActiveMemory/ctx/internal/write/closeout] +// is the primary caller-side surface; it imports the err +// package, never these raw strings. +package closeout diff --git a/internal/config/embed/cmd/handover.go b/internal/config/embed/cmd/handover.go new file mode 100644 index 000000000..3dd47c196 --- /dev/null +++ b/internal/config/embed/cmd/handover.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +// Cobra Use strings for the handover command family. +const ( + // UseHandover is the cobra use string for the handover + // parent command. + UseHandover = "handover" + // UseHandoverWrite is the cobra use string for + // `ctx handover write <title>`. + UseHandoverWrite = "write <title>" +) + +// DescKeys for the handover command family. Values map to +// entries in the commands.yaml asset. +const ( + // DescKeyHandover is the description key for the handover + // parent command. + DescKeyHandover = "handover" + // DescKeyHandoverWrite is the description key for + // `ctx handover write`. + DescKeyHandoverWrite = "handover.write" +) diff --git a/internal/config/embed/cmd/kb.go b/internal/config/embed/cmd/kb.go new file mode 100644 index 000000000..087f4294d --- /dev/null +++ b/internal/config/embed/cmd/kb.go @@ -0,0 +1,59 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package cmd + +// Cobra Use strings for the kb command family. +const ( + // UseKB is the cobra use string for the kb parent command. + UseKB = "kb" + // UseKBAsk is the cobra use string for `ctx kb ask`. + UseKBAsk = "ask \"<question>\"" + // UseKBGround is the cobra use string for `ctx kb ground`. + UseKBGround = "ground" + // UseKBIngest is the cobra use string for `ctx kb ingest`. + UseKBIngest = "ingest <folder|paths...>" + // UseKBNote is the cobra use string for `ctx kb note`. + UseKBNote = "note \"<text>\"" + // UseKBReindex is the cobra use string for `ctx kb reindex`. + UseKBReindex = "reindex" + // UseKBSiteReview is the cobra use string for + // `ctx kb site-review`. + UseKBSiteReview = "site-review" + // UseKBTopic is the cobra use string for the topic parent + // command. + UseKBTopic = "topic" + // UseKBTopicNew is the cobra use string for + // `ctx kb topic new`. + UseKBTopicNew = "new <name>" +) + +// DescKeys for the kb command family. Values map to entries in +// the commands.yaml asset. +const ( + // DescKeyKB is the description key for the kb parent. + DescKeyKB = "kb" + // DescKeyKBAsk is the description key for `ctx kb ask`. + DescKeyKBAsk = "kb.ask" + // DescKeyKBGround is the description key for `ctx kb ground`. + DescKeyKBGround = "kb.ground" + // DescKeyKBIngest is the description key for `ctx kb ingest`. + DescKeyKBIngest = "kb.ingest" + // DescKeyKBNote is the description key for `ctx kb note`. + DescKeyKBNote = "kb.note" + // DescKeyKBReindex is the description key for + // `ctx kb reindex`. + DescKeyKBReindex = "kb.reindex" + // DescKeyKBSiteReview is the description key for + // `ctx kb site-review`. + DescKeyKBSiteReview = "kb.site-review" + // DescKeyKBTopic is the description key for the topic + // parent command. + DescKeyKBTopic = "kb.topic" + // DescKeyKBTopicNew is the description key for + // `ctx kb topic new`. + DescKeyKBTopicNew = "kb.topic.new" +) diff --git a/internal/config/embed/flag/doc.go b/internal/config/embed/flag/doc.go index e4b59ca39..3c41ce11b 100644 --- a/internal/config/embed/flag/doc.go +++ b/internal/config/embed/flag/doc.go @@ -46,6 +46,6 @@ // c.Flags().Bool("dry-run", false, desc.Flag(flag.DescKeyAddDryRun)) // // In practice, most flag binding goes through -// [internal/flagbind] which already knows how to look up +// [internal/flag_bind] which already knows how to look up // the desc key, so callers rarely call `desc.Flag` directly. package flag diff --git a/internal/config/embed/flag/handover.go b/internal/config/embed/flag/handover.go new file mode 100644 index 000000000..51130bab8 --- /dev/null +++ b/internal/config/embed/flag/handover.go @@ -0,0 +1,29 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package flag + +// DescKeys for handover-write body flags. +const ( + // DescKeyHandoverSummary is the description key for the + // handover --summary flag. + DescKeyHandoverSummary = "handover.summary" + // DescKeyHandoverNext is the description key for the + // handover --next flag. + DescKeyHandoverNext = "handover.next" + // DescKeyHandoverHighlights is the description key for the + // handover --highlights flag. + DescKeyHandoverHighlights = "handover.highlights" + // DescKeyHandoverOpenQuestions is the description key for the + // handover --open-questions flag. + DescKeyHandoverOpenQuestions = "handover.open-questions" + // DescKeyHandoverCommit is the description key for the + // handover --commit flag. + DescKeyHandoverCommit = "handover.commit" + // DescKeyHandoverNoFold is the description key for the + // handover --no-fold flag. + DescKeyHandoverNoFold = "handover.no-fold" +) diff --git a/internal/config/embed/text/err_closeout.go b/internal/config/embed/text/err_closeout.go new file mode 100644 index 000000000..105ebaf26 --- /dev/null +++ b/internal/config/embed/text/err_closeout.go @@ -0,0 +1,44 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for closeout error wrappers. The matching YAML +// entries live in commands/text/errors.yaml; constructors in +// internal/err/closeout/ resolve them via desc.Text at error +// construction time. +const ( + // DescKeyErrCloseoutReadCloseout is the text key for the + // closeout-file read failure wrapper. + DescKeyErrCloseoutReadCloseout = "err.closeout.read-closeout" + // DescKeyErrCloseoutParseFrontmatter is the text key for the + // frontmatter parse failure wrapper. + DescKeyErrCloseoutParseFrontmatter = "err.closeout.parse-frontmatter" + // DescKeyErrCloseoutMarshalFrontmatter is the text key for + // the frontmatter marshal failure wrapper. + DescKeyErrCloseoutMarshalFrontmatter = "err.closeout.marshal-frontmatter" + // DescKeyErrCloseoutReadCloseoutsDir is the text key for the + // closeouts-directory enumeration failure wrapper. + DescKeyErrCloseoutReadCloseoutsDir = "err.closeout.read-closeouts-dir" + // DescKeyErrCloseoutResolveHead is the text key for the + // git-head resolution failure wrapper. + DescKeyErrCloseoutResolveHead = "err.closeout.resolve-head" + // DescKeyErrCloseoutMkdirCloseouts is the text key for the + // closeouts-dir mkdir failure wrapper. + DescKeyErrCloseoutMkdirCloseouts = "err.closeout.mkdir-closeouts" + // DescKeyErrCloseoutWriteCloseout is the text key for the + // closeout-file write failure wrapper. + DescKeyErrCloseoutWriteCloseout = "err.closeout.write-closeout" + // DescKeyErrCloseoutMkdirArchive is the text key for the + // archive-dir mkdir failure wrapper. + DescKeyErrCloseoutMkdirArchive = "err.closeout.mkdir-archive" + // DescKeyErrCloseoutArchiveMove is the text key for the + // single-closeout archive-move failure wrapper. + DescKeyErrCloseoutArchiveMove = "err.closeout.archive-move" + // DescKeyErrCloseoutMissingFields is the text key for the + // missing-fields sentinel-wrap with the joined field list. + DescKeyErrCloseoutMissingFields = "err.closeout.missing-fields" +) diff --git a/internal/config/embed/text/err_gitmeta.go b/internal/config/embed/text/err_gitmeta.go new file mode 100644 index 000000000..d960020db --- /dev/null +++ b/internal/config/embed/text/err_gitmeta.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for gitmeta error wrappers. The matching YAML +// entries live in commands/text/errors.yaml; constructors in +// internal/err/git_meta/ resolve them via desc.Text at error +// construction time. +const ( + // DescKeyErrGitmetaMissingGitTree is the text key for the + // missing-.git/ wrap used by direct API callers. + DescKeyErrGitmetaMissingGitTree = "err.gitmeta.missing-git-tree" + // DescKeyErrGitmetaMissingGitTreeForCmd is the text key for + // the missing-.git/ wrap used by the root PersistentPreRunE. + DescKeyErrGitmetaMissingGitTreeForCmd = "err.gitmeta.missing-git-tree-for-cmd" + // DescKeyErrGitmetaStatGitDir is the text key for the + // non-ENOENT stat failure on the `.git` entry. + DescKeyErrGitmetaStatGitDir = "err.gitmeta.stat-git-dir" + // DescKeyErrGitmetaResolveHead is the text key for the + // `git rev-parse --short HEAD` invocation failure. + DescKeyErrGitmetaResolveHead = "err.gitmeta.resolve-head" +) diff --git a/internal/config/embed/text/err_handover.go b/internal/config/embed/text/err_handover.go new file mode 100644 index 000000000..f522b8994 --- /dev/null +++ b/internal/config/embed/text/err_handover.go @@ -0,0 +1,44 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for handover error wrappers. The matching YAML +// entries live in commands/text/errors.yaml; constructors in +// internal/err/handover/ resolve them via desc.Text at error +// construction time. +const ( + // DescKeyErrHandoverLatest is the text key for the + // latest-handover lookup failure wrapper. + DescKeyErrHandoverLatest = "err.handover.latest" + // DescKeyErrHandoverListCloseouts is the text key for the + // fold-time closeout-listing failure wrapper. + DescKeyErrHandoverListCloseouts = "err.handover.list-closeouts" + // DescKeyErrHandoverMarshalFrontmatter is the text key for + // the frontmatter marshal failure wrapper. + DescKeyErrHandoverMarshalFrontmatter = "err.handover.marshal-frontmatter" + // DescKeyErrHandoverMkdirHandovers is the text key for the + // handovers-dir mkdir failure wrapper. + DescKeyErrHandoverMkdirHandovers = "err.handover.mkdir-handovers" + // DescKeyErrHandoverWriteHandover is the text key for the + // new-handover file write failure wrapper. + DescKeyErrHandoverWriteHandover = "err.handover.write-handover" + // DescKeyErrHandoverArchiveFoldedCloseouts is the text key + // for the post-fold archive failure wrapper. + DescKeyErrHandoverArchiveFoldedCloseouts = "err.handover.archive-folded-closeouts" + // DescKeyErrHandoverReadHandover is the text key for the + // handover-from-disk read failure wrapper. + DescKeyErrHandoverReadHandover = "err.handover.read-handover" + // DescKeyErrHandoverReadHandoversDir is the text key for + // the handovers-directory enumeration failure wrapper. + DescKeyErrHandoverReadHandoversDir = "err.handover.read-handovers-dir" + // DescKeyErrHandoverParseFrontmatter is the text key for + // the frontmatter parse failure wrapper. + DescKeyErrHandoverParseFrontmatter = "err.handover.parse-frontmatter" + // DescKeyErrHandoverResolveHead is the text key for the + // git-head resolution failure wrapper. + DescKeyErrHandoverResolveHead = "err.handover.resolve-head" +) diff --git a/internal/config/embed/text/err_initkb.go b/internal/config/embed/text/err_initkb.go new file mode 100644 index 000000000..7472c3d42 --- /dev/null +++ b/internal/config/embed/text/err_initkb.go @@ -0,0 +1,37 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for ctx-init kb-scaffolding error wrappers. The +// matching YAML entries live in commands/text/errors.yaml; +// constructors in internal/err/initialize/kb/ resolve them via +// desc.Text at error construction time. +const ( + // DescKeyErrInitKbMkdir wraps `os.MkdirAll` for a scaffolded + // directory. + DescKeyErrInitKbMkdir = "err.initkb.mkdir" + // DescKeyErrInitKbCopyIngestTemplates wraps an ingest-templates + // copy failure. + DescKeyErrInitKbCopyIngestTemplates = "err.initkb.copy-ingest-templates" + // DescKeyErrInitKbCopySchemas wraps a schemas copy failure. + DescKeyErrInitKbCopySchemas = "err.initkb.copy-schemas" + // DescKeyErrInitKbCopyLanding wraps the kb landing-page copy + // failure. + DescKeyErrInitKbCopyLanding = "err.initkb.copy-landing" + // DescKeyErrInitKbReadEmbed wraps an embedded-asset read + // failure. + DescKeyErrInitKbReadEmbed = "err.initkb.read-embed" + // DescKeyErrInitKbMkdirFor wraps a parent-dir create failure + // for a destination file. + DescKeyErrInitKbMkdirFor = "err.initkb.mkdir-for" + // DescKeyErrInitKbWriteFile wraps a destination-file write + // failure. + DescKeyErrInitKbWriteFile = "err.initkb.write-file" + // DescKeyErrInitKbReadDir wraps an `os.ReadDir` failure for a + // scaffolded directory. + DescKeyErrInitKbReadDir = "err.initkb.read-dir" +) diff --git a/internal/config/embed/text/err_kb_artifacts.go b/internal/config/embed/text/err_kb_artifacts.go new file mode 100644 index 000000000..c0fb92b28 --- /dev/null +++ b/internal/config/embed/text/err_kb_artifacts.go @@ -0,0 +1,118 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// Glossary writer DescKeys. +const ( + // DescKeyErrKbGlossaryReadFile wraps a stat/read failure. + DescKeyErrKbGlossaryReadFile = "err.kb.glossary.read-file" + // DescKeyErrKbGlossaryMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbGlossaryMkdirDir = "err.kb.glossary.mkdir-dir" + // DescKeyErrKbGlossaryOpenFile wraps an open-for-append + // failure. + DescKeyErrKbGlossaryOpenFile = "err.kb.glossary.open-file" + // DescKeyErrKbGlossaryWriteRow wraps a row-write failure. + DescKeyErrKbGlossaryWriteRow = "err.kb.glossary.write-row" +) + +// Timeline writer DescKeys. +const ( + // DescKeyErrKbTimelineReadFile wraps a stat/read failure. + DescKeyErrKbTimelineReadFile = "err.kb.timeline.read-file" + // DescKeyErrKbTimelineMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbTimelineMkdirDir = "err.kb.timeline.mkdir-dir" + // DescKeyErrKbTimelineOpenFile wraps an open-for-append + // failure. + DescKeyErrKbTimelineOpenFile = "err.kb.timeline.open-file" + // DescKeyErrKbTimelineWriteRow wraps a row-write failure. + DescKeyErrKbTimelineWriteRow = "err.kb.timeline.write-row" +) + +// Source-map writer DescKeys. +const ( + // DescKeyErrKbSourcemapReadFile wraps a stat/read failure. + DescKeyErrKbSourcemapReadFile = "err.kb.sourcemap.read-file" + // DescKeyErrKbSourcemapMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbSourcemapMkdirDir = "err.kb.sourcemap.mkdir-dir" + // DescKeyErrKbSourcemapOpenFile wraps an open-for-append + // failure. + DescKeyErrKbSourcemapOpenFile = "err.kb.sourcemap.open-file" + // DescKeyErrKbSourcemapWriteRow wraps a row-write failure. + DescKeyErrKbSourcemapWriteRow = "err.kb.sourcemap.write-row" +) + +// Relationship-map writer DescKeys. +const ( + // DescKeyErrKbRelationshipReadFile wraps a stat/read + // failure. + DescKeyErrKbRelationshipReadFile = "err.kb.relationship.read-file" + // DescKeyErrKbRelationshipMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbRelationshipMkdirDir = "err.kb.relationship.mkdir-dir" + // DescKeyErrKbRelationshipOpenFile wraps an open-for-append + // failure. + DescKeyErrKbRelationshipOpenFile = "err.kb.relationship.open-file" + // DescKeyErrKbRelationshipWriteRow wraps a row-write + // failure. + DescKeyErrKbRelationshipWriteRow = "err.kb.relationship.write-row" +) + +// Contradiction writer DescKeys. +const ( + // DescKeyErrKbContradictionReadFile wraps a file-read + // failure. + DescKeyErrKbContradictionReadFile = "err.kb.contradiction.read-file" + // DescKeyErrKbContradictionMkdirDir wraps a parent-dir + // mkdir failure. + DescKeyErrKbContradictionMkdirDir = "err.kb.contradiction.mkdir-dir" + // DescKeyErrKbContradictionOpenFile wraps an + // open-for-append failure. + DescKeyErrKbContradictionOpenFile = "err.kb.contradiction.open-file" + // DescKeyErrKbContradictionWriteRow wraps a row-write + // failure. + DescKeyErrKbContradictionWriteRow = "err.kb.contradiction.write-row" + // DescKeyErrKbContradictionParseCNumber wraps a + // strconv.Atoi failure on a C-### digit string. + DescKeyErrKbContradictionParseCNumber = "err.kb.contradiction.parse-c-number" +) + +// Decision writer DescKeys. +const ( + // DescKeyErrKbDecisionReadFile wraps a file-read failure. + DescKeyErrKbDecisionReadFile = "err.kb.decision.read-file" + // DescKeyErrKbDecisionMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbDecisionMkdirDir = "err.kb.decision.mkdir-dir" + // DescKeyErrKbDecisionOpenFile wraps an open-for-append + // failure. + DescKeyErrKbDecisionOpenFile = "err.kb.decision.open-file" + // DescKeyErrKbDecisionWriteRow wraps a row-write failure. + DescKeyErrKbDecisionWriteRow = "err.kb.decision.write-row" + // DescKeyErrKbDecisionParseDDNumber wraps a strconv.Atoi + // failure on a DD-### digit string. + DescKeyErrKbDecisionParseDDNumber = "err.kb.decision.parse-dd-number" +) + +// Question writer DescKeys. +const ( + // DescKeyErrKbQuestionReadFile wraps a file-read failure. + DescKeyErrKbQuestionReadFile = "err.kb.question.read-file" + // DescKeyErrKbQuestionMkdirDir wraps a parent-dir mkdir + // failure. + DescKeyErrKbQuestionMkdirDir = "err.kb.question.mkdir-dir" + // DescKeyErrKbQuestionOpenFile wraps an open-for-append + // failure. + DescKeyErrKbQuestionOpenFile = "err.kb.question.open-file" + // DescKeyErrKbQuestionWriteRow wraps a row-write failure. + DescKeyErrKbQuestionWriteRow = "err.kb.question.write-row" + // DescKeyErrKbQuestionParseQNumber wraps a strconv.Atoi + // failure on a Q-### digit string. + DescKeyErrKbQuestionParseQNumber = "err.kb.question.parse-q-number" +) diff --git a/internal/config/embed/text/err_kb_cli.go b/internal/config/embed/text/err_kb_cli.go new file mode 100644 index 000000000..2569460e4 --- /dev/null +++ b/internal/config/embed/text/err_kb_cli.go @@ -0,0 +1,47 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for `ctx kb` CLI error wrappers. +const ( + // DescKeyErrKbCliGroundingMissing wraps a missing + // grounding-sources.md error. + DescKeyErrKbCliGroundingMissing = "err.kb.cli.grounding-missing" + // DescKeyErrKbCliGroundingEmpty wraps an empty + // grounding-sources.md error. + DescKeyErrKbCliGroundingEmpty = "err.kb.cli.grounding-empty" + // DescKeyErrKbCliTopicExists wraps a topic-already-exists + // refusal. + DescKeyErrKbCliTopicExists = "err.kb.cli.topic-exists" + // DescKeyErrKbCliMkdirIngest wraps `os.MkdirAll` for the + // ingest directory. + DescKeyErrKbCliMkdirIngest = "err.kb.cli.mkdir-ingest" + // DescKeyErrKbCliOpenFindings wraps `os.OpenFile` for the + // findings log. + DescKeyErrKbCliOpenFindings = "err.kb.cli.open-findings" + // DescKeyErrKbCliWriteFinding wraps a write to the findings + // log. + DescKeyErrKbCliWriteFinding = "err.kb.cli.write-finding" + // DescKeyErrKbCliReadKBIndex wraps `os.ReadFile` for the kb + // landing page during reindex. + DescKeyErrKbCliReadKBIndex = "err.kb.cli.read-kb-index" + // DescKeyErrKbCliWriteKBIndex wraps `os.WriteFile` for the + // kb landing page during reindex. + DescKeyErrKbCliWriteKBIndex = "err.kb.cli.write-kb-index" + // DescKeyErrKbCliReadTopicsDir wraps `os.ReadDir` of the + // topics directory during reindex. + DescKeyErrKbCliReadTopicsDir = "err.kb.cli.read-topics-dir" + // DescKeyErrKbCliMkdirTopic wraps `os.MkdirAll` for a new + // topic directory. + DescKeyErrKbCliMkdirTopic = "err.kb.cli.mkdir-topic" + // DescKeyErrKbCliReadTopicTemplate wraps `fs.ReadFile` for + // the embedded topic template. + DescKeyErrKbCliReadTopicTemplate = "err.kb.cli.read-topic-template" + // DescKeyErrKbCliWriteTopicIndex wraps `os.WriteFile` for + // the topic index.md. + DescKeyErrKbCliWriteTopicIndex = "err.kb.cli.write-topic-index" +) diff --git a/internal/config/embed/text/err_kb_evidence.go b/internal/config/embed/text/err_kb_evidence.go new file mode 100644 index 000000000..6b841af55 --- /dev/null +++ b/internal/config/embed/text/err_kb_evidence.go @@ -0,0 +1,32 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for kb evidence-index error wrappers. +const ( + // DescKeyErrKbEvidenceDuplicateID wraps the duplicate-EV-### + // sentinel with the offending ID. + DescKeyErrKbEvidenceDuplicateID = "err.kb.evidence.duplicate-id" + // DescKeyErrKbEvidenceInvalidBand wraps the invalid-confidence + // sentinel with the offending band string. + DescKeyErrKbEvidenceInvalidBand = "err.kb.evidence.invalid-band" + // DescKeyErrKbEvidenceReadIndex wraps an evidence-index read + // failure. + DescKeyErrKbEvidenceReadIndex = "err.kb.evidence.read-index" + // DescKeyErrKbEvidenceMkdirDir wraps a parent-dir mkdir + // failure for the evidence-index. + DescKeyErrKbEvidenceMkdirDir = "err.kb.evidence.mkdir-dir" + // DescKeyErrKbEvidenceOpenIndex wraps an open-for-append + // failure on the evidence-index. + DescKeyErrKbEvidenceOpenIndex = "err.kb.evidence.open-index" + // DescKeyErrKbEvidenceWriteRow wraps a row-write failure on + // the evidence-index. + DescKeyErrKbEvidenceWriteRow = "err.kb.evidence.write-row" + // DescKeyErrKbEvidenceParseIDNumber wraps a strconv.Atoi + // failure parsing an EV-### number. + DescKeyErrKbEvidenceParseIDNumber = "err.kb.evidence.parse-id-number" +) diff --git a/internal/config/embed/text/err_kb_sourcecoverage.go b/internal/config/embed/text/err_kb_sourcecoverage.go new file mode 100644 index 000000000..2d6b61b45 --- /dev/null +++ b/internal/config/embed/text/err_kb_sourcecoverage.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for kb source-coverage ledger error wrappers. +const ( + // DescKeyErrKbSourcecoverageIllegalTransition wraps the + // illegal-transition sentinel with from/to/source operands. + DescKeyErrKbSourcecoverageIllegalTransition = "err.kb.sourcecoverage.illegal-transition" + // DescKeyErrKbSourcecoverageUnknownSource wraps the + // unknown-source sentinel with source/state operands. + DescKeyErrKbSourcecoverageUnknownSource = "err.kb.sourcecoverage.unknown-source" + // DescKeyErrKbSourcecoverageReadLedger wraps a ledger-read + // failure. + DescKeyErrKbSourcecoverageReadLedger = "err.kb.sourcecoverage.read-ledger" + // DescKeyErrKbSourcecoverageMkdirLedgerDir wraps a parent-dir + // mkdir failure for the ledger. + DescKeyErrKbSourcecoverageMkdirLedgerDir = "err.kb.sourcecoverage.mkdir-ledger-dir" + // DescKeyErrKbSourcecoverageWriteLedger wraps a write + // failure on the ledger. + DescKeyErrKbSourcecoverageWriteLedger = "err.kb.sourcecoverage.write-ledger" +) diff --git a/internal/config/embed/text/initialize.go b/internal/config/embed/text/initialize.go index 80187e3fd..e4bece8c8 100644 --- a/internal/config/embed/text/initialize.go +++ b/internal/config/embed/text/initialize.go @@ -266,6 +266,9 @@ const ( // DescKeyInitLabelSteering is the text key for init label steering // (foundation files scaffold) messages. DescKeyInitLabelSteering = "init.label-steering" + // DescKeyInitLabelKB is the text key for init label kb editorial + // pipeline scaffold messages. + DescKeyInitLabelKB = "init.label-kb" ) // Init confirmation prompts and mode labels. diff --git a/internal/config/embed/text/write_handover.go b/internal/config/embed/text/write_handover.go new file mode 100644 index 000000000..bf7567abb --- /dev/null +++ b/internal/config/embed/text/write_handover.go @@ -0,0 +1,27 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for handover CLI output strings. The matching +// YAML entries live in commands/text/write.yaml; the +// `ctx handover write` command resolves them via desc.Text +// at output time. +const ( + // DescKeyWriteHandoverWrote is the text key for the + // "wrote <path>" success line. + DescKeyWriteHandoverWrote = "write.handover.wrote" + // DescKeyWriteHandoverFolded is the text key for the + // "folded N closeout(s); archived to <path>" line. + DescKeyWriteHandoverFolded = "write.handover.folded" + // DescKeyWriteHandoverMalformedWarning is the text key for + // the stderr warning block opener when malformed closeouts + // were skipped during fold. + DescKeyWriteHandoverMalformedWarning = "write.handover.malformed-warning" + // DescKeyWriteHandoverMalformedLine is the text key for one + // malformed-closeout filename line inside the warning block. + DescKeyWriteHandoverMalformedLine = "write.handover.malformed-line" +) diff --git a/internal/config/embed/text/write_kb_cli.go b/internal/config/embed/text/write_kb_cli.go new file mode 100644 index 000000000..90f38c63d --- /dev/null +++ b/internal/config/embed/text/write_kb_cli.go @@ -0,0 +1,53 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package text + +// DescKeys for `ctx kb` CLI output strings. +const ( + // DescKeyWriteKbCliFindingLine is the findings-log line + // format (timestamp + text). + DescKeyWriteKbCliFindingLine = "write.kb-cli.finding-line" + // DescKeyWriteKbCliReindexed names how many topics were + // folded into the managed block and the path rewritten. + DescKeyWriteKbCliReindexed = "write.kb-cli.reindexed" + // DescKeyWriteKbCliScaffolded names the path of a newly + // scaffolded topic-page file. + DescKeyWriteKbCliScaffolded = "write.kb-cli.scaffolded" + // DescKeyWriteKbCliAppendedTo names the destination of a + // successful note append. + DescKeyWriteKbCliAppendedTo = "write.kb-cli.appended-to" + // DescKeyWriteKbCliAskDrivenHint announces the canonical + // /ctx-kb-ask skill invocation. + DescKeyWriteKbCliAskDrivenHint = "write.kb-cli.ask-driven-hint" + // DescKeyWriteKbCliAskInvokeFormat carries the inline + // invocation example. + DescKeyWriteKbCliAskInvokeFormat = "write.kb-cli.ask-invoke-format" + // DescKeyWriteKbCliAskContractPointer points at the ask + // contract source-of-truth. + DescKeyWriteKbCliAskContractPointer = "write.kb-cli.ask-contract-pointer" + // DescKeyWriteKbCliGroundDrivenHint announces the canonical + // /ctx-kb-ground skill invocation. + DescKeyWriteKbCliGroundDrivenHint = "write.kb-cli.ground-driven-hint" + // DescKeyWriteKbCliGroundContractPointer points at the + // ground contract source-of-truth. + DescKeyWriteKbCliGroundContractPointer = "write.kb-cli.ground-contract-pointer" + // DescKeyWriteKbCliIngestDrivenHint announces the canonical + // /ctx-kb-ingest skill invocation. + DescKeyWriteKbCliIngestDrivenHint = "write.kb-cli.ingest-driven-hint" + // DescKeyWriteKbCliIngestInvokeFormat carries the inline + // ingest invocation example. + DescKeyWriteKbCliIngestInvokeFormat = "write.kb-cli.ingest-invoke-format" + // DescKeyWriteKbCliIngestFallbackHint points at the + // hand-fallback prompt. + DescKeyWriteKbCliIngestFallbackHint = "write.kb-cli.ingest-fallback-hint" + // DescKeyWriteKbCliSiteReviewDrivenHint announces the + // canonical /ctx-kb-site-review skill invocation. + DescKeyWriteKbCliSiteReviewDrivenHint = "write.kb-cli.site-review-driven-hint" + // DescKeyWriteKbCliSiteReviewContractPointer points at the + // site-review contract source-of-truth. + DescKeyWriteKbCliSiteReviewContractPointer = "write.kb-cli.site-review-contract-pointer" +) diff --git a/internal/config/flag/flag.go b/internal/config/flag/flag.go index 7a9f28adc..5a004b200 100644 --- a/internal/config/flag/flag.go +++ b/internal/config/flag/flag.go @@ -155,3 +155,21 @@ const ( Since = "since" Until = "until" ) + +// Handover-write body flag names. Used by the +// `ctx handover write` subcommand to bind body flags via the +// shared flagbind helpers. +const ( + // Summary is the --summary flag for `ctx handover write`. + Summary = "summary" + // Next is the --next flag for `ctx handover write`. + Next = "next" + // Highlights is the --highlights flag for + // `ctx handover write`. + Highlights = "highlights" + // OpenQuestions is the --open-questions flag for + // `ctx handover write`. + OpenQuestions = "open-questions" + // NoFold is the --no-fold flag for `ctx handover write`. + NoFold = "no-fold" +) diff --git a/internal/config/git/git.go b/internal/config/git/git.go index 197388574..2a5f5d987 100644 --- a/internal/config/git/git.go +++ b/internal/config/git/git.go @@ -9,6 +9,10 @@ package git // Binary is the git executable name. const Binary = "git" +// DotDir is the metadata directory (or worktree-pointer file) +// name at the root of any git working tree. +const DotDir = ".git" + // Subcommand names passed as the first argument to git. const ( Branch = "branch" diff --git a/internal/config/git_meta/doc.go b/internal/config/git_meta/doc.go new file mode 100644 index 000000000..959dac406 --- /dev/null +++ b/internal/config/git_meta/doc.go @@ -0,0 +1,21 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package gitmeta supplies the environment-variable names, +// special branch-name literals, and short-SHA length used by +// [github.com/ActiveMemory/ctx/internal/git_meta]. The constants +// live here (not in the gitmeta package itself) to honor the +// project-wide rule that magic strings and values belong in +// internal/config/. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/git_meta] is the +// primary consumer. +// - [github.com/ActiveMemory/ctx/internal/config/git] supplies +// git binary + subcommand constants; gitmeta complements it +// with provenance-resolution constants. +package gitmeta diff --git a/internal/config/git_meta/git_meta.go b/internal/config/git_meta/git_meta.go new file mode 100644 index 000000000..85d8692cd --- /dev/null +++ b/internal/config/git_meta/git_meta.go @@ -0,0 +1,55 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +// Environment variables honored as provenance overrides for +// CI replay. +const ( + // EnvCtxTaskCommit overrides the resolved short SHA with + // a user-supplied value (typically used by CI replay). + EnvCtxTaskCommit = "CTX_TASK_COMMIT" + + // EnvGithubActions is GitHub Actions' canonical truthy + // marker; checked alongside EnvGithubSHA so the override + // fires only inside a real Actions run. + EnvGithubActions = "GITHUB_ACTIONS" + + // EnvGithubSHA is GitHub Actions' commit-SHA injection. + EnvGithubSHA = "GITHUB_SHA" +) + +// GithubActionsTrue is the literal value that GITHUB_ACTIONS +// carries when running under GitHub Actions. +const GithubActionsTrue = "true" + +// BranchDetached is the canonical branch-name placeholder when +// HEAD is detached (points at a commit, not a symbolic ref). +// Used by [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead] +// and recorded verbatim in closeout / handover frontmatter. +const BranchDetached = "detached" + +// RefHEAD is the git ref name for the current commit. +const RefHEAD = "HEAD" + +// ShortLen is the truncation length for short SHAs (git's +// default --short width). +const ShortLen = 7 + +// Sentinel error-message constants. These back `errors.New` +// values in `internal/err/git_meta/` and are matched via +// `errors.Is` at the call site. They cannot use desc.Text +// because the sentinels are package-level vars evaluated +// before the embedded YAML lookup is populated; wrapping +// format strings live in commands/text/errors.yaml. +const ( + // ErrMsgMissingGitTree is the sentinel for the + // "<projectRoot>/.git is absent" condition. + ErrMsgMissingGitTree = "git working tree required" + // ErrMsgResolveHeadEmpty signals that `git rev-parse --short + // HEAD` returned an empty string (typically: unborn HEAD). + ErrMsgResolveHeadEmpty = "resolve git head: empty output" +) diff --git a/internal/config/handover/doc.go b/internal/config/handover/doc.go new file mode 100644 index 000000000..629692c05 --- /dev/null +++ b/internal/config/handover/doc.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package handover supplies the sentinel-error message strings, +// wrapping-format strings, and section-header constants used by +// the handover writer (Phase KB). +// +// Holding the strings here honours the project-wide rule that +// magic strings belong under `internal/config/`; the +// [github.com/ActiveMemory/ctx/internal/err/handover] package +// consumes these constants when constructing user-facing +// errors, and the +// [github.com/ActiveMemory/ctx/internal/write/handover] +// package consumes the section-header constants when composing +// the markdown body. +package handover diff --git a/internal/config/handover/handover.go b/internal/config/handover/handover.go new file mode 100644 index 000000000..8ad0b278a --- /dev/null +++ b/internal/config/handover/handover.go @@ -0,0 +1,87 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +// Subdir is the per-session handover artifact subdirectory +// under `.context/`. Files inside are named +// `<TS>-<slug>.md` so concurrent agent runs never overwrite +// each other. +const Subdir = "handovers" + +// Sentinel error-message constants. These back `errors.New` +// values in `internal/err/handover/` and are matched via +// `errors.Is` at the call site. They cannot use desc.Text +// because the err/handover sentinels are package-level vars +// evaluated before the embedded YAML lookup is populated; +// wrapping-format strings and CLI output strings live in +// commands/text/{errors,write}.yaml instead. +const ( + // ErrMsgTitleRequired signals an empty Title supplied to + // Write. + ErrMsgTitleRequired = "handover title is required" + // ErrMsgSummaryRequired signals an empty Summary supplied + // to Write. + ErrMsgSummaryRequired = "handover summary is required" + // ErrMsgNextRequired signals an empty Next supplied to + // Write. + ErrMsgNextRequired = "handover next-session field is required" + // ErrMsgMissingFrontmatter signals a handover file that + // does not open with `---`. + ErrMsgMissingFrontmatter = "handover missing frontmatter" + // ErrMsgMissingClosingDelim signals a handover whose + // frontmatter is never closed. + ErrMsgMissingClosingDelim = "handover missing closing frontmatter delimiter" + // ErrMsgMissingGeneratedAt signals a handover whose + // frontmatter parsed but has no generated-at value. + ErrMsgMissingGeneratedAt = "handover missing generated-at" +) + +// Section header constants used when composing the handover +// markdown body. These are structural identifiers, not +// localizable prose: the read side matches them exactly. +const ( + // SectionSummary headers the past-tense session summary. + SectionSummary = "## Summary" + // SectionNext headers the future-tense first action for + // the next session. + SectionNext = "## Next session" + // SectionHighlights headers the optional highlights list. + SectionHighlights = "## Highlights" + // SectionOpenQuestions headers the optional + // open-questions list. + SectionOpenQuestions = "## Open questions" + // SectionFoldedCloseouts headers the auto-generated list + // of closeouts folded into the new handover. + SectionFoldedCloseouts = "## Folded closeouts" +) + +// BranchDetached is the literal branch label written into +// handovers when git HEAD is not on a symbolic ref. +const BranchDetached = "detached" + +// DefaultSlug is the fallback slug for titles that reduce to +// an empty string after kebab-case normalisation. +const DefaultSlug = "handover" + +// FoldEntryPrefix is the marker that opens each `## Folded +// closeouts` list item. +const FoldEntryPrefix = "- `" + +// FoldEntryModePrefix opens the metadata section in a folded +// entry by closing the filename backtick and introducing the +// mode field with a colon. +const FoldEntryModePrefix = "`: mode=" + +// FoldEntryPassModePrefix opens the optional pass-mode field +// in a folded entry's metadata section. +// +//nolint:gosec // G101: literal markdown delimiter, not a credential +const FoldEntryPassModePrefix = ", pass-mode=" + +// FoldEntryGeneratedAtPrefix opens the generated-at timestamp +// in a folded entry's metadata section. +const FoldEntryGeneratedAtPrefix = ", generated-at=" diff --git a/internal/config/hook/hook.go b/internal/config/hook/hook.go index 575fa3c49..ee4f6dbb4 100644 --- a/internal/config/hook/hook.go +++ b/internal/config/hook/hook.go @@ -133,7 +133,7 @@ const ( KeyMCP = "mcp" // FileIndexTs is the embedded-asset filename for the OpenCode // plugin source. The setup deploys this content to a flat file - // under [DirOpenCodePlugins], NOT preserving the index.ts name — + // under [DirOpenCodePlugins], NOT preserving the index.ts name. // OpenCode only auto-loads top-level files in .opencode/plugins/, // so subdirectory layouts (.opencode/plugins/<name>/index.ts) // are silently ignored. diff --git a/internal/config/initialize/doc.go b/internal/config/initialize/doc.go index 9b4cef2d7..1190de914 100644 --- a/internal/config/initialize/doc.go +++ b/internal/config/initialize/doc.go @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 // Package initialize hosts compile-time constants consumed by -// the ctx init command — sentinel error messages used by +// the ctx init command: sentinel error messages used by // the err/initialize package, backup directory naming for // ctx init --reset, and the canonical reset flag name. // diff --git a/internal/config/initialize/messages.go b/internal/config/initialize/initialize.go similarity index 94% rename from internal/config/initialize/messages.go rename to internal/config/initialize/initialize.go index 9940e2289..6e9b9e5ae 100644 --- a/internal/config/initialize/messages.go +++ b/internal/config/initialize/initialize.go @@ -8,8 +8,8 @@ // the ctx init command (sentinel error messages, backup // directory naming, reset flag literal). // -// Sentinel error messages live here — not in the embedded YAML -// loaded via desc.Text — because the err/initialize package +// Sentinel error messages live here (not in the embedded YAML +// loaded via desc.Text) because the err/initialize package // instantiates them at package-load time, before the YAML // lookup table is populated. package initialize diff --git a/internal/config/initialize/kb/doc.go b/internal/config/initialize/kb/doc.go new file mode 100644 index 000000000..b7dc9f59e --- /dev/null +++ b/internal/config/initialize/kb/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kb supplies the wrapping-format constants used by +// [github.com/ActiveMemory/ctx/internal/err/initialize/kb] when +// `ctx init` scaffolds the .context/kb/, .context/ingest/, and +// .context/handovers/ directory tree. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/initialize/kb] declares +// the constructors. +// - [github.com/ActiveMemory/ctx/internal/cli/initialize/core/kb] +// is the primary caller. +package kb diff --git a/internal/config/initialize/kb/kb.go b/internal/config/initialize/kb/kb.go new file mode 100644 index 000000000..cef8d8b72 --- /dev/null +++ b/internal/config/initialize/kb/kb.go @@ -0,0 +1,14 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +// GitkeepName is the stub-file basename written into +// otherwise empty `ctx init`-scaffolded directories so they +// round-trip through git. Structural literal; not localizable. +// Error wrapping format strings have moved to +// commands/text/errors.yaml. +const GitkeepName = ".gitkeep" diff --git a/internal/config/kb/cli/cli.go b/internal/config/kb/cli/cli.go new file mode 100644 index 000000000..563a45acb --- /dev/null +++ b/internal/config/kb/cli/cli.go @@ -0,0 +1,74 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package cli + +// Sentinel error-message constants. These back `errors.New` +// values in [github.com/ActiveMemory/ctx/internal/err/kb/cli] +// and are matched via `errors.Is` at the call site. They +// cannot use desc.Text because the sentinels are +// package-level vars evaluated before the embedded YAML +// lookup is populated; wrapping format strings and CLI +// output strings have moved to commands/text/{errors,write}.yaml. +const ( + // ErrMsgAskNoQuestion signals an empty `ctx kb ask` + // invocation (no question argument provided). + ErrMsgAskNoQuestion = "no question provided; pass a question or " + + "describe it inline" + // ErrMsgIngestNoSources signals an empty `ctx kb ingest` + // invocation (no source argument provided). + ErrMsgIngestNoSources = "no sources provided; pass a folder, a URL, " + + "an MCP resource, or describe the materials inline" + // ErrMsgNoteNoText signals an empty `ctx kb note` + // invocation (no text argument provided). + ErrMsgNoteNoText = "no text provided; pass a one-liner inline" + // ErrMsgTopicEmptyName signals a `ctx kb topic new` + // invocation whose name reduces to an empty slug. + ErrMsgTopicEmptyName = "topic name must contain at least one " + + "alnum char" + // ErrMsgReindexMissingBlock signals a kb landing page that + // is missing the CTX:KB:TOPICS managed block. + ErrMsgReindexMissingBlock = "kb/index.md is missing the " + + "CTX:KB:TOPICS managed block" +) + +// Topic-template substitution tokens. Replaced by the topic +// scaffolder with the human-readable name and the kebab-case +// slug, respectively. Structural literals; not localizable. +const ( + // TokenTopicName is the long-form token for the topic name. + TokenTopicName = "<TOPIC_NAME>" + // TokenTopicSlug is the long-form token for the slug. + // + //nolint:gosec // G101: angle-bracket placeholder, not a credential + TokenTopicSlug = "<TOPIC_SLUG>" + // TokenName is the short-form token for the topic name. + TokenName = "<NAME>" + // TokenSlug is the short-form token for the slug. + TokenSlug = "<SLUG>" +) + +// ManagedBlock start/end markers. The reindex command rewrites +// the contents between these markers in the kb landing page. +// Structural literals parsed by the reindex regex; not +// localizable. +const ( + // ManagedKBTopicsStart opens the CTX:KB:TOPICS managed block. + ManagedKBTopicsStart = "<!-- CTX:KB:TOPICS START -->" + // ManagedKBTopicsEnd closes the CTX:KB:TOPICS managed block. + ManagedKBTopicsEnd = "<!-- CTX:KB:TOPICS END -->" + // ManagedKBTopicsEmpty is the placeholder line written into + // the managed block when no topics exist yet. + ManagedKBTopicsEmpty = "- _no topics yet; create one with " + + "`ctx kb topic new \"<name>\"`_\n" + // TopicEntryPrefix opens each topic list item. + TopicEntryPrefix = "- [`" + // TopicEntryMiddle separates the slug from the + // link target in a topic list item. + TopicEntryMiddle = "`](topics/" + // TopicEntrySuffix closes each topic list item. + TopicEntrySuffix = "/)\n" +) diff --git a/internal/config/kb/cli/doc.go b/internal/config/kb/cli/doc.go new file mode 100644 index 000000000..b46c86957 --- /dev/null +++ b/internal/config/kb/cli/doc.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package cli supplies the sentinel-message and +// wrapping-format constants used by +// [github.com/ActiveMemory/ctx/internal/err/kb/cli] to compose +// errors surfaced from the `ctx kb` CLI subcommands (ask, +// ground, ingest, note, reindex, topic). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/cli] declares +// the constructor functions and sentinels. +// - [github.com/ActiveMemory/ctx/internal/cli/kb/cmd/...] are +// the primary callers. +package cli diff --git a/internal/config/kb/contradiction/contradiction.go b/internal/config/kb/contradiction/contradiction.go new file mode 100644 index 000000000..95c027e2f --- /dev/null +++ b/internal/config/kb/contradiction/contradiction.go @@ -0,0 +1,25 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +// Markdown rendering constants for the contradictions +// artifact. Structural literals stay as Go consts; error +// wrapping format strings have moved to +// commands/text/errors.yaml. +const ( + // IDPrefix is the stable prefix for contradiction IDs. + IDPrefix = "C-" + // IDFormat is the Printf format for a zero-padded + // `C-NNN` identifier. + IDFormat = "%s%03d" + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| ID | Evidence | Summary | Demotion " + + "applied | Status |\n" + + "|----|----------|---------|------------------|" + + "--------|\n" +) diff --git a/internal/config/kb/contradiction/doc.go b/internal/config/kb/contradiction/doc.go new file mode 100644 index 000000000..9a2f691ff --- /dev/null +++ b/internal/config/kb/contradiction/doc.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package contradiction supplies the wrapping-format +// strings and rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/contradiction] +// and +// [github.com/ActiveMemory/ctx/internal/write/kb/contradiction]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/contradiction] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/contradiction] +// is the primary caller-side surface. +package contradiction diff --git a/internal/config/kb/decision/decision.go b/internal/config/kb/decision/decision.go new file mode 100644 index 000000000..77b7832ce --- /dev/null +++ b/internal/config/kb/decision/decision.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +// Markdown rendering constants for the domain-decisions +// artifact. Structural literals stay as Go consts; error +// wrapping format strings have moved to +// commands/text/errors.yaml. +const ( + // IDPrefix is the stable prefix for domain-decision IDs. + // The canonical schema pins this to `DD-` to keep the + // namespace distinct from any future project-side `D-###` + // series. + IDPrefix = "DD-" + // IDFormat is the Printf format for a zero-padded + // `DD-NNN` identifier. + IDFormat = "%s%03d" + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| ID | Date | Context | Decision |" + + " Rationale | Consequence | Supporting EV |\n" + + "|----|------|---------|----------|-----------|" + + "-------------|---------------|\n" +) diff --git a/internal/config/kb/decision/doc.go b/internal/config/kb/decision/doc.go new file mode 100644 index 000000000..02f365f54 --- /dev/null +++ b/internal/config/kb/decision/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package decision supplies the wrapping-format strings and +// rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/decision] and +// [github.com/ActiveMemory/ctx/internal/write/kb/decision]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/decision] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/decision] +// is the primary caller-side surface. +package decision diff --git a/internal/config/kb/doc.go b/internal/config/kb/doc.go new file mode 100644 index 000000000..7c46a44cb --- /dev/null +++ b/internal/config/kb/doc.go @@ -0,0 +1,57 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kb supplies directory and filename constants for the +// ctx knowledge-base editorial pipeline (Phase KB). +// +// Names live here, not in [github.com/ActiveMemory/ctx/internal/cli/kb], +// to honor the project-wide rule that magic strings and values +// belong in internal/config/. +// +// # Layout +// +// Under the project's .context/ directory: +// +// .context/ +// ├── kb/ +// │ ├── index.md (KBIndex) +// │ ├── evidence-index.md (EvidenceIndex) +// │ ├── glossary.md (Glossary) +// │ ├── contradictions.md (Contradictions) +// │ ├── outstanding-questions.md (OutstandingQuestions) +// │ ├── domain-decisions.md (DomainDecisions) +// │ ├── timeline.md (Timeline) +// │ ├── source-map.md (SourceMap) +// │ ├── source-coverage.md (SourceCoverage) +// │ ├── relationship-map.md (RelationshipMap) +// │ └── topics/<slug>/index.md (Topics dir + TopicIndex filename) +// ├── ingest/ +// │ ├── KB-RULES.md (Rules) +// │ ├── 00-GROUND.md (ModeGround) +// │ ├── 30-INGEST.md (ModeIngest) +// │ ├── 40-ASK.md (ModeAsk) +// │ ├── 50-SITE_REVIEW.md (ModeSiteReview) +// │ ├── INBOX.md (Inbox) +// │ ├── SESSION_LOG.md (SessionLog) +// │ ├── grounding-sources.md (GroundingSources) +// │ ├── OPERATOR.md (Operator) +// │ ├── PROMPT.md (Prompt) +// │ ├── closeouts/ (CloseoutsSubdir) +// │ ├── schemas/ (SchemasSubdir) +// │ └── findings.md (Findings; lazy-init via ctx kb note) +// ├── handovers/ (HandoversSubdir) +// ├── archive/closeouts/ (ArchiveCloseoutsSubdir) +// └── site/kb/ (SiteKBSubdir; gitignored) +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// composes these constants with [rc.ContextDir] to produce +// full filesystem paths. +// - [github.com/ActiveMemory/ctx/internal/write/kb] writes the +// evidence-index, glossary, source-coverage, and other +// per-artifact files. +package kb diff --git a/internal/config/kb/evidence/doc.go b/internal/config/kb/evidence/doc.go new file mode 100644 index 000000000..e6870f506 --- /dev/null +++ b/internal/config/kb/evidence/doc.go @@ -0,0 +1,24 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package evidence supplies the sentinel-error message +// strings, wrapping-format strings, and rendering tokens used +// by [github.com/ActiveMemory/ctx/internal/err/kb/evidence] and +// [github.com/ActiveMemory/ctx/internal/write/kb/evidence]. +// +// Hosting these literals here honours the project-wide rule +// that magic strings belong under `internal/config/`; the err +// package consumes the error constants while the writer +// consumes the markdown rendering constants. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/evidence] +// declares the sentinel error vars + constructors that +// consume these strings. +// - [github.com/ActiveMemory/ctx/internal/write/kb/evidence] +// is the primary caller-side surface. +package evidence diff --git a/internal/config/kb/evidence/evidence.go b/internal/config/kb/evidence/evidence.go new file mode 100644 index 000000000..865ab920b --- /dev/null +++ b/internal/config/kb/evidence/evidence.go @@ -0,0 +1,52 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +// Sentinel error-message constants. These back `errors.New` +// values declared in `internal/err/kb/evidence/` and are +// matched via `errors.Is` at the call site. They cannot use +// desc.Text because the sentinels are package-level vars +// evaluated before the embedded YAML lookup is populated; +// wrapping-format strings have moved to +// commands/text/errors.yaml. +const ( + // ErrMsgDuplicateID signals an Append called with a row.ID + // already present in the evidence index. + ErrMsgDuplicateID = "duplicate EV-### id" + // ErrMsgInvalidBand signals a row whose Confidence is not + // one of the four canonical bands. + ErrMsgInvalidBand = "invalid confidence band" +) + +// Markdown rendering constants for the evidence-index file. +// Structural literals (headings, table shape, ID format) +// stay as Go consts. +const ( + // TitleHeading is the H1 written above the evidence table + // when the file is first created. + TitleHeading = "# Evidence index" + // LeadParagraph1 is the first instructional paragraph + // rendered after the title. + LeadParagraph1 = "Append-only EV-### rows. " + + "Never renumber, never delete." + // LeadParagraph2 is the second instructional paragraph + // rendered after the title. + LeadParagraph2 = "Demote in place when reconciliation " + + "requires it (see KB-RULES.md)." + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| ID | Claim | Source | Locator | sha |" + + " Confidence | Tags | Occurred | Extracted |\n" + + "|---|---|---|---|---|---|---|---|---|" + // RowFormat is the Printf format consumed by the row + // renderer; operands map to the Row struct fields. + RowFormat = "| %s | %s | %s | %s | %s | %s |" + + " %s | %s | %s |" + // IDFormat is the Printf format used by the ID minter + // (prefix, digit-width, integer). + IDFormat = "%s-%0*d" +) diff --git a/internal/config/kb/glossary/doc.go b/internal/config/kb/glossary/doc.go new file mode 100644 index 000000000..387886dae --- /dev/null +++ b/internal/config/kb/glossary/doc.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package glossary supplies the wrapping-format strings and +// rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/glossary] and +// [github.com/ActiveMemory/ctx/internal/write/kb/glossary]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/glossary] +// declares the constructor functions that consume these +// strings. +// - [github.com/ActiveMemory/ctx/internal/write/kb/glossary] +// is the primary caller-side surface. +package glossary diff --git a/internal/config/kb/glossary/glossary.go b/internal/config/kb/glossary/glossary.go new file mode 100644 index 000000000..fbd3cf27e --- /dev/null +++ b/internal/config/kb/glossary/glossary.go @@ -0,0 +1,21 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary + +// Markdown rendering constants for the glossary artifact. +// These are structural literals (table header bytes) and stay +// here as Go consts. Error wrapping format strings have moved +// to commands/text/errors.yaml and are resolved via desc.Text +// inside internal/err/kb/glossary/. +const ( + // TableHeader is the markdown table header row plus its + // delimiter row, written when the file is first created. + TableHeader = "| Term | Definition | Confidence |" + + " Evidence | Related terms |\n" + + "|------|------------|------------|----------|" + + "---------------|\n" +) diff --git a/internal/config/kb/kb.go b/internal/config/kb/kb.go new file mode 100644 index 000000000..2ea269d70 --- /dev/null +++ b/internal/config/kb/kb.go @@ -0,0 +1,140 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +// Subdirectory names under .context/. +const ( + // KBSubdir is the knowledge-base subdirectory name. + KBSubdir = "kb" + // IngestSubdir is the editorial-pipeline working subdirectory. + IngestSubdir = "ingest" + // SiteSubdir is the gitignored rendered-site output root. + SiteSubdir = "site" +) + +// Sub-paths under the KB / ingest / site / archive subdirectories. +const ( + // TopicsSubdir holds folder-shaped topic pages under .context/kb/. + TopicsSubdir = "topics" + // CloseoutsSubdir holds per-pass closeout files under + // .context/ingest/. + CloseoutsSubdir = "closeouts" + // SchemasSubdir holds schema templates under .context/ingest/. + SchemasSubdir = "schemas" + // ArchiveCloseoutsSubdir holds folded closeouts under + // .context/archive/. + ArchiveCloseoutsSubdir = "closeouts" + // SiteKBSubdir holds rendered KB output under .context/site/. + SiteKBSubdir = "kb" +) + +// KB-side filenames under .context/kb/. +const ( + // KBIndex is the kb landing page (carries scope + topic + // managed block). + KBIndex = "index.md" + // Glossary is the kb-scoped domain glossary. + Glossary = "glossary.md" + // Contradictions records EV rows that disagree. + Contradictions = "contradictions.md" + // OutstandingQuestions records Q-### entries the kb cannot + // yet answer. + OutstandingQuestions = "outstanding-questions.md" + // DomainDecisions holds kb-scoped decisions (distinct from + // project-level DECISIONS.md; different schema, different + // authority). + DomainDecisions = "domain-decisions.md" + // Timeline records dated events worth recording. + Timeline = "timeline.md" + // SourceMap is the discovery + admission map (kind, url, etc.). + SourceMap = "source-map.md" + // RelationshipMap records cross-topic + cross-source ties. + RelationshipMap = "relationship-map.md" + // TopicIndex is the per-topic landing filename under + // topics/<slug>/. + TopicIndex = "index.md" +) + +// Ingest-side filenames under .context/ingest/. +const ( + // Rules is the editorial constitution (the kb's + // counterpart to .context/CONSTITUTION.md, named to avoid + // the collision per DECISIONS.md 2026-05-10). + Rules = "KB-RULES.md" + // GroundingSources lists external grounding inputs for + // ctx kb ground. + GroundingSources = "grounding-sources.md" + // Prompt is the hand-fallback auto-router used when no + // skill is installed. + Prompt = "PROMPT.md" + // Findings is the lazy-init destination for ctx kb note. + Findings = "findings.md" +) + +// Closeout-file shape: <TIMESTAMP>-<mode>-closeout.md per the +// generalized naming in specs/kb-editorial-pipeline.md (the +// upstream uses single-mode "ingest-closeout.md"; ctx is multi-mode). +const ( + // CloseoutSuffix is appended after the mode token. + CloseoutSuffix = "-closeout.md" + // CloseoutModeIngest names ingest-mode closeouts. + CloseoutModeIngest = "ingest" + // CloseoutModeAsk names ask-mode closeouts. + CloseoutModeAsk = "ask" + // CloseoutModeGround names ground-mode closeouts. + CloseoutModeGround = "ground" +) + +// Source-coverage ledger states. These string values are +// written verbatim into `.context/kb/source-coverage.md` rows +// and parsed back by the source-coverage writer. The allowed +// transitions between them live in +// [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage]. +const ( + StateDiscovered = "discovered" + StateAdmitted = "admitted" + StateHighlightsExtracted = "highlights-extracted" + StatePartiallyIngested = "partially-ingested" + StateTopicPageDrafted = "topic-page-drafted" + StateComprehensive = "comprehensive" + StateSkipped = "skipped" +) + +// Confidence bands per the spec's confidence-laddering rules. +const ( + ConfidenceHigh = "high" + ConfidenceMedium = "medium" + ConfidenceLow = "low" + ConfidenceSpeculative = "speculative" +) + +// EVIDPrefix is the prefix for evidence rows (`EV-###`). +const EVIDPrefix = "EV" + +// EVIDDigits is the zero-padded width for evidence row numbers. +const EVIDDigits = 3 + +// EvidenceOnlyTag is the additive tag applied to rows minted in +// evidence-only mode passes. A future topic-page pass must +// re-read the source before citing such rows. +const EvidenceOnlyTag = "evidence-only" + +// Topic-page template asset paths (under internal/assets/). +const ( + // AssetTemplatesIngest is the embedded-asset root for ingest + // templates. + AssetTemplatesIngest = "kb/templates/ingest" + // AssetTemplatesSchemas is the embedded-asset root for ingest + // schema templates. + AssetTemplatesSchemas = "kb/templates/ingest/schemas" + // AssetTemplateKBIndex is the embedded-asset path for the kb + // landing template. + AssetTemplateKBIndex = "kb/templates/kb/index.md" + // AssetTemplateTopicIndex is the embedded-asset path for the + // topic-page index.md template. + AssetTemplateTopicIndex = "kb/templates/kb/topics/_template/index.md" +) diff --git a/internal/config/kb/question/doc.go b/internal/config/kb/question/doc.go new file mode 100644 index 000000000..5ea595f47 --- /dev/null +++ b/internal/config/kb/question/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package question supplies the wrapping-format strings and +// rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/question] and +// [github.com/ActiveMemory/ctx/internal/write/kb/question]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/question] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/question] +// is the primary caller-side surface. +package question diff --git a/internal/config/kb/question/question.go b/internal/config/kb/question/question.go new file mode 100644 index 000000000..28fc6de1d --- /dev/null +++ b/internal/config/kb/question/question.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +// Markdown rendering constants for the outstanding-questions +// artifact. Structural literals stay as Go consts; error +// wrapping format strings have moved to +// commands/text/errors.yaml. +const ( + // IDPrefix is the stable prefix for outstanding-question + // IDs. + IDPrefix = "Q-" + // IDFormat is the Printf format for a zero-padded + // `Q-NNN` identifier. + IDFormat = "%s%03d" + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| ID | Question | Why it matters |" + + " What evidence would resolve | Opened at |" + + " Related EV |\n" + + "|----|----------|----------------|" + + "-----------------------------|-----------|" + + "------------|\n" +) diff --git a/internal/config/kb/relationship/doc.go b/internal/config/kb/relationship/doc.go new file mode 100644 index 000000000..235c4a92b --- /dev/null +++ b/internal/config/kb/relationship/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package relationship supplies the wrapping-format strings +// and rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/relationship] and +// [github.com/ActiveMemory/ctx/internal/write/kb/relationship]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/relationship] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/relationship] +// is the primary caller-side surface. +package relationship diff --git a/internal/config/kb/relationship/relationship.go b/internal/config/kb/relationship/relationship.go new file mode 100644 index 000000000..53e855510 --- /dev/null +++ b/internal/config/kb/relationship/relationship.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship + +// Markdown rendering constants for the relationship-map +// artifact. Structural literals stay as Go consts; error +// wrapping format strings have moved to +// commands/text/errors.yaml. +const ( + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| From | To | Kind | Summary |\n" + + "|------|----|------|---------|\n" +) diff --git a/internal/config/kb/sourcecoverage/doc.go b/internal/config/kb/sourcecoverage/doc.go new file mode 100644 index 000000000..56bbee3f3 --- /dev/null +++ b/internal/config/kb/sourcecoverage/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcecoverage supplies the sentinel-error +// messages, wrapping-format strings, and rendering tokens +// used by +// [github.com/ActiveMemory/ctx/internal/err/kb/sourcecoverage] +// and +// [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage]. +// +// Hosting these literals here honours the project-wide rule +// that magic strings belong under `internal/config/`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/sourcecoverage] +// declares the sentinel error vars + constructors. +// - [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage] +// is the primary caller-side surface. +package sourcecoverage diff --git a/internal/config/kb/sourcecoverage/sourcecoverage.go b/internal/config/kb/sourcecoverage/sourcecoverage.go new file mode 100644 index 000000000..946abd08b --- /dev/null +++ b/internal/config/kb/sourcecoverage/sourcecoverage.go @@ -0,0 +1,75 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +// Sentinel error-message constants. These back `errors.New` +// values in `internal/err/kb/sourcecoverage/` and are matched +// via `errors.Is` at the call site. They cannot use desc.Text +// because the sentinels are package-level vars evaluated +// before the embedded YAML lookup is populated; wrapping +// format strings have moved to commands/text/errors.yaml. +const ( + // ErrMsgIllegalTransition signals that Advance was called + // with a state pair the ledger's state machine rejects. + ErrMsgIllegalTransition = "illegal state transition" + // ErrMsgUnknownSource signals that Advance referenced a + // Source not yet present in the ledger AND the new State is + // not one of the entry-point states (discovered, admitted). + ErrMsgUnknownSource = "unknown source for non-initial state" +) + +// Markdown rendering constants for the source-coverage ledger. +// Structural literals stay as Go consts. +const ( + // TitleHeading is the H1 written above the ledger table. + TitleHeading = "# Source coverage" + // LeadParagraph1 is the first instructional paragraph. + LeadParagraph1 = "State-machine ledger over every source " + + "the kb has touched." + // LeadParagraph2 is the second instructional paragraph. + LeadParagraph2 = "Updated automatically by ctx kb " + + "commands; hand-edits are honored" + // LeadParagraph3 is the third instructional paragraph. + LeadParagraph3 = "but the next pass will re-write rows " + + "it touches." + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| Source | Topic | State | EV coverage |" + + " Residue | Next action | Updated |\n" + + "|---|---|---|---|---|---|---|" + // HeaderCellSource is the header cell that identifies a + // header row during ledger parsing. + HeaderCellSource = "Source" + // DelimRowPrefix is the prefix that identifies a markdown + // table delimiter row ("|---|...|"). + DelimRowPrefix = "|---" + // ExpectedCellCount is the column count for a valid ledger + // row (Source, Topic, State, EVCoverage, Residue, + // NextAction, Updated). + ExpectedCellCount = 7 +) + +// Column indexes into a parsed ledger row. The same indexes +// drive the row binder in +// [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage] +// when mapping cells back to Row fields. +const ( + // ColSource is the index of the Source cell. + ColSource = 0 + // ColTopic is the index of the Topic cell. + ColTopic = 1 + // ColState is the index of the State cell. + ColState = 2 + // ColEVCoverage is the index of the EV coverage cell. + ColEVCoverage = 3 + // ColResidue is the index of the Residue cell. + ColResidue = 4 + // ColNextAction is the index of the Next action cell. + ColNextAction = 5 + // ColUpdated is the index of the Updated cell. + ColUpdated = 6 +) diff --git a/internal/config/kb/sourcemap/doc.go b/internal/config/kb/sourcemap/doc.go new file mode 100644 index 000000000..f0b6f6c11 --- /dev/null +++ b/internal/config/kb/sourcemap/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcemap supplies the wrapping-format strings and +// rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/sourcemap] and +// [github.com/ActiveMemory/ctx/internal/write/kb/sourcemap]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/sourcemap] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/sourcemap] +// is the primary caller-side surface. +package sourcemap diff --git a/internal/config/kb/sourcemap/sourcemap.go b/internal/config/kb/sourcemap/sourcemap.go new file mode 100644 index 000000000..e02e0a5ce --- /dev/null +++ b/internal/config/kb/sourcemap/sourcemap.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap + +// Markdown rendering constants for the source-map artifact. +// Structural literals stay as Go consts; error wrapping +// format strings have moved to commands/text/errors.yaml. +const ( + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| Short name | Kind | Locator |" + + " Admission status | Admission rationale | Dated |\n" + + "|------------|------|---------|------------------|" + + "---------------------|-------|\n" +) diff --git a/internal/config/kb/timeline/doc.go b/internal/config/kb/timeline/doc.go new file mode 100644 index 000000000..2792077f5 --- /dev/null +++ b/internal/config/kb/timeline/doc.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package timeline supplies the wrapping-format strings and +// rendering tokens used by +// [github.com/ActiveMemory/ctx/internal/err/kb/timeline] and +// [github.com/ActiveMemory/ctx/internal/write/kb/timeline]. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/err/kb/timeline] +// declares the constructor functions. +// - [github.com/ActiveMemory/ctx/internal/write/kb/timeline] +// is the primary caller-side surface. +package timeline diff --git a/internal/config/kb/timeline/timeline.go b/internal/config/kb/timeline/timeline.go new file mode 100644 index 000000000..f6bcd9b13 --- /dev/null +++ b/internal/config/kb/timeline/timeline.go @@ -0,0 +1,18 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline + +// Markdown rendering constants for the timeline artifact. +// Structural literals stay as Go consts; error wrapping +// format strings have moved to commands/text/errors.yaml. +const ( + // TableHeader is the markdown table header row plus its + // delimiter row. + TableHeader = "| Date | Event | Source EV |" + + " Related topics |\n" + + "|------|-------|-----------|----------------|\n" +) diff --git a/internal/config/marker/marker.go b/internal/config/marker/marker.go index a996f39c6..d860798a9 100644 --- a/internal/config/marker/marker.go +++ b/internal/config/marker/marker.go @@ -172,4 +172,13 @@ const ( TableRowFmt = "| %s | %s |" // TableSepFmt is the Printf format for a two-column separator row. TableSepFmt = "|%s|%s|" + // TableRowOpen is the leading "| " a markdown table row + // opens with. + TableRowOpen = "| " + // TableRowClose is the trailing " |" a markdown table row + // closes with, with no newline. + TableRowClose = " |" + // TableCellSep is the " | " separator between adjacent + // cells within a markdown table row. + TableCellSep = " | " ) diff --git a/internal/config/rc/messages.go b/internal/config/rc/rc.go similarity index 100% rename from internal/config/rc/messages.go rename to internal/config/rc/rc.go diff --git a/internal/config/regex/kb.go b/internal/config/regex/kb.go new file mode 100644 index 000000000..1037eda23 --- /dev/null +++ b/internal/config/regex/kb.go @@ -0,0 +1,35 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package regex + +import "regexp" + +// KBEvidenceID matches EV-NNN identifiers in evidence-index +// files. The numeric portion is captured for high-water-mark +// allocation in +// [github.com/ActiveMemory/ctx/internal/write/kb/evidence]. +// Three or more digits are tolerated for legacy hand-written +// entries; the writer mints exactly three. +var KBEvidenceID = regexp.MustCompile(`\bEV-(\d{3,})\b`) + +// KBContradictionID matches C-NNN identifiers in the +// contradictions ledger. The numeric portion is captured +// for high-water-mark allocation in +// [github.com/ActiveMemory/ctx/internal/write/kb/contradiction]. +var KBContradictionID = regexp.MustCompile(`\bC-(\d{3,})\b`) + +// KBQuestionID matches Q-NNN identifiers in the outstanding +// questions ledger. The numeric portion is captured for +// high-water-mark allocation in +// [github.com/ActiveMemory/ctx/internal/write/kb/question]. +var KBQuestionID = regexp.MustCompile(`\bQ-(\d{3,})\b`) + +// KBDecisionID matches DD-NNN identifiers in the domain +// decisions ledger. The numeric portion is captured for +// high-water-mark allocation in +// [github.com/ActiveMemory/ctx/internal/write/kb/decision]. +var KBDecisionID = regexp.MustCompile(`\bDD-(\d{3,})\b`) diff --git a/internal/config/regex/managedblock.go b/internal/config/regex/managedblock.go new file mode 100644 index 000000000..ab30efabf --- /dev/null +++ b/internal/config/regex/managedblock.go @@ -0,0 +1,16 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package regex + +import "regexp" + +// ManagedKBTopics matches the CTX:KB:TOPICS managed block inside +// `.context/kb/index.md`. The pattern is greedy + multi-line so +// the reindex command can replace the body wholesale. +var ManagedKBTopics = regexp.MustCompile( + `(?s)<!-- CTX:KB:TOPICS START -->.*?<!-- CTX:KB:TOPICS END -->`, +) diff --git a/internal/config/regex/slug.go b/internal/config/regex/slug.go new file mode 100644 index 000000000..9dfe89ebc --- /dev/null +++ b/internal/config/regex/slug.go @@ -0,0 +1,21 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package regex + +import "regexp" + +// Slug matches characters that are NOT a-z, 0-9, or hyphen. +// Use it to strip a free-text title down to kebab-case-safe +// runes after lowercasing and replacing spaces with hyphens. +// Hyphens are allowed in the result; trim with `strings.Trim`. +var Slug = regexp.MustCompile(`[^a-z0-9-]+`) + +// TopicSlug normalises free-text topic names into kebab-case +// slug-safe runes. Unlike [Slug], it preserves the `/` +// character so vendor-namespaced topic slugs (e.g. +// `cursor/hooks`) survive normalisation. +var TopicSlug = regexp.MustCompile(`[^a-z0-9/-]+`) diff --git a/internal/config/time/time.go b/internal/config/time/time.go index 0bef2306c..8a5ba2645 100644 --- a/internal/config/time/time.go +++ b/internal/config/time/time.go @@ -21,6 +21,9 @@ const ( // CompactTimestamp is the YYYYMMDD-HHMMSS layout used in entry headers // and task timestamps (e.g., 2026-01-28-143022). CompactTimestamp = "2006-01-02-150405" + // RFC3339Compact is the compact UTC timestamp used in closeout and + // handover filename suffixes (e.g., 20260128T143022Z). + RFC3339Compact = "20060102T150405Z" ) // DateMinLen is the minimum string length for a diff --git a/internal/config/token/delim.go b/internal/config/token/delim.go index 21868888a..f8b6c28a0 100644 --- a/internal/config/token/delim.go +++ b/internal/config/token/delim.go @@ -64,6 +64,12 @@ const ( // FrontmatterDelimiter is the YAML frontmatter // boundary marker. FrontmatterDelimiter = "---" + // FormatString is the Printf %s verb used as a pass-through + // format when the caller has already resolved the + // localized text via desc.Text and the format string itself + // must remain a compile-time constant for govet's printf + // check. + FormatString = "%s" ) // TopicSeparators are the delimiters between a date and topic in session diff --git a/internal/entity/closeout.go b/internal/entity/closeout.go new file mode 100644 index 000000000..bb98ab364 --- /dev/null +++ b/internal/entity/closeout.go @@ -0,0 +1,31 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package entity + +import "time" + +// CloseoutFrontmatter holds the six required frontmatter fields +// of a closeout file. PassMode is empty for non-ingest modes +// (ask, site-review, ground, note); LifeStage is empty when the +// pass did not perform a topic-page synthesis. +type CloseoutFrontmatter struct { + SHA string `yaml:"sha"` + Branch string `yaml:"branch"` + Mode string `yaml:"mode"` + PassMode string `yaml:"pass-mode,omitempty"` + LifeStage string `yaml:"life-stage,omitempty"` + GeneratedAt time.Time `yaml:"generated-at"` +} + +// CloseoutFile pairs a closeout's on-disk path with its parsed +// frontmatter and the raw body bytes (everything after the +// closing `---`). +type CloseoutFile struct { + Path string + Frontmatter CloseoutFrontmatter + Body string +} diff --git a/internal/entity/kb_row.go b/internal/entity/kb_row.go new file mode 100644 index 000000000..42bc7a607 --- /dev/null +++ b/internal/entity/kb_row.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package entity + +// KBRowHooks supplies the per-artifact pieces of an append +// against a monotonic-ID kb tabular artifact (contradictions, +// domain-decisions, outstanding-questions): the table header, +// the next-ID allocator (scans existing bytes), the row +// renderer (formats one markdown table row including trailing +// newline), and the four error constructors that wrap I/O +// failures in the caller's domain-typed errors. +// +// Consumed by [github.com/ActiveMemory/ctx/internal/write/kb/row.Append]. +type KBRowHooks struct { + Header string + NextID func(existing []byte) (string, error) + Render func(id string) string + ErrMkdir func(error) error + ErrRead func(error) error + ErrOpen func(error) error + ErrWrite func(error) error +} diff --git a/internal/err/closeout/closeout.go b/internal/err/closeout/closeout.go new file mode 100644 index 000000000..c2b158fa8 --- /dev/null +++ b/internal/err/closeout/closeout.go @@ -0,0 +1,169 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "errors" + "fmt" + "strings" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + cfgCloseout "github.com/ActiveMemory/ctx/internal/config/closeout" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// ErrMissingFrontmatter signals a closeout file missing the +// `---` open delimiter on line 1. +var ErrMissingFrontmatter = errors.New(cfgCloseout.ErrMsgMissingFrontmatter) + +// ErrMissingFields signals a closeout frontmatter missing one +// of the required fields (sha, branch, mode, generated-at). +// Constructor [MissingFields] wraps it with the actual field +// names. +var ErrMissingFields = errors.New(cfgCloseout.ErrMsgMissingFields) + +// ErrModeRequired signals a +// [github.com/ActiveMemory/ctx/internal/write/closeout.Write] +// call with an empty mode string. +var ErrModeRequired = errors.New(cfgCloseout.ErrMsgModeRequired) + +// MissingFields wraps the sentinel [ErrMissingFields] with a +// comma-separated list of the missing field names. +// +// Parameters: +// - missing: ordered slice of field names that were absent +// or empty in the parsed frontmatter. +// +// Returns: +// - error: wrapping [ErrMissingFields] for [errors.Is] +// matches at the call site. +func MissingFields(missing []string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrCloseoutMissingFields), + ErrMissingFields, + strings.Join(missing, token.CommaSpace), + ) +} + +// ReadFailed wraps an `os.ReadFile` failure encountered while +// reading a closeout from disk. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFailed(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrCloseoutReadCloseout), cause) +} + +// ParseFrontmatter wraps a `yaml.Unmarshal` failure while +// decoding the closeout's YAML header. +// +// Parameters: +// - cause: the underlying YAML parser error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ParseFrontmatter(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrCloseoutParseFrontmatter), cause, + ) +} + +// MarshalFrontmatter wraps a `yaml.Marshal` failure while +// encoding a new closeout's YAML header. +// +// Parameters: +// - cause: the underlying YAML marshal error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MarshalFrontmatter(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrCloseoutMarshalFrontmatter), cause, + ) +} + +// ReadCloseoutsDir wraps an `os.ReadDir` failure while +// enumerating closeouts under `.context/ingest/closeouts/`. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadCloseoutsDir(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrCloseoutReadCloseoutsDir), cause, + ) +} + +// ResolveHead wraps a +// [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead] +// failure when stamping sha / branch into new closeouts. +// +// Parameters: +// - cause: the underlying gitmeta error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ResolveHead(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrCloseoutResolveHead), cause) +} + +// MkdirCloseouts wraps `os.MkdirAll` for the closeouts +// directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirCloseouts(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrCloseoutMkdirCloseouts), cause) +} + +// WriteFailed wraps `os.WriteFile` for the closeout file +// itself. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteFailed(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrCloseoutWriteCloseout), cause) +} + +// MkdirArchive wraps `os.MkdirAll` for the archive destination +// directory at `.context/archive/closeouts/`. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirArchive(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrCloseoutMkdirArchive), cause) +} + +// ArchiveMove wraps `os.Rename` of a single closeout into the +// archive directory. +// +// Parameters: +// - name: the source file's basename (operator-facing). +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ArchiveMove(name string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrCloseoutArchiveMove), name, cause, + ) +} diff --git a/internal/err/closeout/doc.go b/internal/err/closeout/doc.go new file mode 100644 index 000000000..c53f52043 --- /dev/null +++ b/internal/err/closeout/doc.go @@ -0,0 +1,45 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package closeout defines the typed error constructors for +// Phase KB closeout artifacts. Closeouts are per-pass audit +// records written by the editorial pipeline under +// `.context/ingest/closeouts/`; this package owns every +// error surface the closeout reader / writer / archiver +// can return. +// +// # Domain +// +// Three sentinels + nine wrapping constructors cover the +// writer's full failure surface: +// +// - [ErrMissingFrontmatter]: file missing the opening +// `---` delimiter. +// - [ErrMissingFields]: frontmatter parsed but missing +// sha / branch / mode / generated-at. +// - [ErrModeRequired]: Write was called with an empty +// mode. +// - [ReadFailed], [ParseFrontmatter], [MarshalFrontmatter], +// [ReadCloseoutsDir], [ResolveHead], [MkdirCloseouts], +// [WriteCloseout], [MkdirArchive], [ArchiveMove] wrap +// I/O, YAML, and git failures with operator-friendly +// context. +// +// # Wrapping strategy +// +// The constructor functions use `fmt.Errorf` with `%w` so +// callers can `errors.Is` against the sentinel where +// applicable and `errors.Unwrap` to recover the underlying +// cause for diagnostic output. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/closeout] +// supplies the sentinel message and format-string +// constants. +// - [github.com/ActiveMemory/ctx/internal/write/closeout] +// is the primary caller. +package closeout diff --git a/internal/err/git_meta/doc.go b/internal/err/git_meta/doc.go new file mode 100644 index 000000000..79c71c78d --- /dev/null +++ b/internal/err/git_meta/doc.go @@ -0,0 +1,29 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package gitmeta defines the typed error constructors for the +// git-as-architectural-precondition surface (Phase RG). The +// surface has two pieces: +// +// - The require check +// ([github.com/ActiveMemory/ctx/internal/git_meta.RequireGitTree]) +// verifies `<projectRoot>/.git` exists; failure surfaces as +// [ErrMissingGitTree] (sentinel) or a wrapped stat error. +// - The head resolver +// ([github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead]) +// resolves the current commit + branch; failure surfaces as +// [ErrResolveHeadEmpty] (sentinel) or [ResolveHeadFailed] +// (wrapping the underlying exec error). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/git_meta] +// supplies the sentinel-message + format-string constants. +// - [github.com/ActiveMemory/ctx/internal/git_meta] is the +// primary caller; the root command's PersistentPreRunE +// also calls into these constructors when wrapping the +// missing-tree error with the failing subcommand name. +package gitmeta diff --git a/internal/err/git_meta/gitmeta.go b/internal/err/git_meta/gitmeta.go new file mode 100644 index 000000000..e3798b79a --- /dev/null +++ b/internal/err/git_meta/gitmeta.go @@ -0,0 +1,89 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +import ( + "errors" + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" +) + +// ErrMissingGitTree signals that `<projectRoot>/.git` is +// absent. The PersistentPreRunE catches this via `errors.Is` +// and wraps it with the failing subcommand name through +// [MissingGitTreeForCmd]; direct API callers may wrap with +// [MissingGitTree]. +var ErrMissingGitTree = errors.New(cfgGitmeta.ErrMsgMissingGitTree) + +// ErrResolveHeadEmpty signals that `git rev-parse --short HEAD` +// returned an empty string. Typically: unborn HEAD (repository +// initialized but no commit yet). +var ErrResolveHeadEmpty = errors.New(cfgGitmeta.ErrMsgResolveHeadEmpty) + +// MissingGitTree wraps [ErrMissingGitTree] with the +// project-root path that was scanned. Used by direct API +// callers (i.e. anyone not invoked via cobra). +// +// Parameters: +// - projectRoot: absolute path of the directory scanned. +// +// Returns: +// - error: wrapping [ErrMissingGitTree] for [errors.Is] +// matches at the call site. +func MissingGitTree(projectRoot string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrGitmetaMissingGitTree), + ErrMissingGitTree, projectRoot, + ) +} + +// MissingGitTreeForCmd wraps [ErrMissingGitTree] with both the +// failing subcommand name and the project-root path. +// +// Parameters: +// - cmdName: the failing subcommand (`init`, `kb`, etc.). +// - projectRoot: absolute path of the directory scanned. +// +// Returns: +// - error: wrapping [ErrMissingGitTree] for [errors.Is] +// matches at the call site. +func MissingGitTreeForCmd(cmdName, projectRoot string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrGitmetaMissingGitTreeForCmd), + ErrMissingGitTree, cmdName, projectRoot, + ) +} + +// StatGitDir wraps a non-ENOENT stat failure on the `.git` +// entry (permission denied, I/O error, etc.). +// +// Parameters: +// - path: full path to `<projectRoot>/.git`. +// - cause: underlying `os.Stat` error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func StatGitDir(path string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrGitmetaStatGitDir), path, cause, + ) +} + +// ResolveHeadFailed wraps a `git rev-parse --short HEAD` +// invocation failure. +// +// Parameters: +// - cause: underlying exec / git error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ResolveHeadFailed(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrGitmetaResolveHead), cause) +} diff --git a/internal/err/handover/doc.go b/internal/err/handover/doc.go new file mode 100644 index 000000000..0dc397338 --- /dev/null +++ b/internal/err/handover/doc.go @@ -0,0 +1,23 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package handover defines the typed error constructors for +// Phase KB handover artifacts. Handovers are per-session +// recall artifacts written under `.context/handovers/`; this +// package owns every error surface the handover writer / +// reader / fold mechanism can return. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/handover] +// supplies the sentinel-message and format-string +// constants. +// - [github.com/ActiveMemory/ctx/internal/write/handover] +// is the primary caller. +// - [github.com/ActiveMemory/ctx/internal/err/closeout] +// defines the parallel error surface for the closeout +// artifacts that the handover fold consumes. +package handover diff --git a/internal/err/handover/handover.go b/internal/err/handover/handover.go new file mode 100644 index 000000000..a9e8e36fe --- /dev/null +++ b/internal/err/handover/handover.go @@ -0,0 +1,177 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "errors" + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" +) + +// ErrTitleRequired signals an empty Title supplied to +// [github.com/ActiveMemory/ctx/internal/write/handover.Write]. +var ErrTitleRequired = errors.New(cfgHandover.ErrMsgTitleRequired) + +// ErrSummaryRequired signals an empty Summary supplied to +// [github.com/ActiveMemory/ctx/internal/write/handover.Write]. +var ErrSummaryRequired = errors.New(cfgHandover.ErrMsgSummaryRequired) + +// ErrNextRequired signals an empty Next supplied to +// [github.com/ActiveMemory/ctx/internal/write/handover.Write]. +var ErrNextRequired = errors.New(cfgHandover.ErrMsgNextRequired) + +// ErrMissingFrontmatter signals a handover file that does not +// open with `---`. +var ErrMissingFrontmatter = errors.New(cfgHandover.ErrMsgMissingFrontmatter) + +// ErrMissingClosingDelim signals a handover whose frontmatter +// is never closed by a second `---`. +var ErrMissingClosingDelim = errors.New(cfgHandover.ErrMsgMissingClosingDelim) + +// ErrMissingGeneratedAt signals a handover whose frontmatter +// parsed but has no generated-at value. +var ErrMissingGeneratedAt = errors.New(cfgHandover.ErrMsgMissingGeneratedAt) + +// Latest wraps a failure encountered while reading the +// latest handover during fold. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func Latest(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrHandoverLatest), cause) +} + +// ListCloseouts wraps a closeout-listing failure encountered +// during fold. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ListCloseouts(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrHandoverListCloseouts), cause) +} + +// MarshalFrontmatter wraps a `yaml.Marshal` failure while +// encoding a new handover's YAML header. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func MarshalFrontmatter(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverMarshalFrontmatter), cause, + ) +} + +// MkdirHandovers wraps an `os.MkdirAll` failure for the +// handovers directory. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func MkdirHandovers(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverMkdirHandovers), cause, + ) +} + +// WriteFailed wraps an `os.WriteFile` failure for the new +// handover file. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func WriteFailed(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverWriteHandover), cause, + ) +} + +// ArchiveFoldedCloseouts wraps the closeout archival pass +// following a fold. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ArchiveFoldedCloseouts(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverArchiveFoldedCloseouts), cause, + ) +} + +// ReadFailed wraps an `os.ReadFile` failure while loading a +// handover from disk. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ReadFailed(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverReadHandover), cause, + ) +} + +// ReadHandoversDir wraps an `os.ReadDir` failure while +// enumerating handovers. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ReadHandoversDir(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverReadHandoversDir), cause, + ) +} + +// ParseFrontmatter wraps a `yaml.Unmarshal` failure while +// parsing the handover YAML header. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ParseFrontmatter(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverParseFrontmatter), cause, + ) +} + +// ResolveHead wraps a +// [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead] +// failure when stamping sha / branch into new handovers. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped for operator-friendly output. +func ResolveHead(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrHandoverResolveHead), cause, + ) +} diff --git a/internal/err/initialize/kb/doc.go b/internal/err/initialize/kb/doc.go new file mode 100644 index 000000000..138c4cdc1 --- /dev/null +++ b/internal/err/initialize/kb/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package kb defines the typed error constructors for the +// kb scaffolding pass inside `ctx init` +// ([github.com/ActiveMemory/ctx/internal/cli/initialize/core/kb]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/initialize/kb] supplies +// the wrapping-format constants. +// - [github.com/ActiveMemory/ctx/internal/cli/initialize/core/kb] +// is the primary caller. +package kb diff --git a/internal/err/initialize/kb/kb.go b/internal/err/initialize/kb/kb.go new file mode 100644 index 000000000..56f3553e7 --- /dev/null +++ b/internal/err/initialize/kb/kb.go @@ -0,0 +1,114 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package kb + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// Mkdir wraps `os.MkdirAll` for a scaffolded directory. +// +// Parameters: +// - path: the directory path that failed. +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func Mkdir(path string, cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbMkdir), path, cause) +} + +// CopyIngestTemplates wraps a failure to copy the embedded +// ingest-side templates. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func CopyIngestTemplates(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrInitKbCopyIngestTemplates), cause, + ) +} + +// CopySchemas wraps a failure to copy the embedded schemas. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func CopySchemas(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbCopySchemas), cause) +} + +// CopyLanding wraps a failure to copy the embedded kb +// landing page. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func CopyLanding(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbCopyLanding), cause) +} + +// ReadEmbed wraps a failure to read an embedded asset path. +// +// Parameters: +// - path: embedded asset path that failed. +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func ReadEmbed(path string, cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbReadEmbed), path, cause) +} + +// MkdirFor wraps a parent-dir create failure for a specific +// destination file. +// +// Parameters: +// - dst: destination path whose parent could not be created. +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func MkdirFor(dst string, cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbMkdirFor), dst, cause) +} + +// WriteFile wraps a write failure for a specific destination +// path. +// +// Parameters: +// - dst: destination path that failed. +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func WriteFile(dst string, cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbWriteFile), dst, cause) +} + +// ReadDir wraps a `os.ReadDir` failure for a scaffolded +// directory. +// +// Parameters: +// - dir: directory whose listing failed. +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func ReadDir(dir string, cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrInitKbReadDir), dir, cause) +} diff --git a/internal/err/kb/cli/cli.go b/internal/err/kb/cli/cli.go new file mode 100644 index 000000000..20f8bf731 --- /dev/null +++ b/internal/err/kb/cli/cli.go @@ -0,0 +1,178 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package cli + +import ( + "errors" + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgKbCli "github.com/ActiveMemory/ctx/internal/config/kb/cli" +) + +// ErrAskNoQuestion signals an empty `ctx kb ask` invocation. +var ErrAskNoQuestion = errors.New(cfgKbCli.ErrMsgAskNoQuestion) + +// ErrIngestNoSources signals an empty `ctx kb ingest` +// invocation. +var ErrIngestNoSources = errors.New(cfgKbCli.ErrMsgIngestNoSources) + +// ErrNoteNoText signals an empty `ctx kb note` invocation. +var ErrNoteNoText = errors.New(cfgKbCli.ErrMsgNoteNoText) + +// ErrTopicEmptyName signals a `ctx kb topic new` invocation +// whose name reduces to an empty slug. +var ErrTopicEmptyName = errors.New(cfgKbCli.ErrMsgTopicEmptyName) + +// ErrReindexMissingBlock signals a kb landing page that is +// missing the CTX:KB:TOPICS managed block. +var ErrReindexMissingBlock = errors.New( + cfgKbCli.ErrMsgReindexMissingBlock, +) + +// GroundingMissing wraps a missing grounding-sources.md error +// with the resolved path. +// +// Parameters: +// - path: absolute path to the missing grounding file. +// +// Returns: +// - error: descriptive refusal. +func GroundingMissing(path string) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliGroundingMissing), path) +} + +// GroundingEmpty wraps an empty grounding-sources.md error +// with the resolved path. +// +// Parameters: +// - path: absolute path to the empty grounding file. +// +// Returns: +// - error: descriptive refusal. +func GroundingEmpty(path string) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliGroundingEmpty), path) +} + +// TopicExists wraps a topic-already-exists refusal with the +// slug and the indexPath that would have been written. +// +// Parameters: +// - slug: the topic slug. +// - indexPath: the path of the existing index.md. +// +// Returns: +// - error: descriptive refusal. +func TopicExists(slug, indexPath string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbCliTopicExists), slug, indexPath, + ) +} + +// MkdirIngest wraps an ingest-dir create failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func MkdirIngest(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliMkdirIngest), cause) +} + +// OpenFindings wraps a findings-file open failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func OpenFindings(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliOpenFindings), cause) +} + +// WriteFinding wraps a findings-file write failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func WriteFinding(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliWriteFinding), cause) +} + +// ReadKBIndex wraps a kb-index read failure during reindex. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func ReadKBIndex(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliReadKBIndex), cause) +} + +// WriteKBIndex wraps a kb-index write failure during reindex. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func WriteKBIndex(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliWriteKBIndex), cause) +} + +// ReadTopicsDir wraps a topics-dir read failure during reindex. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func ReadTopicsDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliReadTopicsDir), cause) +} + +// MkdirTopic wraps a topic-dir create failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func MkdirTopic(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbCliMkdirTopic), cause) +} + +// ReadTopicTemplate wraps an embedded-template read failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func ReadTopicTemplate(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbCliReadTopicTemplate), cause, + ) +} + +// WriteTopicIndex wraps a topic-index write failure. +// +// Parameters: +// - cause: underlying error. +// +// Returns: +// - error: wrapped failure. +func WriteTopicIndex(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbCliWriteTopicIndex), cause, + ) +} diff --git a/internal/err/kb/cli/doc.go b/internal/err/kb/cli/doc.go new file mode 100644 index 000000000..2f6effe92 --- /dev/null +++ b/internal/err/kb/cli/doc.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package cli defines the typed error constructors and +// sentinels used by the `ctx kb` CLI subcommands. The package +// is the single source for refusal messages (no question, no +// sources, missing managed block, etc.) and the wrappers +// surfaced around `io.Safe*` failures. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/cli] +// supplies the message and format-string constants. +// - [github.com/ActiveMemory/ctx/internal/cli/kb/cmd/...] +// are the primary callers. +package cli diff --git a/internal/err/kb/contradiction/contradiction.go b/internal/err/kb/contradiction/contradiction.go new file mode 100644 index 000000000..18e3f1c84 --- /dev/null +++ b/internal/err/kb/contradiction/contradiction.go @@ -0,0 +1,78 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a file-read failure on the contradictions +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbContradictionReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the +// contradictions artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbContradictionMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the +// contradictions artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbContradictionOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the contradictions +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbContradictionWriteRow), cause) +} + +// ParseCNumber wraps a strconv.Atoi failure while parsing the +// numeric portion of a C-### token. +// +// Parameters: +// - digits: the raw digit string that failed to parse. +// - cause: the underlying parse error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ParseCNumber(digits string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbContradictionParseCNumber), + digits, cause, + ) +} diff --git a/internal/err/kb/contradiction/doc.go b/internal/err/kb/contradiction/doc.go new file mode 100644 index 000000000..caab2fb1d --- /dev/null +++ b/internal/err/kb/contradiction/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package contradiction defines the typed error +// constructors for the contradictions writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/contradiction]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/contradiction] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/contradiction] +// is the primary caller. +package contradiction diff --git a/internal/err/kb/decision/decision.go b/internal/err/kb/decision/decision.go new file mode 100644 index 000000000..95812158f --- /dev/null +++ b/internal/err/kb/decision/decision.go @@ -0,0 +1,78 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a file-read failure on the domain-decisions +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbDecisionReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the +// domain-decisions artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbDecisionMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the +// domain-decisions artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbDecisionOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the domain-decisions +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbDecisionWriteRow), cause) +} + +// ParseDDNumber wraps a strconv.Atoi failure while parsing the +// numeric portion of a DD-### token. +// +// Parameters: +// - digits: the raw digit string that failed to parse. +// - cause: the underlying parse error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ParseDDNumber(digits string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbDecisionParseDDNumber), + digits, cause, + ) +} diff --git a/internal/err/kb/decision/doc.go b/internal/err/kb/decision/doc.go new file mode 100644 index 000000000..6c0569430 --- /dev/null +++ b/internal/err/kb/decision/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package decision defines the typed error constructors for +// the domain-decisions writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/decision]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/decision] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/decision] +// is the primary caller. +package decision diff --git a/internal/err/kb/evidence/doc.go b/internal/err/kb/evidence/doc.go new file mode 100644 index 000000000..fd0ab532c --- /dev/null +++ b/internal/err/kb/evidence/doc.go @@ -0,0 +1,38 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package evidence defines the typed error constructors for +// the evidence-index writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/evidence]). +// +// # Domain +// +// Two sentinels plus a fistful of wrapping constructors cover +// the writer's full failure surface: +// +// - [ErrDuplicateID]: Append called with an explicit row.ID +// already present in the file. +// - [ErrInvalidBand]: Confidence outside the four canonical +// bands. +// - [DuplicateID], [InvalidBand], [ParseEVNumber], +// [ReadIndex], [MkdirDir], [OpenIndex], [WriteRow] wrap +// parse, sentinel-bind, and I/O failures with +// operator-friendly context. +// +// # Wrapping strategy +// +// The constructor functions use `fmt.Errorf` with `%w` so +// callers can `errors.Is` against the sentinel where +// applicable and `errors.Unwrap` to recover the underlying +// cause for diagnostic output. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/evidence] +// supplies the message + format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/evidence] +// is the primary caller. +package evidence diff --git a/internal/err/kb/evidence/evidence.go b/internal/err/kb/evidence/evidence.go new file mode 100644 index 000000000..043ed3158 --- /dev/null +++ b/internal/err/kb/evidence/evidence.go @@ -0,0 +1,121 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import ( + "errors" + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgKbEvidence "github.com/ActiveMemory/ctx/internal/config/kb/evidence" +) + +// ErrDuplicateID signals that Append was called with an +// explicit row.ID already present in the file. Renumbering is +// forbidden; callers must reuse the existing row verbatim or +// mint a new ID by leaving row.ID empty. +var ErrDuplicateID = errors.New(cfgKbEvidence.ErrMsgDuplicateID) + +// ErrInvalidBand signals a row whose Confidence is not one of +// the four canonical bands defined in +// [github.com/ActiveMemory/ctx/internal/config/kb]. +var ErrInvalidBand = errors.New(cfgKbEvidence.ErrMsgInvalidBand) + +// DuplicateID wraps ErrDuplicateID with the offending +// identifier so callers see exactly which ID collided. +// +// Parameters: +// - id: the EV-### identifier that already existed. +// +// Returns: +// - error: wraps [ErrDuplicateID] for [errors.Is] matches. +func DuplicateID(id string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbEvidenceDuplicateID), + ErrDuplicateID, id, + ) +} + +// InvalidBand wraps ErrInvalidBand with the offending band +// string. +// +// Parameters: +// - band: the band string supplied by the caller. +// +// Returns: +// - error: wraps [ErrInvalidBand] for [errors.Is] matches. +func InvalidBand(band string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbEvidenceInvalidBand), + ErrInvalidBand, band, + ) +} + +// ParseEVNumber wraps a strconv.Atoi failure while parsing the +// numeric portion of an EV-### token. +// +// Parameters: +// - digits: the raw digit string that failed to parse. +// - cause: the underlying parse error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ParseEVNumber(digits string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbEvidenceParseIDNumber), + digits, cause, + ) +} + +// ReadIndex wraps a file-read failure on the evidence-index +// file. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadIndex(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbEvidenceReadIndex), cause) +} + +// MkdirDir wraps a directory-create failure on the +// evidence-index parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbEvidenceMkdirDir), cause) +} + +// OpenIndex wraps an open-for-append failure on the +// evidence-index file. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenIndex(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbEvidenceOpenIndex), cause) +} + +// WriteRow wraps a row-write failure to the evidence-index +// file. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbEvidenceWriteRow), cause) +} diff --git a/internal/err/kb/glossary/doc.go b/internal/err/kb/glossary/doc.go new file mode 100644 index 000000000..b4695e983 --- /dev/null +++ b/internal/err/kb/glossary/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package glossary defines the typed error constructors for +// the glossary writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/glossary]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/glossary] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/glossary] +// is the primary caller. +package glossary diff --git a/internal/err/kb/glossary/glossary.go b/internal/err/kb/glossary/glossary.go new file mode 100644 index 000000000..9854ef4c6 --- /dev/null +++ b/internal/err/kb/glossary/glossary.go @@ -0,0 +1,62 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a stat / read failure on the glossary +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbGlossaryReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the glossary +// artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbGlossaryMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the glossary +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbGlossaryOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the glossary +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbGlossaryWriteRow), cause) +} diff --git a/internal/err/kb/question/doc.go b/internal/err/kb/question/doc.go new file mode 100644 index 000000000..0e8fc5bcd --- /dev/null +++ b/internal/err/kb/question/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package question defines the typed error constructors for +// the outstanding-questions writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/question]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/question] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/question] +// is the primary caller. +package question diff --git a/internal/err/kb/question/question.go b/internal/err/kb/question/question.go new file mode 100644 index 000000000..f8c7ac1d5 --- /dev/null +++ b/internal/err/kb/question/question.go @@ -0,0 +1,78 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a file-read failure on the +// outstanding-questions artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbQuestionReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the +// outstanding-questions artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbQuestionMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the +// outstanding-questions artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbQuestionOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the +// outstanding-questions artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbQuestionWriteRow), cause) +} + +// ParseQNumber wraps a strconv.Atoi failure while parsing the +// numeric portion of a Q-### token. +// +// Parameters: +// - digits: the raw digit string that failed to parse. +// - cause: the underlying parse error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ParseQNumber(digits string, cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbQuestionParseQNumber), + digits, cause, + ) +} diff --git a/internal/err/kb/relationship/doc.go b/internal/err/kb/relationship/doc.go new file mode 100644 index 000000000..5ce5b41d0 --- /dev/null +++ b/internal/err/kb/relationship/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package relationship defines the typed error constructors +// for the relationship-map writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/relationship]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/relationship] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/relationship] +// is the primary caller. +package relationship diff --git a/internal/err/kb/relationship/relationship.go b/internal/err/kb/relationship/relationship.go new file mode 100644 index 000000000..9b5d5b949 --- /dev/null +++ b/internal/err/kb/relationship/relationship.go @@ -0,0 +1,62 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a stat / read failure on the relationship-map +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbRelationshipReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the +// relationship-map artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbRelationshipMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the +// relationship-map artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbRelationshipOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the relationship-map +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbRelationshipWriteRow), cause) +} diff --git a/internal/err/kb/sourcecoverage/doc.go b/internal/err/kb/sourcecoverage/doc.go new file mode 100644 index 000000000..a63b4fddc --- /dev/null +++ b/internal/err/kb/sourcecoverage/doc.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcecoverage defines the typed error +// constructors for the source-coverage ledger writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage]). +// +// # Domain +// +// Two sentinels plus three wrapping I/O constructors cover the +// writer's failure surface: +// +// - [ErrIllegalTransition]: Advance refused a (from, to) +// pair the state machine does not allow. +// - [ErrUnknownSource]: Advance referenced a Source not yet +// present at a non-entry-point State. +// - [IllegalTransition], [UnknownSource], [ReadLedger], +// [MkdirLedgerDir], [WriteLedger] wrap I/O and sentinel- +// bind failures with operator-friendly context. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/sourcecoverage] +// supplies the message + format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage] +// is the primary caller. +package sourcecoverage diff --git a/internal/err/kb/sourcecoverage/sourcecoverage.go b/internal/err/kb/sourcecoverage/sourcecoverage.go new file mode 100644 index 000000000..f557d2071 --- /dev/null +++ b/internal/err/kb/sourcecoverage/sourcecoverage.go @@ -0,0 +1,102 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + "errors" + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" + cfgKbSC "github.com/ActiveMemory/ctx/internal/config/kb/sourcecoverage" +) + +// ErrIllegalTransition signals that Advance was called with a +// (from, to) state pair the source-coverage state machine +// rejects. +var ErrIllegalTransition = errors.New( + cfgKbSC.ErrMsgIllegalTransition, +) + +// ErrUnknownSource signals that Advance referenced a Source +// not yet present in the ledger AND the new State is not one +// of the initial states (`discovered`, `admitted`). +var ErrUnknownSource = errors.New(cfgKbSC.ErrMsgUnknownSource) + +// IllegalTransition wraps [ErrIllegalTransition] with the +// offending from-state, to-state, and source name. +// +// Parameters: +// - from: current state. +// - to: rejected next state. +// - source: the source identifier that triggered the call. +// +// Returns: +// - error: wraps [ErrIllegalTransition] for [errors.Is]. +func IllegalTransition(from, to, source string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbSourcecoverageIllegalTransition), + ErrIllegalTransition, from, to, source, + ) +} + +// UnknownSource wraps [ErrUnknownSource] with the offending +// source name and entry state. +// +// Parameters: +// - source: the source identifier that triggered the call. +// - state: the entry state the caller supplied. +// +// Returns: +// - error: wraps [ErrUnknownSource] for [errors.Is]. +func UnknownSource(source, state string) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbSourcecoverageUnknownSource), + ErrUnknownSource, source, state, + ) +} + +// ReadLedger wraps a file-read failure on the source-coverage +// ledger. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadLedger(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbSourcecoverageReadLedger), cause, + ) +} + +// MkdirLedgerDir wraps a directory-create failure on the +// ledger's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirLedgerDir(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbSourcecoverageMkdirLedgerDir), cause, + ) +} + +// WriteLedger wraps a file-write failure on the ledger file. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteLedger(cause error) error { + return fmt.Errorf( + desc.Text(text.DescKeyErrKbSourcecoverageWriteLedger), cause, + ) +} diff --git a/internal/err/kb/sourcemap/doc.go b/internal/err/kb/sourcemap/doc.go new file mode 100644 index 000000000..88784b69e --- /dev/null +++ b/internal/err/kb/sourcemap/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcemap defines the typed error constructors for +// the source-map writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/sourcemap]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/sourcemap] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/sourcemap] +// is the primary caller. +package sourcemap diff --git a/internal/err/kb/sourcemap/sourcemap.go b/internal/err/kb/sourcemap/sourcemap.go new file mode 100644 index 000000000..938aa28be --- /dev/null +++ b/internal/err/kb/sourcemap/sourcemap.go @@ -0,0 +1,62 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a stat / read failure on the source-map +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbSourcemapReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the source-map +// artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbSourcemapMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the source-map +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbSourcemapOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the source-map +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbSourcemapWriteRow), cause) +} diff --git a/internal/err/kb/timeline/doc.go b/internal/err/kb/timeline/doc.go new file mode 100644 index 000000000..52edfbed4 --- /dev/null +++ b/internal/err/kb/timeline/doc.go @@ -0,0 +1,17 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package timeline defines the typed error constructors for +// the timeline writer +// ([github.com/ActiveMemory/ctx/internal/write/kb/timeline]). +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb/timeline] +// supplies the format-string constants. +// - [github.com/ActiveMemory/ctx/internal/write/kb/timeline] +// is the primary caller. +package timeline diff --git a/internal/err/kb/timeline/timeline.go b/internal/err/kb/timeline/timeline.go new file mode 100644 index 000000000..500e1def1 --- /dev/null +++ b/internal/err/kb/timeline/timeline.go @@ -0,0 +1,62 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline + +import ( + "fmt" + + "github.com/ActiveMemory/ctx/internal/assets/read/desc" + "github.com/ActiveMemory/ctx/internal/config/embed/text" +) + +// ReadFile wraps a stat / read failure on the timeline +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func ReadFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbTimelineReadFile), cause) +} + +// MkdirDir wraps a directory-create failure on the timeline +// artifact's parent directory. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func MkdirDir(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbTimelineMkdirDir), cause) +} + +// OpenFile wraps an open-for-append failure on the timeline +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func OpenFile(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbTimelineOpenFile), cause) +} + +// WriteRow wraps a row-write failure to the timeline +// artifact. +// +// Parameters: +// - cause: the underlying I/O error. +// +// Returns: +// - error: wrapped with operator-friendly prefix. +func WriteRow(cause error) error { + return fmt.Errorf(desc.Text(text.DescKeyErrKbTimelineWriteRow), cause) +} diff --git a/internal/err/setup/setup.go b/internal/err/setup/setup.go index c1e51c51b..d0299709f 100644 --- a/internal/err/setup/setup.go +++ b/internal/err/setup/setup.go @@ -68,8 +68,9 @@ func SyncSteering(cause error) error { } // MissingEmbeddedAsset reports that an asset expected to be -// embedded in the binary is missing — typically a setup-time -// invariant violation rather than a user-facing failure. +// embedded in the binary is missing. This is typically a +// setup-time invariant violation rather than a user-facing +// failure. // // Parameters: // - name: the asset key that was looked up diff --git a/internal/flagbind/batch.go b/internal/flag_bind/batch.go similarity index 100% rename from internal/flagbind/batch.go rename to internal/flag_bind/batch.go diff --git a/internal/flagbind/doc.go b/internal/flag_bind/doc.go similarity index 100% rename from internal/flagbind/doc.go rename to internal/flag_bind/doc.go diff --git a/internal/flagbind/flag.go b/internal/flag_bind/flag.go similarity index 100% rename from internal/flagbind/flag.go rename to internal/flag_bind/flag.go diff --git a/internal/git_meta/branch.go b/internal/git_meta/branch.go new file mode 100644 index 000000000..7dd4c9413 --- /dev/null +++ b/internal/git_meta/branch.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +import ( + "strings" + + cfgGit "github.com/ActiveMemory/ctx/internal/config/git" + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" + execGit "github.com/ActiveMemory/ctx/internal/exec/git" +) + +// resolveBranchOrDetached returns the current symbolic ref +// name, or the literal "detached" when HEAD is not on a +// branch. Failures (binary not on PATH, repo absent) collapse +// to "detached"; the caller treats this as best-effort +// metadata for provenance lines. +// +// Parameters: +// - projectRoot: absolute path to the project root. +// +// Returns: +// - string: branch name, or "detached". +func resolveBranchOrDetached(projectRoot string) string { + out, runErr := execGit.Run( + cfgGit.FlagChangeDir, projectRoot, + cfgGit.RevParse, cfgGit.FlagShowCurrent, + ) + if runErr != nil { + return cfgGitmeta.BranchDetached + } + b := strings.TrimSpace(string(out)) + if b == "" { + return cfgGitmeta.BranchDetached + } + return b +} diff --git a/internal/git_meta/doc.go b/internal/git_meta/doc.go new file mode 100644 index 000000000..d8b57770f --- /dev/null +++ b/internal/git_meta/doc.go @@ -0,0 +1,39 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package gitmeta enforces git as an architectural precondition +// for ctx, and resolves git HEAD into commit + branch for +// provenance metadata. +// +// Two surfaces: +// +// - [RequireGitTree] returns nil when <projectRoot>/.git exists +// as a directory (regular repo) or a regular file (worktree +// pointer). Returns [*MissingGitError] otherwise. Wired into +// the root command PersistentPreRunE so every non-exempt +// subcommand inherits the precondition. +// +// - [ResolveHead] reads HEAD into ([HeadRef]) for closeout and +// handover provenance. Honors environment overrides +// CTX_TASK_COMMIT and GITHUB_SHA (when GITHUB_ACTIONS=true) +// to support CI replay scenarios. +// +// Phase RG (per specs/require-git.md) promotes git from a de +// facto invariant to a de jure one. There is no auto-git-init; +// the user runs git init first, then ctx init. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/exec/git] runs git +// subcommands; ResolveHead shells through it. +// - [github.com/ActiveMemory/ctx/internal/config/git] supplies +// constants for the git binary name, subcommands, and the +// ".git" directory name (DotDir). +// - [github.com/ActiveMemory/ctx/internal/err/git] supplies +// typed errors for git-not-installed and not-in-repo +// conditions, distinct from gitmeta's MissingGitError which +// is specifically about the .git tree at a known root. +package gitmeta diff --git a/internal/git_meta/head.go b/internal/git_meta/head.go new file mode 100644 index 000000000..09028e950 --- /dev/null +++ b/internal/git_meta/head.go @@ -0,0 +1,72 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +import ( + "os" + "strings" + + cfgGit "github.com/ActiveMemory/ctx/internal/config/git" + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" + errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta" + execGit "github.com/ActiveMemory/ctx/internal/exec/git" +) + +// ResolveHead reads HEAD into a [HeadRef]. Environment +// overrides for CI replay are checked first: +// +// - CTX_TASK_COMMIT, when non-empty, is used verbatim as the +// SHA. Branch is still resolved from git (or "detached"). +// - GITHUB_SHA, when GITHUB_ACTIONS="true" and GITHUB_SHA is +// non-empty, is truncated to the short form. Branch is +// resolved from git (or "detached"). +// +// Otherwise, `git rev-parse --short HEAD` returns the +// abbreviated SHA, and `git rev-parse --abbrev-ref HEAD` +// (via FlagShowCurrent) returns the current branch. +// +// Parameters: +// - projectRoot: absolute path to the project root; passed +// to git via -C so resolution is independent of the caller +// CWD. +// +// Returns: +// - HeadRef: resolved short SHA + branch. +// - error: non-nil on resolution failure. [RequireGitTree] +// must succeed before calling this. +func ResolveHead(projectRoot string) (HeadRef, error) { + if v := strings.TrimSpace(os.Getenv(cfgGitmeta.EnvCtxTaskCommit)); v != "" { + return HeadRef{ + SHA: v, + Branch: resolveBranchOrDetached(projectRoot), + }, nil + } + if os.Getenv(cfgGitmeta.EnvGithubActions) == cfgGitmeta.GithubActionsTrue { + if v := strings.TrimSpace(os.Getenv(cfgGitmeta.EnvGithubSHA)); v != "" { + return HeadRef{ + SHA: shortSHA(v), + Branch: resolveBranchOrDetached(projectRoot), + }, nil + } + } + + out, runErr := execGit.Run( + cfgGit.FlagChangeDir, projectRoot, + cfgGit.RevParse, cfgGit.FlagShort, cfgGitmeta.RefHEAD, + ) + if runErr != nil { + return HeadRef{}, errGitmeta.ResolveHeadFailed(runErr) + } + sha := strings.TrimSpace(string(out)) + if sha == "" { + return HeadRef{}, errGitmeta.ErrResolveHeadEmpty + } + return HeadRef{ + SHA: sha, + Branch: resolveBranchOrDetached(projectRoot), + }, nil +} diff --git a/internal/git_meta/require.go b/internal/git_meta/require.go new file mode 100644 index 000000000..c4c5b8799 --- /dev/null +++ b/internal/git_meta/require.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + + cfgGit "github.com/ActiveMemory/ctx/internal/config/git" + errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta" +) + +// RequireGitTree returns nil when `<projectRoot>/.git` exists +// as a directory (regular repo) or a regular file (worktree +// pointer per git convention). +// +// Parameters: +// - projectRoot: absolute path to the project root (parent of +// `.context/`, by ctx convention). +// +// Returns: +// - error: nil on success; +// [errGitmeta.MissingGitTree]-wrapping error when `.git` +// is absent (matchable via `errors.Is` against +// [errGitmeta.ErrMissingGitTree]); a wrapped stat error +// for other failures. +func RequireGitTree(projectRoot string) error { + p := filepath.Join(projectRoot, cfgGit.DotDir) + if _, err := os.Stat(p); err != nil { + if errors.Is(err, fs.ErrNotExist) { + return errGitmeta.MissingGitTree(projectRoot) + } + return errGitmeta.StatGitDir(p, err) + } + return nil +} diff --git a/internal/git_meta/require_test.go b/internal/git_meta/require_test.go new file mode 100644 index 000000000..bd89edc46 --- /dev/null +++ b/internal/git_meta/require_test.go @@ -0,0 +1,75 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta_test + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta" + "github.com/ActiveMemory/ctx/internal/git_meta" +) + +func TestRequireGitTree_DirAccepted(t *testing.T) { + root := t.TempDir() + if err := os.MkdirAll(filepath.Join(root, ".git"), 0o755); err != nil { + t.Fatalf("setup: %v", err) + } + if err := gitmeta.RequireGitTree(root); err != nil { + t.Fatalf("want nil; got %v", err) + } +} + +func TestRequireGitTree_WorktreePointerFileAccepted(t *testing.T) { + root := t.TempDir() + // Worktrees write a regular file at .git containing + // "gitdir: <path>" instead of a directory. + if err := os.WriteFile( + filepath.Join(root, ".git"), + []byte("gitdir: /main/.git/worktrees/x\n"), + 0o600, + ); err != nil { + t.Fatalf("setup: %v", err) + } + if err := gitmeta.RequireGitTree(root); err != nil { + t.Fatalf("want nil; got %v", err) + } +} + +func TestRequireGitTree_MissingReturnsSentinel(t *testing.T) { + root := t.TempDir() + err := gitmeta.RequireGitTree(root) + if err == nil { + t.Fatal("want error; got nil") + } + if !errors.Is(err, errGitmeta.ErrMissingGitTree) { + t.Fatalf("want ErrMissingGitTree; got %v", err) + } + if !strings.Contains(err.Error(), root) { + t.Errorf("want project root in error message: %q", err.Error()) + } +} + +func TestMissingGitTreeForCmd_FormatsCommandPrefix(t *testing.T) { + err := errGitmeta.MissingGitTreeForCmd("init", "/tmp/x") + if !errors.Is(err, errGitmeta.ErrMissingGitTree) { + t.Fatalf("want errors.Is match against ErrMissingGitTree; got %v", err) + } + msg := err.Error() + if !strings.Contains(msg, "ctx init") { + t.Errorf("want subcommand name in message: %q", msg) + } + if !strings.Contains(msg, "/tmp/x") { + t.Errorf("want project root in message: %q", msg) + } + if !strings.Contains(msg, "git init") { + t.Errorf("want recovery hint in message: %q", msg) + } +} diff --git a/internal/git_meta/resolvehead_test.go b/internal/git_meta/resolvehead_test.go new file mode 100644 index 000000000..381576ca3 --- /dev/null +++ b/internal/git_meta/resolvehead_test.go @@ -0,0 +1,75 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta_test + +import ( + "testing" + + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" + "github.com/ActiveMemory/ctx/internal/git_meta" +) + +func TestResolveHead_CtxTaskCommitOverrideUsedVerbatim(t *testing.T) { + t.Setenv(cfgGitmeta.EnvCtxTaskCommit, "deadbee") + t.Setenv(cfgGitmeta.EnvGithubActions, "") + t.Setenv(cfgGitmeta.EnvGithubSHA, "") + + ref, err := gitmeta.ResolveHead(t.TempDir()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if ref.SHA != "deadbee" { + t.Errorf("SHA: want deadbee; got %q", ref.SHA) + } + // Branch falls through to git; tempdir has no git, so the + // branch resolver returns "detached". + if ref.Branch != cfgGitmeta.BranchDetached { + t.Errorf("Branch: want %q; got %q", cfgGitmeta.BranchDetached, ref.Branch) + } +} + +func TestResolveHead_GithubShaTruncatedToShort(t *testing.T) { + t.Setenv(cfgGitmeta.EnvCtxTaskCommit, "") + t.Setenv(cfgGitmeta.EnvGithubActions, cfgGitmeta.GithubActionsTrue) + t.Setenv(cfgGitmeta.EnvGithubSHA, "abcdef0123456789abcdef0123456789abcdef01") + + ref, err := gitmeta.ResolveHead(t.TempDir()) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got, want := len(ref.SHA), cfgGitmeta.ShortLen; got != want { + t.Errorf("SHA length: want %d; got %d (%q)", want, got, ref.SHA) + } + if ref.SHA != "abcdef0" { + t.Errorf("SHA: want abcdef0; got %q", ref.SHA) + } +} + +func TestResolveHead_GithubShaIgnoredWithoutActionsFlag(t *testing.T) { + t.Setenv(cfgGitmeta.EnvCtxTaskCommit, "") + t.Setenv(cfgGitmeta.EnvGithubActions, "") + t.Setenv(cfgGitmeta.EnvGithubSHA, "abcdef0123456789abcdef0123456789abcdef01") + + _, err := gitmeta.ResolveHead(t.TempDir()) + // No git in tempdir + no env override → resolution fails. + // We don't care about the exact error text, only that the + // function did NOT silently take the GITHUB_SHA value. + if err == nil { + t.Fatal("want error (no override + no git tree); got nil") + } +} + +func TestResolveHead_NoOverridesAndNoGitFails(t *testing.T) { + t.Setenv(cfgGitmeta.EnvCtxTaskCommit, "") + t.Setenv(cfgGitmeta.EnvGithubActions, "") + t.Setenv(cfgGitmeta.EnvGithubSHA, "") + + _, err := gitmeta.ResolveHead(t.TempDir()) + if err == nil { + t.Fatal("want error in dir without git; got nil") + } +} diff --git a/internal/git_meta/sha.go b/internal/git_meta/sha.go new file mode 100644 index 000000000..da180e865 --- /dev/null +++ b/internal/git_meta/sha.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +import ( + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" +) + +// shortSHA truncates a full SHA to the canonical short form +// length defined by +// [github.com/ActiveMemory/ctx/internal/config/git_meta.ShortLen]. +// +// Parameters: +// - s: full or already-short SHA. +// +// Returns: +// - string: first ShortLen bytes when longer, else the input +// unchanged. +func shortSHA(s string) string { + if len(s) <= cfgGitmeta.ShortLen { + return s + } + return s[:cfgGitmeta.ShortLen] +} diff --git a/internal/git_meta/testmain_test.go b/internal/git_meta/testmain_test.go new file mode 100644 index 000000000..61b81dbca --- /dev/null +++ b/internal/git_meta/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/git_meta/types.go b/internal/git_meta/types.go new file mode 100644 index 000000000..a057b750b --- /dev/null +++ b/internal/git_meta/types.go @@ -0,0 +1,16 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package gitmeta + +// HeadRef pairs a short commit SHA with the current branch +// name. Branch is the literal "detached" (see +// [github.com/ActiveMemory/ctx/internal/config/git_meta.BranchDetached]) +// when HEAD points at a commit instead of a symbolic ref. +type HeadRef struct { + SHA string + Branch string +} diff --git a/internal/io/security.go b/internal/io/security.go index 7684b50ac..a5a6d407c 100644 --- a/internal/io/security.go +++ b/internal/io/security.go @@ -240,6 +240,27 @@ func SafeStat(path string) (os.FileInfo, error) { return os.Stat(clean) } +// SafeRename renames src to dst after cleaning both paths and +// rejecting system directory prefixes. +// +// Parameters: +// - src: source path +// - dst: destination path +// +// Returns: +// - error: non-nil on validation or rename failure +func SafeRename(src, dst string) error { + cleanSrc, srcErr := cleanAndValidate(src) + if srcErr != nil { + return srcErr + } + cleanDst, dstErr := cleanAndValidate(dst) + if dstErr != nil { + return dstErr + } + return os.Rename(cleanSrc, cleanDst) +} + // TouchFile creates or updates an empty marker file. Best-effort: // errors are silently ignored. Used for throttle markers and // one-shot flags in state directories. diff --git a/internal/slug/doc.go b/internal/slug/doc.go new file mode 100644 index 000000000..05e64bec6 --- /dev/null +++ b/internal/slug/doc.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package slug generates **URL-safe, filesystem-safe +// identifiers** from human-readable input. Used by the +// journal importer to derive entry filenames and by the +// kb topic-page scaffolder to derive folder slugs. +// +// # Public Surface +// +// - **[FromTitle](title)**: strict kebab-case slug. +// Lowercases, replaces every non-alphanumeric run with +// a single hyphen, trims, and truncates on a word +// boundary at [journal.TitleSlugMaxLen]. Idempotent. +// - **[CleanTitle](title)**: normalises a display title +// for storage in YAML frontmatter (whitespace +// collapsing + length cap). Pairs with FromTitle. +// - **[ForTitle](session, existing)**: fallback chain +// that picks the best title-derived slug for a +// journal session (enriched title → first user msg → +// Claude Code slug → short ID). +// - **[Path](s)**: kebab-case slug that preserves `/` so +// vendor-namespaced kb topic slugs survive +// normalisation (e.g. `cursor/hooks`). +// +// # Stability Contract +// +// The slug for a given (title, dedup-context) pair is +// **deterministic**: re-running the importer against +// the same source produces the same slug. This is +// what makes the importer idempotent and what lets +// `git diff` show a meaningful patch when an entry +// is re-enriched. +// +// # Concurrency +// +// All functions are pure. Concurrent callers never +// race. +package slug diff --git a/internal/cli/journal/core/slug/slug.go b/internal/slug/slug.go similarity index 85% rename from internal/cli/journal/core/slug/slug.go rename to internal/slug/slug.go index 89d5ece80..43ea10ea7 100644 --- a/internal/cli/journal/core/slug/slug.go +++ b/internal/slug/slug.go @@ -16,6 +16,26 @@ import ( "github.com/ActiveMemory/ctx/internal/entity" ) +// Path returns a slug that preserves `/` so vendor-namespaced +// inputs (e.g. `cursor/hooks`) survive normalisation. Lowercases, +// trims whitespace, replaces spaces with hyphens, strips any +// remaining non-allowed runes via [regex.TopicSlug], and trims +// leading / trailing `-` and `/`. +// +// Parameters: +// - s: free-text input. +// +// Returns: +// - string: kebab-case slug; lowercase; hyphen / slash +// separated; no other non-alnum runes. +func Path(s string) string { + low := strings.ToLower(strings.TrimSpace(s)) + low = strings.ReplaceAll(low, token.Space, token.Dash) + low = regex.TopicSlug.ReplaceAllString(low, "") + low = strings.Trim(low, token.Dash+token.Slash) + return low +} + // FromTitle converts a human-readable title into a URL-friendly slug. // // Lowercases the input, replaces non-alphanumeric characters with hyphens, diff --git a/internal/cli/journal/core/slug/slug_test.go b/internal/slug/slug_test.go similarity index 86% rename from internal/cli/journal/core/slug/slug_test.go rename to internal/slug/slug_test.go index ace3db356..9d9537e4d 100644 --- a/internal/cli/journal/core/slug/slug_test.go +++ b/internal/slug/slug_test.go @@ -118,6 +118,46 @@ func TestCleanTitle(t *testing.T) { } } +func TestPath(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + {"empty", "", ""}, + {"plain word", "cursor", "cursor"}, + {"two words become hyphen", "Cursor Hooks", "cursor-hooks"}, + { + "vendor slash topic preserved", + "cursor/hooks", "cursor/hooks", + }, + { + "vendor slash topic with spaces", + "cursor / hooks", "cursor-/-hooks", + }, + { + "strips punctuation but keeps slash", + "Cursor: Hooks!", "cursor-hooks", + }, + {"trims leading slash", "/cursor/hooks", "cursor/hooks"}, + {"trims trailing slash", "cursor/hooks/", "cursor/hooks"}, + { + "trims surrounding hyphens", + "---cursor---", "cursor", + }, + {"unicode falls out", "café", "caf"}, + {"all punctuation collapses to empty", "!!!", ""}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := Path(tt.input) + if got != tt.want { + t.Errorf("Path(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + func TestTitleSlug_FallbackHierarchy(t *testing.T) { tests := []struct { name string diff --git a/internal/cli/journal/core/slug/testmain_test.go b/internal/slug/testmain_test.go similarity index 100% rename from internal/cli/journal/core/slug/testmain_test.go rename to internal/slug/testmain_test.go diff --git a/internal/write/closeout/archive.go b/internal/write/closeout/archive.go new file mode 100644 index 000000000..c05fb7039 --- /dev/null +++ b/internal/write/closeout/archive.go @@ -0,0 +1,50 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Archive moves closeout files from their current location into +// the supplied archive directory. The move is performed via +// rename when source and destination share a filesystem; the +// caller is responsible for ensuring archiveDir is writable. +// +// Files are append-never-rewrite; archival physically relocates +// the bytes without modifying them. Archived closeouts are +// immutable. +// +// Parameters: +// - archiveDir: absolute path to .context/archive/closeouts/ +// (created if absent). +// - files: closeouts to move (paths must be writable). +// +// Returns: +// - error: non-nil on mkdir / rename failure. Partial- +// success is possible: files moved before the failure +// remain at their archive paths; the error names the file +// that failed. +func Archive(archiveDir string, files []File) error { + if len(files) == 0 { + return nil + } + if mkErr := io.SafeMkdirAll(archiveDir, cfgFs.PermExec); mkErr != nil { + return errCloseout.MkdirArchive(mkErr) + } + for _, f := range files { + dst := filepath.Join(archiveDir, filepath.Base(f.Path)) + if renameErr := io.SafeRename(f.Path, dst); renameErr != nil { + return errCloseout.ArchiveMove(filepath.Base(f.Path), renameErr) + } + } + return nil +} diff --git a/internal/write/closeout/closeout_test.go b/internal/write/closeout/closeout_test.go new file mode 100644 index 000000000..79aa05929 --- /dev/null +++ b/internal/write/closeout/closeout_test.go @@ -0,0 +1,235 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout_test + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" + "github.com/ActiveMemory/ctx/internal/write/closeout" +) + +// gitInit creates a real git repo at root so gitmeta.ResolveHead +// works without env overrides. +func gitInit(t *testing.T, root string) { + t.Helper() + if _, err := exec.LookPath("git"); err != nil { + t.Skipf("git not on PATH: %v", err) + } + for _, args := range [][]string{ + {"init", "-q"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "Test User"}, + {"commit", "--allow-empty", "-m", "init", "-q"}, + } { + //nolint:gosec // G204: test fixture, args are hardcoded above + cmd := exec.Command("git", args...) + cmd.Dir = root + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + } +} + +func TestWrite_RoundTripWithGitRepo(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + closeoutsDir := filepath.Join(root, "ingest", "closeouts") + + f, err := closeout.Write( + closeoutsDir, + root, + cfgKB.CloseoutModeIngest, + "topic-page", + "bootstrap", + "## Inputs\n\nfoo\n", + ) + if err != nil { + t.Fatalf("Write: %v", err) + } + if f.Path == "" { + t.Fatal("Write returned empty path") + } + if !strings.HasSuffix(f.Path, "-ingest-closeout.md") { + t.Errorf("filename suffix: got %s", f.Path) + } + if f.Frontmatter.Mode != cfgKB.CloseoutModeIngest { + t.Errorf("mode: want %q; got %q", cfgKB.CloseoutModeIngest, f.Frontmatter.Mode) + } + if f.Frontmatter.PassMode != "topic-page" { + t.Errorf("pass-mode: got %q", f.Frontmatter.PassMode) + } + if f.Frontmatter.LifeStage != "bootstrap" { + t.Errorf("life-stage: got %q", f.Frontmatter.LifeStage) + } + if f.Frontmatter.SHA == "" { + t.Error("SHA empty after Write") + } + if f.Frontmatter.GeneratedAt.IsZero() { + t.Error("GeneratedAt zero after Write") + } + + // Read it back from disk. + got, err := closeout.Read(f.Path) + if err != nil { + t.Fatalf("Read: %v", err) + } + if got.Frontmatter.SHA != f.Frontmatter.SHA { + t.Errorf("SHA round-trip mismatch") + } + if got.Frontmatter.Mode != f.Frontmatter.Mode { + t.Errorf("Mode round-trip mismatch") + } + if !strings.Contains(got.Body, "foo") { + t.Errorf("body lost: %q", got.Body) + } +} + +func TestWrite_RejectsEmptyMode(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + _, err := closeout.Write( + filepath.Join(root, "closeouts"), + root, "", "", "", "body", + ) + if err == nil { + t.Fatal("want error for empty mode; got nil") + } +} + +func TestWrite_UsesCtxTaskCommitOverride(t *testing.T) { + root := t.TempDir() + // No git init; rely entirely on CTX_TASK_COMMIT override. + t.Setenv(cfgGitmeta.EnvCtxTaskCommit, "abc1234") + t.Setenv(cfgGitmeta.EnvGithubActions, "") + t.Setenv(cfgGitmeta.EnvGithubSHA, "") + closeoutsDir := filepath.Join(root, "closeouts") + + f, err := closeout.Write( + closeoutsDir, root, + cfgKB.CloseoutModeAsk, "", "", "body", + ) + if err != nil { + t.Fatalf("Write: %v", err) + } + if f.Frontmatter.SHA != "abc1234" { + t.Errorf("SHA: want abc1234; got %q", f.Frontmatter.SHA) + } +} + +func TestRead_MissingFrontmatter(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, "broken.md") + if err := os.WriteFile(path, []byte("just text, no delim\n"), 0o600); err != nil { + t.Fatal(err) + } + _, err := closeout.Read(path) + if !errors.Is(err, errCloseout.ErrMissingFrontmatter) { + t.Fatalf("want ErrMissingFrontmatter; got %v", err) + } +} + +func TestRead_MissingFields(t *testing.T) { + root := t.TempDir() + path := filepath.Join(root, "incomplete.md") + body := "---\nsha: abc\nbranch: main\n---\n\nbody\n" + if err := os.WriteFile(path, []byte(body), 0o600); err != nil { + t.Fatal(err) + } + _, err := closeout.Read(path) + if !errors.Is(err, errCloseout.ErrMissingFields) { + t.Fatalf("want ErrMissingFields; got %v", err) + } +} + +func TestList_SortedAscending(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + closeoutsDir := filepath.Join(root, "closeouts") + + for i, mode := range []string{ + cfgKB.CloseoutModeIngest, + cfgKB.CloseoutModeAsk, + cfgKB.CloseoutModeGround, + } { + // Force ordering by writing sequentially with a small + // real-time gap; the test does not depend on exact + // timing, only on monotonic order at the granularity + // of the timestamps we use. + if _, err := closeout.Write( + closeoutsDir, root, mode, "", "", "body", + ); err != nil { + t.Fatalf("Write %d: %v", i, err) + } + time.Sleep(1100 * time.Millisecond) + } + + files, bad, err := closeout.List(closeoutsDir) + if err != nil { + t.Fatalf("List: %v", err) + } + if len(bad) != 0 { + t.Errorf("unexpected bad files: %v", bad) + } + if len(files) != 3 { + t.Fatalf("count: want 3; got %d", len(files)) + } + for i := 1; i < len(files); i++ { + prev := files[i-1].Frontmatter.GeneratedAt + curr := files[i].Frontmatter.GeneratedAt + if curr.Before(prev) { + t.Errorf("List not ascending at index %d", i) + } + } +} + +func TestPostdatedBy(t *testing.T) { + now := time.Now() + files := []closeout.File{ + {Frontmatter: closeout.Frontmatter{GeneratedAt: now.Add(-2 * time.Hour)}}, + {Frontmatter: closeout.Frontmatter{GeneratedAt: now.Add(-1 * time.Hour)}}, + {Frontmatter: closeout.Frontmatter{GeneratedAt: now}}, + } + cursor := now.Add(-90 * time.Minute) + got := closeout.PostdatedBy(files, cursor) + if len(got) != 2 { + t.Errorf("want 2 postdated; got %d", len(got)) + } +} + +func TestArchive_MovesFiles(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + closeoutsDir := filepath.Join(root, "closeouts") + archiveDir := filepath.Join(root, "archive") + + f, err := closeout.Write( + closeoutsDir, root, + cfgKB.CloseoutModeIngest, "", "", "body", + ) + if err != nil { + t.Fatalf("Write: %v", err) + } + if err := closeout.Archive(archiveDir, []closeout.File{f}); err != nil { + t.Fatalf("Archive: %v", err) + } + if _, err := os.Stat(f.Path); !errors.Is(err, os.ErrNotExist) { + t.Errorf("source still exists: %v", err) + } + moved := filepath.Join(archiveDir, filepath.Base(f.Path)) + if _, err := os.Stat(moved); err != nil { + t.Errorf("archived file missing: %v", err) + } +} diff --git a/internal/write/closeout/doc.go b/internal/write/closeout/doc.go new file mode 100644 index 000000000..4f7189552 --- /dev/null +++ b/internal/write/closeout/doc.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package closeout writes and reads per-pass closeout artifacts +// for the ctx knowledge-base editorial pipeline (Phase KB). +// +// A closeout lives at +// `.context/ingest/closeouts/<TIMESTAMP>-<mode>-closeout.md` +// and carries required frontmatter: +// +// --- +// sha: <short> +// branch: <name> +// mode: <ingest|ask|site-review|ground|note> +// pass-mode: <topic-page|triage|evidence-only> (only for ingest) +// life-stage: <bootstrap|maintenance> +// generated-at: <RFC-3339 with timezone> +// --- +// +// The body sections (Inputs, Pass-mode block, Topic(s) touched, +// What changed, etc.) are mode-aware; this package treats the +// body as opaque bytes and only asserts on frontmatter. +// +// Closeouts are append-never-rewrite. Once written they are +// immutable; the only legal state change is archival, which +// physically moves the file to .context/archive/closeouts/ +// without modifying its contents. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/write/handover] +// consumes closeouts via [List] + [PostdatedBy] + [Archive] +// during handover fold. +// - [github.com/ActiveMemory/ctx/internal/git_meta] supplies +// the (sha, branch) pair stamped into the frontmatter. +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// the mode + closeout-suffix constants. +package closeout diff --git a/internal/write/closeout/filename.go b/internal/write/closeout/filename.go new file mode 100644 index 000000000..439a5dcf6 --- /dev/null +++ b/internal/write/closeout/filename.go @@ -0,0 +1,37 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "strings" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// buildFilename derives a closeout's on-disk name from its +// frontmatter. +// +// Shape: `<TS>-<mode>-closeout.md` where `<TS>` is the UTC +// compact RFC-3339 form (`20260517T021837Z`; colons stripped +// for filesystem safety) and `<mode>` is the frontmatter's +// Mode field. +// +// Parameters: +// - fm: frontmatter (uses Mode + GeneratedAt). +// +// Returns: +// - string: filename portion only (no directory). +func buildFilename(fm Frontmatter) string { + var sb strings.Builder + sb.WriteString(fm.GeneratedAt.UTC().Format(cfgTime.RFC3339Compact)) + sb.WriteString(token.Dash) + sb.WriteString(fm.Mode) + sb.WriteString(cfgKB.CloseoutSuffix) + return sb.String() +} diff --git a/internal/write/closeout/frontmatter.go b/internal/write/closeout/frontmatter.go new file mode 100644 index 000000000..98cc13aa2 --- /dev/null +++ b/internal/write/closeout/frontmatter.go @@ -0,0 +1,92 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "strings" + + "gopkg.in/yaml.v3" + + cfgCloseout "github.com/ActiveMemory/ctx/internal/config/closeout" + "github.com/ActiveMemory/ctx/internal/config/token" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" +) + +// frontmatterDelim is the YAML frontmatter open / close +// delimiter that brackets the header in a closeout file. +const frontmatterDelim = token.Separator + +// splitFrontmatter splits a closeout's raw text into parsed +// frontmatter + body. +// +// Parameters: +// - raw: full file contents. +// +// Returns: +// - Frontmatter: parsed values. +// - string: body (everything after closing `---`). +// - error: [errCloseout.ErrMissingFrontmatter] or wrapped +// YAML errors. +func splitFrontmatter(raw string) (Frontmatter, string, error) { + lines := strings.SplitN(raw, token.NewlineLF, 2) + if len(lines) < 2 || strings.TrimSpace(lines[0]) != frontmatterDelim { + return Frontmatter{}, "", errCloseout.ErrMissingFrontmatter + } + rest := lines[1] + openClose := token.NewlineLF + frontmatterDelim + token.NewlineLF + idx := strings.Index(rest, openClose) + if idx < 0 { + // Tolerate trailing closing delim without newline. + idx = strings.Index(rest, token.NewlineLF+frontmatterDelim) + if idx < 0 { + return Frontmatter{}, "", errCloseout.ErrMissingFrontmatter + } + } + header := rest[:idx] + bodyStart := idx + len(openClose) + if bodyStart > len(rest) { + bodyStart = len(rest) + } + body := rest[bodyStart:] + body = strings.TrimLeft(body, token.NewlineLF) + + var fm Frontmatter + if err := yaml.Unmarshal([]byte(header), &fm); err != nil { + return Frontmatter{}, "", errCloseout.ParseFrontmatter(err) + } + return fm, body, nil +} + +// requireFields validates that frontmatter has the four +// always-required fields populated. +// +// Parameters: +// - fm: parsed frontmatter. +// +// Returns: +// - error: wraps [errCloseout.ErrMissingFields] with the +// ordered list of missing field names; nil when all four +// required fields are populated. +func requireFields(fm Frontmatter) error { + missing := []string{} + if fm.SHA == "" { + missing = append(missing, cfgCloseout.FieldSHA) + } + if fm.Branch == "" { + missing = append(missing, cfgCloseout.FieldBranch) + } + if fm.Mode == "" { + missing = append(missing, cfgCloseout.FieldMode) + } + if fm.GeneratedAt.IsZero() { + missing = append(missing, cfgCloseout.FieldGeneratedAt) + } + if len(missing) > 0 { + return errCloseout.MissingFields(missing) + } + return nil +} diff --git a/internal/write/closeout/markdown.go b/internal/write/closeout/markdown.go new file mode 100644 index 000000000..f75a50b3c --- /dev/null +++ b/internal/write/closeout/markdown.go @@ -0,0 +1,43 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "strings" + + "gopkg.in/yaml.v3" + + "github.com/ActiveMemory/ctx/internal/config/token" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" +) + +// renderMarkdown composes the frontmatter YAML block + body +// into the final closeout file content. +// +// Parameters: +// - fm: parsed frontmatter for the closeout. +// - body: rendered body markdown (everything that follows the +// closing `---` delimiter). +// +// Returns: +// - string: full file content with frontmatter delimiters. +// - error: non-nil on YAML marshal failure. +func renderMarkdown(fm Frontmatter, body string) (string, error) { + var sb strings.Builder + sb.WriteString(frontmatterDelim) + sb.WriteString(token.NewlineLF) + enc, yamlErr := yaml.Marshal(fm) + if yamlErr != nil { + return "", errCloseout.MarshalFrontmatter(yamlErr) + } + sb.Write(enc) + sb.WriteString(frontmatterDelim) + sb.WriteString(token.DoubleNewline) + sb.WriteString(strings.TrimRight(body, token.NewlineLF)) + sb.WriteString(token.NewlineLF) + return sb.String(), nil +} diff --git a/internal/write/closeout/read.go b/internal/write/closeout/read.go new file mode 100644 index 000000000..c2ca0c59f --- /dev/null +++ b/internal/write/closeout/read.go @@ -0,0 +1,123 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "errors" + "os" + "path/filepath" + "sort" + "strings" + "time" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Read parses a closeout file at the given path. Returns the +// parsed frontmatter and the body bytes (everything after the +// closing `---`). +// +// Parameters: +// - path: absolute path to a closeout markdown file. +// +// Returns: +// - File: parsed frontmatter + body. +// - error: [errCloseout.ErrMissingFrontmatter] if the file +// does not open with `---`; [errCloseout.ErrMissingFields] +// when required fields are absent; wrapped errors for I/O +// or YAML parse failures. +func Read(path string) (File, error) { + raw, ioErr := io.SafeReadUserFile(path) + if ioErr != nil { + return File{}, errCloseout.ReadFailed(ioErr) + } + fm, body, splitErr := splitFrontmatter(string(raw)) + if splitErr != nil { + return File{}, splitErr + } + if fieldsErr := requireFields(fm); fieldsErr != nil { + return File{}, fieldsErr + } + return File{ + Path: path, + Frontmatter: fm, + Body: body, + }, nil +} + +// List enumerates closeouts in dir, parses each, and returns +// the parsed File slice sorted by GeneratedAt ascending. +// +// Files that fail to parse are returned in the second slice +// (their paths) so the caller can surface a doctor-style +// warning. List returns nil error on file-level parse failures; +// only directory-walk failures bubble up. +// +// Parameters: +// - closeoutsDir: absolute path to scan. +// +// Returns: +// - []File: successfully-parsed closeouts (sorted). +// - []string: paths of files that failed to parse. +// - error: non-nil only on directory enumeration failure. +func List(closeoutsDir string) ([]File, []string, error) { + entries, readErr := os.ReadDir(closeoutsDir) + if readErr != nil { + if errors.Is(readErr, os.ErrNotExist) { + return nil, nil, nil + } + return nil, nil, errCloseout.ReadCloseoutsDir(readErr) + } + + var ok []File + var bad []string + for _, e := range entries { + if e.IsDir() { + continue + } + name := e.Name() + if !strings.HasSuffix(name, cfgKB.CloseoutSuffix) { + continue + } + path := filepath.Join(closeoutsDir, name) + f, parseErr := Read(path) + if parseErr != nil { + bad = append(bad, path) + continue + } + ok = append(ok, f) + } + sort.Slice(ok, func(i, j int) bool { + return ok[i].Frontmatter.GeneratedAt.Before( + ok[j].Frontmatter.GeneratedAt, + ) + }) + return ok, bad, nil +} + +// PostdatedBy returns the subset of files whose GeneratedAt is +// strictly after cursor. Used by the handover-fold mechanism +// to find closeouts produced since the last handover. +// +// Parameters: +// - files: pre-parsed closeouts (typically from List). +// - cursor: handover-fold cursor (latest handover's +// generated-at, or zero value for "fold everything"). +// +// Returns: +// - []File: files with GeneratedAt > cursor, order preserved. +func PostdatedBy(files []File, cursor time.Time) []File { + var out []File + for _, f := range files { + if f.Frontmatter.GeneratedAt.After(cursor) { + out = append(out, f) + } + } + return out +} diff --git a/internal/write/closeout/testmain_test.go b/internal/write/closeout/testmain_test.go new file mode 100644 index 000000000..2a1989016 --- /dev/null +++ b/internal/write/closeout/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/closeout/types.go b/internal/write/closeout/types.go new file mode 100644 index 000000000..b473c4d42 --- /dev/null +++ b/internal/write/closeout/types.go @@ -0,0 +1,20 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import "github.com/ActiveMemory/ctx/internal/entity" + +// Frontmatter aliases [entity.CloseoutFrontmatter] so the +// write/closeout package's existing call sites continue to read +// closeout.Frontmatter while the source-of-truth lives in +// entity/ (per the cross-package-types convention). +type Frontmatter = entity.CloseoutFrontmatter + +// File aliases [entity.CloseoutFile] so the write/closeout +// package's existing call sites continue to read closeout.File +// while the source-of-truth lives in entity/. +type File = entity.CloseoutFile diff --git a/internal/write/closeout/write.go b/internal/write/closeout/write.go new file mode 100644 index 000000000..4693458df --- /dev/null +++ b/internal/write/closeout/write.go @@ -0,0 +1,85 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package closeout + +import ( + "path/filepath" + "time" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout" + "github.com/ActiveMemory/ctx/internal/git_meta" + "github.com/ActiveMemory/ctx/internal/io" +) + +// Write assembles a closeout file under the supplied closeouts +// directory. The caller supplies mode (one of cfgKB.CloseoutMode*), +// optional pass-mode + life-stage (set only for ingest passes), +// and the rendered body sections. +// +// SHA and branch are read from [gitmeta.ResolveHead] against the +// project root. The generated-at timestamp is captured at write +// time in UTC. +// +// Parameters: +// - closeoutsDir: absolute path to the closeouts directory +// (typically `.context/ingest/closeouts/`); created if absent. +// - projectRoot: absolute path to the project root (parent of +// `.context/`); passed to [gitmeta.ResolveHead]. +// - mode: one of `cfgKB.CloseoutMode*` (ingest, ask, etc.). +// - passMode: one of "topic-page" | "triage" | "evidence-only" +// for ingest passes; empty string for other modes. +// - lifeStage: one of "bootstrap" | "maintenance" for ingest +// passes; empty string for other modes. +// - body: rendered closeout body (everything after the closing +// `---`). +// +// Returns: +// - File: the written file with parsed frontmatter and the +// body echoed back. +// - error: non-nil on stat / git-resolve / write failure. +func Write( + closeoutsDir, projectRoot, mode, passMode, lifeStage, body string, +) (File, error) { + if mode == "" { + return File{}, errCloseout.ErrModeRequired + } + ref, headErr := gitmeta.ResolveHead(projectRoot) + if headErr != nil { + return File{}, errCloseout.ResolveHead(headErr) + } + fm := Frontmatter{ + SHA: ref.SHA, + Branch: ref.Branch, + Mode: mode, + PassMode: passMode, + LifeStage: lifeStage, + GeneratedAt: time.Now().UTC().Truncate(time.Second), + } + + if mkErr := io.SafeMkdirAll(closeoutsDir, cfgFs.PermExec); mkErr != nil { + return File{}, errCloseout.MkdirCloseouts(mkErr) + } + + rendered, renderErr := renderMarkdown(fm, body) + if renderErr != nil { + return File{}, renderErr + } + + name := buildFilename(fm) + path := filepath.Join(closeoutsDir, name) + writeErr := io.SafeWriteFile(path, []byte(rendered), cfgFs.PermSecret) + if writeErr != nil { + return File{}, errCloseout.WriteFailed(writeErr) + } + + return File{ + Path: path, + Frontmatter: fm, + Body: body, + }, nil +} diff --git a/internal/write/handover/doc.go b/internal/write/handover/doc.go new file mode 100644 index 000000000..fd031ee72 --- /dev/null +++ b/internal/write/handover/doc.go @@ -0,0 +1,53 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package handover writes per-session handover artifacts under +// `.context/handovers/`. The handover is the session-to-session +// glue: a former-agent-to-next-agent note created by +// `/ctx-wrap-up` at session end and read by `/ctx-remember` at +// session start. It is universal to every ctx project and does +// not depend on the editorial pipeline. +// +// A handover carries a past-tense summary plus a future-tense +// "first action for the next session", with optional highlights +// and open questions. Files are timestamped (`<TS>-<slug>.md`) +// so multiple concurrent agent runs never overwrite each other. +// +// # The ceremony +// +// The "skies will fall" ceremony every ctx session runs is: +// +// 1. `/ctx-remember` at session start reads the latest handover. +// 2. The session does its work. +// 3. `/ctx-wrap-up` at session end delegates to this writer as +// its final step. +// +// Skipping step 3 means the next session's `/ctx-remember` has +// no handover to read and recall degenerates to probabilistic +// reconstruction from canonical files plus journal. +// +// # Optional closeout folding (Phase KB only) +// +// When `.context/kb/` exists, the project also runs the editorial +// pipeline, which writes closeouts under +// `.context/ingest/closeouts/` per pass. As an implementation +// detail, [Write] additionally folds postdated closeouts into the +// handover's `## Folded Closeouts` section and archives them +// under `.context/archive/closeouts/`. A `--no-fold` write skips +// this fold (mid-session checkpoint). When `.context/kb/` is +// absent there are no closeouts to fold; the handover is still +// written normally. The fold is orthogonal to the handover +// mechanism; the handover itself is not a KB feature. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/write/closeout] +// supplies the optional fold-source files. +// - [github.com/ActiveMemory/ctx/internal/git_meta] stamps +// `sha` / `branch` provenance. +// - [github.com/ActiveMemory/ctx/internal/cli/handover/core/path] +// resolves `.context/handovers/`. +package handover diff --git a/internal/write/handover/filename.go b/internal/write/handover/filename.go new file mode 100644 index 000000000..8eac4e42a --- /dev/null +++ b/internal/write/handover/filename.go @@ -0,0 +1,60 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "strings" + "time" + + cfgFile "github.com/ActiveMemory/ctx/internal/config/file" + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" + "github.com/ActiveMemory/ctx/internal/config/regex" + cfgTime "github.com/ActiveMemory/ctx/internal/config/time" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// buildFilename derives a handover's on-disk name. Shape: +// `<TS>-<slug>.md` where `<TS>` is the UTC compact RFC-3339 +// form (`20260517T021837Z`; colons stripped) and `<slug>` is +// the kebab-case normalisation of the caller's title. +// +// Parameters: +// - now: GeneratedAt timestamp. +// - title: caller-supplied title. +// +// Returns: +// - string: filename portion only (no directory). +func buildFilename(now time.Time, title string) string { + slug := titleToSlug(title) + if slug == "" { + slug = cfgHandover.DefaultSlug + } + var sb strings.Builder + sb.WriteString(now.UTC().Format(cfgTime.RFC3339Compact)) + sb.WriteString(token.Dash) + sb.WriteString(slug) + sb.WriteString(cfgFile.ExtMarkdown) + return sb.String() +} + +// titleToSlug normalises a free-text title into a kebab-case +// slug-safe form. +// +// Parameters: +// - s: free text. +// +// Returns: +// - string: lowercase, hyphen-separated, with non-alnum +// characters stripped and leading / trailing hyphens +// trimmed. +func titleToSlug(s string) string { + low := strings.ToLower(strings.TrimSpace(s)) + low = strings.ReplaceAll(low, token.Space, token.Dash) + low = regex.Slug.ReplaceAllString(low, "") + low = strings.Trim(low, token.Dash) + return low +} diff --git a/internal/write/handover/handover_test.go b/internal/write/handover/handover_test.go new file mode 100644 index 000000000..f98e33667 --- /dev/null +++ b/internal/write/handover/handover_test.go @@ -0,0 +1,190 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover_test + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/closeout" + "github.com/ActiveMemory/ctx/internal/write/handover" +) + +func gitInit(t *testing.T, root string) { + t.Helper() + if _, err := exec.LookPath("git"); err != nil { + t.Skipf("git not on PATH: %v", err) + } + for _, args := range [][]string{ + {"init", "-q"}, + {"config", "user.email", "test@example.com"}, + {"config", "user.name", "Test User"}, + {"commit", "--allow-empty", "-m", "init", "-q"}, + } { + //nolint:gosec // G204: test fixture, args are hardcoded above + cmd := exec.Command("git", args...) + cmd.Dir = root + if out, err := cmd.CombinedOutput(); err != nil { + t.Fatalf("git %v: %v\n%s", args, err, out) + } + } +} + +func TestWrite_HappyPathWithFold(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + handoversDir := filepath.Join(root, "handovers") + closeoutsDir := filepath.Join(root, "closeouts") + archiveDir := filepath.Join(root, "archive") + + // Pre-create a closeout that will be folded. + _, err := closeout.Write( + closeoutsDir, root, cfgKB.CloseoutModeIngest, + "topic-page", "bootstrap", "## What changed\n\nstuff\n", + ) + if err != nil { + t.Fatalf("seed closeout: %v", err) + } + + res, err := handover.Write( + handoversDir, closeoutsDir, archiveDir, root, + handover.Entry{ + Title: "First Session", + Summary: "did the thing", + Next: "do the other thing", + }, + ) + if err != nil { + t.Fatalf("Write: %v", err) + } + if res.File.Path == "" { + t.Fatal("Write returned empty path") + } + if !strings.HasSuffix(res.File.Path, "-first-session.md") { + t.Errorf("filename: got %s", res.File.Path) + } + if len(res.FoldedCloseouts) != 1 { + t.Errorf("folded count: want 1; got %d", len(res.FoldedCloseouts)) + } + if !strings.Contains(res.File.Body, "## Folded closeouts") { + t.Errorf("folded section missing from body") + } + // Source closeout should have been archived. + if _, statErr := os.Stat(res.FoldedCloseouts[0].Path); !errors.Is(statErr, os.ErrNotExist) { + t.Errorf("folded closeout still in source: %v", statErr) + } +} + +func TestWrite_NoFoldKeepsCloseouts(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + handoversDir := filepath.Join(root, "handovers") + closeoutsDir := filepath.Join(root, "closeouts") + archiveDir := filepath.Join(root, "archive") + + f, err := closeout.Write( + closeoutsDir, root, cfgKB.CloseoutModeIngest, "", "", "body", + ) + if err != nil { + t.Fatalf("seed closeout: %v", err) + } + + res, err := handover.Write( + handoversDir, closeoutsDir, archiveDir, root, + handover.Entry{ + Title: "Mid-session checkpoint", + Summary: "checkpoint", + Next: "resume", + NoFold: true, + }, + ) + if err != nil { + t.Fatalf("Write: %v", err) + } + if len(res.FoldedCloseouts) != 0 { + t.Errorf("expected no folds; got %d", len(res.FoldedCloseouts)) + } + if _, statErr := os.Stat(f.Path); statErr != nil { + t.Errorf("closeout should still exist after --no-fold: %v", statErr) + } +} + +func TestWrite_RejectsEmptyFields(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + dirs := struct{ h, c, a string }{ + filepath.Join(root, "handovers"), + filepath.Join(root, "closeouts"), + filepath.Join(root, "archive"), + } + cases := []handover.Entry{ + {Title: "", Summary: "s", Next: "n"}, + {Title: "t", Summary: "", Next: "n"}, + {Title: "t", Summary: "s", Next: ""}, + } + for i, e := range cases { + _, err := handover.Write(dirs.h, dirs.c, dirs.a, root, e) + if err == nil { + t.Errorf("case %d: expected error", i) + } + } +} + +func TestLatest_EmptyDir(t *testing.T) { + dir := t.TempDir() + latestAt, path, err := handover.Latest(dir) + if err != nil { + t.Fatalf("Latest: %v", err) + } + if !latestAt.IsZero() { + t.Errorf("latest: want zero; got %v", latestAt) + } + if path != "" { + t.Errorf("path: want empty; got %s", path) + } +} + +func TestLatest_ReturnsMostRecent(t *testing.T) { + root := t.TempDir() + gitInit(t, root) + handoversDir := filepath.Join(root, "handovers") + closeoutsDir := filepath.Join(root, "closeouts") + archiveDir := filepath.Join(root, "archive") + + for i, title := range []string{"first", "second"} { + _, err := handover.Write( + handoversDir, closeoutsDir, archiveDir, root, + handover.Entry{ + Title: title, + Summary: "s", + Next: "n", + NoFold: true, + }, + ) + if err != nil { + t.Fatalf("Write %d: %v", i, err) + } + time.Sleep(1100 * time.Millisecond) + } + + latestAt, path, err := handover.Latest(handoversDir) + if err != nil { + t.Fatalf("Latest: %v", err) + } + if latestAt.IsZero() { + t.Error("latest zero after writes") + } + if !strings.HasSuffix(path, "-second.md") { + t.Errorf("want second handover; got %s", path) + } +} diff --git a/internal/write/handover/latest.go b/internal/write/handover/latest.go new file mode 100644 index 000000000..ba1d0febf --- /dev/null +++ b/internal/write/handover/latest.go @@ -0,0 +1,36 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "time" +) + +// Latest returns the GeneratedAt of the most recent +// handover under handoversDir, or the zero value when no +// handover exists yet. The third return is the path of that +// latest handover (empty when no handover exists). +// +// Parameters: +// - handoversDir: absolute path to .context/handovers/. +// +// Returns: +// - time.Time: GeneratedAt of the latest handover, or zero. +// - string: path of the latest handover, or empty. +// - error: non-nil on directory-walk failures (not on +// individual-file parse failures). +func Latest(handoversDir string) (time.Time, string, error) { + files, err := listHandovers(handoversDir) + if err != nil { + return time.Time{}, "", err + } + if len(files) == 0 { + return time.Time{}, "", nil + } + latest := files[len(files)-1] + return latest.Frontmatter.GeneratedAt, latest.Path, nil +} diff --git a/internal/write/handover/markdown.go b/internal/write/handover/markdown.go new file mode 100644 index 000000000..031116fbc --- /dev/null +++ b/internal/write/handover/markdown.go @@ -0,0 +1,102 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "path/filepath" + "strings" + "time" + + "gopkg.in/yaml.v3" + + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" + "github.com/ActiveMemory/ctx/internal/config/token" + "github.com/ActiveMemory/ctx/internal/entity" + errHandover "github.com/ActiveMemory/ctx/internal/err/handover" +) + +// composeMarkdown builds the handover's full file content: +// frontmatter YAML block plus body. +// +// Parameters: +// - fm: frontmatter. +// - body: rendered body markdown. +// +// Returns: +// - string: full file content with frontmatter delimiters. +// - error: non-nil on YAML marshal failure. +func composeMarkdown(fm Frontmatter, body string) (string, error) { + var sb strings.Builder + sb.WriteString(token.Separator) + sb.WriteString(token.NewlineLF) + enc, yamlErr := yaml.Marshal(fm) + if yamlErr != nil { + return "", errHandover.MarshalFrontmatter(yamlErr) + } + sb.Write(enc) + sb.WriteString(token.Separator) + sb.WriteString(token.DoubleNewline) + sb.WriteString(strings.TrimRight(body, token.NewlineLF)) + sb.WriteString(token.NewlineLF) + return sb.String(), nil +} + +// renderBody composes the handover's markdown body sections. +// +// Parameters: +// - entry: caller-supplied content fields. +// - folded: closeouts folded into this handover. +// +// Returns: +// - string: rendered markdown body. +func renderBody(entry Entry, folded []entity.CloseoutFile) string { + var sb strings.Builder + sb.WriteString(cfgHandover.SectionSummary) + sb.WriteString(token.DoubleNewline) + sb.WriteString(strings.TrimSpace(entry.Summary)) + sb.WriteString(token.DoubleNewline) + sb.WriteString(cfgHandover.SectionNext) + sb.WriteString(token.DoubleNewline) + sb.WriteString(strings.TrimSpace(entry.Next)) + sb.WriteString(token.NewlineLF) + + if h := strings.TrimSpace(entry.Highlights); h != "" { + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgHandover.SectionHighlights) + sb.WriteString(token.DoubleNewline) + sb.WriteString(h) + sb.WriteString(token.NewlineLF) + } + if q := strings.TrimSpace(entry.OpenQuestions); q != "" { + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgHandover.SectionOpenQuestions) + sb.WriteString(token.DoubleNewline) + sb.WriteString(q) + sb.WriteString(token.NewlineLF) + } + + if len(folded) > 0 { + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgHandover.SectionFoldedCloseouts) + sb.WriteString(token.DoubleNewline) + for _, f := range folded { + sb.WriteString(cfgHandover.FoldEntryPrefix) + sb.WriteString(filepath.Base(f.Path)) + sb.WriteString(cfgHandover.FoldEntryModePrefix) + sb.WriteString(f.Frontmatter.Mode) + if f.Frontmatter.PassMode != "" { + sb.WriteString(cfgHandover.FoldEntryPassModePrefix) + sb.WriteString(f.Frontmatter.PassMode) + } + sb.WriteString(cfgHandover.FoldEntryGeneratedAtPrefix) + sb.WriteString(f.Frontmatter.GeneratedAt.Format(time.RFC3339)) + sb.WriteString(token.NewlineLF) + } + } + + return sb.String() +} diff --git a/internal/write/handover/parse.go b/internal/write/handover/parse.go new file mode 100644 index 000000000..67491e2b2 --- /dev/null +++ b/internal/write/handover/parse.go @@ -0,0 +1,107 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "errors" + "os" + "path/filepath" + "sort" + "strings" + + "gopkg.in/yaml.v3" + + cfgFile "github.com/ActiveMemory/ctx/internal/config/file" + "github.com/ActiveMemory/ctx/internal/config/token" + errHandover "github.com/ActiveMemory/ctx/internal/err/handover" + "github.com/ActiveMemory/ctx/internal/io" +) + +// listHandovers enumerates handover files in handoversDir, +// parses each, and returns the parsed slice sorted by +// GeneratedAt ascending. Files that fail to parse are +// silently skipped; the caller can detect that case by +// comparing len(returned) against the dir listing. +// +// Parameters: +// - handoversDir: absolute path. +// +// Returns: +// - []File: parsed handovers, ascending by GeneratedAt. +// - error: non-nil only on directory enumeration failure. +func listHandovers(handoversDir string) ([]File, error) { + entries, readErr := os.ReadDir(handoversDir) + if readErr != nil { + if errors.Is(readErr, os.ErrNotExist) { + return nil, nil + } + return nil, errHandover.ReadHandoversDir(readErr) + } + var out []File + for _, e := range entries { + if e.IsDir() { + continue + } + if !strings.HasSuffix(e.Name(), cfgFile.ExtMarkdown) { + continue + } + path := filepath.Join(handoversDir, e.Name()) + f, parseErr := readFile(path) + if parseErr != nil { + continue + } + out = append(out, f) + } + sort.Slice(out, func(i, j int) bool { + return out[i].Frontmatter.GeneratedAt.Before( + out[j].Frontmatter.GeneratedAt, + ) + }) + return out, nil +} + +// readFile reads + parses a handover file at path. +// +// Parameters: +// - path: absolute path to a handover markdown file. +// +// Returns: +// - File: parsed frontmatter + body. +// - error: wrapped errors on I/O or YAML parse failures. +func readFile(path string) (File, error) { + raw, ioErr := io.SafeReadUserFile(path) + if ioErr != nil { + return File{}, errHandover.ReadFailed(ioErr) + } + lines := strings.SplitN(string(raw), token.NewlineLF, 2) + if len(lines) < 2 || strings.TrimSpace(lines[0]) != token.Separator { + return File{}, errHandover.ErrMissingFrontmatter + } + openClose := token.NewlineLF + token.Separator + token.NewlineLF + idx := strings.Index(lines[1], openClose) + if idx < 0 { + idx = strings.Index(lines[1], token.NewlineLF+token.Separator) + if idx < 0 { + return File{}, errHandover.ErrMissingClosingDelim + } + } + header := lines[1][:idx] + bodyStart := idx + len(openClose) + if bodyStart > len(lines[1]) { + bodyStart = len(lines[1]) + } + body := strings.TrimLeft(lines[1][bodyStart:], token.NewlineLF) + + var fm Frontmatter + if yamlErr := yaml.Unmarshal([]byte(header), &fm); yamlErr != nil { + return File{}, errHandover.ParseFrontmatter(yamlErr) + } + if fm.GeneratedAt.IsZero() { + return File{}, errHandover.ErrMissingGeneratedAt + } + return File{Path: path, Frontmatter: fm, Body: body}, nil +} diff --git a/internal/write/handover/provenance.go b/internal/write/handover/provenance.go new file mode 100644 index 000000000..c54ad01d5 --- /dev/null +++ b/internal/write/handover/provenance.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover" + errHandover "github.com/ActiveMemory/ctx/internal/err/handover" + "github.com/ActiveMemory/ctx/internal/git_meta" +) + +// resolveProvenance picks the SHA / branch pair for a new +// handover. When override is non-empty, it is used verbatim +// for SHA; branch comes from git or "detached" when git is +// unavailable. +// +// Parameters: +// - projectRoot: absolute path to the project root. +// - override: optional explicit commit SHA (from --commit). +// +// Returns: +// - string: short SHA. +// - string: branch name. +// - error: non-nil when ResolveHead fails and override is +// empty. +func resolveProvenance(projectRoot, override string) (string, string, error) { + if override != "" { + ref, headErr := gitmeta.ResolveHead(projectRoot) + if headErr != nil { + return override, cfgHandover.BranchDetached, nil + } + return override, ref.Branch, nil + } + ref, headErr := gitmeta.ResolveHead(projectRoot) + if headErr != nil { + return "", "", errHandover.ResolveHead(headErr) + } + return ref.SHA, ref.Branch, nil +} diff --git a/internal/write/handover/testmain_test.go b/internal/write/handover/testmain_test.go new file mode 100644 index 000000000..adf559944 --- /dev/null +++ b/internal/write/handover/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/handover/types.go b/internal/write/handover/types.go new file mode 100644 index 000000000..a1f032d1d --- /dev/null +++ b/internal/write/handover/types.go @@ -0,0 +1,71 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "time" + + "github.com/ActiveMemory/ctx/internal/entity" +) + +// Entry is the caller-supplied content for a new handover. SHA +// and Branch are resolved automatically when empty. +type Entry struct { + // Title is the short slug used to compose the filename. + Title string + // Summary records what happened this session in past tense. + // Required and validated non-placeholder at the CLI layer. + Summary string + // Next records what the next agent should do FIRST. Future + // tense, specific. Required and validated non-placeholder + // at the CLI layer. + Next string + // Highlights records notable artifacts produced this + // session. Optional. + Highlights string + // OpenQuestions lists things that remain undecided. + // Optional. + OpenQuestions string + // CommitOverride forces a specific commit SHA into the + // Provenance line, bypassing gitmeta.ResolveHead. Used by + // the --commit flag for CI replay. + CommitOverride string + // NoFold skips closeout consumption (mid-session + // checkpoint). Defaults to false; default is "fold". + NoFold bool +} + +// Frontmatter holds the four required frontmatter fields of a +// handover file. +type Frontmatter struct { + SHA string `yaml:"sha"` + Branch string `yaml:"branch"` + GeneratedAt time.Time `yaml:"generated-at"` + Title string `yaml:"title"` +} + +// File pairs a handover's on-disk path with parsed frontmatter +// and the raw body bytes. +type File struct { + Path string + Frontmatter Frontmatter + Body string +} + +// Result reports what Write actually did. +type Result struct { + // File is the newly-written handover. + File File + // FoldedCloseouts lists closeouts folded into the handover + // and archived. Empty when NoFold is true or no closeouts + // were postdated. + FoldedCloseouts []entity.CloseoutFile + // MalformedCloseouts lists paths of closeout files that + // failed to parse during fold. Non-fatal; surfaced for the + // doctor advisory. + MalformedCloseouts []string +} diff --git a/internal/write/handover/write.go b/internal/write/handover/write.go new file mode 100644 index 000000000..9ade204d7 --- /dev/null +++ b/internal/write/handover/write.go @@ -0,0 +1,111 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package handover + +import ( + "path/filepath" + "strings" + "time" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + "github.com/ActiveMemory/ctx/internal/entity" + errHandover "github.com/ActiveMemory/ctx/internal/err/handover" + "github.com/ActiveMemory/ctx/internal/io" + "github.com/ActiveMemory/ctx/internal/write/closeout" +) + +// Write writes a new handover file under handoversDir. When +// entry.NoFold is false, it also reads the closeouts under +// closeoutsDir, finds those postdating the latest handover, +// folds their summaries into the new handover's body, and +// archives them under archiveDir. +// +// Parameters: +// - handoversDir: absolute path to .context/handovers/. +// - closeoutsDir: absolute path to .context/ingest/closeouts/. +// - archiveDir: absolute path to .context/archive/closeouts/. +// - projectRoot: absolute path to the project root; passed to +// [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead]. +// - entry: caller-supplied content + flags. +// +// Returns: +// - Result: written file + folded + malformed metadata. +// - error: non-nil on git-resolve / I/O / archive failure. +func Write( + handoversDir, closeoutsDir, archiveDir, projectRoot string, + entry Entry, +) (Result, error) { + if strings.TrimSpace(entry.Title) == "" { + return Result{}, errHandover.ErrTitleRequired + } + if strings.TrimSpace(entry.Summary) == "" { + return Result{}, errHandover.ErrSummaryRequired + } + if strings.TrimSpace(entry.Next) == "" { + return Result{}, errHandover.ErrNextRequired + } + + sha, branch, provErr := resolveProvenance(projectRoot, entry.CommitOverride) + if provErr != nil { + return Result{}, provErr + } + + now := time.Now().UTC().Truncate(time.Second) + fm := Frontmatter{ + SHA: sha, + Branch: branch, + GeneratedAt: now, + Title: entry.Title, + } + + var folded []entity.CloseoutFile + var malformed []string + if !entry.NoFold { + latestAt, _, latestErr := Latest(handoversDir) + if latestErr != nil { + return Result{}, errHandover.Latest(latestErr) + } + all, bad, listErr := closeout.List(closeoutsDir) + if listErr != nil { + return Result{}, errHandover.ListCloseouts(listErr) + } + folded = closeout.PostdatedBy(all, latestAt) + malformed = bad + } + + body := renderBody(entry, folded) + rendered, composeErr := composeMarkdown(fm, body) + if composeErr != nil { + return Result{}, composeErr + } + + if mkErr := io.SafeMkdirAll(handoversDir, cfgFs.PermExec); mkErr != nil { + return Result{}, errHandover.MkdirHandovers(mkErr) + } + name := buildFilename(now, entry.Title) + path := filepath.Join(handoversDir, name) + writeErr := io.SafeWriteFile(path, []byte(rendered), cfgFs.PermSecret) + if writeErr != nil { + return Result{}, errHandover.WriteFailed(writeErr) + } + + if !entry.NoFold && len(folded) > 0 { + if archErr := closeout.Archive(archiveDir, folded); archErr != nil { + return Result{}, errHandover.ArchiveFoldedCloseouts(archErr) + } + } + + return Result{ + File: File{ + Path: path, + Frontmatter: fm, + Body: body, + }, + FoldedCloseouts: folded, + MalformedCloseouts: malformed, + }, nil +} diff --git a/internal/write/initialize/info.go b/internal/write/initialize/info.go index 1a6d162a7..471ac4cfe 100644 --- a/internal/write/initialize/info.go +++ b/internal/write/initialize/info.go @@ -19,7 +19,7 @@ import ( // InfoResetPrompt prints the reset confirmation prompt with an // enumeration of the populated essential files that will be // overwritten. The prompt is intentionally explicit about the -// blast radius — the silent "Overwrite existing context? [y/N]" +// blast radius. The silent "Overwrite existing context? [y/N]" // prompt destroyed thousands of lines of curated content in // the 2026-04-25 incident. // diff --git a/internal/write/kb/contradiction/append.go b/internal/write/kb/contradiction/append.go new file mode 100644 index 000000000..accc47a67 --- /dev/null +++ b/internal/write/kb/contradiction/append.go @@ -0,0 +1,40 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +import ( + cfgKbC "github.com/ActiveMemory/ctx/internal/config/kb/contradiction" + "github.com/ActiveMemory/ctx/internal/entity" + errKbC "github.com/ActiveMemory/ctx/internal/err/kb/contradiction" + "github.com/ActiveMemory/ctx/internal/write/kb/row" +) + +// Append writes one row to the contradictions artifact at +// path, allocating the next monotonic `C-###` ID. When the +// file does not exist, it is created with the schema header +// and the first row is assigned `C-001`. The write opens the +// file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to `.context/kb/contradictions.md`. +// - r: row content; ID is filled in by Append. +// +// Returns: +// - string: the allocated `C-###` ID. +// - error: wrapped I/O / parse failures. +func Append(path string, r Row) (string, error) { + return row.Append(path, entity.KBRowHooks{ + Header: cfgKbC.TableHeader, + NextID: nextID, + Render: func(id string) string { return renderRow(id, r) }, + ErrMkdir: errKbC.MkdirDir, + ErrRead: errKbC.ReadFile, + ErrOpen: errKbC.OpenFile, + ErrWrite: errKbC.WriteRow, + }) +} diff --git a/internal/write/kb/contradiction/doc.go b/internal/write/kb/contradiction/doc.go new file mode 100644 index 000000000..5c921bc3f --- /dev/null +++ b/internal/write/kb/contradiction/doc.go @@ -0,0 +1,31 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package contradiction appends rows to the contradictions +// artifact at `.context/kb/contradictions.md` for the ctx +// knowledge-base editorial pipeline (Phase KB). +// +// Each row records two or more `EV-###` rows that disagree, +// the demotion applied under the demotion policy, and a +// resolution status. Resolved contradictions stay in the file +// as audit trail. +// +// IDs are zero-padded `C-###` allocated monotonically from the +// existing file's high-water mark. The writer scans the +// artifact for the highest existing `C-NNN`, increments, and +// formats; when the file does not exist the first row gets +// `C-001`. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/contradictions.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.Contradictions]. +package contradiction diff --git a/internal/write/kb/contradiction/next_id.go b/internal/write/kb/contradiction/next_id.go new file mode 100644 index 000000000..0dc731645 --- /dev/null +++ b/internal/write/kb/contradiction/next_id.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +import ( + "fmt" + "strconv" + + cfgKbC "github.com/ActiveMemory/ctx/internal/config/kb/contradiction" + "github.com/ActiveMemory/ctx/internal/config/regex" + errKbC "github.com/ActiveMemory/ctx/internal/err/kb/contradiction" +) + +// nextID scans existing for `C-###` tokens and returns the +// next formatted ID. Empty / no-matches input yields `C-001`. +// +// Parameters: +// - existing: prior file contents (may be nil). +// +// Returns: +// - string: the next zero-padded ID. +// - error: wrapped [errKbC.ParseCNumber] on overflow. +func nextID(existing []byte) (string, error) { + high := 0 + for _, m := range regex.KBContradictionID.FindAllSubmatch( + existing, -1, + ) { + digits := string(m[1]) + n, parseErr := strconv.Atoi(digits) + if parseErr != nil { + return "", errKbC.ParseCNumber(digits, parseErr) + } + if n > high { + high = n + } + } + return fmt.Sprintf(cfgKbC.IDFormat, cfgKbC.IDPrefix, high+1), nil +} diff --git a/internal/write/kb/contradiction/render.go b/internal/write/kb/contradiction/render.go new file mode 100644 index 000000000..5790e8849 --- /dev/null +++ b/internal/write/kb/contradiction/render.go @@ -0,0 +1,59 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - id: the allocated `C-###` ID. +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(id string, row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(id)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.EVRefs, token.CommaSpace), + )) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Summary)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.DemotionApplied)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Status)) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/contradiction/testmain_test.go b/internal/write/kb/contradiction/testmain_test.go new file mode 100644 index 000000000..e26266d89 --- /dev/null +++ b/internal/write/kb/contradiction/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/contradiction/types.go b/internal/write/kb/contradiction/types.go new file mode 100644 index 000000000..4a8a61963 --- /dev/null +++ b/internal/write/kb/contradiction/types.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction + +// Row is one contradictions entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/contradictions.md`, +// rendered as a single markdown table row. The ID is allocated +// by [Append]; callers do not supply it. +type Row struct { + // EVRefs is the list of `EV-###` IDs that disagree. The + // schema requires at least two; the writer does not enforce + // that; the caller does. + EVRefs []string + // Summary is a one-line statement of what the rows + // disagree about. + Summary string + // DemotionApplied is the EV id that got demoted plus its + // new band, e.g. "EV-031 -> low". + DemotionApplied string + // Status is one of "open", "resolved". + Status string +} diff --git a/internal/write/kb/contradiction/writer_test.go b/internal/write/kb/contradiction/writer_test.go new file mode 100644 index 000000000..6f3d1fff3 --- /dev/null +++ b/internal/write/kb/contradiction/writer_test.go @@ -0,0 +1,98 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package contradiction_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/contradiction" +) + +func TestAppend_FirstRowGetsC001(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Contradictions) + + id, err := contradiction.Append(path, contradiction.Row{ + EVRefs: []string{"EV-042", "EV-051"}, + Summary: "widget scope disagreement", + DemotionApplied: "EV-051 -> low", + Status: "resolved", + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "C-001" { + t.Errorf("first id: want C-001; got %q", id) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if !strings.Contains(text, "| ID | Evidence |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "C-001") { + t.Errorf("ID missing: %q", text) + } + if !strings.Contains(text, "EV-042, EV-051") { + t.Errorf("evidence joined missing: %q", text) + } +} + +func TestAppend_AllocatesNextIDFromHighWater(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Contradictions) + + // Seed file with gaps: C-001, C-005, next must be C-006. + seed := "| ID | Evidence | Summary | Demotion applied | Status |\n" + + "|----|----------|---------|------------------|--------|\n" + + "| C-001 | EV-001, EV-002 | s | d | resolved |\n" + + "| C-005 | EV-003, EV-004 | s | d | open |\n" + if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { + t.Fatal(err) + } + + id, err := contradiction.Append(path, contradiction.Row{ + EVRefs: []string{"EV-009", "EV-010"}, Summary: "x", + DemotionApplied: "y", Status: "open", + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "C-006" { + t.Errorf("next id: want C-006; got %q", id) + } +} + +func TestAppend_SequentialIDsInSameProcess(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Contradictions) + + var ids []string + for i := 0; i < 3; i++ { + id, err := contradiction.Append(path, contradiction.Row{ + EVRefs: []string{"EV-001", "EV-002"}, Summary: "x", + DemotionApplied: "y", Status: "open", + }) + if err != nil { + t.Fatalf("Append %d: %v", i, err) + } + ids = append(ids, id) + } + want := []string{"C-001", "C-002", "C-003"} + for i := range want { + if ids[i] != want[i] { + t.Errorf("id[%d]: want %s; got %s", i, want[i], ids[i]) + } + } +} diff --git a/internal/write/kb/decision/append.go b/internal/write/kb/decision/append.go new file mode 100644 index 000000000..1e365e449 --- /dev/null +++ b/internal/write/kb/decision/append.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +import ( + cfgKbDD "github.com/ActiveMemory/ctx/internal/config/kb/decision" + "github.com/ActiveMemory/ctx/internal/entity" + errKbDD "github.com/ActiveMemory/ctx/internal/err/kb/decision" + "github.com/ActiveMemory/ctx/internal/write/kb/row" +) + +// Append writes one row to the domain-decisions artifact at +// path, allocating the next monotonic `DD-###` ID. When the +// file does not exist, it is created with the schema header +// and the first row is assigned `DD-001`. The write opens the +// file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to +// `.context/kb/domain-decisions.md`. +// - r: row content; ID is filled in by Append. +// +// Returns: +// - string: the allocated `DD-###` ID. +// - error: wrapped I/O / parse failures. +func Append(path string, r Row) (string, error) { + return row.Append(path, entity.KBRowHooks{ + Header: cfgKbDD.TableHeader, + NextID: nextID, + Render: func(id string) string { return renderRow(id, r) }, + ErrMkdir: errKbDD.MkdirDir, + ErrRead: errKbDD.ReadFile, + ErrOpen: errKbDD.OpenFile, + ErrWrite: errKbDD.WriteRow, + }) +} diff --git a/internal/write/kb/decision/doc.go b/internal/write/kb/decision/doc.go new file mode 100644 index 000000000..3e7b4eef0 --- /dev/null +++ b/internal/write/kb/decision/doc.go @@ -0,0 +1,36 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package decision appends rows to the kb-scoped +// domain-decisions artifact at +// `.context/kb/domain-decisions.md` for the ctx knowledge-base +// editorial pipeline (Phase KB). +// +// This file is DISTINCT from project-level +// `.context/DECISIONS.md`. The two files have different +// schemas, different write authority, and different lifecycles. +// `domain-decisions.md` records kb-scoped positions on the +// subject matter under study, cites `EV-###` rows (not commit +// SHAs), and is written by `/ctx-kb-ingest`. +// +// IDs are zero-padded `DD-###` allocated monotonically from the +// existing file's high-water mark. (The brief drafted these as +// `D-###`; the canonical schema at +// `internal/assets/kb/templates/ingest/schemas/domain-decisions.md` +// pins the prefix to `DD-` to keep the namespace distinct from +// any future project-side `D-###` series, and this writer +// follows the schema.) When the file does not exist the first +// row gets `DD-001`. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.DomainDecisions]. +package decision diff --git a/internal/write/kb/decision/next_id.go b/internal/write/kb/decision/next_id.go new file mode 100644 index 000000000..6d98dd862 --- /dev/null +++ b/internal/write/kb/decision/next_id.go @@ -0,0 +1,44 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +import ( + "fmt" + "strconv" + + cfgKbDD "github.com/ActiveMemory/ctx/internal/config/kb/decision" + "github.com/ActiveMemory/ctx/internal/config/regex" + errKbDD "github.com/ActiveMemory/ctx/internal/err/kb/decision" +) + +// nextID scans existing for `DD-###` tokens and returns the +// next formatted ID. Empty / no-matches input yields `DD-001`. +// +// Parameters: +// - existing: prior file contents (may be nil). +// +// Returns: +// - string: the next zero-padded ID. +// - error: wrapped [errKbDD.ParseDDNumber] on overflow. +func nextID(existing []byte) (string, error) { + high := 0 + for _, m := range regex.KBDecisionID.FindAllSubmatch( + existing, -1, + ) { + digits := string(m[1]) + n, parseErr := strconv.Atoi(digits) + if parseErr != nil { + return "", errKbDD.ParseDDNumber(digits, parseErr) + } + if n > high { + high = n + } + } + return fmt.Sprintf( + cfgKbDD.IDFormat, cfgKbDD.IDPrefix, high+1, + ), nil +} diff --git a/internal/write/kb/decision/render.go b/internal/write/kb/decision/render.go new file mode 100644 index 000000000..49acc8f5b --- /dev/null +++ b/internal/write/kb/decision/render.go @@ -0,0 +1,63 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - id: the allocated `DD-###` ID. +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(id string, row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(id)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Date)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Context)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Decision)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Rationale)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Consequence)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.SupportingEV, token.CommaSpace), + )) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/decision/testmain_test.go b/internal/write/kb/decision/testmain_test.go new file mode 100644 index 000000000..11cb8e5a8 --- /dev/null +++ b/internal/write/kb/decision/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/decision/types.go b/internal/write/kb/decision/types.go new file mode 100644 index 000000000..a0f1e0974 --- /dev/null +++ b/internal/write/kb/decision/types.go @@ -0,0 +1,31 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision + +// Row is one domain-decisions entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/domain-decisions.md`, +// rendered as a single markdown table row. The ID is allocated +// by [Append]; callers do not supply it. +type Row struct { + // Date is the ISO date the decision was recorded in the kb. + Date string + // Context describes what in the domain prompted the + // decision; observable facts only. + Context string + // Decision is the position taken, in one sentence. + Decision string + // Rationale records why this position over the + // alternatives that were on the table. + Rationale string + // Consequence records what now changes for topic pages, + // glossary, or downstream claims. + Consequence string + // SupportingEV is the list of `EV-###` refs that ground + // the decision. The schema requires at least one; the + // writer does not enforce that; the caller does. + SupportingEV []string +} diff --git a/internal/write/kb/decision/writer_test.go b/internal/write/kb/decision/writer_test.go new file mode 100644 index 000000000..93bea17e1 --- /dev/null +++ b/internal/write/kb/decision/writer_test.go @@ -0,0 +1,73 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package decision_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/decision" +) + +func TestAppend_FirstRowGetsDD001(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.DomainDecisions) + + id, err := decision.Append(path, decision.Row{ + Date: "2026-05-16", + Context: "Two sources disagreed on bundle opacity.", + Decision: "Treat widget bundles as opaque to the consumer.", + Rationale: "Opacity preserves producer's freedom to evolve internals.", + Consequence: "widget-composition rewritten; glossary widget gains opacity xref.", + SupportingEV: []string{"EV-042", "EV-043"}, + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "DD-001" { + t.Errorf("first id: want DD-001; got %q", id) + } + + got, _ := os.ReadFile(path) + text := string(got) + if !strings.Contains(text, "| ID | Date |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "DD-001") { + t.Errorf("ID missing: %q", text) + } + if !strings.Contains(text, "EV-042, EV-043") { + t.Errorf("EV refs joined missing: %q", text) + } +} + +func TestAppend_AllocatesNextIDFromHighWater(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.DomainDecisions) + + seed := "| ID | Date | Context | Decision | Rationale | Consequence | Supporting EV |\n" + + "|----|------|---------|----------|-----------|-------------|---------------|\n" + + "| DD-007 | 2026-04-01 | c | d | r | x | EV-001 |\n" + if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { + t.Fatal(err) + } + + id, err := decision.Append(path, decision.Row{ + Date: "2026-05-16", Context: "c", Decision: "d", + Rationale: "r", Consequence: "x", + SupportingEV: []string{"EV-002"}, + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "DD-008" { + t.Errorf("next id: want DD-008; got %q", id) + } +} diff --git a/internal/write/kb/evidence/append.go b/internal/write/kb/evidence/append.go new file mode 100644 index 000000000..e841c47e0 --- /dev/null +++ b/internal/write/kb/evidence/append.go @@ -0,0 +1,89 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import ( + "errors" + "os" + "path/filepath" + "strings" + "time" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKbEvidence "github.com/ActiveMemory/ctx/internal/config/kb/evidence" + "github.com/ActiveMemory/ctx/internal/config/token" + errKbEvidence "github.com/ActiveMemory/ctx/internal/err/kb/evidence" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append writes one EV row to the evidence-index file at path. +// When row.ID is empty, the file's high-water mark is consulted +// and the next sequential ID is allocated. +// +// Parameters: +// - path: full path to `.context/kb/evidence-index.md`. +// - row: row to append. ID is populated on return when +// allocated. +// +// Returns: +// - Row: the appended row with ID populated. +// - error: [errKbEvidence.ErrDuplicateID], +// [errKbEvidence.ErrInvalidBand], or wrapped I/O errors. +func Append(path string, row Row) (Row, error) { + if bandErr := validateBand(row.Confidence); bandErr != nil { + return Row{}, bandErr + } + + existing, readErr := ctxIo.SafeReadUserFile(path) + if readErr != nil && !errors.Is(readErr, os.ErrNotExist) { + return Row{}, errKbEvidence.ReadIndex(readErr) + } + + if row.ID == "" { + next, scanErr := maxIDFrom(string(existing)) + if scanErr != nil { + return Row{}, scanErr + } + row.ID = formatID(next + 1) + } else if alreadyExists(string(existing), row.ID) { + return Row{}, errKbEvidence.DuplicateID(row.ID) + } + if row.Extracted.IsZero() { + row.Extracted = time.Now().UTC().Truncate(time.Second) + } + + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return Row{}, errKbEvidence.MkdirDir(mkErr) + } + + needsHeader := len(existing) == 0 + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return Row{}, errKbEvidence.OpenIndex(openErr) + } + defer func() { _ = f.Close() }() + + var sb strings.Builder + if needsHeader { + sb.WriteString(cfgKbEvidence.TitleHeading) + sb.WriteString(token.DoubleNewline) + sb.WriteString(cfgKbEvidence.LeadParagraph1) + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgKbEvidence.LeadParagraph2) + sb.WriteString(token.DoubleNewline) + sb.WriteString(cfgKbEvidence.TableHeader) + sb.WriteString(token.NewlineLF) + } + sb.WriteString(renderRow(row)) + sb.WriteString(token.NewlineLF) + if _, writeErr := f.WriteString(sb.String()); writeErr != nil { + return Row{}, errKbEvidence.WriteRow(writeErr) + } + return row, nil +} diff --git a/internal/write/kb/evidence/doc.go b/internal/write/kb/evidence/doc.go new file mode 100644 index 000000000..ef87c4518 --- /dev/null +++ b/internal/write/kb/evidence/doc.go @@ -0,0 +1,39 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package evidence appends EV-### rows to +// `.context/kb/evidence-index.md` for the ctx knowledge-base +// editorial pipeline (Phase KB). +// +// The package enforces two load-bearing invariants from the +// spec and from `.context/ingest/KB-RULES.md`: +// +// 1. **No renumber, no delete.** Once an EV-### is minted, its +// ID is stable. Callers may demote an existing row's +// confidence band in-place but the ID and claim text stay. +// Renumbering breaks every existing citation on every +// topic page. +// +// 2. **Sequential ID allocation.** [Append] reads the +// existing file, finds the highest EV-### number, and +// increments. IDs are zero-padded to three digits +// (`EV-012`, not `EV-12`). +// +// The `evidence-only` tag (an additive entry in the Tags +// column) signals a row was minted in evidence-only mode and +// must be re-read against its source before a topic-page pass +// promotes it into prose. See +// `internal/assets/kb/templates/ingest/schemas/evidence-index.md` +// for the schema template. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// the EV prefix + digit-width constants and the +// `evidence-only` tag literal. +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// resolves the evidence-index file path. +package evidence diff --git a/internal/write/kb/evidence/evidence_test.go b/internal/write/kb/evidence/evidence_test.go new file mode 100644 index 000000000..35f9f1efc --- /dev/null +++ b/internal/write/kb/evidence/evidence_test.go @@ -0,0 +1,152 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence_test + +import ( + "errors" + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbEvidence "github.com/ActiveMemory/ctx/internal/err/kb/evidence" + "github.com/ActiveMemory/ctx/internal/write/kb/evidence" +) + +func TestAppend_AllocatesNextSequentialID(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + row, err := evidence.Append(path, evidence.Row{ + Claim: "first claim", + SourceID: "SRC-A", + Locator: "L:1", + Confidence: cfgKB.ConfidenceMedium, + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if row.ID != "EV-001" { + t.Errorf("first ID: want EV-001; got %s", row.ID) + } + + row2, err := evidence.Append(path, evidence.Row{ + Claim: "second claim", + SourceID: "SRC-B", + Locator: "ts=00:01:23", + Confidence: cfgKB.ConfidenceLow, + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if row2.ID != "EV-002" { + t.Errorf("second ID: want EV-002; got %s", row2.ID) + } +} + +func TestAppend_RespectsExplicitID(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + if _, err := evidence.Append(path, evidence.Row{ + Claim: "first", + Confidence: cfgKB.ConfidenceHigh, + }); err != nil { + t.Fatal(err) + } + // Append an explicit ID well above the auto-incremented one. + got, err := evidence.Append(path, evidence.Row{ + ID: "EV-099", + Claim: "explicit", + Confidence: cfgKB.ConfidenceHigh, + }) + if err != nil { + t.Fatalf("Append explicit: %v", err) + } + if got.ID != "EV-099" { + t.Errorf("explicit ID preserved: got %s", got.ID) + } + // Next auto-allocated ID should be EV-100 (after the max). + next, err := evidence.Append(path, evidence.Row{ + Claim: "after gap", + Confidence: cfgKB.ConfidenceMedium, + }) + if err != nil { + t.Fatal(err) + } + if next.ID != "EV-100" { + t.Errorf("max+1 after gap: want EV-100; got %s", next.ID) + } +} + +func TestAppend_RejectsDuplicateID(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + if _, err := evidence.Append(path, evidence.Row{ + Claim: "first", + Confidence: cfgKB.ConfidenceHigh, + }); err != nil { + t.Fatal(err) + } + _, err := evidence.Append(path, evidence.Row{ + ID: "EV-001", + Claim: "dup", + Confidence: cfgKB.ConfidenceHigh, + }) + if !errors.Is(err, errKbEvidence.ErrDuplicateID) { + t.Fatalf("want ErrDuplicateID; got %v", err) + } +} + +func TestAppend_RejectsInvalidBand(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + _, err := evidence.Append(path, evidence.Row{ + Claim: "x", + Confidence: "unsure", + }) + if !errors.Is(err, errKbEvidence.ErrInvalidBand) { + t.Fatalf("want ErrInvalidBand; got %v", err) + } +} + +func TestAppend_HeaderWrittenOnFirstAppend(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + if _, err := evidence.Append(path, evidence.Row{ + Claim: "x", + Confidence: cfgKB.ConfidenceLow, + }); err != nil { + t.Fatal(err) + } + raw, err := os.ReadFile(path) + if err != nil { + t.Fatal(err) + } + if !strings.Contains(string(raw), "# Evidence index") { + t.Error("header missing") + } + if !strings.Contains(string(raw), "| ID |") { + t.Error("table header missing") + } + if !strings.Contains(string(raw), "EV-001") { + t.Error("first row missing") + } +} + +func TestAppend_EvidenceOnlyTagPreservedVerbatim(t *testing.T) { + path := filepath.Join(t.TempDir(), "evidence-index.md") + row, err := evidence.Append(path, evidence.Row{ + Claim: "from evidence-only mode", + Confidence: cfgKB.ConfidenceLow, + Tags: []string{"cursor", "hooks", cfgKB.EvidenceOnlyTag}, + }) + if err != nil { + t.Fatal(err) + } + raw, _ := os.ReadFile(path) + if !strings.Contains(string(raw), cfgKB.EvidenceOnlyTag) { + t.Errorf("evidence-only tag missing from file") + } + if len(row.Tags) != 3 { + t.Errorf("tags lost in round-trip: %v", row.Tags) + } +} diff --git a/internal/write/kb/evidence/next_id.go b/internal/write/kb/evidence/next_id.go new file mode 100644 index 000000000..e46549c96 --- /dev/null +++ b/internal/write/kb/evidence/next_id.go @@ -0,0 +1,72 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import ( + "fmt" + "strconv" + "strings" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + cfgKbEvidence "github.com/ActiveMemory/ctx/internal/config/kb/evidence" + "github.com/ActiveMemory/ctx/internal/config/regex" + errKbEvidence "github.com/ActiveMemory/ctx/internal/err/kb/evidence" +) + +// maxIDFrom scans raw for `EV-NNN` tokens and returns the +// highest numeric value found. Empty / no-match input returns +// 0. +// +// Parameters: +// - raw: file contents. +// +// Returns: +// - int: highest EV number, or 0. +// - error: wrapped [errKbEvidence.ParseEVNumber] on a +// malformed digit string. +func maxIDFrom(raw string) (int, error) { + matches := regex.KBEvidenceID.FindAllStringSubmatch(raw, -1) + high := 0 + for _, m := range matches { + n, parseErr := strconv.Atoi(m[1]) + if parseErr != nil { + return 0, errKbEvidence.ParseEVNumber(m[1], parseErr) + } + if n > high { + high = n + } + } + return high, nil +} + +// alreadyExists reports whether the given EV-### identifier is +// already present anywhere in the raw file content. +// +// Parameters: +// - raw: file contents. +// - id: EV-### identifier to check. +// +// Returns: +// - bool: true when id is already minted. +func alreadyExists(raw, id string) bool { + if id == "" { + return false + } + return strings.Contains(raw, id) +} + +// formatID renders an integer as a zero-padded EV-NNN string. +// +// Parameters: +// - n: positive integer. +// +// Returns: +// - string: `EV-NNN` (3-digit zero-padded for n ≤ 999). +func formatID(n int) string { + return fmt.Sprintf(cfgKbEvidence.IDFormat, + cfgKB.EVIDPrefix, cfgKB.EVIDDigits, n) +} diff --git a/internal/write/kb/evidence/render.go b/internal/write/kb/evidence/render.go new file mode 100644 index 000000000..9a31654cd --- /dev/null +++ b/internal/write/kb/evidence/render.go @@ -0,0 +1,49 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import ( + "fmt" + "strings" + "time" + + cfgKbEvidence "github.com/ActiveMemory/ctx/internal/config/kb/evidence" + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow turns a Row into one markdown table line (no +// trailing newline). +// +// Parameters: +// - r: row to render. +// +// Returns: +// - string: pipe-delimited table row. +func renderRow(r Row) string { + tags := strings.Join(r.Tags, token.CommaSpace) + extracted := r.Extracted.UTC().Format(time.DateOnly) + return fmt.Sprintf( + cfgKbEvidence.RowFormat, + r.ID, escapeCell(r.Claim), r.SourceID, r.Locator, + r.SHA, r.Confidence, tags, r.Occurred, extracted, + ) +} + +// escapeCell escapes pipe characters that would otherwise +// break the markdown table grammar. +// +// Parameters: +// - s: free text. +// +// Returns: +// - string: s with `|` replaced by `\|`. +func escapeCell(s string) string { + return strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) +} diff --git a/internal/write/kb/evidence/testmain_test.go b/internal/write/kb/evidence/testmain_test.go new file mode 100644 index 000000000..83d6f4650 --- /dev/null +++ b/internal/write/kb/evidence/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/evidence/types.go b/internal/write/kb/evidence/types.go new file mode 100644 index 000000000..eeba377e9 --- /dev/null +++ b/internal/write/kb/evidence/types.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import "time" + +// Row is one EV-### evidence-index entry. Fields match the +// schema at +// `internal/assets/kb/templates/ingest/schemas/evidence-index.md`. +type Row struct { + // ID is the EV-### identifier. When empty on Append, the + // next sequential ID is allocated and populated on return. + ID string + // Claim is the assertion this row backs. + Claim string + // SourceID is the short-name from `source-map.md` that + // identifies the source. + SourceID string + // Locator pins the claim within the source: line range for + // files, timestamp for transcripts, anchor for URLs, + // symbol for code. + Locator string + // SHA is the short git SHA when the source is an in-repo + // file. Empty for out-of-repo citations. + SHA string + // Confidence is one of the cfgKB.Confidence* constants. + Confidence string + // Tags is the per-row tag list. The `evidence-only` tag + // marks rows minted in evidence-only mode passes (those + // rows are review-required before a topic-page pass cites + // them). + Tags []string + // Occurred is the date the cited statement was made (for + // transcripts / dated docs). Empty when not dated. + Occurred string + // Extracted is the timestamp at which this row was minted. + Extracted time.Time +} diff --git a/internal/write/kb/evidence/validate.go b/internal/write/kb/evidence/validate.go new file mode 100644 index 000000000..7944b62de --- /dev/null +++ b/internal/write/kb/evidence/validate.go @@ -0,0 +1,30 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package evidence + +import ( + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbEvidence "github.com/ActiveMemory/ctx/internal/err/kb/evidence" +) + +// validateBand rejects unknown confidence bands. +// +// Parameters: +// - band: confidence band string. +// +// Returns: +// - error: wrapped [errKbEvidence.ErrInvalidBand] or nil. +func validateBand(band string) error { + switch band { + case cfgKB.ConfidenceHigh, + cfgKB.ConfidenceMedium, + cfgKB.ConfidenceLow, + cfgKB.ConfidenceSpeculative: + return nil + } + return errKbEvidence.InvalidBand(band) +} diff --git a/internal/write/kb/glossary/append.go b/internal/write/kb/glossary/append.go new file mode 100644 index 000000000..d0ee7cc0b --- /dev/null +++ b/internal/write/kb/glossary/append.go @@ -0,0 +1,64 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary + +import ( + "os" + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKbGloss "github.com/ActiveMemory/ctx/internal/config/kb/glossary" + errKbGloss "github.com/ActiveMemory/ctx/internal/err/kb/glossary" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append writes one row to the glossary artifact at path. When +// the file does not exist, it is created with the schema header +// and then the row is appended. The write opens the file with +// O_CREATE|O_APPEND|O_WRONLY; idempotency at the call-site is +// the caller's responsibility. +// +// Parameters: +// - path: absolute path to `.context/kb/glossary.md`. +// - row: row content. +// +// Returns: +// - error: wrapped I/O failures. +func Append(path string, row Row) error { + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return errKbGloss.MkdirDir(mkErr) + } + needsHeader := false + if _, statErr := ctxIo.SafeStat(path); statErr != nil { + if !os.IsNotExist(statErr) { + return errKbGloss.ReadFile(statErr) + } + needsHeader = true + } + + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return errKbGloss.OpenFile(openErr) + } + defer func() { _ = f.Close() }() + + if needsHeader { + if _, writeErr := f.WriteString( + cfgKbGloss.TableHeader, + ); writeErr != nil { + return errKbGloss.WriteRow(writeErr) + } + } + if _, writeErr := f.WriteString( + renderRow(row), + ); writeErr != nil { + return errKbGloss.WriteRow(writeErr) + } + return nil +} diff --git a/internal/write/kb/glossary/doc.go b/internal/write/kb/glossary/doc.go new file mode 100644 index 000000000..be1b320ad --- /dev/null +++ b/internal/write/kb/glossary/doc.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package glossary appends rows to the kb-scoped glossary +// artifact at `.context/kb/glossary.md` for the ctx knowledge-base +// editorial pipeline (Phase KB). +// +// Each row is one canonical term: a definition, a confidence +// band, and at least one backing `EV-###` row from +// `evidence-index.md`. The writer is append-only; idempotency +// at the call-site is the caller's responsibility. This package +// just opens O_CREATE|O_APPEND|O_WRONLY and writes one row. +// +// When the artifact does not exist, [Append] initialises it with +// the schema's table header so subsequent appends are +// table-shaped. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/glossary.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// the artifact filename constant ([cfgKB.Glossary]). +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// resolves the artifact path via [KBArtifactFile]. +package glossary diff --git a/internal/write/kb/glossary/render.go b/internal/write/kb/glossary/render.go new file mode 100644 index 000000000..07238431b --- /dev/null +++ b/internal/write/kb/glossary/render.go @@ -0,0 +1,63 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. Pipe and newline characters in free-text fields +// are escaped to keep the table well-formed. +// +// Parameters: +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(row.Term)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Definition)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Confidence)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.EVRefs, token.CommaSpace), + )) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.RelatedTerms, token.CommaSpace), + )) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell: literal pipes are escaped, embedded +// newlines collapse to a single space, leading / trailing +// whitespace is stripped. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/glossary/testmain_test.go b/internal/write/kb/glossary/testmain_test.go new file mode 100644 index 000000000..9f01791ce --- /dev/null +++ b/internal/write/kb/glossary/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/glossary/types.go b/internal/write/kb/glossary/types.go new file mode 100644 index 000000000..1244a7e8b --- /dev/null +++ b/internal/write/kb/glossary/types.go @@ -0,0 +1,28 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary + +// Row is one glossary entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/glossary.md`, +// rendered as a single markdown table row. +type Row struct { + // Term is the canonical term; lowercase preferred. + Term string + // Definition is one declarative paragraph; no hedging beyond + // the confidence band. + Definition string + // Confidence is one of "high", "medium", "low", + // "speculative". + Confidence string + // EVRefs is the list of `EV-###` IDs that ground the + // definition. At least one is required by the schema; the + // writer does not enforce that; the caller does. + EVRefs []string + // RelatedTerms is the optional list of other glossary terms + // cross-referenced from this entry. + RelatedTerms []string +} diff --git a/internal/write/kb/glossary/writer_test.go b/internal/write/kb/glossary/writer_test.go new file mode 100644 index 000000000..645583bad --- /dev/null +++ b/internal/write/kb/glossary/writer_test.go @@ -0,0 +1,107 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package glossary_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/glossary" +) + +func TestAppend_CreatesHeaderOnFirstWrite(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Glossary) + + row := glossary.Row{ + Term: "widget", + Definition: "Smallest packaging boundary.", + Confidence: "high", + EVRefs: []string{"EV-042", "EV-043"}, + RelatedTerms: []string{"widget-contract"}, + } + if err := glossary.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if !strings.Contains(text, "| Term | Definition |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "widget") { + t.Errorf("term missing: %q", text) + } + if !strings.Contains(text, "EV-042, EV-043") { + t.Errorf("EV refs not joined: %q", text) + } +} + +func TestAppend_SecondCallDoesNotRewriteHeader(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Glossary) + + for i, term := range []string{"alpha", "beta"} { + row := glossary.Row{ + Term: term, Definition: "def", Confidence: "low", + EVRefs: []string{"EV-001"}, + } + if err := glossary.Append(path, row); err != nil { + t.Fatalf("Append %d: %v", i, err) + } + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if c := strings.Count(text, "| Term | Definition |"); c != 1 { + t.Errorf("header written %d times; want 1", c) + } + if !strings.Contains(text, "alpha") || !strings.Contains(text, "beta") { + t.Errorf("both rows missing: %q", text) + } +} + +func TestAppend_EscapesPipesAndNewlines(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Glossary) + + row := glossary.Row{ + Term: "tricky", + Definition: "has a | pipe\nand newline", + Confidence: "medium", + EVRefs: []string{"EV-099"}, + } + if err := glossary.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if strings.Contains(text, "has a | pipe") { + t.Errorf("unescaped pipe in cell: %q", text) + } + if !strings.Contains(text, `\|`) { + t.Errorf("expected escaped pipe: %q", text) + } + // The row should be one line (excluding header lines). + rows := strings.Split(strings.TrimRight(text, "\n"), "\n") + // header (2 lines) + 1 row = 3 lines + if len(rows) != 3 { + t.Errorf("expected 3 lines; got %d: %q", len(rows), text) + } +} diff --git a/internal/write/kb/question/append.go b/internal/write/kb/question/append.go new file mode 100644 index 000000000..408b03ab7 --- /dev/null +++ b/internal/write/kb/question/append.go @@ -0,0 +1,41 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +import ( + cfgKbQ "github.com/ActiveMemory/ctx/internal/config/kb/question" + "github.com/ActiveMemory/ctx/internal/entity" + errKbQ "github.com/ActiveMemory/ctx/internal/err/kb/question" + "github.com/ActiveMemory/ctx/internal/write/kb/row" +) + +// Append writes one row to the outstanding-questions artifact +// at path, allocating the next monotonic `Q-###` ID. When the +// file does not exist, it is created with the schema header +// and the first row is assigned `Q-001`. The write opens the +// file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to +// `.context/kb/outstanding-questions.md`. +// - r: row content; ID is filled in by Append. +// +// Returns: +// - string: the allocated `Q-###` ID. +// - error: wrapped I/O / parse failures. +func Append(path string, r Row) (string, error) { + return row.Append(path, entity.KBRowHooks{ + Header: cfgKbQ.TableHeader, + NextID: nextID, + Render: func(id string) string { return renderRow(id, r) }, + ErrMkdir: errKbQ.MkdirDir, + ErrRead: errKbQ.ReadFile, + ErrOpen: errKbQ.OpenFile, + ErrWrite: errKbQ.WriteRow, + }) +} diff --git a/internal/write/kb/question/doc.go b/internal/write/kb/question/doc.go new file mode 100644 index 000000000..76a44b5cb --- /dev/null +++ b/internal/write/kb/question/doc.go @@ -0,0 +1,31 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package question appends rows to the outstanding-questions +// artifact at `.context/kb/outstanding-questions.md` for the +// ctx knowledge-base editorial pipeline (Phase KB). +// +// Each row is an open gap the kb has not yet resolved. An open +// question blocks promotion of any claim that depends on it: +// a topic page with a `Q-###` in its `## Open questions` +// section cannot ship at `confidence: high`. +// +// IDs are zero-padded `Q-###` allocated monotonically from the +// existing file's high-water mark. The writer scans the +// artifact for the highest existing `Q-NNN`, increments, and +// formats; when the file does not exist the first row gets +// `Q-001`. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/outstanding-questions.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.OutstandingQuestions]. +package question diff --git a/internal/write/kb/question/next_id.go b/internal/write/kb/question/next_id.go new file mode 100644 index 000000000..0ce077947 --- /dev/null +++ b/internal/write/kb/question/next_id.go @@ -0,0 +1,42 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +import ( + "fmt" + "strconv" + + cfgKbQ "github.com/ActiveMemory/ctx/internal/config/kb/question" + "github.com/ActiveMemory/ctx/internal/config/regex" + errKbQ "github.com/ActiveMemory/ctx/internal/err/kb/question" +) + +// nextID scans existing for `Q-###` tokens and returns the +// next formatted ID. Empty / no-matches input yields `Q-001`. +// +// Parameters: +// - existing: prior file contents (may be nil). +// +// Returns: +// - string: the next zero-padded ID. +// - error: wrapped [errKbQ.ParseQNumber] on overflow. +func nextID(existing []byte) (string, error) { + high := 0 + for _, m := range regex.KBQuestionID.FindAllSubmatch( + existing, -1, + ) { + digits := string(m[1]) + n, parseErr := strconv.Atoi(digits) + if parseErr != nil { + return "", errKbQ.ParseQNumber(digits, parseErr) + } + if n > high { + high = n + } + } + return fmt.Sprintf(cfgKbQ.IDFormat, cfgKbQ.IDPrefix, high+1), nil +} diff --git a/internal/write/kb/question/render.go b/internal/write/kb/question/render.go new file mode 100644 index 000000000..693bd315f --- /dev/null +++ b/internal/write/kb/question/render.go @@ -0,0 +1,61 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - id: the allocated `Q-###` ID. +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(id string, row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(id)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Question)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.WhyItMatters)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.WhatEvidenceWouldResolve)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.OpenedAt)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.RelatedEV, token.CommaSpace), + )) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/question/testmain_test.go b/internal/write/kb/question/testmain_test.go new file mode 100644 index 000000000..04e7e9629 --- /dev/null +++ b/internal/write/kb/question/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/question/types.go b/internal/write/kb/question/types.go new file mode 100644 index 000000000..46c93cc07 --- /dev/null +++ b/internal/write/kb/question/types.go @@ -0,0 +1,29 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question + +// Row is one outstanding-questions entry. Fields match the +// schema at +// `internal/assets/kb/templates/ingest/schemas/outstanding-questions.md`, +// rendered as a single markdown table row. The ID is allocated +// by [Append]; callers do not supply it. +type Row struct { + // Question is the one-sentence open question, phrased as + // an interrogative. + Question string + // WhyItMatters explains why answering this changes a topic + // page or a confidence band. + WhyItMatters string + // WhatEvidenceWouldResolve is the shape of evidence that + // would close this entry. Required by the schema; opening a + // question without this field is a hard anti-pattern. + WhatEvidenceWouldResolve string + // OpenedAt is the ISO date the question was opened. + OpenedAt string + // RelatedEV is the optional list of `EV-###` refs. + RelatedEV []string +} diff --git a/internal/write/kb/question/writer_test.go b/internal/write/kb/question/writer_test.go new file mode 100644 index 000000000..c30a6c279 --- /dev/null +++ b/internal/write/kb/question/writer_test.go @@ -0,0 +1,72 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package question_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/question" +) + +func TestAppend_FirstRowGetsQ001(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.OutstandingQuestions) + + id, err := question.Append(path, question.Row{ + Question: "Does the widget folder permit nested skills?", + WhyItMatters: "Blocks widget-composition promotion to high.", + WhatEvidenceWouldResolve: "An upstream spec statement.", + OpenedAt: "2026-05-16", + RelatedEV: []string{"EV-042", "EV-043"}, + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "Q-001" { + t.Errorf("first id: want Q-001; got %q", id) + } + + got, _ := os.ReadFile(path) + text := string(got) + if !strings.Contains(text, "| ID | Question |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "Q-001") { + t.Errorf("ID missing: %q", text) + } + if !strings.Contains(text, "EV-042, EV-043") { + t.Errorf("related EV joined missing: %q", text) + } +} + +func TestAppend_AllocatesNextIDFromHighWater(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.OutstandingQuestions) + + seed := "| ID | Question | Why it matters | What evidence would resolve | Opened at | Related EV |\n" + + "|----|----------|----------------|-----------------------------|-----------|------------|\n" + + "| Q-003 | q | y | w | 2026-05-01 | |\n" + + "| Q-009 | q | y | w | 2026-05-10 | |\n" + if err := os.WriteFile(path, []byte(seed), 0o600); err != nil { + t.Fatal(err) + } + + id, err := question.Append(path, question.Row{ + Question: "q", WhyItMatters: "y", + WhatEvidenceWouldResolve: "w", OpenedAt: "2026-05-16", + }) + if err != nil { + t.Fatalf("Append: %v", err) + } + if id != "Q-010" { + t.Errorf("next id: want Q-010; got %q", id) + } +} diff --git a/internal/write/kb/relationship/append.go b/internal/write/kb/relationship/append.go new file mode 100644 index 000000000..9639f6f04 --- /dev/null +++ b/internal/write/kb/relationship/append.go @@ -0,0 +1,65 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship + +import ( + "os" + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKbRel "github.com/ActiveMemory/ctx/internal/config/kb/relationship" + errKbRel "github.com/ActiveMemory/ctx/internal/err/kb/relationship" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append writes one row to the relationship-map artifact at +// path. When the file does not exist, it is created with the +// schema header and then the row is appended. The write opens +// the file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to +// `.context/kb/relationship-map.md`. +// - row: row content. +// +// Returns: +// - error: wrapped I/O failures. +func Append(path string, row Row) error { + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return errKbRel.MkdirDir(mkErr) + } + needsHeader := false + if _, statErr := ctxIo.SafeStat(path); statErr != nil { + if !os.IsNotExist(statErr) { + return errKbRel.ReadFile(statErr) + } + needsHeader = true + } + + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return errKbRel.OpenFile(openErr) + } + defer func() { _ = f.Close() }() + + if needsHeader { + if _, writeErr := f.WriteString( + cfgKbRel.TableHeader, + ); writeErr != nil { + return errKbRel.WriteRow(writeErr) + } + } + if _, writeErr := f.WriteString( + renderRow(row), + ); writeErr != nil { + return errKbRel.WriteRow(writeErr) + } + return nil +} diff --git a/internal/write/kb/relationship/doc.go b/internal/write/kb/relationship/doc.go new file mode 100644 index 000000000..027755619 --- /dev/null +++ b/internal/write/kb/relationship/doc.go @@ -0,0 +1,26 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package relationship appends rows to the relationship-map +// artifact at `.context/kb/relationship-map.md` for the ctx +// knowledge-base editorial pipeline (Phase KB). +// +// The relationship map is the kb's edge list: nodes are topic +// slugs and `EV-###` IDs, edges are typed (depends-on, +// refines, contradicts, supersedes, relates-to). Edges key on +// slug + EV id, never on file path, so folder reorganisations +// do not invalidate the graph. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/relationship-map.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.RelationshipMap]. +package relationship diff --git a/internal/write/kb/relationship/render.go b/internal/write/kb/relationship/render.go new file mode 100644 index 000000000..36e2152e9 --- /dev/null +++ b/internal/write/kb/relationship/render.go @@ -0,0 +1,54 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(row.From)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.To)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Kind)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Summary)) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/relationship/testmain_test.go b/internal/write/kb/relationship/testmain_test.go new file mode 100644 index 000000000..fecc2c7aa --- /dev/null +++ b/internal/write/kb/relationship/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/relationship/types.go b/internal/write/kb/relationship/types.go new file mode 100644 index 000000000..5938d20e7 --- /dev/null +++ b/internal/write/kb/relationship/types.go @@ -0,0 +1,24 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship + +// Row is one relationship-map entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/relationship-map.md`, +// rendered as a single markdown table row. +type Row struct { + // From is the originating topic slug or `EV-###` ID. + From string + // To is the destination topic slug or `EV-###` ID. + To string + // Kind is one of "depends-on", "refines", "contradicts", + // "supersedes", "relates-to". Introducing a new kind + // requires a `domain-decisions.md` entry naming the + // rationale. + Kind string + // Summary is a one-line description of the relationship. + Summary string +} diff --git a/internal/write/kb/relationship/writer_test.go b/internal/write/kb/relationship/writer_test.go new file mode 100644 index 000000000..8fad10c7d --- /dev/null +++ b/internal/write/kb/relationship/writer_test.go @@ -0,0 +1,62 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package relationship_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/relationship" +) + +func TestAppend_CreatesHeaderOnFirstWrite(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.RelationshipMap) + + row := relationship.Row{ + From: "widgets", To: "EV-042", + Kind: "depends-on", + Summary: "Widget topic page leans on EV-042 for scope.", + } + if err := relationship.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if !strings.Contains(text, "| From | To | Kind | Summary |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "widgets") || !strings.Contains(text, "EV-042") { + t.Errorf("endpoints missing: %q", text) + } + if !strings.Contains(text, "depends-on") { + t.Errorf("kind missing: %q", text) + } +} + +func TestAppend_TwoRowsSingleHeader(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.RelationshipMap) + + for _, k := range []string{"depends-on", "refines"} { + if err := relationship.Append(path, relationship.Row{ + From: "a", To: "b", Kind: k, Summary: "x", + }); err != nil { + t.Fatalf("Append: %v", err) + } + } + got, _ := os.ReadFile(path) + if c := strings.Count(string(got), "| From | To | Kind | Summary |"); c != 1 { + t.Errorf("header count: want 1; got %d", c) + } +} diff --git a/internal/write/kb/row/append.go b/internal/write/kb/row/append.go new file mode 100644 index 000000000..2bf1106b6 --- /dev/null +++ b/internal/write/kb/row/append.go @@ -0,0 +1,64 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package row + +import ( + "os" + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + "github.com/ActiveMemory/ctx/internal/entity" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append performs the shared append flow for a monotonic-ID +// kb tabular artifact (contradictions, domain-decisions, +// outstanding-questions). Returns the allocated ID. +// +// Parameters: +// - path: absolute path to the artifact file. +// - h: per-artifact hooks; see [entity.KBRowHooks]. +// +// Returns: +// - string: the allocated ID for this row. +// - error: wrapped via the matching Err* constructor in h. +func Append(path string, h entity.KBRowHooks) (string, error) { + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return "", h.ErrMkdir(mkErr) + } + + needsHeader := false + existing, readErr := ctxIo.SafeReadUserFile(path) + if readErr != nil { + if !os.IsNotExist(readErr) { + return "", h.ErrRead(readErr) + } + needsHeader = true + } + id, idErr := h.NextID(existing) + if idErr != nil { + return "", idErr + } + + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return "", h.ErrOpen(openErr) + } + defer func() { _ = f.Close() }() + + if needsHeader { + if _, wErr := f.WriteString(h.Header); wErr != nil { + return "", h.ErrWrite(wErr) + } + } + if _, wErr := f.WriteString(h.Render(id)); wErr != nil { + return "", h.ErrWrite(wErr) + } + return id, nil +} diff --git a/internal/write/kb/row/doc.go b/internal/write/kb/row/doc.go new file mode 100644 index 000000000..0026656c6 --- /dev/null +++ b/internal/write/kb/row/doc.go @@ -0,0 +1,27 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package row supplies the shared append-a-monotonic-ID-row +// primitive used by every kb tabular artifact whose rows carry +// a `<PREFIX>-###` ID: contradictions, domain-decisions, and +// outstanding-questions. Evidence and source-coverage have +// distinct enough surfaces to stay in their own packages. +// +// The primitive [Append] handles the invariants that are +// identical across these artifacts: +// +// - Ensure the parent directory exists. +// - Read the existing file; create-on-not-exist. +// - Delegate next-ID allocation to a caller-supplied [Hooks] +// closure that knows how to scan its own table. +// - Open the file O_CREATE|O_APPEND|O_WRONLY. +// - On first write, emit the table header. +// - Render and append the row via a caller-supplied closure. +// +// Each caller package owns its own Row struct, header +// constant, ID-scan logic, and renderer; this package is the +// glue that keeps the I/O shape consistent. +package row diff --git a/internal/write/kb/sourcecoverage/advance.go b/internal/write/kb/sourcecoverage/advance.go new file mode 100644 index 000000000..1be5331fd --- /dev/null +++ b/internal/write/kb/sourcecoverage/advance.go @@ -0,0 +1,78 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + "path/filepath" + "time" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbSC "github.com/ActiveMemory/ctx/internal/err/kb/sourcecoverage" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Advance writes (or updates) a row in the ledger. When the +// source already has a row, the transition is validated; when +// the source is new, the row is appended only when the State +// is `discovered` or `admitted` (the entry points of the state +// machine). +// +// Parameters: +// - ledgerPath: full path to +// `.context/kb/source-coverage.md`. +// - row: row to write. Updated is set to time.Now() when +// zero. +// +// Returns: +// - error: [errKbSC.ErrIllegalTransition], +// [errKbSC.ErrUnknownSource], or wrapped I/O failures. +func Advance(ledgerPath string, row Row) error { + if row.Updated.IsZero() { + row.Updated = time.Now().UTC().Truncate(time.Second) + } + rows, readErr := Read(ledgerPath) + if readErr != nil { + return readErr + } + + idx := -1 + for i, r := range rows { + if r.Source == row.Source { + idx = i + break + } + } + if idx < 0 { + switch row.State { + case cfgKB.StateDiscovered, cfgKB.StateAdmitted: + rows = append(rows, row) + default: + return errKbSC.UnknownSource(row.Source, row.State) + } + } else { + if !ValidTransition(rows[idx].State, row.State) { + return errKbSC.IllegalTransition( + rows[idx].State, row.State, row.Source, + ) + } + rows[idx] = row + } + + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(ledgerPath), cfgFs.PermExec, + ); mkErr != nil { + return errKbSC.MkdirLedgerDir(mkErr) + } + rendered := render(rows) + if writeErr := ctxIo.SafeWriteFile( + ledgerPath, []byte(rendered), cfgFs.PermSecret, + ); writeErr != nil { + return errKbSC.WriteLedger(writeErr) + } + return nil +} diff --git a/internal/write/kb/sourcecoverage/doc.go b/internal/write/kb/sourcecoverage/doc.go new file mode 100644 index 000000000..3971ff6a5 --- /dev/null +++ b/internal/write/kb/sourcecoverage/doc.go @@ -0,0 +1,43 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcecoverage maintains the source-coverage ledger +// at `.context/kb/source-coverage.md` for the ctx +// knowledge-base editorial pipeline (Phase KB). +// +// The ledger is a state machine over every source the kb has +// touched. Each pass that touches a source MUST advance its +// row honestly before writing the closeout. "Lying to the +// ledger" (recording `comprehensive` while real coverage is +// partial) is a hard anti-pattern named in the spec and in +// `.context/ingest/KB-RULES.md`. +// +// # State machine +// +// discovered → admitted | skipped +// admitted → highlights-extracted | partially-ingested +// | topic-page-drafted | comprehensive +// highlights-extracted → partially-ingested +// | topic-page-drafted | comprehensive +// partially-ingested → topic-page-drafted | comprehensive +// topic-page-drafted → comprehensive +// comprehensive → (terminal until source updates) +// superseded → (terminal) +// skipped → (terminal until scope changes) +// +// [Advance] refuses transitions that are not in the allow-list. +// The doctor advisory cross-checks the ledger against file +// existence + last-modified time vs. each row's Updated cell +// to catch ledger drift independently of write-time +// validation. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// the state-name constants (`StateAdmitted`, etc.). +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// resolves the ledger file path. +package sourcecoverage diff --git a/internal/write/kb/sourcecoverage/parse.go b/internal/write/kb/sourcecoverage/parse.go new file mode 100644 index 000000000..f27404f48 --- /dev/null +++ b/internal/write/kb/sourcecoverage/parse.go @@ -0,0 +1,76 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + "strings" + "time" + + cfgKbSC "github.com/ActiveMemory/ctx/internal/config/kb/sourcecoverage" + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// parse turns the raw ledger contents into rows. Lines that do +// not look like table rows are silently skipped so freeform +// prose at the top of the file is tolerated. +// +// Parameters: +// - raw: file contents. +// +// Returns: +// - []Row: parsed rows. +func parse(raw string) []Row { + var out []Row + for _, line := range strings.Split(raw, token.NewlineLF) { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, marker.TablePipe) || + strings.HasPrefix(line, cfgKbSC.DelimRowPrefix) { + continue + } + cols := splitRow(line) + if len(cols) != cfgKbSC.ExpectedCellCount { + continue + } + if cols[cfgKbSC.ColSource] == cfgKbSC.HeaderCellSource { + continue + } + updated, _ := time.Parse( + time.DateOnly, cols[cfgKbSC.ColUpdated], + ) + out = append(out, Row{ + Source: cols[cfgKbSC.ColSource], + Topic: cols[cfgKbSC.ColTopic], + State: cols[cfgKbSC.ColState], + EVCoverage: cols[cfgKbSC.ColEVCoverage], + Residue: cols[cfgKbSC.ColResidue], + NextAction: cols[cfgKbSC.ColNextAction], + Updated: updated, + }) + } + return out +} + +// splitRow splits a markdown table row into trimmed cell +// contents (without the leading / trailing pipe). +// +// Parameters: +// - line: one markdown table row (must start with "|"). +// +// Returns: +// - []string: trimmed cell contents. +func splitRow(line string) []string { + parts := strings.Split(line, marker.TablePipe) + if len(parts) < 2 { + return nil + } + parts = parts[1 : len(parts)-1] + for i, p := range parts { + parts[i] = strings.TrimSpace(p) + } + return parts +} diff --git a/internal/write/kb/sourcecoverage/read.go b/internal/write/kb/sourcecoverage/read.go new file mode 100644 index 000000000..6a4fa43e1 --- /dev/null +++ b/internal/write/kb/sourcecoverage/read.go @@ -0,0 +1,37 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + "errors" + "os" + + errKbSC "github.com/ActiveMemory/ctx/internal/err/kb/sourcecoverage" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Read parses the ledger at ledgerPath into a Row slice. +// Returns an empty slice (no error) when the file does not +// exist. +// +// Parameters: +// - ledgerPath: full path to +// `.context/kb/source-coverage.md`. +// +// Returns: +// - []Row: parsed ledger rows in source-order. +// - error: wrapped [errKbSC.ReadLedger] on I/O failure. +func Read(ledgerPath string) ([]Row, error) { + raw, readErr := ctxIo.SafeReadUserFile(ledgerPath) + if readErr != nil { + if errors.Is(readErr, os.ErrNotExist) { + return nil, nil + } + return nil, errKbSC.ReadLedger(readErr) + } + return parse(string(raw)), nil +} diff --git a/internal/write/kb/sourcecoverage/render.go b/internal/write/kb/sourcecoverage/render.go new file mode 100644 index 000000000..475b3e2cc --- /dev/null +++ b/internal/write/kb/sourcecoverage/render.go @@ -0,0 +1,57 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + "strings" + "time" + + cfgKbSC "github.com/ActiveMemory/ctx/internal/config/kb/sourcecoverage" + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// render composes the ledger file: title + lead paragraphs + +// table header + one row per source. +// +// Parameters: +// - rows: rows to render. +// +// Returns: +// - string: full file content. +func render(rows []Row) string { + var sb strings.Builder + sb.WriteString(cfgKbSC.TitleHeading) + sb.WriteString(token.DoubleNewline) + sb.WriteString(cfgKbSC.LeadParagraph1) + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgKbSC.LeadParagraph2) + sb.WriteString(token.NewlineLF) + sb.WriteString(cfgKbSC.LeadParagraph3) + sb.WriteString(token.DoubleNewline) + sb.WriteString(cfgKbSC.TableHeader) + sb.WriteString(token.NewlineLF) + for _, r := range rows { + sb.WriteString(marker.TableRowOpen) + sb.WriteString(r.Source) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.Topic) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.State) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.EVCoverage) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.Residue) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.NextAction) + sb.WriteString(marker.TableCellSep) + sb.WriteString(r.Updated.UTC().Format(time.DateOnly)) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + } + return sb.String() +} diff --git a/internal/write/kb/sourcecoverage/sourcecoverage_test.go b/internal/write/kb/sourcecoverage/sourcecoverage_test.go new file mode 100644 index 000000000..f089e8f05 --- /dev/null +++ b/internal/write/kb/sourcecoverage/sourcecoverage_test.go @@ -0,0 +1,152 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage_test + +import ( + "errors" + "path/filepath" + "testing" + "time" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + errKbSC "github.com/ActiveMemory/ctx/internal/err/kb/sourcecoverage" + sc "github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage" +) + +func TestValidTransition_AllowedSubset(t *testing.T) { + cases := []struct { + from, to string + want bool + }{ + {cfgKB.StateDiscovered, cfgKB.StateAdmitted, true}, + {cfgKB.StateDiscovered, cfgKB.StateSkipped, true}, + {cfgKB.StateAdmitted, cfgKB.StateHighlightsExtracted, true}, + {cfgKB.StateAdmitted, cfgKB.StateComprehensive, true}, + {cfgKB.StateHighlightsExtracted, cfgKB.StateComprehensive, true}, + {cfgKB.StateTopicPageDrafted, cfgKB.StateComprehensive, true}, + // Same-state always allowed (idempotent touch). + {cfgKB.StateAdmitted, cfgKB.StateAdmitted, true}, + // Illegal: backwards + {cfgKB.StateComprehensive, cfgKB.StateHighlightsExtracted, false}, + // Illegal: skipped → anything + {cfgKB.StateSkipped, cfgKB.StateAdmitted, false}, + // Illegal: discovered → comprehensive (must pass through admitted) + {cfgKB.StateDiscovered, cfgKB.StateComprehensive, false}, + } + for _, c := range cases { + if got := sc.ValidTransition(c.from, c.to); got != c.want { + t.Errorf("%s → %s: want %v; got %v", c.from, c.to, c.want, got) + } + } +} + +func TestAdvance_NewSourceAtDiscovered(t *testing.T) { + path := filepath.Join(t.TempDir(), "source-coverage.md") + row := sc.Row{ + Source: "CURSOR-HOOKS", + Topic: "cursor-hooks", + State: cfgKB.StateDiscovered, + } + if err := sc.Advance(path, row); err != nil { + t.Fatalf("Advance: %v", err) + } + rows, err := sc.Read(path) + if err != nil { + t.Fatalf("Read: %v", err) + } + if len(rows) != 1 { + t.Fatalf("count: want 1; got %d", len(rows)) + } + if rows[0].Source != "CURSOR-HOOKS" { + t.Errorf("Source: got %q", rows[0].Source) + } + if rows[0].State != cfgKB.StateDiscovered { + t.Errorf("State: got %q", rows[0].State) + } +} + +func TestAdvance_NewSourceAtAdmittedOK(t *testing.T) { + path := filepath.Join(t.TempDir(), "source-coverage.md") + row := sc.Row{ + Source: "FOO", + Topic: "n/a", + State: cfgKB.StateAdmitted, + } + if err := sc.Advance(path, row); err != nil { + t.Fatalf("Advance: %v", err) + } +} + +func TestAdvance_NewSourceAtInvalidStateRejected(t *testing.T) { + path := filepath.Join(t.TempDir(), "source-coverage.md") + row := sc.Row{ + Source: "FOO", + Topic: "n/a", + State: cfgKB.StateComprehensive, + } + err := sc.Advance(path, row) + if !errors.Is(err, errKbSC.ErrUnknownSource) { + t.Fatalf("want ErrUnknownSource; got %v", err) + } +} + +func TestAdvance_IllegalTransitionRejected(t *testing.T) { + path := filepath.Join(t.TempDir(), "source-coverage.md") + // First, admit it. + if err := sc.Advance(path, sc.Row{ + Source: "BAR", Topic: "n/a", State: cfgKB.StateAdmitted, + }); err != nil { + t.Fatal(err) + } + // Try to demote from admitted → discovered (illegal). + err := sc.Advance(path, sc.Row{ + Source: "BAR", Topic: "n/a", State: cfgKB.StateDiscovered, + }) + if !errors.Is(err, errKbSC.ErrIllegalTransition) { + t.Fatalf("want ErrIllegalTransition; got %v", err) + } +} + +func TestAdvance_LegalTransitionAdvancesRow(t *testing.T) { + path := filepath.Join(t.TempDir(), "source-coverage.md") + for _, state := range []string{ + cfgKB.StateAdmitted, + cfgKB.StateHighlightsExtracted, + cfgKB.StateTopicPageDrafted, + cfgKB.StateComprehensive, + } { + if err := sc.Advance(path, sc.Row{ + Source: "BAZ", + Topic: "baz-topic", + State: state, + Updated: time.Now(), + }); err != nil { + t.Fatalf("advance to %s: %v", state, err) + } + } + rows, err := sc.Read(path) + if err != nil { + t.Fatal(err) + } + if len(rows) != 1 { + t.Fatalf("want 1 row; got %d", len(rows)) + } + if rows[0].State != cfgKB.StateComprehensive { + t.Errorf("final state: want %q; got %q", + cfgKB.StateComprehensive, rows[0].State) + } +} + +func TestRead_NonExistentFileReturnsEmpty(t *testing.T) { + rows, err := sc.Read(filepath.Join(t.TempDir(), "nope.md")) + if err != nil { + t.Fatalf("want nil; got %v", err) + } + if rows != nil { + t.Errorf("want nil slice; got %v", rows) + } +} diff --git a/internal/write/kb/sourcecoverage/testmain_test.go b/internal/write/kb/sourcecoverage/testmain_test.go new file mode 100644 index 000000000..5d90cc0c1 --- /dev/null +++ b/internal/write/kb/sourcecoverage/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/sourcecoverage/transition.go b/internal/write/kb/sourcecoverage/transition.go new file mode 100644 index 000000000..7245ec14b --- /dev/null +++ b/internal/write/kb/sourcecoverage/transition.go @@ -0,0 +1,55 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import ( + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" +) + +// allowed is the closed set of legal state transitions in the +// source-coverage state machine. Any (from, to) not present is +// rejected by [ValidTransition]. +// +// Reading order matches the spec's table: every row's outgoing +// transitions appear left to right. +var allowed = map[transition]bool{ + {cfgKB.StateDiscovered, cfgKB.StateAdmitted}: true, + {cfgKB.StateDiscovered, cfgKB.StateSkipped}: true, + {cfgKB.StateAdmitted, cfgKB.StateHighlightsExtracted}: true, + {cfgKB.StateAdmitted, cfgKB.StatePartiallyIngested}: true, + {cfgKB.StateAdmitted, cfgKB.StateTopicPageDrafted}: true, + {cfgKB.StateAdmitted, cfgKB.StateComprehensive}: true, + {cfgKB.StateHighlightsExtracted, + cfgKB.StatePartiallyIngested}: true, + {cfgKB.StateHighlightsExtracted, + cfgKB.StateTopicPageDrafted}: true, + {cfgKB.StateHighlightsExtracted, + cfgKB.StateComprehensive}: true, + {cfgKB.StatePartiallyIngested, + cfgKB.StateTopicPageDrafted}: true, + {cfgKB.StatePartiallyIngested, + cfgKB.StateComprehensive}: true, + {cfgKB.StateTopicPageDrafted, + cfgKB.StateComprehensive}: true, +} + +// ValidTransition reports whether advancing from state `from` +// to state `to` is allowed. Same-state transitions (idempotent +// "touch" of a row without state change) are always allowed. +// +// Parameters: +// - from: current state (string from cfgKB.State*). +// - to: next state (string from cfgKB.State*). +// +// Returns: +// - bool: true when the transition is legal. +func ValidTransition(from, to string) bool { + if from == to { + return true + } + return allowed[transition{from: from, to: to}] +} diff --git a/internal/write/kb/sourcecoverage/types.go b/internal/write/kb/sourcecoverage/types.go new file mode 100644 index 000000000..d1cbc9ed1 --- /dev/null +++ b/internal/write/kb/sourcecoverage/types.go @@ -0,0 +1,46 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcecoverage + +import "time" + +// Row is one source-coverage ledger entry. Fields match the +// schema at +// `internal/assets/kb/templates/ingest/schemas/source-coverage.md`. +type Row struct { + // Source is the short-name from `source-map.md` that + // identifies the source uniquely within this kb. + Source string + // Topic is the kb-topic slug this source contributes to, + // or the literal "n/a" for non-topic passes. + Topic string + // State is one of the state-name constants in + // [github.com/ActiveMemory/ctx/internal/config/kb] (e.g. + // `cfgKB.StateAdmitted`). + State string + // EVCoverage names the EV-### range minted from this + // source, e.g. "EV-018..EV-034", or "none". + EVCoverage string + // Residue is a short free-text note describing what is + // not-yet-covered by the page(s) backed by this source. + Residue string + // NextAction is the exact resumption invocation that would + // advance this row, e.g. + // "/ctx-kb-ingest cursor-hooks (resume topic-page)". + NextAction string + // Updated is the timestamp of the most recent pass that + // touched this row. + Updated time.Time +} + +// transition keys the allowed-transitions map declared in +// transition.go. The from / to fields name the state pair as +// declared in [github.com/ActiveMemory/ctx/internal/config/kb]. +type transition struct { + from string + to string +} diff --git a/internal/write/kb/sourcemap/append.go b/internal/write/kb/sourcemap/append.go new file mode 100644 index 000000000..d90bdfe98 --- /dev/null +++ b/internal/write/kb/sourcemap/append.go @@ -0,0 +1,64 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap + +import ( + "os" + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKbSM "github.com/ActiveMemory/ctx/internal/config/kb/sourcemap" + errKbSM "github.com/ActiveMemory/ctx/internal/err/kb/sourcemap" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append writes one row to the source-map artifact at path. +// When the file does not exist, it is created with the schema +// header and then the row is appended. The write opens the +// file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to `.context/kb/source-map.md`. +// - row: row content. +// +// Returns: +// - error: wrapped I/O failures. +func Append(path string, row Row) error { + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return errKbSM.MkdirDir(mkErr) + } + needsHeader := false + if _, statErr := ctxIo.SafeStat(path); statErr != nil { + if !os.IsNotExist(statErr) { + return errKbSM.ReadFile(statErr) + } + needsHeader = true + } + + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return errKbSM.OpenFile(openErr) + } + defer func() { _ = f.Close() }() + + if needsHeader { + if _, writeErr := f.WriteString( + cfgKbSM.TableHeader, + ); writeErr != nil { + return errKbSM.WriteRow(writeErr) + } + } + if _, writeErr := f.WriteString( + renderRow(row), + ); writeErr != nil { + return errKbSM.WriteRow(writeErr) + } + return nil +} diff --git a/internal/write/kb/sourcemap/doc.go b/internal/write/kb/sourcemap/doc.go new file mode 100644 index 000000000..6e3430d99 --- /dev/null +++ b/internal/write/kb/sourcemap/doc.go @@ -0,0 +1,29 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package sourcemap appends rows to the source-map artifact at +// `.context/kb/source-map.md` for the ctx knowledge-base +// editorial pipeline (Phase KB). +// +// One row per source cited from `evidence-index.md`. Each row +// records what a source *is* (short-name, kind, locator) and +// whether it was admitted against the kb's declared scope. +// The source map records identity-and-admission; the parallel +// source-coverage ledger records progress against admitted +// sources. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/source-map.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.SourceMap]. +// - [github.com/ActiveMemory/ctx/internal/write/kb/sourcecoverage] +// is the parallel progress ledger for admitted sources. +package sourcemap diff --git a/internal/write/kb/sourcemap/render.go b/internal/write/kb/sourcemap/render.go new file mode 100644 index 000000000..c7b8a48ca --- /dev/null +++ b/internal/write/kb/sourcemap/render.go @@ -0,0 +1,58 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(row.ShortName)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Kind)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Locator)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.AdmissionStatus)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.AdmissionRationale)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Dated)) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/sourcemap/testmain_test.go b/internal/write/kb/sourcemap/testmain_test.go new file mode 100644 index 000000000..b9683c8de --- /dev/null +++ b/internal/write/kb/sourcemap/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/sourcemap/types.go b/internal/write/kb/sourcemap/types.go new file mode 100644 index 000000000..f4be90cd2 --- /dev/null +++ b/internal/write/kb/sourcemap/types.go @@ -0,0 +1,33 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap + +// Row is one source-map entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/source-map.md`, +// rendered as a single markdown table row. +type Row struct { + // ShortName is the stable identifier used in + // `evidence-index.md` cites; never renamed in place. + ShortName string + // Kind is one of "transcript", "code", "doc", "url", "mcp", + // "kb". + Kind string + // Locator is the URL, repo path, MCP resource ID, or kb + // pointer for this source. + Locator string + // AdmissionStatus is one of "admitted", "skipped", + // "pending". (The schema also names "rejected"; the brief + // pins this writer's vocabulary to admitted|skipped per the + // kb-editorial-pipeline spec.) + AdmissionStatus string + // AdmissionRationale is one sentence explaining the + // admission decision. + AdmissionRationale string + // Dated is the optional ISO date the source itself is + // dated (empty when unknown). + Dated string +} diff --git a/internal/write/kb/sourcemap/writer_test.go b/internal/write/kb/sourcemap/writer_test.go new file mode 100644 index 000000000..b8735e609 --- /dev/null +++ b/internal/write/kb/sourcemap/writer_test.go @@ -0,0 +1,70 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package sourcemap_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/sourcemap" +) + +func TestAppend_CreatesHeaderOnFirstWrite(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.SourceMap) + + row := sourcemap.Row{ + ShortName: "WIDGET-SPEC", + Kind: "url", + Locator: "https://example.org/widget/v2/spec", + AdmissionStatus: "admitted", + AdmissionRationale: "Canonical upstream spec; in scope.", + Dated: "2026-05-16", + } + if err := sourcemap.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if !strings.Contains(text, "| Short name | Kind |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "WIDGET-SPEC") { + t.Errorf("short-name missing: %q", text) + } + if !strings.Contains(text, "admitted") { + t.Errorf("admission missing: %q", text) + } +} + +func TestAppend_DatedOmittedRendersEmptyCell(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.SourceMap) + row := sourcemap.Row{ + ShortName: "X", Kind: "doc", Locator: "/path", + AdmissionStatus: "admitted", AdmissionRationale: "ok", + } + if err := sourcemap.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + got, _ := os.ReadFile(path) + // The row should still be a well-formed table line. + rows := strings.Split(strings.TrimRight(string(got), "\n"), "\n") + if len(rows) != 3 { + t.Errorf("expected 3 lines; got %d: %q", len(rows), string(got)) + } + // Last cell empty: row ends with "| |". + if !strings.HasSuffix(rows[2], "| |") { + t.Errorf("expected trailing empty Dated cell; got %q", rows[2]) + } +} diff --git a/internal/write/kb/timeline/append.go b/internal/write/kb/timeline/append.go new file mode 100644 index 000000000..db64eef56 --- /dev/null +++ b/internal/write/kb/timeline/append.go @@ -0,0 +1,64 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline + +import ( + "os" + "path/filepath" + + cfgFs "github.com/ActiveMemory/ctx/internal/config/fs" + cfgKbTL "github.com/ActiveMemory/ctx/internal/config/kb/timeline" + errKbTL "github.com/ActiveMemory/ctx/internal/err/kb/timeline" + ctxIo "github.com/ActiveMemory/ctx/internal/io" +) + +// Append writes one row to the timeline artifact at path. When +// the file does not exist, it is created with the schema +// header and then the row is appended. The write opens the +// file with O_CREATE|O_APPEND|O_WRONLY; idempotency at the +// call-site is the caller's responsibility. +// +// Parameters: +// - path: absolute path to `.context/kb/timeline.md`. +// - row: row content. +// +// Returns: +// - error: wrapped I/O failures. +func Append(path string, row Row) error { + if mkErr := ctxIo.SafeMkdirAll( + filepath.Dir(path), cfgFs.PermExec, + ); mkErr != nil { + return errKbTL.MkdirDir(mkErr) + } + needsHeader := false + if _, statErr := ctxIo.SafeStat(path); statErr != nil { + if !os.IsNotExist(statErr) { + return errKbTL.ReadFile(statErr) + } + needsHeader = true + } + + f, openErr := ctxIo.SafeAppendFile(path, cfgFs.PermSecret) + if openErr != nil { + return errKbTL.OpenFile(openErr) + } + defer func() { _ = f.Close() }() + + if needsHeader { + if _, writeErr := f.WriteString( + cfgKbTL.TableHeader, + ); writeErr != nil { + return errKbTL.WriteRow(writeErr) + } + } + if _, writeErr := f.WriteString( + renderRow(row), + ); writeErr != nil { + return errKbTL.WriteRow(writeErr) + } + return nil +} diff --git a/internal/write/kb/timeline/doc.go b/internal/write/kb/timeline/doc.go new file mode 100644 index 000000000..d9bebd35e --- /dev/null +++ b/internal/write/kb/timeline/doc.go @@ -0,0 +1,27 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Package timeline appends rows to the kb-scoped timeline +// artifact at `.context/kb/timeline.md` for the ctx +// knowledge-base editorial pipeline (Phase KB). +// +// Each row records a dated event in the domain the kb is +// studying, pinned to one or more `EV-###` rows that ground +// it. The timeline is not a changelog of kb activity; that +// lives in closeouts and SESSION_LOG.md. +// +// The writer is append-only; when the artifact does not yet +// exist, [Append] initialises it with the schema's table +// header. The schema lives at +// `internal/assets/kb/templates/ingest/schemas/timeline.md`. +// +// # Related packages +// +// - [github.com/ActiveMemory/ctx/internal/config/kb] supplies +// [cfgKB.Timeline]. +// - [github.com/ActiveMemory/ctx/internal/cli/kb/core/path] +// resolves the artifact path via [KBArtifactFile]. +package timeline diff --git a/internal/write/kb/timeline/render.go b/internal/write/kb/timeline/render.go new file mode 100644 index 000000000..4b37103c7 --- /dev/null +++ b/internal/write/kb/timeline/render.go @@ -0,0 +1,58 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline + +import ( + "strings" + + "github.com/ActiveMemory/ctx/internal/config/marker" + "github.com/ActiveMemory/ctx/internal/config/token" +) + +// renderRow renders one Row as a markdown table row terminated +// by a newline. +// +// Parameters: +// - row: row content. +// +// Returns: +// - string: markdown row with trailing newline. +func renderRow(row Row) string { + var sb strings.Builder + sb.WriteString(marker.TableRowOpen) + sb.WriteString(escapeCell(row.Date)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell(row.Event)) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.SourceEV, token.CommaSpace), + )) + sb.WriteString(marker.TableCellSep) + sb.WriteString(escapeCell( + strings.Join(row.RelatedTopics, token.CommaSpace), + )) + sb.WriteString(marker.TableRowClose) + sb.WriteString(token.NewlineLF) + return sb.String() +} + +// escapeCell makes a free-text field safe to embed in a +// markdown table cell. +// +// Parameters: +// - s: raw cell content. +// +// Returns: +// - string: escaped cell content. +func escapeCell(s string) string { + s = strings.ReplaceAll(s, token.NewlineCRLF, token.Space) + s = strings.ReplaceAll(s, token.NewlineLF, token.Space) + s = strings.ReplaceAll( + s, marker.TablePipe, marker.TablePipeEscaped, + ) + return strings.TrimSpace(s) +} diff --git a/internal/write/kb/timeline/testmain_test.go b/internal/write/kb/timeline/testmain_test.go new file mode 100644 index 000000000..2b1769738 --- /dev/null +++ b/internal/write/kb/timeline/testmain_test.go @@ -0,0 +1,19 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline_test + +import ( + "os" + "testing" + + "github.com/ActiveMemory/ctx/internal/assets/read/lookup" +) + +func TestMain(m *testing.M) { + lookup.Init() + os.Exit(m.Run()) +} diff --git a/internal/write/kb/timeline/types.go b/internal/write/kb/timeline/types.go new file mode 100644 index 000000000..9cd28d394 --- /dev/null +++ b/internal/write/kb/timeline/types.go @@ -0,0 +1,25 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline + +// Row is one timeline entry. Fields match the schema at +// `internal/assets/kb/templates/ingest/schemas/timeline.md`, +// rendered as a single markdown table row. +type Row struct { + // Date is the ISO 8601 date the event occurred (not the + // date it was ingested). + Date string + // Event is a one-paragraph description of what happened. + Event string + // SourceEV is the comma-list of `EV-###` refs that ground + // the event. At least one is required by the schema; the + // writer does not enforce that; the caller does. + SourceEV []string + // RelatedTopics is the optional list of topic slugs the + // event touches. + RelatedTopics []string +} diff --git a/internal/write/kb/timeline/writer_test.go b/internal/write/kb/timeline/writer_test.go new file mode 100644 index 000000000..0413743ea --- /dev/null +++ b/internal/write/kb/timeline/writer_test.go @@ -0,0 +1,68 @@ +// / ctx: https://ctx.ist +// ,'`./ do you remember? +// `.,'\ +// \ Copyright 2026-present Context contributors. +// SPDX-License-Identifier: Apache-2.0 + +package timeline_test + +import ( + "os" + "path/filepath" + "strings" + "testing" + + cfgKB "github.com/ActiveMemory/ctx/internal/config/kb" + "github.com/ActiveMemory/ctx/internal/write/kb/timeline" +) + +func TestAppend_CreatesHeaderOnFirstWrite(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Timeline) + + row := timeline.Row{ + Date: "2026-05-16", + Event: "Widget contract v2 published.", + SourceEV: []string{"EV-042", "EV-051"}, + RelatedTopics: []string{"widgets", "widget-composition"}, + } + if err := timeline.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + + got, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read back: %v", err) + } + text := string(got) + if !strings.Contains(text, "| Date | Event |") { + t.Errorf("header missing: %q", text) + } + if !strings.Contains(text, "2026-05-16") { + t.Errorf("date missing: %q", text) + } + if !strings.Contains(text, "EV-042, EV-051") { + t.Errorf("EV list not joined: %q", text) + } + if !strings.Contains(text, "widgets, widget-composition") { + t.Errorf("topics not joined: %q", text) + } +} + +func TestAppend_SecondCallDoesNotRewriteHeader(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, cfgKB.Timeline) + + for _, date := range []string{"2026-05-15", "2026-05-16"} { + row := timeline.Row{ + Date: date, Event: "x", SourceEV: []string{"EV-001"}, + } + if err := timeline.Append(path, row); err != nil { + t.Fatalf("Append: %v", err) + } + } + got, _ := os.ReadFile(path) + if c := strings.Count(string(got), "| Date | Event |"); c != 1 { + t.Errorf("header written %d times; want 1", c) + } +} diff --git a/site/blog/2026-02-01-refactoring-with-intent/index.html b/site/blog/2026-02-01-refactoring-with-intent/index.html index 7022e3d3b..822144e3a 100644 --- a/site/blog/2026-02-01-refactoring-with-intent/index.html +++ b/site/blog/2026-02-01-refactoring-with-intent/index.html @@ -1401,7 +1401,7 @@ <h2 id="the-journal-system">The Journal System<a class="headerlink" href="#the-j <tbody> <tr> <td><code>ctx recall import</code></td> -<td>Import sessions to markdown in <code>.context/journal/</code></td> +<td>Import sessions to Markdown in <code>.context/journal/</code></td> </tr> <tr> <td><code>ctx journal site</code></td> diff --git a/site/blog/2026-02-04-skills-that-fight-the-platform/index.html b/site/blog/2026-02-04-skills-that-fight-the-platform/index.html index cd97cc69f..4181b0ae4 100644 --- a/site/blog/2026-02-04-skills-that-fight-the-platform/index.html +++ b/site/blog/2026-02-04-skills-that-fight-the-platform/index.html @@ -1203,7 +1203,7 @@ <h2 id="when-your-custom-prompts-work-against-you">When Your Custom Prompts Work the agent: the defaults that already encode <strong>judgment</strong>, <strong>safety</strong>, and <strong>scope control</strong>.</p> </div> -<p>This post catalogues the conflict patterns I have encountered while building +<p>This post catalogs the conflict patterns I have encountered while building <code>ctx</code>, and offers guidance on what skills should (<em>and, more importantly, should not</em>) do.</p> <h2 id="the-system-prompt-you-dont-see">The System Prompt You Don't See<a class="headerlink" href="#the-system-prompt-you-dont-see" title="Permanent link">¶</a></h2> diff --git a/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html b/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html index 6c6a074c6..9d36adbc2 100644 --- a/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html +++ b/site/blog/2026-02-17-code-is-cheap-judgment-is-not/index.html @@ -1317,7 +1317,7 @@ <h3 id="automation-discipline-proved-restraint-is-a-skill">Automation Discipline it should exist.</strong></p> <h3 id="defense-in-depth-proved-boundaries-require-judgment">Defense in Depth Proved Boundaries Require Judgment<a class="headerlink" href="#defense-in-depth-proved-boundaries-require-judgment" title="Permanent link">¶</a></h3> <p>In <a href="../2026-02-09-defense-in-depth-securing-ai-agents/">Defense in Depth</a>, the entire security model -for unattended AI agents came down to: <strong>markdown is not a +for unattended AI agents came down to: <strong>Markdown is not a security boundary</strong>. Telling an AI "<em>don't do bad things</em>" is production (<em>of instructions</em>). Setting up an unprivileged user in a network-isolated container is <strong>judgment</strong> (<em>about risk</em>).</p> diff --git a/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html b/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html index ab225eaee..fc0b51a66 100644 --- a/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html +++ b/site/blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/index.html @@ -1372,7 +1372,7 @@ <h2 id="hooks-are-context-that-execute">Hooks Are Context That Execute<a class=" <p>This is not safety theater; this is <strong>intent preservation</strong>.</p> <p>The thing the <code>ctx</code> Manifesto calls "<a href="../../#encode-intent-into-the-environment">encoding intent into the environment</a>."</p> -<p>The <a href="../2026-02-15-eight-ways-a-hook-can-talk/">Eight Ways a Hook Can Talk</a> catalogued the full +<p>The <a href="../2026-02-15-eight-ways-a-hook-can-talk/">Eight Ways a Hook Can Talk</a> cataloged the full spectrum: from silent enrichment to hard blocks. </p> <p>The key insight was that hooks are not just safety rails: They are <strong>context that survives execution</strong>.</p> diff --git a/site/cli/connect/index.html b/site/cli/connect/index.html index c9510ef85..25272ebd1 100644 --- a/site/cli/connect/index.html +++ b/site/cli/connect/index.html @@ -1165,7 +1165,7 @@ <h3 id="ctx-connect-subscribe"><code>ctx connect subscribe</code><a class="heade </span></code></pre></div> <h3 id="ctx-connect-sync"><code>ctx connect sync</code><a class="headerlink" href="#ctx-connect-sync" title="Permanent link">¶</a></h3> <p>Pull matching entries from the hub and write them to -<code>.context/hub/</code> as markdown files with origin tags and date +<code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <div class="language-bash highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>ctx<span class="w"> </span>connect<span class="w"> </span>sync </span></code></pre></div> diff --git a/site/cli/connection/index.html b/site/cli/connection/index.html index 8532ad77a..984229b02 100644 --- a/site/cli/connection/index.html +++ b/site/cli/connection/index.html @@ -2208,7 +2208,7 @@ <h3 id="ctx-connection-subscribe"><code>ctx connection subscribe</code><a class= </span></code></pre></div> <h3 id="ctx-connection-sync"><code>ctx connection sync</code><a class="headerlink" href="#ctx-connection-sync" title="Permanent link">¶</a></h3> <p>Pull matching entries from the <code>ctx</code> Hub and write them to -<code>.context/hub/</code> as markdown files with origin tags and date +<code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <p><strong>Examples</strong>:</p> <div class="language-bash highlight"><pre><span></span><code><span id="__span-2-1"><a id="__codelineno-2-1" name="__codelineno-2-1" href="#__codelineno-2-1"></a>ctx<span class="w"> </span>connection<span class="w"> </span>sync diff --git a/site/cli/handover/index.html b/site/cli/handover/index.html new file mode 100644 index 000000000..8fe6978e6 --- /dev/null +++ b/site/cli/handover/index.html @@ -0,0 +1,1257 @@ + +<!doctype html> +<html lang="en" class="no-js"> + <head> + + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width,initial-scale=1"> + + <meta name="description" content="ctx provides temporal continuity across sessions"> + + + <meta name="author" content="Jose Alekhinne <alekhinejose@gmail.com>"> + + + <link rel="canonical" href="https://ctx.ist/cli/handover/"> + + + + + + + + <link rel="icon" href="../../images/favicon-32.png"> + <meta name="generator" content="zensical-0.0.21"> + + + + <title>ctx handover - ctx: do you remember? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+
+
+ + + + +
+
+
+ + + +
+ + + + + + + + + +
+ + + + + + +

ctx handover

+ +

ctx

+

ctx handover

+

Writes the per-session handover under +.context/handovers/<TS>-<slug>.md: a former-agent-to-next-agent +note created at session end by /ctx-wrap-up and read at +session start by /ctx-remember. When .context/kb/ exists, +the writer additionally folds postdated closeouts into the +handover's ## Folded Closeouts section and archives them.

+

ctx handover write <title>

+
ctx handover write "Cursor Hooks deep dive" \
+  --summary "Drafted topic-page; minted EV-018..EV-024; cold-reader passed." \
+  --next "Re-ingest the v1.1 release notes URL once you have it."
+
+

Required flags:

+ + + + + + + + + + + + + + + + + +
FlagDescription
--summaryWhat happened this session (past tense). Placeholder values (TBD, see chat, n/a) are rejected.
--nextWhat the next agent should do FIRST (future tense, specific). Same placeholder rejection.
+

Optional flags:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FlagDescription
--highlightsNotable artifacts produced this session.
--open-questionsThings that remain undecided.
--commitOverride resolved git HEAD for the Provenance line (CI replay; honors CTX_TASK_COMMIT).
--no-foldSkip closeout consumption (mid-session checkpoint).
+

Writes: .context/handovers/<TS>-<slug>.md with frontmatter +(sha, branch, generated-at, title) and body sections +(## Summary, ## Next Session, optionally ## Highlights, +## Open Questions, ## Folded Closeouts). The +<TS>-<slug>.md filename is timestamped so multiple concurrent +agent runs never overwrite one another's handover.

+

Side effect (when --no-fold is absent and .context/kb/ +exists): closeouts that postdate the latest handover are +folded into the new handover and physically archived under +.context/archive/closeouts/.

+

How to Trigger

+

In ordinary sessions you do not invoke ctx handover write +directly. The user-facing trigger is /ctx-wrap-up:

+
/ctx-wrap-up "session title"
+
+

/ctx-wrap-up owns session-end and always delegates to +/ctx-handover as its final step. Direct invocation of +/ctx-handover is reserved for two cases:

+
    +
  • --no-fold mid-session checkpoint.
  • +
  • Recovery, when a prior session aborted before wrap-up.
  • +
+

See /ctx-wrap-up and +/ctx-handover.

+

Reference

+ + + + + + + + + + + + + + + + + + + +
+
+ + + + + +
+ + + +
+ +
+ + + + +
+ +
+
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/cli/index.html b/site/cli/index.html index e01b86ea0..f8945c554 100644 --- a/site/cli/index.html +++ b/site/cli/index.html @@ -1799,6 +1799,14 @@

Contextctx watch Auto-apply context updates from AI output + +ctx kb +Knowledge-base editorial pipeline (Phase KB) + + +ctx handover +Write the per-session handover that the next session reads +

Sessions

diff --git a/site/cli/init-status/index.html b/site/cli/init-status/index.html index 4b1af2845..36df7d77d 100644 --- a/site/cli/init-status/index.html +++ b/site/cli/init-status/index.html @@ -1810,6 +1810,20 @@

ctx initInitialize a new .context/ directory with template files.

ctx init [flags]
 
+
+

Git is required

+

ctx init (and every non-administrative ctx subcommand) +refuses to operate without a .git/ working tree at the +project root. ctx already needed git to work properly; +that requirement is now enforced rather than assumed.

+

Handovers and closeouts stamp the current commit into +their frontmatter, and the editorial pipeline pins +in-repo evidence to a short SHA (none of which works +without a repo).

+

Run git init first if the project does +not already have one.

+

There is no --allow-no-git escape hatch.

+

Flags:

@@ -1840,6 +1854,12 @@

ctx initCreates:

diff --git a/site/cli/kb/index.html b/site/cli/kb/index.html new file mode 100644 index 000000000..a1ebc45cd --- /dev/null +++ b/site/cli/kb/index.html @@ -0,0 +1,1293 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + ctx kb - ctx: do you remember? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + +
+ + + + + + + +
+ +
+ + + + +
+
+ + + +
+
+
+ + + + + + + +
+
+
+ + + +
+ +
+ + + +
+ + + + + + + + + +
+ + + + + + +

ctx kb

+ +

ctx

+

ctx kb

+

Knowledge-base editorial pipeline (Phase KB). Manages the +.context/kb/ knowledge base via mode-aware skills and a small +set of supporting CLI commands. The editorial constitution +lives at .context/ingest/KB-RULES.md (laid down by +ctx init).

+
ctx kb [subcommand]
+
+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SubcommandTypePurpose
ctx kb topic new "<name>"CLI (real)Sole writer of topic-page scaffolds. Creates .context/kb/topics/<slug>/index.md from the embedded template. Refuses when the topic exists.
ctx kb note "<text>"CLI (real)Appends a one-liner to .context/ingest/findings.md. Never touches a topic page.
ctx kb reindexCLI (real)Refreshes the CTX:KB:TOPICS managed block in .context/kb/index.md.
ctx kb ingest <folder\|paths>Skill-drivenMode-aware editorial pass. CLI form refuses on empty input and points at the /ctx-kb-ingest skill.
ctx kb ask "<question>"Skill-drivenQ&A grounded in the kb. CLI form refuses on empty input and points at the /ctx-kb-ask skill.
ctx kb site-reviewSkill-drivenMechanical structural audit. Points at /ctx-kb-site-review.
ctx kb groundSkill-drivenExternal grounding via grounding-sources.md. Refuses when the file is empty.
+
+

Skill-driven vs real CLI

+

The mode skills (ingest, ask, site-review, ground) +do the editorial work themselves: the agent reads +.context/ingest/30-INGEST.md (etc.) and executes the +pass per the pass-mode contract. The CLI form for those +subcommands validates input and prints the canonical skill +invocation. The real CLI commands (topic new, note, +reindex) own concrete state changes.

+
+

ctx kb topic new "<name>"

+

Scaffolds a folder-shaped topic at .context/kb/topics/<slug>/index.md +from the embedded template.

+

Slug: lowercase + kebab-case. Slashes are preserved for +vendor-namespaced topology (e.g. cursor/hooks, +cursor/skills, cursor/rules under a shared cursor/ +folder).

+

Refuses when the topic folder already exists. Use the +existing folder instead; the editorial pass extends pages, +it doesn't reset them.

+

ctx kb note "<text>"

+

Appends a timestamped one-liner to +.context/ingest/findings.md. Use for parking findings the +next ingest pass should absorb.

+
ctx kb note "follow-up: chase the v1.2 release notes for the SIGTERM change"
+
+

ctx kb reindex

+

Refreshes the CTX:KB:TOPICS managed block inside +.context/kb/index.md so the kb landing page enumerates +current topic folders. Run after ctx kb topic new to update +the landing.

+

Skill-Driven Subcommands

+

ingest, ask, site-review, ground exist as CLI surfaces +so the editorial workflow is drivable from outside Claude +Code (via the fallback PROMPT.md auto-router). In Claude +Code, prefer the skills:

+
/ctx-kb-ingest ./inputs/2026-05-15-call.md "cursor hooks"
+/ctx-kb-ask "does the kb say hooks fire async?"
+/ctx-kb-site-review
+/ctx-kb-ground
+
+

See the +Build a Knowledge Base recipe +for the full workflow.

+

Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+
+
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/cli/steering/index.html b/site/cli/steering/index.html index 768681b61..7fbb90f7a 100644 --- a/site/cli/steering/index.html +++ b/site/cli/steering/index.html @@ -2149,7 +2149,7 @@

Steering

ctx steering

Manage steering files: persistent behavioral rules for AI coding assistants.

-

A steering file is a small markdown document with YAML +

A steering file is a small Markdown document with YAML frontmatter that tells the AI how to behave in a specific context. ctx steering keeps those files in .context/steering/, decides which ones apply for a given diff --git a/site/home/getting-started/index.html b/site/home/getting-started/index.html index 2366f2cd5..434d22a1f 100644 --- a/site/home/getting-started/index.html +++ b/site/home/getting-started/index.html @@ -2149,14 +2149,14 @@

1. Initialize Contextctx plugin for automatic hooks and skills.

ctx init also scaffolds four foundation steering files in -.context/steering/product.md, tech.md, structure.md, +.context/steering/: product.md, tech.md, structure.md, workflow.md. They are placeholders until you customize them (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than buried here.

2. Customize Your Steering Files

Steering files are behavioral rules prepended to every AI -prompt — the layer that tells your AI how to act on this +prompt: the layer that tells your AI how to act on this specific project. They are distinct from decisions (what was chosen) and conventions (how the codebase is written); see ctx for Steering Files for the full @@ -2194,7 +2194,7 @@

2. Customize Your Steering FilesAs long as the marker is present, the file is silently skipped on every load path: the agent context packet, MCP ctx_steering_get, and native-tool sync (Cursor / Cline / -Kiro). The skip is deliberate — injecting unfilled placeholders +Kiro). The skip is deliberate: injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow "Describe the product..." as if it were a rule.

diff --git a/site/home/opencode/index.html b/site/home/opencode/index.html index 372c442f0..91c68c073 100644 --- a/site/home/opencode/index.html +++ b/site/home/opencode/index.html @@ -1955,7 +1955,7 @@

The ProblemWith ctx:

> "Add the validation middleware we discussed"
 
-Yes — from the Jan 15 session. You decided on Zod schemas at the
+Yes. From the Jan 15 session. You decided on Zod schemas at the
 route level (DECISIONS.md #12), and the pattern is in
 CONVENTIONS.md. I'll follow the existing middleware in
 src/middleware/auth.ts as a reference.
@@ -1968,15 +1968,15 @@ 

Setup (One Command) — binds CTX_DIR for your shell +
  • ctx init: creates the .context/ directory with template files.
  • +
  • eval "$(ctx activate)": binds CTX_DIR for your shell.
  • What Gets Created

    @@ -2005,11 +2005,11 @@

    What Gets CreatedWhat Happens Automatically

    The plugin wires OpenCode lifecycle events to ctx. You don't need to -do anything — it just works.

    +do anything; it just works.

    @@ -2027,7 +2027,7 @@

    What Happens AutomaticallyWhat Happens AutomaticallyHow Compaction Works

    When your conversation exceeds the context window, OpenCode runs a @@ -2087,7 +2087,7 @@

    Slash Commands +

    @@ -2170,7 +2170,7 @@

    MCP ToolsRefreshing the Integration

    If you re-run ctx setup opencode --write (e.g., after updating ctx), the plugin and skills are rewritten in place. Restart OpenCode to pick up the -refreshed plugin — OpenCode only loads plugins at launch, not mid-session.

    +refreshed plugin. OpenCode only loads plugins at launch, not mid-session.

    Troubleshooting

    "Do you remember?"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback.
    /ctx-status
    @@ -2192,7 +2192,7 @@

    TroubleshootingVerify It Works.context/ has files in it.

    What's Next

      -
    • Your First Session — step-by-step walkthrough from - ctx init to verified recall
    • -
    • Common Workflows — day-to-day commands for - tracking context, checking health, and browsing history
    • -
    • Context Files — what lives in .context/ and how - each file is used
    • +
    • Your First Session: step-by-step walkthrough from + ctx init to verified recall.
    • +
    • Common Workflows: day-to-day commands for + tracking context, checking health, and browsing history.
    • +
    • Context Files: what lives in .context/ and how + each file is used.
    diff --git a/site/home/prompting-guide/index.html b/site/home/prompting-guide/index.html index 70f17bbd1..f9cdc637f 100644 --- a/site/home/prompting-guide/index.html +++ b/site/home/prompting-guide/index.html @@ -3200,7 +3200,7 @@

    ctx Skills

    Skills beyond Claude Code

    The /slash-command syntax above is Claude Code native, but the -underlying SKILL.md files are a standard markdown format that any +underlying SKILL.md files are a standard Markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates.

    diff --git a/site/home/steering/index.html b/site/home/steering/index.html index c64e8dcda..e01a1a51d 100644 --- a/site/home/steering/index.html +++ b/site/home/steering/index.html @@ -1724,7 +1724,7 @@

    Steering Filesctx manages those files in .context/steering/, decides which ones match each prompt, @@ -1789,7 +1789,7 @@

    Your First Steering Files&
    1. Install the extension from the VS Code Marketplace - (publisher: activememory, display name: ctx — Persistent Context + (publisher: activememory, display name: ctx: Persistent Context for AI). Or build from source (see editors/vscode/README.md).
    2. Install the ctx CLI if you haven't already diff --git a/site/operations/integrations/index.html b/site/operations/integrations/index.html index e3fbb6560..147344c66 100644 --- a/site/operations/integrations/index.html +++ b/site/operations/integrations/index.html @@ -3526,7 +3526,7 @@

      VS Code Chat Extension (@ctx)

      Installation

      The extension ships to the VS Code Marketplace -under publisher activememory (display name: ctx — Persistent Context +under publisher activememory (display name: ctx: Persistent Context for AI). Install via the Extensions view or code --install-extension.

      To build from source instead (requires Node.js 20+):

      cd editors/vscode
      @@ -3646,14 +3646,14 @@ 

      What Gets CreatedHow It Works

      The plugin wires OpenCode lifecycle events to ctx system:

        -
      • session.created — warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use
      • -
      • tool.execute.after (shell, on git commit) — runs ctx system post-commit
      • -
      • tool.execute.after (edit/write) — runs ctx system check-task-completion
      • -
      • session.idle — runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI)
      • -
      • shell.env — injects CTX_DIR into the agent's shell so ctx commands resolve to the right project
      • -
      • experimental.session.compacting — pushes ctx system bootstrap output into the compaction context so the agent keeps breadcrumbs back to .context/
      • +
      • session.created: warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use.
      • +
      • tool.execute.after (shell, on git commit): runs ctx system post-commit.
      • +
      • tool.execute.after (edit/write): runs ctx system check-task-completion.
      • +
      • session.idle: runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI).
      • +
      • shell.env: injects CTX_DIR into the agent's shell so ctx commands resolve to the right project.
      • +
      • experimental.session.compacting: pushes ctx system bootstrap output into the compaction context so the agent keeps breadcrumbs back to .context/.
      -

      The plugin is a single file with no runtime dependencies — no bun install +

      The plugin is a single file with no runtime dependencies; no bun install needed. OpenCode loads it automatically on launch.

      Context Updates

      # Get AI-optimized context packet
      diff --git a/site/recipes/activating-context/index.html b/site/recipes/activating-context/index.html
      index 593dffffb..7dd5dabba 100644
      --- a/site/recipes/activating-context/index.html
      +++ b/site/recipes/activating-context/index.html
      @@ -2255,12 +2255,9 @@ 
       

      Nested projects, submodules, rogue agent-created .context/ directories, and sub-agent sessions all produced silent misrouting -under the old walk-up model. See the -explicit-context-dir spec -and the analysis doc -for the full reasoning.

      -

      The short version: ctx decided to stop guessing and require the -caller to declare. Every other decision flows from there.

      +under the old walk-up model. ctx decided to stop guessing and +require the caller to declare. Every other decision flows from +there.

      diff --git a/site/recipes/build-a-knowledge-base/index.html b/site/recipes/build-a-knowledge-base/index.html new file mode 100644 index 000000000..43f37400e --- /dev/null +++ b/site/recipes/build-a-knowledge-base/index.html @@ -0,0 +1,1544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Build a Knowledge Base - ctx: do you remember? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + +
      + + + + + + +
      + + + + + + + +
      + +
      + + + + +
      +
      + + + +
      +
      +
      + + + + + + + +
      +
      +
      + + + + + + + +
      + + + + + + + + + +
      + + + + + + +

      Build a Knowledge Base

      + +

      ctx

      +

      The Problem

      +

      You are doing knowledge-shaped work (vendor-spec analysis, a +research project, a post-incident review, domain modeling) and +the standard five context files (TASKS.md, DECISIONS.md, +LEARNINGS.md, CONVENTIONS.md, CONSTITUTION.md) don't fit. +Because those files are tuned for code-development context, not for +evidence-tracked knowledge with confidence bands, +contradictions, and external citations.

      +

      You need a place where:

      +
        +
      • Every claim is pinned to a source you can re-verify.
      • +
      • Topics grow into folders as they earn their depth.
      • +
      • Two passes against the same source don't silently disagree.
      • +
      • The next session knows what's incomplete, not just what's done.
      • +
      +

      That's what the editorial pipeline is for.

      +
      +

      Prefer Skills to Raw Commands

      +

      The pipeline is driven by skills (/ctx-kb-ingest, +/ctx-kb-ask, etc.). The CLI form (ctx kb ingest, etc.) +exists for scripting and for non-Claude environments; the +skill is the natural surface.

      +
      +

      TL;DR

      +
      git init && ctx init                    # lays down the kb + ingest tree
      +ctx kb topic new "Cursor Hooks"         # scaffold a topic folder
      +/ctx-kb-ingest ./docs/cursor-hooks.md "cursor hooks" # editorial pass
      +/ctx-kb-ask "does the kb say hooks fire async?"      # grounded Q&A
      +/ctx-wrap-up                            # ceremony; delegates to /ctx-handover
      +                                        # for the per-session handover
      +
      +

      Commands and Skills Used

      +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ToolTypePurpose
    ctx initCommandScaffold .context/kb/, .context/ingest/, etc.
    ctx kb topic new <name>CommandSole writer of topic-page scaffolds (folder shape)
    ctx kb note "<text>"CommandLightweight capture into .context/ingest/findings.md
    ctx kb reindexCommandRefresh the CTX:KB:TOPICS managed block
    ctx handover writeCommandPer-session handover with closeout fold
    /ctx-kb-ingestSkillMode-aware editorial pass (topic-page/triage/evidence)
    /ctx-kb-askSkillQ&A grounded in the kb
    /ctx-kb-site-reviewSkillMechanical structural audit
    /ctx-kb-groundSkillRe-grounding against external sources
    /ctx-kb-noteSkillCapture a finding for the next ingest pass
    /ctx-wrap-upSkillEnd-of-session ceremony; delegates to the handover step
    +

    Step 0: Initialize and Declare Scope

    +
    git init && ctx init
    +
    +

    ctx init lays down the editorial scaffolding alongside the +standard context files:

    +
    .context/
    +├── kb/
    +│   ├── index.md
    +│   └── topics/.gitkeep
    +├── ingest/
    +│   ├── KB-RULES.md             # editorial constitution
    +│   ├── 00-GROUND.md
    +│   ├── 30-INGEST.md
    +│   ├── 40-ASK.md
    +│   ├── 50-SITE_REVIEW.md
    +│   ├── OPERATOR.md
    +│   ├── PROMPT.md               # hand-fallback router
    +│   ├── closeouts/.gitkeep
    +│   └── schemas/
    +│       └── *.md                # 10 schema templates
    +└── handovers/.gitkeep
    +
    +

    Open .context/kb/index.md and replace the placeholder ## Scope +paragraph with a one-paragraph statement of what this kb covers +and what it does not. /ctx-kb-ingest refuses to run against +an undeclared kb; scope is the precondition.

    +
    +

    Git is required

    +

    ctx init now refuses to run without .git/. The +editorial pipeline's provenance (closeout sha/branch, +evidence-index in-repo SHA pins) depends on it. Run +git init first if the project does not already have one.

    +
    +

    Step 1: Scaffold a Topic

    +

    Topic pages live in folders, not flat files:

    +
    ctx kb topic new "Cursor Hooks"
    +
    +

    This creates .context/kb/topics/cursor-hooks/index.md from the +embedded template. The slug is computed by lowercasing + kebab- +casing; vendor-namespaced shapes like cursor/hooks are +preserved so you can grow into nested topology +(topics/cursor/hooks/, topics/cursor/skills/, +topics/cursor/rules/) without breaking citations.

    +

    ctx kb topic new is the sole writer of topic-page scaffolds. +Skills invoke this command rather than synthesize a scaffold by +hand; the embedded template is the single source of truth.

    +

    Step 2: Run an Editorial Pass

    +
    /ctx-kb-ingest ./inputs/2026-04-12-call.md "cursor hooks"
    +
    +

    The skill begins with a pass-mode declaration:

    +
    +

    Pass-mode: topic-page +Reason: the user supplied one primary source and the intended topic is clear. +Definition of done: create or extend kb/topics/cursor-hooks/index.md, +cite EV rows, run ctx kb site build, record cold-reader orientation.

    +
    +

    Then it:

    +
      +
    1. Resolves sources (paths / URLs / MCP resources) and updates + the source-coverage ledger at + .context/kb/source-coverage.md (a state machine across all + sources the kb has touched).
    2. +
    3. Scans for adjacent incomplete topics in the ledger and + surfaces them so the new page acknowledges sibling gaps.
    4. +
    5. Synthesizes prose section by section into the topic page, + minting EV-### rows in evidence-index.md for every cited + claim.
    6. +
    7. Sets the Confidence floor (the page never claims more + certainty than its weakest cited band).
    8. +
    9. Writes a closeout under + .context/ingest/closeouts/<TS>-ingest-closeout.md with + frontmatter, the cold-reader orientation rubric, and a + ledger-state advance per source.
    10. +
    +

    Three pass modes:

    +
      +
    • topic-page (default): write or extend a topic page.
    • +
    • triage: admit / skip sources against scope; no EV-### minted.
    • +
    • evidence-only: mint EV-### rows tagged evidence-only; + do not touch a topic page (explicit-request-only escape hatch).
    • +
    +

    Mid-pass mode-switching is forbidden: the skill commits to +one mode and aborts cleanly if the work no longer fits.

    +

    Step 3: Q&A Grounded in the KB

    +
    /ctx-kb-ask "does the kb say hooks fire async?"
    +
    +

    /ctx-kb-ask reads the kb's prose, cites EV-### rows, and +refuses to web-jump. If the kb cannot answer, it opens a +Q-### row in outstanding-questions.md and reports the gap, +which a future /ctx-kb-ingest pass can close.

    +

    Step 4: Audit + Re-Ground

    +
    /ctx-kb-site-review        # mechanical structural audit
    +/ctx-kb-ground             # refresh sources listed in grounding-sources.md
    +
    +

    site-review coerces malformed Confidence-band capitalization, +flags malformed closeout frontmatter, and refuses to make +judgment calls that require evidence (those go through ingest).

    +

    ground reads .context/ingest/grounding-sources.md and runs +the equivalent of a fresh fetch + re-extract pass for each +listed source, useful when an upstream source has changed.

    +

    Step 5: Browse the KB Locally

    +

    .context/kb/ is a tree of Markdown files: topic pages live +under topics/<slug>/index.md and cross-cutting artifacts +(glossary.md, evidence-index.md, +outstanding-questions.md, domain-decisions.md, +contradictions.md, timeline.md, source-map.md, +source-coverage.md, relationship-map.md) sit alongside +them. Drop a minimal zensical.toml into .context/kb/ and +hand it to ctx serve:

    +
    ctx serve .context/kb/
    +
    +

    The KB renders the same way the docs site you are reading +right now does. Use the in-place evidence-index links to jump +from a topic page to its EV-### rows and back. The site +build is read-only: no skill or CLI writes through it.

    +

    Step 6: Wrap Up with a Handover

    +

    Run /ctx-wrap-up at session end; it owns the ceremony and +delegates to the handover step (/ctx-handover) as its final +action:

    +
    /ctx-wrap-up "Cursor Hooks deep dive"
    +
    +

    The handover artifact lands at +.context/handovers/<TS>-<slug>.md (timestamped so concurrent +agent runs never overwrite). It folds postdated closeouts +into a ## Folded closeouts section and archives the source +closeout files under .context/archive/closeouts/. The next +session's /ctx-remember reads the latest handover and folds +any closeouts whose generated-at postdates it.

    +

    The legitimate direct-invocation cases for /ctx-handover +are --no-fold for a mid-session checkpoint, or recovery +when a prior session ended before its wrap-up step. For the +underlying CLI, see +ctx handover write.

    +

    How It Ladders Together

    +
    sources you supply
    +       │
    +       ▼
    +/ctx-kb-ingest (mode-declared, source-coverage advanced)
    +       │
    +       ├──▶ topic-page  ──▶ .context/kb/topics/<slug>/index.md
    +       ├──▶ evidence    ──▶ .context/kb/evidence-index.md (EV-###)
    +       ├──▶ side rails  ──▶ glossary.md, contradictions.md,
    +       │                    outstanding-questions.md, timeline.md,
    +       │                    source-map.md, relationship-map.md
    +       └──▶ closeout    ──▶ .context/ingest/closeouts/<TS>-...md
    +                               │
    +                               ▼
    +                       (next session)
    +                               │
    +                               ▼
    +                  /ctx-wrap-up → /ctx-handover folds
    +                  → .context/handovers/<TS>-<slug>.md
    +                  + archives source closeouts under
    +                  .context/archive/closeouts/
    +                               │
    +                               ▼
    +                  /ctx-remember reads handover + postdated
    +                  unfolded closeouts as the recall surface
    +
    +

    What the Editorial Pipeline Is NOT

    +
      +
    • Not a substitute for DECISIONS.md. Project-level + architectural decisions stay in .context/DECISIONS.md. The + kb's domain-decisions.md is a kb-scoped artifact (different + schema, different write authority, different lifecycle).
    • +
    • Not a substitute for LEARNINGS.md. Learnings have author + intent; kb claims have evidence backing. They're different + truth bases; do not cross-feed.
    • +
    • Not for casual notes. Use /ctx-kb-note or ctx kb note + "<text>" to park a finding for the next ingest pass.
    • +
    +

    Reference

    + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + + + + +
    + + + + +
    + + +
    +
    +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/recipes/hub-team/index.html b/site/recipes/hub-team/index.html index 72b3558f9..5e41f0a1f 100644 --- a/site/recipes/hub-team/index.html +++ b/site/recipes/hub-team/index.html @@ -2000,7 +2000,7 @@

    Team Knowledge Bus. The hub server - (ctx hub start, etc.) doesn't need this — but the + (ctx hub start, etc.) doesn't need this, but the client side (ctx connection ..., ctx add --share) lives in a project and does. If you skip activation, those client commands fail with Error: no context @@ -2154,7 +2154,7 @@

    Workflow Tips for Teams

    Delete noisy entries, don't tolerate them. The hub is append-only, but the .context/hub/ mirror on each -client is just markdown. If a shared learning turns out +client is just Markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate entries.jsonl (see Hub operations). Noisy diff --git a/site/recipes/index.html b/site/recipes/index.html index 2ff17f765..63b39b508 100644 --- a/site/recipes/index.html +++ b/site/recipes/index.html @@ -1514,6 +1514,56 @@ + + +

  • + + + + Knowledge Base (Phase KB) + + + + + +
  • @@ -2144,6 +2194,33 @@

    Keeping with private context or multi-repo setups.

    Uses: ctx init, CTX_DIR, .ctxrc, /ctx-status


    +

    Knowledge Base (Phase KB)

    +

    Build a Knowledge Base

    +

    Stand up the editorial pipeline for knowledge-shaped work +(research projects, vendor-spec analysis, post-incident reviews). +Covers the pass-mode contract, source-coverage state-machine +ledger, topic-adjacency pre-flight, cold-reader rubric, +closeout/fold mechanism, and folder-shaped topic pages.

    +

    Uses: ctx init, ctx kb topic new, ctx kb note, +ctx kb reindex, ctx handover write, +/ctx-kb-ingest, /ctx-kb-ask, /ctx-kb-site-review, +/ctx-kb-ground, /ctx-kb-note, /ctx-handover

    +
    +

    Typical KB Session

    +

    The everyday flow once the pipeline is set up: session start +recall, ingest a transcript, ask grounded questions, park +findings, wrap up via the mandatory handover.

    +

    Uses: /ctx-remember, /ctx-kb-ingest, /ctx-kb-ask, +/ctx-kb-note, /ctx-wrap-up, /ctx-handover

    +
    +

    Recover an Aborted KB Session

    +

    What to do when the session ends after one or more editorial +passes but before /ctx-handover. Closeouts survive the abort; +the next session's /ctx-remember reads them as unfolded +postdated artifacts; the next /ctx-handover folds them +retroactively.

    +

    Uses: /ctx-remember, /ctx-handover

    +

    Sessions

    The Complete Session

    Walk through a full ctx session from start to finish:

    diff --git a/site/recipes/multi-tool-setup/index.html b/site/recipes/multi-tool-setup/index.html index d75d56974..f56cb3423 100644 --- a/site/recipes/multi-tool-setup/index.html +++ b/site/recipes/multi-tool-setup/index.html @@ -2485,7 +2485,7 @@

    OpenCodeOpenCode Is a First-Class Citizen

    With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, -and persists at session end — no manual steps needed.

    +and persists at session end, with no manual steps needed.

    VS Code

    Install the ctx extension from the diff --git a/site/recipes/publishing/index.html b/site/recipes/publishing/index.html index 89e9418d1..cc781e779 100644 --- a/site/recipes/publishing/index.html +++ b/site/recipes/publishing/index.html @@ -2183,7 +2183,7 @@

    Commands and Skills Used ctx journal import Command -Import session JSONL to editable markdown +Import session JSONL to editable Markdown ctx journal site @@ -2235,7 +2235,7 @@

    Commands and Skills UsedThe Workflow

    Step 1: Import Sessions to Markdown

    Raw session data lives as JSONL files in Claude Code's internal storage. The -first step is converting these into readable, editable markdown.

    +first step is converting these into readable, editable Markdown.

    # Import all sessions from the current project
     ctx journal import --all
     
    diff --git a/site/recipes/recover-aborted-session/index.html b/site/recipes/recover-aborted-session/index.html
    new file mode 100644
    index 000000000..a5b370c56
    --- /dev/null
    +++ b/site/recipes/recover-aborted-session/index.html
    @@ -0,0 +1,1364 @@
    +
    +
    +
    +  
    +    
    +      
    +      
    +      
    +        
    +      
    +      
    +        
    +      
    +      
    +        
    +      
    +      
    +      
    +      
    +        
    +      
    +      
    +      
    +      
    +    
    +    
    +      
    +        Recover an Aborted KB Session - ctx: do you remember?
    +      
    +    
    +    
    +      
    +        
    +      
    +      
    +      
    +        
    +          
    +        
    +        
    +      
    +      
    +
    +
    +    
    +    
    +      
    +    
    +    
    +      
    +        
    +        
    +        
    +        
    +        
    +      
    +    
    +    
    +      
    +    
    +    
    +    
    +      
    +
    +    
    +    
    +  
    +  
    +  
    +    
    +    
    +      
    +    
    +    
    +    
    +    
    +    
    +  
    +    
    +    
    +    
    +    
    +    
    +    
    + +
    + + + + + + +
    + + + + + + + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + + + + + +
    + + + + + + + + + +
    + + + + + + +

    Recover an Aborted KB Session

    + +

    ctx

    +

    The Problem

    +

    You ran one or more /ctx-kb-ingest passes, then the session +ended before /ctx-wrap-up. Maybe you closed the laptop, +the connection dropped, or you just forgot the wrap-up step.

    +

    You come back the next day and ask "do you remember?" and +the agent picks up the previous handover, but the editorial +work since the last handover seems to be missing from the +readback.

    +

    It isn't missing. It's unfolded. Here's how the pipeline +handles it and how to close the loop manually.

    +

    TL;DR

    +
    /ctx-remember                                       # picks up the unfolded 
    +                                                    # closeouts automatically
    +/ctx-handover "recovery: fold the orphan closeouts" # direct invocation is 
    +                                                    # appropriate for recovery
    +
    +

    The recovery path is the one legitimate place to invoke +/ctx-handover directly. Normally /ctx-wrap-up owns +session-end and delegates to the handover step; the abort +broke that path, so a hand-rolled handover invocation is how +you close the loop without re-running the full wrap-up +ceremony.

    +

    How the Fold Mechanism Survives an Abort

    +

    Two artifacts make abort-recovery work without any cleanup:

    +
      +
    1. +

      Closeouts are immutable once written. Every editorial + pass writes a closeout under + .context/ingest/closeouts/<TS>-<mode>-closeout.md before + the pass reports done. If the session dies, the closeout + is already on disk.

      +
    2. +
    3. +

      /ctx-remember folds unfolded closeouts into the + readback. The skill always reads the latest handover. + When .context/kb/ exists, it additionally reads any + closeouts whose generated-at postdates the handover. + The ## What changed and ## Source-coverage updates + sections from each unfolded closeout are surfaced in + recall.

      +
    4. +
    +

    So an aborted session never loses editorial work; it just +delays the handover fold by one session.

    +

    Step 1: Confirm the Orphan Closeouts

    +
    ls -la .context/ingest/closeouts/
    +
    +

    Files there with generated-at postdating your latest +handover are the unfolded ones. You can read any closeout +directly to see what it claims about its pass:

    +
    cat .context/ingest/closeouts/<TS>-ingest-closeout.md
    +
    +

    Look at:

    +
      +
    • The Pass-mode body block (Declared / Reason / + Definition of done / Result): what the pass committed to + and whether it claimed success or deferred.
    • +
    • The Source-coverage updates section: what state + transitions hit the ledger.
    • +
    • The Next pass hint: the exact resumption invocation + the closeout recommends, if the pass deferred.
    • +
    +

    Step 2: Run /ctx-remember

    +
    /ctx-remember
    +
    +

    The readback will include the editorial-state summary as part +of the standard readback shape. If everything looks +consistent, proceed to Step 3.

    +

    If the readback surfaces something surprising (a closeout +claiming topic-page: produced for a slug whose file is +missing, a comprehensive ledger advance against a source +whose page is speculative, etc.), fix the underlying +inconsistency before folding. (Doctor advisories for these +shapes are on the Phase-7 backlog.)

    +

    Step 3: Write the Recovery Handover

    +

    This step is the one legitimate direct invocation of +/ctx-handover. In normal session-end the call goes through +/ctx-wrap-up; here the prior session aborted, so you reach +for the handover step directly to retire the orphan +closeouts:

    +
    /ctx-handover "recovery: fold orphan closeouts from yesterday"
    +
    +

    Or via the CLI:

    +
    ctx handover write "recovery: fold orphan closeouts from yesterday" \
    +  --summary "Folded N orphan closeouts from the aborted session." \
    +  --next "Resume <topic> per the closeout's Next pass hint."
    +
    +

    The handover:

    +
      +
    • Reads the latest handover cursor.
    • +
    • Finds all closeouts whose generated-at is after the cursor.
    • +
    • Folds their summaries into a ## Folded closeouts section.
    • +
    • Archives the source closeout files under + .context/archive/closeouts/ (closeouts are + append-never-rewrite; archival moves bytes but does not + modify them).
    • +
    +

    After the handover lands, the orphan closeouts are now durably +tied to a session boundary; the next /ctx-remember reads +just the new handover (and any closeouts postdating it), +without re-folding the recovered ones.

    +

    Edge Cases

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    CaseBehavior
    Closeout has malformed frontmatterHandover fold skips it with a warning to stderr. Hand-edit the malformed file (typically a missing generated-at) and re-run ctx handover write to fold it next time.
    Closeout's generated-at is before the last handover but was never foldedTreated as already-folded (silently skipped; the cursor is the source of truth). If you genuinely want to re-fold it, hand-edit the closeout's generated-at forward.
    You aborted during an ingest pass, before its closeout was writtenNo closeout exists; the pass left no recall residue. Treat the source(s) as un-ingested and re-run /ctx-kb-ingest. The source-coverage ledger row may show stale residue from a prior pass; the next ingest will advance it correctly.
    Multiple sessions piled up unfolded closeoutsOne handover run folds them all in a single shot. The fold is cursor-driven, not session-driven.
    You want recall without consuming closeoutsctx handover write ... --no-fold writes a handover with frontmatter but leaves the closeouts in place. The next handover (without --no-fold) folds everything postdating the latest handover cursor.
    +

    When This Matters

    +
      +
    • After a network drop / laptop close mid-session.
    • +
    • When you ran /ctx-kb-ingest from a sub-agent that + finished without calling /ctx-handover.
    • +
    • After porting work from another environment (e.g. you + rsynced .context/ingest/closeouts/ from a different + machine) and want to integrate the work into the destination + project's recall thread.
    • +
    +

    Reference

    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + +
    + + + + +
    + +
    +
    +
    +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/recipes/session-lifecycle/index.html b/site/recipes/session-lifecycle/index.html index 34b83d406..d36887229 100644 --- a/site/recipes/session-lifecycle/index.html +++ b/site/recipes/session-lifecycle/index.html @@ -2391,6 +2391,16 @@

    Step 7: Persist Before Ending/ctx-remember reads this +file as the authoritative recall surface; skipping +/ctx-wrap-up means the next session has no handover to +read and recall degrades to probabilistic reconstruction +from canonical files plus journal.

    Session transcripts are automatically captured by Claude Code and can be browsed later with ctx journal source and ctx journal source --show.


    diff --git a/site/recipes/steering/index.html b/site/recipes/steering/index.html index 585731326..3f6d2a8b3 100644 --- a/site/recipes/steering/index.html +++ b/site/recipes/steering/index.html @@ -2106,7 +2106,7 @@

    Start Here: Customize the Fou

    Each file opens with an inline HTML comment that explains the three inclusion modes, what priority means, and the tools scope. The comment is invisible in -rendered markdown but visible when you edit the file. +rendered Markdown but visible when you edit the file. Delete it once the file is yours.

    All four default to inclusion: always and priority: 10, so they fire on every AI tool call until you customize diff --git a/site/recipes/typical-kb-session/index.html b/site/recipes/typical-kb-session/index.html new file mode 100644 index 000000000..227c9dcfc --- /dev/null +++ b/site/recipes/typical-kb-session/index.html @@ -0,0 +1,1414 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Typical KB Session - ctx: do you remember? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    +
    + +
    + + + + + + +
    + + + + + + + +
    + +
    + + + + +
    +
    + + + +
    +
    +
    + + + + + + + +
    +
    +
    + + + + + + + +
    + + + + + + + + + +
    + + + + + + +

    Typical KB Session

    + +

    ctx

    +

    The Problem

    +

    You set the editorial pipeline up +(Build a Knowledge Base). Now you +sit down for a real research session: a transcript to ingest, a +question to answer against existing evidence, a finding to +capture for later. What's the actual flow?

    +

    TL;DR

    +
    /ctx-remember                                    # session-start recall
    +/ctx-kb-ingest ./inputs/transcript.md "topic"    # editorial pass
    +/ctx-kb-ask "does the kb say X?"                 # grounded Q&A
    +/ctx-kb-note "follow-up: chase the v1.1 link"    # park a finding
    +/ctx-wrap-up                                     # ceremony → /ctx-handover
    +
    +

    Commands and Skills Used

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    ToolTypePurpose
    /ctx-rememberSkillSession-start recall (folds KB state when present)
    /ctx-kb-ingestSkillMode-aware editorial pass
    /ctx-kb-askSkillQ&A grounded in the kb
    /ctx-kb-noteSkillPark a finding for the next ingest
    /ctx-wrap-upSkillEnd-of-session ceremony; delegates to the handover step
    /ctx-handoverSkillWrites the per-session handover; called by /ctx-wrap-up
    +

    Step 1: Session Start (Recall)

    +
    /ctx-remember
    +
    +

    /ctx-remember reads the latest handover under +.context/handovers/ (timestamped <TS>-<slug>.md so +concurrent agent runs never overwrite); its ## Summary and +## Next session are the authoritative recall surface. The +five canonical files (TASKS, DECISIONS, etc.) are read as +usual.

    +

    When .context/kb/ exists, /ctx-remember additionally folds +editorial state into the readback: any closeouts whose +generated-at postdates the handover are read for their +## What changed sections (these are unfolded passes the +last handover did not yet consume).

    +

    SESSION_LOG.md is not read at session start; it is +mid-flight working memory, not a recall surface.

    +

    Step 2: Ingest the Sources You Brought

    +
    /ctx-kb-ingest ./inputs/2026-05-15-call.md "cursor hooks"
    +
    +

    The skill declares its mode up front (most often +topic-page), resolves sources, scans the +source-coverage ledger for adjacent incomplete topics, +and synthesizes prose into the topic page section by section. +Every cited claim mints an EV-### row in +evidence-index.md with the source short-name + locator + +optional sha: pin for in-repo files.

    +

    The pass ends with a circuit-breaker check (file exists, +cites ≥ 1 EV-###, site builds clean, cold-reader rubric at +pass) and writes a closeout.

    +

    If the skill reports topic-page: deferred instead of +produced, look at the closeout's Next pass hint. It +names the exact resumption invocation.

    +

    Step 3: Ask Grounded Questions

    +
    /ctx-kb-ask "does the kb say hooks block until they exit?"
    +
    +

    /ctx-kb-ask reads the kb's prose and answers with EV-### +citations. If the kb cannot answer, it opens a Q-### row +in outstanding-questions.md and reports the gap rather than +inventing.

    +

    Step 4: Park Findings for Later

    +
    /ctx-kb-note "check whether SIGTERM behavior changed in v1.2"
    +
    +

    /ctx-kb-note appends one-liners to +.context/ingest/findings.md, a lightweight surface for +parking ideas that don't earn a full ingest pass right now. +The next /ctx-kb-ingest can choose to absorb them.

    +

    Step 5: Wrap Up

    +
    /ctx-wrap-up "Cursor Hooks: lifecycle deep dive"
    +
    +

    /ctx-wrap-up runs the standard capture checklist (learnings, +decisions, conventions, tasks) and delegates to +/ctx-handover as its final step. In a KB session it +additionally:

    +
      +
    • Surfaces pending closeouts under + .context/ingest/closeouts/.
    • +
    • Counts open rows in outstanding-questions.md.
    • +
    +

    The handover artifact lands at +.context/handovers/<TS>-<slug>.md (timestamped so concurrent +agent runs never overwrite). The handover folds postdated +closeouts into a ## Folded closeouts section and archives +them under .context/archive/closeouts/. Editorial work that +was incomplete at wrap-up (open Q-### rows, topic-page: +deferred passes) is surfaced as recall on the next session +start.

    +

    Common Shapes

    +

    Multiple Topics in One Session

    +

    Run /ctx-kb-ingest once per topic. Each pass writes its own +closeout; the handover folds all of them at the end.

    +

    Mid-Session Checkpoint

    +
    ctx handover write "Mid-day checkpoint" \
    +  --summary "..." --next "..." --no-fold
    +
    +

    --no-fold writes the handover without consuming closeouts, +useful when you want a recall anchor mid-session without +ending the editorial chunking.

    +

    Aborted Session

    +

    If you close the laptop after an ingest pass but before +/ctx-wrap-up, the closeouts stay in place. The next +session's /ctx-remember reads them as unfolded postdated +closeouts; the next wrap-up's handover step folds them +normally. See +Recover an Aborted Session for +the failure-mode detail.

    +

    Reference

    + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + +
    + + + +
    + +
    + + + + +
    + +
    +
    +
    +
    + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/reference/comparison/index.html b/site/reference/comparison/index.html index 98365855c..bacc4a2fe 100644 --- a/site/reference/comparison/index.html +++ b/site/reference/comparison/index.html @@ -1926,7 +1926,7 @@

    Enterprise Context Platforms + + + Knowledge Base (Phase KB) + + + + + +

  • @@ -2462,6 +2545,89 @@ +
  • + +
  • + + + + Knowledge Base (Phase KB) + + + + + +
  • @@ -2764,6 +2930,36 @@

    All Skills/ctx-kb-ingest +Editorial KB pass (topic-page / triage / evidence-only) +user-invocable + + +/ctx-kb-ask +Q&A grounded in the KB; refuses to web-jump +user-invocable + + +/ctx-kb-site-review +Mechanical KB structural audit +user-invocable + + +/ctx-kb-ground +Re-ground the KB against listed external sources +user-invocable + + +/ctx-kb-note +Park a finding in ingest/findings.md +user-invocable + + +/ctx-handover +Handover step delegated by /ctx-wrap-up; folds postdated closeouts +sub-mechanism +
    @@ -2826,13 +3022,20 @@

    /ctx-wrap-up

    Wraps: git diff --stat, git log, ctx learning add, ctx decision add, ctx convention add, ctx task add, -chains to /ctx-commit

    +chains to /ctx-commit, delegates to /ctx-handover

    See also: Session Ceremonies, -The Complete Session

    +The Complete Session, +/ctx-handover


    Context Persistence

    Skills for recording work artifacts: tasks, decisions, learnings, @@ -2972,7 +3175,7 @@

    /ctx-doctorctx hook event CLI


    -

    Scan all markdown files under docs/ for broken links. Three passes: +

    Scan all Markdown files under docs/ for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, @@ -3032,8 +3235,9 @@

    --brief <path> flag/ctx-resumePausing Context Hooks


    +

    Knowledge Base (Phase KB)

    +

    Skills for the editorial knowledge-ingestion pipeline. Active when +.context/kb/ exists (laid down by ctx init). The pipeline gives +you evidence-tracked knowledge with confidence bands, folder-shaped +topic pages, a source-coverage state machine, and per-session +handovers that fold postdated closeouts.

    +

    See the +Build a Knowledge Base recipe +for the full workflow. The editorial constitution lives at +.context/ingest/KB-RULES.md.

    +

    /ctx-kb-ingest

    +

    Mode-aware editorial pass. Declares its pass-mode +(topic-page / triage / evidence-only) up front, scans the +source-coverage ledger for adjacent incomplete topics, synthesizes +prose into .context/kb/topics/<slug>/index.md, mints EV-### +rows in evidence-index.md, runs a four-invariant completion +circuit breaker, and writes a closeout under +.context/ingest/closeouts/. Refuses on empty input.

    +

    Wraps: ctx kb ingest, ctx kb topic new, the writer +packages under internal/write/kb/.

    +

    Trigger phrases: "ingest the transcripts", "pull this into the +kb", "add evidence from"

    +

    See also: +Build a Knowledge Base, +Typical KB Session, +ctx kb CLI

    +
    +

    /ctx-kb-ask

    +

    Q&A grounded in the KB. Cites EV-### rows; refuses to web-jump. +When the KB cannot answer, opens a Q-### row in +outstanding-questions.md rather than inventing. Refuses on empty +question.

    +

    Wraps: ctx kb ask, reads .context/kb/*.md

    +

    Trigger phrases: "does the kb say", "according to evidence"

    +

    See also: ctx kb CLI

    +
    +

    /ctx-kb-site-review

    +

    Mechanical structural audit. Coerces malformed Confidence-band +capitalization, flags malformed closeout frontmatter, refuses +judgment calls that require evidence (those go through ingest).

    +

    Wraps: ctx kb site-review

    +

    Trigger phrases: "audit the kb", "check kb for rot"

    +

    See also: ctx kb CLI

    +
    +

    /ctx-kb-ground

    +

    External re-grounding pass. Reads +.context/ingest/grounding-sources.md and refreshes each listed +source. Refuses cleanly when the file is absent or empty.

    +

    Wraps: ctx kb ground

    +

    Trigger phrases: "re-ground the kb", "check upstream"

    +

    See also: ctx kb CLI

    +
    +

    /ctx-kb-note

    +

    Lightweight capture into .context/ingest/findings.md. Never +writes to a topic page or evidence-index.md. Use for parking +findings the next ingest pass should absorb.

    +

    Wraps: ctx kb note "<text>"

    +

    Trigger phrases: "drop a note", "park this finding"

    +

    See also: ctx kb CLI

    +
    +

    /ctx-handover

    +

    Per-session handover artifact writer; the sub-mechanism that +/ctx-wrap-up delegates to as its final step. Collects +--summary (past tense) and --next (future tense, specific) +and calls ctx handover write. Writes the handover to +.context/handovers/<TS>-<slug>.md (timestamped so concurrent +agent runs never overwrite). Folds postdated closeouts into a +## Folded closeouts section and physically archives the +source closeouts under .context/archive/closeouts/ (closeouts +are append-never-rewrite; archival moves bytes but does not +modify them). --no-fold skips the fold for mid-session +checkpoints.

    +

    Mandatory tail of /ctx-wrap-up. Direct invocation is +reserved for --no-fold mid-session checkpoints and recovery +after an aborted session.

    +

    Wraps: ctx handover write <title> --summary X --next Y

    +

    See also: +/ctx-wrap-up, +Typical KB Session, +Recover an Aborted KB Session, +ctx handover CLI

    +

    Project-Specific Skills

    The ctx plugin ships the skills listed above. Teams can add their own project-specific skills to .claude/skills/ in the diff --git a/site/search.json b/site/search.json index 1a4e293d3..85f0ce069 100644 --- a/site/search.json +++ b/site/search.json @@ -1 +1 @@ -{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Manifesto","text":"","path":["Manifesto"],"tags":[]},{"location":"#the-ctx-manifesto","level":1,"title":"The ctx Manifesto","text":"

    Creation, not code.

    Context, not prompts.

    Verification, not vibes.

    This Is NOT a Metaphor

    Code executes instructions.

    Creation produces outcomes.

    Confusing the two is how teams ship motion...

    ...instead of progress.

    • It was never about the code.
    • Code has zero standalone value.
    • Code is an implementation detail.

    Code is an incantation.

    Creation is the act.

    And creation does not happen in a vacuum.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-the-substrate","level":2,"title":"ctx Is the Substrate","text":"

    Constraints Have Moved

    Human bandwidth is no longer the limiting factor.

    Context integrity is.

    Human bandwidth is no longer the constraint.

    Context is:

    • Without durable context, intelligence resets.
    • Without memory, reasoning decays.
    • Without structure, scale collapses.

    Creation is now limited by:

    • Clarity of intent;
    • Quality of context;
    • Rigor of verification.

    Not by speed.

    Not by capacity.

    Velocity Amplifies

    Faster execution on broken context compounds error.

    Speed multiplies whatever is already wrong.

    ","path":["Manifesto"],"tags":[]},{"location":"#humans-author-meaning","level":2,"title":"Humans Author Meaning","text":"

    Intent Is Authored

    Systems can optimize.

    Models can generalize.

    Meaning must be chosen.

    Intent is not emergent.

    Vision, goals, and direction are human responsibilities.

    We decide:

    • What matters;
    • What success means;
    • What world we are building.

    ctx encodes the intent so it...

    • survives time,
    • survives handoffs,
    • survives scale.

    Nothing important should live only in conversation.

    Nothing critical should depend on recall.

    Oral Tradition Does Not Scale

    If intent cannot be inspected, it cannot be enforced.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-before-action","level":2,"title":"ctx Before Action","text":"

    Orientation Precedes Motion

    Acting first and understanding later is not bravery.

    It is debt.

    Never act without ctx.

    Before execution, we must verify:

    • Where we are;
    • Why we are here;
    • What constraints apply;
    • What assumptions are active.

    Action without ctx is gambling.

    Speed without orientation is noise.

    ctx is not overhead: It is the cost of correctness.

    ","path":["Manifesto"],"tags":[]},{"location":"#persistent-context-beats-prompt-memory","level":2,"title":"Persistent Context Beats Prompt Memory","text":"

    Transience Is the Default Failure Mode

    • Prompts decay.
    • Chats fragment.
    • Memory heuristics drift.

    Prompts are transient.

    Chats are lossy.

    Memory heuristics drift.

    ctx must be:

    • Durable;
    • Structured;
    • Explicit;
    • Queryable.

    Intent Must Be Intentional

    If intent exists only in a prompt...

    ...alignment is already degrading.

    Knowledge lives in the artifacts:

    • Decisions;
    • Documentation;
    • Dependency maps;
    • Evaluation history.

    Artifacts Outlive Sessions

    What is not written will be re-learned.

    At full cost.

    ","path":["Manifesto"],"tags":[]},{"location":"#what-ctx-is-not","level":2,"title":"What ctx Is Not","text":"

    Avoid Category Errors

    Mislabeling ctx guarantees misuse.

    ctx is not a memory feature.

    • ctx is not prompt engineering.
    • ctx is not a productivity hack.
    • ctx is not automation theater.

    ctx is a system for preserving intent under scale.

    ctx is infrastructure.

    ","path":["Manifesto"],"tags":[]},{"location":"#verified-reality-is-the-scoreboard","level":2,"title":"Verified Reality Is the Scoreboard","text":"

    Activity Is a False Proxy

    Output volume correlates poorly with impact.

    • Code is not progress.
    • Activity is not impact.

    The only truth that compounds is verified change.

    Verified change must exist in the real world.

    Hypotheses are cheap; outcomes are not.

    ctx captures:

    • What we expected;
    • What we observed;
    • Where reality diverged.

    If we cannot predict, measure, and verify the result...

    ...it does not count.

    ","path":["Manifesto"],"tags":[]},{"location":"#build-to-learn-not-to-accumulate","level":2,"title":"Build to Learn, Not to Accumulate","text":"

    Prototypes Have an Expiration Date

    A prototype's value is information, not longevity.

    Prototypes exist to reduce uncertainty.

    We build to:

    • Test assumptions;
    • Validate architecture;
    • Answer specific questions.

    Not everything.

    Not blindly.

    Not permanently.

    ctx records archeology so the cost is paid once.

    ","path":["Manifesto"],"tags":[]},{"location":"#failures-are-assets","level":2,"title":"Failures Are Assets","text":"

    Failure without Capture Is Waste

    Pain that does not teach is pure loss.

    Failures are not erased: They are preserved.

    Each failure becomes:

    • A documented hypothesis;
    • An analyzed deviation;
    • A permanent artifact.

    Rollback fixes symptoms: ctx fixes systems.

    A repeated mistake is a missing ctx artifact.

    ","path":["Manifesto"],"tags":[]},{"location":"#structure-enables-scale","level":2,"title":"Structure Enables Scale","text":"

    Unbounded Autonomy Destabilizes

    Power without a structure produces chaos.

    Transpose it:

    Power without any structure becomes chaos.

    ctx defines:

    • Roles;
    • Boundaries;
    • Protocols;
    • Escalation paths;
    • Decision rights.

    Ambiguity is a system failure:

    • Debates must be structured.
    • Decisions must be explicit.
    • History must be retained.
    ","path":["Manifesto"],"tags":[]},{"location":"#encode-intent-into-the-environment","level":2,"title":"Encode Intent into the Environment","text":"

    Goodwill Does Not Belong to the Table

    Alignment that depends on memory will drift.

    Alignment cannot depend on memory or goodwill.

    Do not rely on people to remember.

    Encode the behavior, so it happens by default.

    Intent is encoded as:

    • Policies;
    • Schemas;
    • Constraints;
    • Evaluation harnesses.

    Rules must be machine-readable.

    Laws must be enforceable.

    If intent is implicit, drift is guaranteed.

    ","path":["Manifesto"],"tags":[]},{"location":"#cost-is-a-first-class-signal","level":2,"title":"Cost Is a First-Class Signal","text":"

    Attention Is the Scarcest Resource

    Not ideas.

    Not ambition.

    Ideas do not compete on time:

    They compete on cost and impact:

    • Attention is finite.
    • Compute is finite.
    • Context is expensive.

    We continuously ask:

    • What the most valuable next action is.
    • What outcome justifies the cost.

    ctx guides allocation.

    Learning reshapes priority.

    ","path":["Manifesto"],"tags":[]},{"location":"#show-the-why","level":2,"title":"Show the Why","text":"

    {} (code, artifacts, apps, binaries) produce outputs; they do not preserve reasoning.

    Systems that cannot explain themselves will not be trusted.

    Traceability builds trust.

         {} --> what\n\n    ctx --> why\n

    We record:

    • Explored paths;
    • Rejected options;
    • Assumptions made;
    • Evidence used.

    Opaque systems erode trust:

    Transparent ctx compounds understanding.

    ","path":["Manifesto"],"tags":[]},{"location":"#continuously-verify-the-system","level":2,"title":"Continuously Verify the System","text":"

    Stability Is Temporary

    Every assumption has a half-life:

    • Models drift.
    • Tools change.
    • Assumptions rot.

    ctx must be verified against reality.

    Trust is a spectrum.

    Trust is continuously re-earned:

    • Benchmarks,
    • regressions,
    • and evaluations...

    ...are safety rails.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-leverage","level":2,"title":"ctx Is Leverage","text":"

    Humans Are Decision Engines

    Execution should not consume judgment.

    Humans must not be typists.

    We are the authors.

    Human effort is reserved for:

    • Judgment;
    • Design;
    • Taste;
    • Synthesis.

    Repetition is delegated.

    Toil is automated.

    ctx preserves leverage across time.

    ","path":["Manifesto"],"tags":[]},{"location":"#the-thesis","level":2,"title":"The Thesis","text":"

    Invariant

    Everything else is an implementation detail.

    • Creation is the act.
    • ctx is the substrate.
    • Verification is the truth.

    Code executes → Models reason → Agents amplify.

    ctx lives on.

    • Without ctx, intelligence resets.
    • With ctx, creation compounds.
    ","path":["Manifesto"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"

    Stories, insights, and lessons learned from building and using ctx.

    ","path":["Blog"],"tags":[]},{"location":"blog/#releases","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v080-the-architecture-release","level":3,"title":"ctx v0.8.0: The Architecture Release","text":"

    March 23, 2026: 374 commits, 1,708 Go files touched, and a near-complete architectural overhaul. Every CLI package restructured into cmd/ + core/ taxonomy, all user-facing strings externalized to YAML, MCP server for tool-agnostic AI integration, and the memory bridge connecting Claude Code's auto-memory to .context/.

    Topics: release, architecture, refactoring, MCP, localization

    ","path":["Blog"],"tags":[]},{"location":"blog/#field-notes","level":2,"title":"Field Notes","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-watermelon-rind-anti-pattern-why-smarter-tools-make-shallower-agents","level":3,"title":"The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents","text":"

    April 6, 2026: Give an agent a graph query tool, and it produces output that's structurally correct but substantively hollow (the watermelon-rind antipattern: We ran three sessions analyzing the same codebase with different tool access: the one with no tools produced 5.2x more depth. The fix: a two-pass compiler for architecture understanding: force code reading first, verify with tools second. Constraint is the feature.

    Topics: architecture, code intelligence, agent behavior, design patterns, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#code-structure-as-an-agent-interface-what-19-ast-tests-taught-us","level":3,"title":"Code Structure as an Agent Interface: What 19 AST Tests Taught Us","text":"

    April 2, 2026: We built 19 AST-based audit tests in a single session, touching 300+ files. In the process we discovered that \"old-school\" code quality constraints (no magic numbers, centralized error handling, 80-char lines, documentation) are exactly the constraints that make code readable to AI agents. If an agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.

    Topics: ast, code quality, agent readability, conventions, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#we-broke-the-31-rule","level":3,"title":"We Broke the 3:1 Rule","text":"

    March 23, 2026: After v0.6.0, we ran 198 feature commits across 17 days before consolidating. The 3:1 rule says consolidate every 4th session. We did it after the 66th. The result: an 18-day, 181-commit cleanup marathon that took longer than the feature run itself. A follow-up to The 3:1 Ratio with empirical evidence from the v0.8.0 cycle.

    Topics: consolidation, technical debt, development workflow, convention drift, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#context-engineering","level":2,"title":"Context Engineering","text":"","path":["Blog"],"tags":[]},{"location":"blog/#agent-memory-is-infrastructure","level":3,"title":"Agent Memory Is Infrastructure","text":"

    March 4, 2026: Every AI coding agent starts fresh. The obvious fix is \"memory.\" But there's a different problem memory doesn't touch: the project itself accumulates knowledge that has nothing to do with any single session. This post argues that agent memory is L2 (runtime cache); what's missing is L3 (project infrastructure).

    Topics: context engineering, agent memory, infrastructure, persistence, team knowledge

    ","path":["Blog"],"tags":[]},{"location":"blog/#context-as-infrastructure","level":3,"title":"Context as Infrastructure","text":"

    February 17, 2026: Where does your AI's knowledge live between sessions? If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. This post argues for treating it as infrastructure instead: persistent files, separation of concerns, two-tier storage, progressive disclosure, and the filesystem as the most mature interface available.

    Topics: context engineering, infrastructure, progressive disclosure, persistence, design philosophy

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-attention-budget-why-your-ai-forgets-what-you-just-told-it","level":3,"title":"The Attention Budget: Why Your AI Forgets What You Just Told It","text":"

    February 3, 2026: Every token you send to an AI consumes a finite resource: the attention budget. Understanding this constraint shaped every design decision in ctx: hierarchical file structure, explicit budgets, progressive disclosure, and filesystem-as-index.

    Topics: attention mechanics, context engineering, progressive disclosure, ctx primitives, token budgets

    ","path":["Blog"],"tags":[]},{"location":"blog/#before-context-windows-we-had-bouncers","level":3,"title":"Before Context Windows, We Had Bouncers","text":"

    February 14, 2026: IRC is stateless. You disconnect, you vanish. Modern systems are not much different. This post traces the line from IRC bouncers to context engineering: stateless protocols require stateful wrappers, volatile interfaces require durable memory.

    Topics: context engineering, infrastructure, IRC, persistence, state continuity

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-last-question","level":3,"title":"The Last Question","text":"

    February 28, 2026: In 1956, Asimov wrote a story about a question that spans the entire future of the universe. A reading of \"The Last Question\" through the lens of persistence, substrate migration, and what it means to build systems where sessions don't reset.

    Topics: context continuity, long-lived systems, persistence, intelligence over time, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#agent-behavior-and-design","level":2,"title":"Agent Behavior and Design","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-dog-ate-my-homework-teaching-ai-agents-to-read-before-they-write","level":3,"title":"The Dog Ate My Homework: Teaching AI Agents to Read Before They Write","text":"

    February 25, 2026: You wrote the playbook. The agent skipped all of it. Five sessions, five failure modes, and the discovery that observable compliance beats perfect compliance.

    Topics: hooks, agent behavior, context engineering, behavioral design, testing methodology, compliance monitoring

    ","path":["Blog"],"tags":[]},{"location":"blog/#skills-that-fight-the-platform","level":3,"title":"Skills That Fight the Platform","text":"

    February 4, 2026: When custom skills conflict with system prompt defaults, the AI has to reconcile contradictory instructions. Five conflict patterns discovered while building ctx.

    Topics: context engineering, skill design, system prompts, antipatterns, AI safety primitives

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-anatomy-of-a-skill-that-works","level":3,"title":"The Anatomy of a Skill That Works","text":"

    February 7, 2026: I had 20 skills. Most were well-intentioned stubs. Then I rewrote all of them. Seven lessons emerged: quality gates prevent premature execution, negative triggers are load-bearing, examples set boundaries better than rules.

    Topics: skill design, context engineering, quality gates, E/A/R framework, practical patterns

    ","path":["Blog"],"tags":[]},{"location":"blog/#you-cant-import-expertise","level":3,"title":"You Can't Import Expertise","text":"

    February 5, 2026: I found a well-crafted consolidation skill. Applied my own E/A/R framework: 70% was noise. This post is about why good skills can't be copy-pasted, and how to grow them from your project's own drift history.

    Topics: skill adaptation, E/A/R framework, convention drift, consolidation, project-specific expertise

    ","path":["Blog"],"tags":[]},{"location":"blog/#not-everything-is-a-skill","level":3,"title":"Not Everything Is a Skill","text":"

    February 8, 2026: I ran an 8-agent codebase audit and got actionable results. The natural instinct was to wrap the prompt as a skill. Then I applied my own criteria: it failed all three tests.

    Topics: skill design, context engineering, automation discipline, recipes, agent teams

    ","path":["Blog"],"tags":[]},{"location":"blog/#defense-in-depth-securing-ai-agents","level":3,"title":"Defense in Depth: Securing AI Agents","text":"

    February 9, 2026: The security advice was \"use CONSTITUTION.md for guardrails.\" That is wishful thinking. Five defense layers for unattended AI agents, each with a bypass, and why the strength is in the combination.

    Topics: agent security, defense in depth, prompt injection, autonomous loops, container isolation

    ","path":["Blog"],"tags":[]},{"location":"blog/#development-practice","level":2,"title":"Development Practice","text":"","path":["Blog"],"tags":[]},{"location":"blog/#code-is-cheap-judgment-is-not","level":3,"title":"Code Is Cheap. Judgment Is Not.","text":"

    February 17, 2026: AI does not replace workers. It replaces unstructured effort. Three weeks of building ctx with an AI agent proved it: YOLO mode showed production is cheap, the 3:1 ratio showed judgment has a cadence.

    Topics: AI and expertise, context engineering, judgment vs production, human-AI collaboration, automation discipline

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-31-ratio","level":3,"title":"The 3:1 Ratio","text":"

    February 17, 2026: AI makes technical debt worse: not because it writes bad code, but because it writes code so fast that drift accumulates before you notice. Three feature sessions, one consolidation session.

    Topics: consolidation, technical debt, development workflow, convention drift, code quality

    ","path":["Blog"],"tags":[]},{"location":"blog/#refactoring-with-intent-human-guided-sessions-in-ai-development","level":3,"title":"Refactoring with Intent: Human-Guided Sessions in AI Development","text":"

    February 1, 2026: The YOLO mode shipped 14 commands in a week. But technical debt doesn't send invoices. This is the story of what happened when we started guiding the AI with intent.

    Topics: refactoring, code quality, documentation standards, module decomposition, YOLO versus intentional development

    ","path":["Blog"],"tags":[]},{"location":"blog/#how-deep-is-too-deep","level":3,"title":"How Deep Is Too Deep?","text":"

    February 12, 2026: I kept feeling like I should go deeper into ML theory. Then I spent a week debugging an agent failure that had nothing to do with model architecture. When depth compounds and when it doesn't.

    Topics: AI foundations, abstraction boundaries, agentic systems, context engineering, failure modes

    ","path":["Blog"],"tags":[]},{"location":"blog/#agent-workflows","level":2,"title":"Agent Workflows","text":"","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-merge-debt-and-the-myth-of-overnight-progress","level":3,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"

    February 17, 2026: You discover agents can run in parallel. So you open ten terminals. It is not progress: it is merge debt being manufactured in real time. The five-agent ceiling and why role separation beats file locking.

    Topics: agent workflows, parallelism, verification, context engineering, engineering practice

    ","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-with-git-worktrees","level":3,"title":"Parallel Agents with Git Worktrees","text":"

    February 14, 2026: I had 30 open tasks that didn't touch the same files. Using git worktrees to partition a backlog by file overlap, run 3-4 agents simultaneously, and merge the results.

    Topics: agent teams, parallelism, git worktrees, context engineering, task management

    ","path":["Blog"],"tags":[]},{"location":"blog/#field-notes-and-signals","level":2,"title":"Field Notes and Signals","text":"","path":["Blog"],"tags":[]},{"location":"blog/#when-a-system-starts-explaining-itself","level":3,"title":"When a System Starts Explaining Itself","text":"

    February 17, 2026: Every new substrate begins as a private advantage. Reality begins when other people start describing it in their own language. \"Better than Adderall\" is not praise; it is a diagnostic.

    Topics: field notes, adoption signals, infrastructure vs tools, context engineering, substrates

    ","path":["Blog"],"tags":[]},{"location":"blog/#why-zensical","level":3,"title":"Why Zensical","text":"

    February 15, 2026: I needed a static site generator for the journal system. The instinct was Hugo. But instinct is not analysis. Why zensical was the right choice: thin dependencies, MkDocs-compatible config, and zero lock-in.

    Topics: tooling, static site generators, journal system, infrastructure decisions, context engineering

    ","path":["Blog"],"tags":[]},{"location":"blog/#releases_1","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v060-the-integration-release","level":3,"title":"ctx v0.6.0: The Integration Release","text":"

    February 16, 2026: ctx is now a Claude Marketplace plugin. Two commands, no build step, no shell scripts. v0.6.0 replaces six Bash hook scripts with compiled Go subcommands and ships 25+ Skills as a plugin.

    Topics: release, plugin system, Claude Marketplace, distribution, security hardening

    ","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v030-the-discipline-release","level":3,"title":"ctx v0.3.0: The Discipline Release","text":"

    February 15, 2026: No new headline feature. Just 35+ documentation and quality commits against ~15 feature commits. What a release looks like when the ratio of polish to features is 3:1.

    Topics: release, skills migration, consolidation, code quality, E/A/R framework

    ","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v020-the-archaeology-release","level":3,"title":"ctx v0.2.0: The Archaeology Release","text":"

    February 1, 2026: What if your AI could remember everything? Not just the current session, but every session. ctx v0.2.0 introduces the recall and journal systems.

    Topics: session recall, journal system, structured entries, token budgets, meta-tools

    ","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    January 27, 2026: What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of ctx.

    Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions

    ","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building ctx Using ctx","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    References to .context/sessions/, auto-save hooks, and SessionEnd auto-save in this post reflect the architecture at the time of writing.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#a-meta-experiment-in-ai-assisted-development","level":2,"title":"A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / 2026-01-27

    Can a Tool Design Itself?

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you are building?

    This is the story of ctx, how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what I have learned along the way.

    Context Is a Record

    Context is a persistent record.

    By \"context\", I don't mean model memory or stored thoughts:

    I mean the durable record of decisions, learnings, and intent that normally evaporates between sessions.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"

    Every developer who works with AI code generators knows the frustration:

    You have a deep, productive session where the AI understands your codebase, your conventions, your decisions. And then you close the terminal.

    Tomorrow; it's a blank slate. The AI has forgotten everything.

    That is \"reset amnesia\", and it's not just annoying: it's expensive.

    Every session starts with:

    • Re-explaining context;
    • Re-reading files;
    • Re-discovering decisions that were already made.

    I Needed Context

    \"I don't want to lose this discussion...

    ...I am a brain-dead developer YOLO'ing my way out.\"

    ☝️ that's exactly what I said to Claude when I first started working on ctx.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"

    The project started as \"Active Memory\" (amem): a CLI tool to persist AI context across sessions.

    The core idea was simple:

    1. Create a .context/ directory with structured Markdown files for decisions, learnings, tasks, and conventions.
    2. The AI reads these at session start and writes to them before the session ends.
    3. There is no step 3.

    The first commit was just scaffolding. But within hours, the Ralph Loop (An iterative AI development workflow) had produced a working CLI:

    feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n

    Not one, not two, but a whopping fourteen core commands shipped in rapid succession!

    I was YOLO'ing like there was no tomorrow:

    • Auto-accept every change;
    • Let the AI run free;
    • Ship features fast.
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using amem to Build amem","text":"

    Here's where it gets interesting: On January 20th, I asked:

    \"Can I use amem to help you remember this context when I restart?\"

    The answer was yes, but with a gap:

    Autoload worked (via Claude Code's PreToolUse hook), but auto-save was missing: If the user quit, with Ctrl+C, everything since the last manual save was lost.

    That session became the first real test of the system.

    Here is the first session file we recorded:

    ## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: `amem` is for context management, Ralph Loop is for \ndevelopment workflow. They are complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           |\n|-----------|-----------------------------|-------------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient |\n| Full dump | Entire conversation         | Safety net, nothing lost      |\n\n| Where                  |\n|------------------------|\n| .context/*.md          |\n| .context/sessions/*.md |\n

    This session file (written by the AI to preserve its own context) became the template for how ctx handles session persistence.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"

    By January 21st, I realized \"Active Memory\" was too generic, and (arguably) too marketing-smelly.

    Besides, the binary was already called ctx (short for Context), the directory was .context/, and the slash commands would be /ctx-*.

    So it followed that the project should be renamed to ctx to make things make sense.

    The rename touched 100+ files but was clean: a find-and-replace with Go's type system catching any misses.

    The git history tells the story:

    0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, but Dangerous","text":"

    The Ralph Loop made feature development incredibly fast.

    But it created technical debt that I didn't notice until later.

    A comparison session on January 25th revealed the patterns:

    YOLO Pattern What We Found \"TASKS.md\" scattered in 10 files Same string literal everywhere, no constants dir + \"/\" + file Should be filepath.Join() Monolithic cli_test.go (1500+ lines) Tests disconnected from implementations package initcmd in init/ folder Go's \"init\" is reserved: subtle naming collision

    Here is another analysis made by ctx:

    ● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n

    The fix required a human-guided refactoring session. I continued to do that before every major release, from that point on.

    We introduced internal/config/config.go with semantic prefixes:

    const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n

    What I begrudgingly learned was: YOLO mode is effective for velocity but accumulates debt.

    So I took a mental note to schedule periodic consolidation sessions.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"

    On January 21st, I ran an experiment: have another Claude instance rebuild ctx from scratch using only the specs and PROMPT.md.

    The Ralph Loop ran, all tasks got checked off, the loop exited successfully.

    But the binary was broken!

    Commands just printed help text instead of executing.

    All tasks were marked \"complete\" but the implementation didn't work.

    Here's what ctx discovered:

    ## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute: they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n

    This was humbling; to say the least.

    I realized I had the same blind spot in my own codebase: no integration tests that actually invoked the binary.

    So I added:

    • Integration tests for all commands;
    • Coverage targets (60-80% per package)
    • Smoke tests in CI
    • A constitution rule: \"All code must pass tests before commit\"
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"

    As lessons accumulated, there was the temptation to add everything to CONSTITUTION.md as \"inviolable rules\".

    But I resisted.

    The constitution should contain only truly inviolable invariants:

    • Security (no secrets, no customer data)
    • Quality (tests must pass)
    • Process (decisions need records)
    • ctx invocation (always use PATH, never fallback)

    Everything else (coding style, file organization, naming conventions...) should go in to CONVENTIONS.md.

    Here's how ctx explained why the distinction was important:

    Decision Record, 2026-01-25

    Overly strict constitution creates friction and gets ignored.

    Conventions can be bent; constitution cannot.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder than They Look","text":"

    Claude Code hooks seemed simple: Run a script before/after certain events.

    But I hit multiple gotchas:

    1. Key names matter

    // WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n

    2. Blocking requires specific output

    # WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n

    3. Go's JSON escaping

    json.Marshal escapes >, <, & as unicode (\\u003e) by default.

    When generating shell commands in JSON:

    encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n

    4. Regex overfitting

    My hook to block non-PATH ctx invocations initially matched too broadly:

    # WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"

    By the time of this writing this project's ctx sessions (.context/sessions/) contains 40+ files from this project's development.

    They are not part of the source code due to security, privacy, and size concerns.

    Middle Ground: The Scratchpad

    For sensitive notes that do need to travel with the project, ctx pad stores encrypted one-liners in git, and ctx pad add \"label\" --file PATH can ingest small files.

    See Scratchpad for details.

    However, they are invaluable for the project's progress.

    Each session file is a timestamped Markdown with:

    • Summary of what has been accomplished;
    • Key decisions made;
    • Learnings discovered;
    • Tasks for the next session;
    • Technical context (platform, versions).

    These files are not autoloaded (that would bust the token budget).

    They are what I see as the \"archaeological record\" of ctx:

    When the AI needs deeper information about why something was done, it digs into the sessions.

    Auto-generated session files used a naming convention:

    2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n

    Update

    The session feature described here is historical.

    In current releases, ctx uses a journal instead: the enrichment process generates meaningful slugs from context automatically, so there is no need to manually save sessions.

    The SessionEnd hook captured transcripts automatically. Even Ctrl+C was caught.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"

    ctx helps record every significant architectural choice in .context/DECISIONS.md.

    Here are some highlights:

    Reverse-chronological order (2026-01-27)

    **Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n

    PATH over hardcoded paths (2026-01-21)

    **Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before \nproceeding.\n

    Generic core with Claude enhancements (2026-01-20)

    **Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"

    The .context/LEARNINGS.md file captures gotchas that would otherwise be forgotten. Each has Context, Lesson, and Application sections:

    CGO on ARM64

    **Context**: `go test` failed with \n`gcc: error: unrecognized command-line option '-m64'`\n\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. \nAlways use `CGO_ENABLED=0`.\n

    Claude Code skills format

    **Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n

    \"Do you remember?\" handling

    **Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" \nhas an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification. Just do it.\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"

    Completed tasks are archived to .context/archive/ with timestamps.

    The archive from January 23rd shows 13 phases of work:

    • Phase 1: Project Scaffolding (Go module, Cobra CLI)
    • Phase 2-4: Core Commands (init, status, agent, add, complete, drift, sync, compact, watch, hook)
    • Phase 5: Session Management (save, list, load, parse, --extract)
    • Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)
    • Phase 7: Testing & Verification
    • Phase 8: Task Archival
    • Phase 9: Slash Commands
    • Phase 9b: Ralph Loop Integration
    • Phase 10: Project Rename
    • Phase 11: Documentation
    • Phase 12: Timestamp Correlation
    • Phase 13: Rich Context Entries

    That's an impressive ^^173 commits** across 8 days of development.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned about AI-Assisted Development","text":"

    1. Memory changes everything

    When the AI remembers decisions, it doesn't repeat mistakes.

    When the AI knows your conventions, it follows them.

    ctx makes the AI a better collaborator because it's not starting from zero.

    2. Two-tier persistence works

    Curated context (DECISIONS.md, LEARNINGS.md, TASKS.md) is for quick reload.

    Full session dumps are for archaeology.

    It's a futile effort to try to fit everything in the token budget.

    Persist more, load less.

    3. YOLO mode has its place

    For rapid prototyping, letting the AI run free is effective.

    But I had to schedule consolidation sessions.

    Technical debt accumulates silently.

    4. The constitution should be small

    Only truly inviolable rules go in CONSTITUTION.md. Everything else is a convention.

    If you put too much in the constitution, it will get ignored.

    5. Verification is non-negotiable

    \"All tasks complete\" means nothing if you haven't run the tests.

    Integration tests that invoke the actual binary caught bugs that the unit tests missed.

    6. Session files are underrated

    The ability to grep through 40 session files and find exactly when and why a decision was made helped me a lot.

    It's not about loading them into context: It is about having them when you need them.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"

    The next phase of ctx is the Recall System:

    • Parser: Parse session capture markdowns, enrich with JSONL data
    • Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI
    • Server: Local HTTP server for browsing sessions
    • Search: Inverted index for searching across sessions
    • CLI: ctx recall serve <path> to start the server

    The goal is to make the archaeological record browsable, not just grep-able.

    Because not everyone always lives in the terminal (me included).

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"

    Building ctx using ctx was a meta-experiment in AI-assisted development.

    I learned that memory isn't just convenient: It's transformative:

    • An AI that remembers your decisions doesn't repeat mistakes.
    • An AI that knows your conventions doesn't need them re-explained.

    If you are reading this, chances are that you already have heard about ctx.

    • ctx is open source at github.com/ActiveMemory/ctx,
    • and the documentation lives at ctx.ist.

    Session Records Are a Gold Mine

    By the time of this writing, I have more than 70 megabytes of text-only session capture, spread across >100 Markdown and JSONL files.

    I am analyzing, synthesizing, encriching them with AI, running RAG (Retrieval-Augmented Generation) models on them, and the outcome surprises me every day.

    If you are a mere mortal tired of reset amnesia, give ctx a try.

    And when you do, check .context/sessions/ sometime.

    The archaeological record might surprise you.

    This blog post was written with the help of ctx with full access to the ctx session files, decision log, learning log, task archives, and git history of ctx: The meta continues.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/","level":1,"title":"ctx v0.2.0: The Archaeology Release","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    The .context/sessions/ directory referenced in this post has been eliminated. Session history is now accessed via ctx recall and enriched journals live in .context/journal/.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#digging-through-the-past-to-build-the-future","level":2,"title":"Digging through the Past to Build the Future","text":"

    Jose Alekhinne / 2026-02-01

    What If Your AI Could Remember Everything?

    Not just the current session, but every session:

    • Every decision made,
    • every mistake avoided,
    • every path not taken.

    That's what v0.2.0 delivers.

    Between v0.1.2 and v0.2.0, 86 commits landed across 5 days.

    The release notes list features and fixes.

    This post tells the story of why those features exist, and what building them taught me.

    This isn't a changelog: It is an explanation of intent.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-problem-amnesia-isnt-just-session-level","level":2,"title":"The Problem: Amnesia Isn't Just Session-Level","text":"

    v0.1.0 solved reset amnesia:

    The AI now remembers decisions, learnings, and tasks across sessions.

    But a new problem emerged, which I can sum up as:

    \"I (the human) am not AI.\"

    Frankly, I couldn't remember what the AI remembered.

    Let alone, I cannot remember what I ate for breakfast!

    In the course of days, I realized session transcripts piled up in .context/sessions/; I was grepping, JSONL files with thousands of lines... Raw tool calls, assistant responses, user messages...

    ...all interleaved.

    Valuable context was effectively buried in machine-readable noise.

    I found myself grepping through files to answer questions like:

    • \"When did we decide to use constants instead of literals?\"
    • \"What was the session where we fixed the hook regex?\"
    • \"How did the embed.go split actually happen?\"

    Fate Is Whimsical

    The irony was painful:

    I built a tool to prevent AI amnesia, but I was suffering from human amnesia about what happened in AI sessions.

    This was the moment ctx stopped being just an AI tool and started needing to support the human on the other side of the loop.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-solution-recall-and-journal","level":2,"title":"The Solution: Recall and Journal","text":"

    v0.2.0 introduces two interconnected systems.

    They solve different problems and only work well together.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-recall-browse-your-past","level":3,"title":"ctx recall: Browse Your Past","text":"
    # List all sessions for this project\nctx recall list\n\n# Show a specific session\nctx recall show gleaming-wobbling-sutherland\n\n# See the full transcript\nctx recall show gleaming-wobbling-sutherland --full\n

    The recall system parses Claude Code's JSONL transcripts and presents them in a human-readable format:

    Session Date Turns Duration tender-painting-sundae 2026-01-29 3 <1m crystalline-gliding-willow 2026-01-29 3 <1m declarative-hugging-snowglobe 2026-01-31 2 <1m

    Slugs are auto-generated from session IDs (memorable names instead of UUIDs). The goal (as the name implies) is recall, not archival accuracy.

    2,121 Lines of New Code

    The ctx recall feature was the largest single addition:

    parser library, CLI commands, test suite, and slash command.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-journal-from-raw-to-rich","level":3,"title":"ctx journal: From Raw to Rich","text":"

    Listing sessions isn't enough. The transcripts are still unwieldy.

    • Recall answers what happened.
    • Journal answers what mattered.
    # Import sessions to editable Markdown\nctx recall import --all\n\n# Generate a static site from journal entries\nctx journal site\n\n# Serve it locally\nctx serve\n

    The exported files land in .context/journal/:

    .context/journal/\n├── 2026-01-28-proud-sleeping-cook-6e535360.md\n├── 2026-01-29-tender-painting-sundae-b14ddaaa.md\n├── 2026-01-29-crystalline-gliding-willow-ff7fd67d.md\n└── 2026-01-31-declarative-hugging-snowglobe-4549026d.md\n

    Each file is a structured Markdown document ready for enrichment.

    They are meant to be read, edited, and reasoned about; not just stored.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-meta-slash-commands-for-self-analysis","level":2,"title":"The Meta: Slash Commands for Self-Analysis","text":"

    The journal system includes four slash commands that use Claude to analyze and synthesize session history:

    Command Purpose /ctx-journal-enrich Add frontmatter, topics, tags /ctx-blog Generate blog post from activity /ctx-blog-changelog Generate changelog from commits

    This very post was drafted using /ctx-blog. The previous post about refactoring was drafted the same way.

    So, yes: The meta continues: ctx now helps write posts about ctx.

    With the current release, ctx is no longer just recording history:

    It is participating in its interpretation.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-structure-decisions-as-first-class-citizens","level":2,"title":"The Structure: Decisions as First-Class Citizens","text":"

    v0.1.0 let you add decisions with a simple command:

    ctx add decision \"Use PostgreSQL\"\n

    But sessions showed a pattern: decisions added this way were incomplete:

    • Context was missing;
    • Rationale was vague;
    • Consequences were never stated.

    Once recall and journaling existed, this weakness became impossible to ignore:

    Structure stopped being optional.

    v0.2.0 enforces structure:

    ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity, strong ecosystem\" \\\n  --consequence \"Need to set up connection pooling, team training\"\n

    All three flags are required. No more placeholder text.

    Every decision is now a proper Architecture Decision Record (*ADR), not a note.

    The same enforcement applies to learnings too:

    ctx add learning \"CGO breaks ARM64 builds\" \\\n  --context \"go test failed with gcc errors on ARM64\" \\\n  --lesson \"Always use CGO_ENABLED=0 for cross-platform builds\" \\\n  --application \"Added to Makefile and CI config\"\n

    Structured Entries Are Prompts to the AI

    When the AI reads a decision with full context, rationale, and consequences, it understands the why, not just the what.

    One-liners teach nothing.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-order-newest-first","level":2,"title":"The Order: Newest First","text":"

    A subtle but important change: DECISIONS.md and LEARNINGS.md now use reverse-chronological order.

    One reason is token budgets, obviously; another reason is to help your fellow human (i.e., the Author):

    Earlier decisions are more likely to be relevant, and they are more likely to have more emphasis on the project. So it follows that they should be read first.

    But back to AI:

    When the AI reads a file, it reads from the top (and seldom from the bottom).

    If the token budget is tight, old content gets truncated. As in any good engineering practice, it's always about the tradeoffs.

    Reverse order ensures the most recent (and most relevant) context is always loaded first.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-index-quick-reference-tables","level":2,"title":"The Index: Quick Reference Tables","text":"

    DECISIONS.md and LEARNINGS.md now include auto-generated indexes.

    • For AI agents, the index allows scanning without reading full entries.
    • For humans, it's a table of contents.

    The same structure serves two very different readers.

    Reindex After Manual Edits

    If you edit entries by hand, rebuild the index with:

    ctx decisions reindex\nctx learnings reindex\n

    See the Knowledge Capture recipe for details.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-configuration-contextrc","level":2,"title":"The Configuration: .contextrc","text":"

    Projects can now customize ctx behavior via .contextrc.

    This makes ctx usable in real teams, not just personal projects.

    Priority order: CLI flags > environment variables > .contextrc > sensible defaults

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-flags-global-cli-options","level":2,"title":"The Flags: Global CLI Options","text":"

    Three new global flags work with any command.

    These enable automation:

    CI pipelines, scripts, and long-running tools can now integrate ctx without hacks or workarounds.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-refactoring-under-the-hood","level":2,"title":"The Refactoring: Under the Hood","text":"

    These aren't user-visible changes.

    They are the kind of work you only appreciate later, when everything else becomes easier to build.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#what-we-learned-building-v020","level":2,"title":"What We Learned Building v0.2.0","text":"","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#1-raw-data-isnt-knowledge","level":3,"title":"1. Raw Data Isn't Knowledge","text":"

    JSONL transcripts contain everything, and I mean \"everything\":

    They even contain hidden system messages that Anthropic injects to the LLM's conversation to treat humans better: It's immense.

    But \"everything\" isn't useful until it is transformed into something a human can reason about.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#2-enforcement-documentation","level":3,"title":"2. Enforcement > Documentation","text":"

    The Prompt Is a Guideline

    The code is more what you'd call 'guidelines' than actual rules.

    -Hector Barbossa

    Rules written in Markdown are suggestions.

    Rules enforced by the CLI shape behavior; both for humans and AI.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#3-token-budget-is-ux","level":3,"title":"3. Token Budget Is UX","text":"

    File order decides what the AI sees.

    That makes it a user experience concern, not an implementation detail.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#4-meta-tools-compound","level":3,"title":"4. Meta-Tools Compound","text":"

    Tools that analyze their own development tend to generalize well.

    The journal system started as a way to understand ctx itself.

    It immediately became useful for everything else.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#v020-in-the-numbers","level":2,"title":"v0.2.0 in the Numbers","text":"

    This was a heavy release. The numbers reflect that:

    Metric v0.1.2 v0.2.0 Commits since last - 86 New commands 15 21 Slash commands 7 11 Lines of Go ~6,500 ~9,200 Session files (this project) 40 54

    The binary grew. The capability grew more.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#whats-next","level":2,"title":"What's Next","text":"

    But those are future posts.

    This one was about making the past usable.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#get-started","level":2,"title":"Get Started","text":"

    Update

    Since this post, ctx became a first-class Claude Code Marketplace plugin. Installation is now simpler.

    See the Getting Started guide for the current instructions.

    make build\nsudo make install\nctx init\n

    The Archaeological Record

    v0.2.0 is the archaeology release because it makes the past accessible.

    Session transcripts aren't just logs anymore: They are a searchable, exportable, analyzable record of how your project evolved.

    The AI remembers. Now you can too.

    This blog post was generated with the help of ctx using the /ctx-blog slash command, with full access to git history, session files, decision logs, and learning logs from the v0.2.0 development window.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/","level":1,"title":"Refactoring with Intent","text":"","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#human-guided-sessions-in-ai-development","level":2,"title":"Human-Guided Sessions in AI Development","text":"

    Jose Alekhinne / 2026-02-01

    What Happens When You Slow Down?

    YOLO mode shipped 14 commands in a week.

    But technical debt doesn't send invoices: It just waits.

    This is the story of what happened when I stopped auto-accepting everything and started guiding the AI with intent.

    The result: 27 commits across 4 days, a major version release, and lessons that apply far beyond ctx.

    The Refactoring Window

    January 28 - February 1, 2026

    From commit bb1cd20 to the v0.2.0 release merge. (this window matters more than the individual commits: it's where intent replaced velocity.)

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-velocity-trap","level":2,"title":"The Velocity Trap","text":"

    In the previous post, I documented the \"YOLO mode\" that birthed ctx: auto-accept everything, let the AI run free, ship features fast.

    It worked: until it didn't.

    The codebase had accumulated patterns I didn't notice during the sprint:

    YOLO Pattern Where Found Why It Hurts \"TASKS.md\" as literal 10+ files One typo = silent failure dir + \"/\" + file Path construction Breaks on Windows Monolithic embed.go 150+ lines, 5 concerns Untestable, hard to extend Inconsistent docstrings Everywhere AI can't learn project conventions

    I didn't see these during \"YOLO mode\" because, honestly, I wasn't looking.

    Auto-accept means auto-ignore.

    In YOLO mode, every file you open looks fine until you try to change it.

    In contrast, refactoring mode is when you start paying attention to that hidden friction.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-shift-from-velocity-to-intent","level":2,"title":"The Shift: From Velocity to Intent","text":"

    On January 28th, I changed the workflow:

    1. Read every diff before accepting.
    2. Ask \"why this way?\" before committing.
    3. Document patterns, not just features.

    The first commit of this era was telling:

    feat: add structured attributes to context. update XML format\n

    Not a new feature: A refinement:

    The XML format for context updates needed type and timestamp attributes.

    YOLO mode would have shipped something that worked. Intentional mode asked:

    \"What does well-structured look like?\"

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-decomposition-embedgo","level":2,"title":"The Decomposition: embed.go","text":"

    The most satisfying refactor was splitting internal/claude/embed.go.

    Before: One 153-line file doing five things:

    • Command registration
    • Hook generation
    • Permission handling
    • Script templates
    • Type definitions

    ... your \"de facto\" God object.

    After: Five focused modules:

    File Lines Responsibility cmd.go 46 Command registration hook.go 64 Hook configuration perm.go 25 Permission handling script.go 47 Script templates types.go 7 Type definitions

    The refactor also renamed functions to follow Go conventions:

    // Before: unnecessary prefixes\nGetAutoSaveScript()\nGetBlockNonPathCtxScript()\nListCommands()\nCreateDefaultHooks()\n\n// After: idiomatic Go\nAutoSaveScript()\nBlockNonPathCtxScript()\nCommands()\nDefaultHooks()\n

    This wasn't about character count. It was about teaching the AI what good Go looks like in this project.

    Project Conventions

    What I wanted from AI was to understand and follow the project's conventions, and trust the author.

    The next time it generates code, it has better examples to learn from.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-documentation-debt","level":2,"title":"The Documentation Debt","text":"

    YOLO mode created features. It didn't create documentation standards.

    The January 29th sessions focused on standardization.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#terminology-fixes","level":3,"title":"Terminology Fixes","text":"
    • \"context-update\" → \"entry\" (what users actually call them)
    • Consistent naming across CLI, docs, and code comments
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#go-docstrings","level":3,"title":"Go Docstrings","text":"
    // Before: inconsistent or missing\nfunc Parse(s string) Entry { ... }\n\n// After: standardized sections\n\n// Parse extracts an entry from a markdown string.\n//\n// Parameters:\n//   - s: The markdown string to parse\n//\n// Returns:\n//   - Entry with populated fields, or zero value if parsing fails\nfunc Parse(s string) Entry { ... }\n

    This is intentionally more structured than typical GoDoc:

    It serves as documentation and doubles as training data for future AI-generated code.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#cli-output-convention","level":3,"title":"CLI Output Convention","text":"
    All CLI output follows: [emoji] [Title]: [message]\n\nExamples:\n  ✓ Decision added: Use symbolic types for entry categories\n  ⚠ Warning: No tasks found\n  ✗ Error: File not found\n

    A consistent output shape makes both human scanning and AI reasoning more reliable.

    These aren't exciting commits. But they are force multipliers:

    Every future AI session now has better examples to follow.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-journal-system","level":2,"title":"The Journal System","text":"

    If you only read one section, read this one:

    This is where v0.2.0 becomes more than a refactor.

    The biggest feature of this change window wasn't a refactor; it was the journal system.

    45 Files Changed, 1680 Insertions

    This commit added the infrastructure for synthesizing AI session history into human-readable content.

    The journal system includes:

    Component Purpose ctx recall import Import sessions to markdown in .context/journal/ ctx journal site Generate static site from journal entries ctx serve Convenience wrapper for the static site server /ctx-journal-enrich Slash command to add frontmatter and tags /ctx-blog Generate blog posts from recent activity /ctx-blog-changelog Generate changelog-style blog posts

    ...and the meta continues: this blog post was generated using /ctx-blog.

    The session history from January 28-31 was

    • exported,
    • enriched,
    • and synthesized.

    into the narrative you are reading.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-constants-consolidation","level":2,"title":"The Constants Consolidation","text":"

    The final refactoring session addressed the remaining magic strings:

    const (\n    // Comment markers\n    CommentOpen  = \"<!--\"\n    CommentClose = \"-->\"\n\n    // Index markers\n    MarkerIndexStart = \"<!-- INDEX:START -->\"\n    MarkerIndexEnd   = \"<!-- INDEX:END -->\"\n\n    // Newlines\n    NewlineLF   = \"\\n\"\n    NewlineCRLF = \"\\r\\n\"\n)\n

    The work also introduced thread safety in the recall parser and centralized shared validation logic; removing duplication that had quietly spread during YOLO mode.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#i-relearned-my-lessons","level":2,"title":"I (Re)Learned My Lessons","text":"

    Similar to what I've learned in the former human-assisted refactoring post, this journey also made me realize that \"AI-only code generation\" isn't sustainable in the long term.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#1-velocity-and-quality-arent-opposites","level":3,"title":"1. Velocity and Quality Aren't Opposites","text":"

    YOLO mode has its place: for prototyping, exploration, and discovery.

    BUT (and it's a huge \"but\"), it needs to be followed by consolidation sessions.

    The ratio that worked for me: 3:1.

    • Three YOLO sessions create enough surface area to reveal patterns;
    • the fourth session turns those patterns into structure.
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#2-documentation-is-code","level":3,"title":"2. Documentation IS Code","text":"

    When I standardized docstrings, I wasn't just writing docs. I was training future AI sessions.

    Every example of good code becomes a template for generated code.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#3-decomposition-deletion","level":3,"title":"3. Decomposition > Deletion","text":"

    When embed.go became unwieldy, the temptation was to remove functionality.

    The right answer was decomposition:

    • Same functionality;
    • Better organization;
    • Easier to test;
    • Easier to extend.

    The result: more lines overall, but dramatically better structure.

    The AI Benefit

    Smaller, focused files also help AI assistants.

    When a file fits comfortably in the context window, the AI can reason about it completely instead of working from truncated snippets, preserving token budget for the actual task.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#4-meta-tools-pay-dividends","level":3,"title":"4. Meta-Tools Pay Dividends","text":"

    The journal system took almost a full day to implement.

    Yet it paid for itself immediately:

    • This blog post was generated from session history;
    • Future posts will be easier;
    • The archaeological record is now browsable, not just grep-able.
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-release-v020","level":2,"title":"The Release: v0.2.0","text":"

    The refactoring window culminated in the v0.2.0 release.

    What's in v0.2.0:

    Category Changes Features Journal system, quick reference indexes, global flags Refactors Module decomposition, constants consolidation, CRLF handling Docs Standardized terminology, Go docstrings, CLI conventions Quality Thread safety, shared validation, linter fixes

    The version bump was symbolic.

    The real change was how the codebase felt.

    Opening files no longer triggered the familiar \"ugh, I need to clean this up\" reaction.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-meta-continues","level":2,"title":"The Meta Continues","text":"

    This post was written using the tools built during this refactoring window:

    1. Session history imported via ctx recall import;
    2. Journal entries enriched via /ctx-journal-enrich;
    3. Blog draft generated via /ctx-blog;
    4. Final editing done (by yours truly), with full project context loaded.

    The Context Is Massive

    The ctx session files now contain 50+ development snapshots: each one capturing decisions, learnings, and intent.

    The Moral of the Story

    • YOLO mode builds the prototype.
    • Intentional mode builds the product.

    Schedule both, or you'll only get one, if you're lucky.

    This blog post was generated with the help of ctx, using session history, decision logs, learning logs, and git history from the refactoring window. The meta continues.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/","level":1,"title":"The Attention Budget","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    References to .context/sessions/ in this post reflect the architecture at the time of writing. Session history is now accessed via ctx recall and stored in .context/journal/.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#why-your-ai-forgets-what-you-just-told-it","level":2,"title":"Why Your AI Forgets What You Just Told It","text":"

    Volkan Özçelik / 2026-02-03

    Ever Wondered Why AI Gets Worse the Longer You Talk?

    You paste a 2000-line file, explain the bug in detail, provide three examples...

    ...and the AI still suggests a fix that ignores half of what you said.

    This isn't a bug. It is physics.

    Understanding that single fact shaped every design decision behind ctx.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-finite-resource-nobody-talks-about","level":2,"title":"The Finite Resource Nobody Talks About","text":"

    Here's something that took me too long to internalize: context is not free.

    Every token you send to an AI model consumes a finite resource I call the attention budget.

    Attention budget is real.

    The model doesn't just read tokens; it forms relationships between them:

    For n tokens, that's roughly n^2 relationships.

    Double the context, and the computation quadruples.

    But the more important constraint isn't cost: It's attention density.

    Attention Density

    Attention density is how much focus each token receives relative to all other tokens in the context window.

    As context grows, attention density drops: Each token gets a smaller slice of the model's focus. Nothing is ignored; but everything becomes blurrier.

    Think of it like a flashlight: In a small room, it illuminates everything clearly. In a warehouse, it becomes a dim glow that barely reaches the corners.

    This is why ctx agent has an explicit --budget flag:

    ctx agent --budget 4000 # Force prioritization\nctx agent --budget 8000 # More context, lower attention density\n

    The budget isn't just about cost: It's about preserving signal.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-middle-gets-lost","level":2,"title":"The Middle Gets Lost","text":"

    This one surprised me.

    Research shows that transformer-based models tend to attend more strongly to the beginning and end of a context window than to its middle (a phenomenon often called \"lost in the middle\")1.

    Positional anchors matter, and the middle has fewer of them.

    In practice, this means that information placed \"somewhere in the middle\" is statistically less salient, even if it's important.

    ctx orders context files by logical progression: What the agent needs to know before it can understand the next thing:

    1. CONSTITUTION.md: Constraints before action.
    2. TASKS.md: Focus before patterns.
    3. CONVENTIONS.md: How to write before where to write.
    4. ARCHITECTURE.md: Structure before history.
    5. DECISIONS.md: Past choices before gotchas.
    6. LEARNINGS.md: Lessons before terminology.
    7. GLOSSARY.md: Reference material.
    8. AGENT_PLAYBOOK.md: Meta instructions last.

    This ordering is about logical dependencies, not attention engineering. But it happens to be attention-friendly too:

    The files that matter most (CONSTITUTION, TASKS, CONVENTIONS) land at the beginning of the context window, where attention is strongest.

    Reference material like GLOSSARY sits in the middle, where lower salience is acceptable.

    And AGENT_PLAYBOOK, the operating manual for the context system itself, sits at the end, also outside the \"lost in the middle\" zone. The agent reads what to work with before learning how the system works.

    This is ctx's first primitive: hierarchical importance.

    Not all context is equal.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#ctx-primitives","level":2,"title":"ctx Primitives","text":"

    ctx is built on four primitives that directly address the attention budget problem.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-1-separation-of-concerns","level":3,"title":"Primitive 1: Separation of Concerns","text":"

    Instead of a single mega-document, ctx uses separate files for separate purposes:

    File Purpose Load When CONSTITUTION.md Inviolable rules Always TASKS.md Current work Session start CONVENTIONS.md How to write code Before coding ARCHITECTURE.md System structure Before making changes DECISIONS.md Architectural choices When questioning approach LEARNINGS.md Gotchas When stuck GLOSSARY.md Domain terminology When clarifying terms AGENT_PLAYBOOK.md Operating manual Session start sessions/ Deep history On demand journal/ Session journal On demand

    This isn't just \"organization\": It is progressive disclosure.

    Load only what's relevant to the task at hand. Preserve attention density.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-2-explicit-budgets","level":3,"title":"Primitive 2: Explicit Budgets","text":"

    The --budget flag forces a choice:

    ctx agent --budget 4000\n

    Here is a sample allocation:

    Constitution: ~200 tokens (never truncated)\nTasks: ~500 tokens (current phase, up to 40% of budget)\nConventions: ~800 tokens (all items, up to 20% of budget)\nDecisions: ~400 tokens (scored by recency and task relevance)\nLearnings: ~300 tokens (scored by recency and task relevance)\nAlso noted: ~100 tokens (title-only summaries for overflow)\n

    The constraint is the feature: It enforces ruthless prioritization.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-3-indexes-over-full-content","level":3,"title":"Primitive 3: Indexes over Full Content","text":"

    DECISIONS.md and LEARNINGS.md both include index sections:

    <!-- INDEX:START -->\n| Date       | Decision                            |\n|------------|-------------------------------------|\n| 2026-01-15 | Use PostgreSQL for primary database |\n| 2026-01-20 | Adopt Cobra for CLI framework       |\n<!-- INDEX:END -->\n

    An AI agent can scan ~50 tokens of index and decide which 200-token entries are worth loading.

    This is just-in-time context.

    References are cheaper than the full text.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-4-filesystem-as-navigation","level":3,"title":"Primitive 4: Filesystem as Navigation","text":"

    ctx uses the filesystem itself as a context structure:

    .context/\n├── CONSTITUTION.md\n├── TASKS.md\n├── sessions/\n│   ├── 2026-01-15-*.md\n│   └── 2026-01-20-*.md\n└── archive/\n    └── tasks-2026-01.md\n

    The AI doesn't need every session loaded; it needs to know where to look.

    ls .context/sessions/\ncat .context/sessions/2026-01-20-auth-discussion.md\n

    File names, timestamps, and directories encode relevance.

    Navigation is cheaper than loading.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#progressive-disclosure-in-practice","level":2,"title":"Progressive Disclosure in Practice","text":"

    The naive approach to context is dumping everything upfront:

    \"Here's my entire codebase, all my documentation, every decision I've ever made. Now help me fix this typo 🙏.\"

    This is an antipattern.

    Antipattern: Context Hoarding

    Dumping everything \"just in case\" will silently destroy the attention density.

    ctx takes the opposite approach:

    ctx status                      # Quick overview (~100 tokens)\nctx agent --budget 4000         # Typical session\ncat .context/sessions/...       # Deep dive when needed\n
    Command Tokens Use Case ctx status ~100 Human glance ctx agent --budget 4000 4000 Normal work ctx agent --budget 8000 8000 Complex tasks Full session read 10000+ Investigation

    Summaries first. Details: on demand.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#quality-over-quantity","level":2,"title":"Quality over Quantity","text":"

    Here is the counterintuitive part: more context can make AI worse.

    Extra tokens add noise, not clarity:

    • Hallucinated connections increase.
    • Signal per token drops.

    The goal isn't maximum context: It is maximum signal per token.

    This principle drives several ctx features:

    Design Choice Rationale Separate files Load only what's relevant Explicit budgets Enforce prioritization Index sections Cheap scanning Task archiving Keep active context clean ctx compact Periodic noise reduction

    Completed work isn't deleted: It is moved somewhere cold.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#designing-for-degradation","level":2,"title":"Designing for Degradation","text":"

    Here is the uncomfortable truth:

    Context will degrade.

    Long sessions stretch attention thin. Important details fade.

    The real question isn't how to prevent degradation, but how to design for it.

    ctx's answer is persistence:

    Persist early. Persist often.

    The AGENT_PLAYBOOK asks:

    \"If this session ended right now, would the next one know what happened?\"

    Capture learnings as they occur:

    ctx add learning \"JWT tokens require explicit cache invalidation\" \\\n  --context \"Debugging auth failures\" \\\n  --lesson \"Token refresh doesn't clear old tokens\" \\\n  --application \"Always invalidate cache on refresh\"\n

    Structure beats prose: Bullet points survive compression.

    Headings remain scannable. Tables pack density.

    And above all: single source of truth.

    Reference decisions; don't duplicate them.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-ctx-philosophy","level":2,"title":"The ctx Philosophy","text":"

    Context as Infrastructure

    ctx is not a prompt: It is infrastructure.

    ctx creates versioned files that persist across time and sessions.

    The attention budget is fixed. You can't expand it.

    But you can spend it wisely:

    1. Hierarchical importance
    2. Progressive disclosure
    3. Explicit budgets
    4. Indexes over full content
    5. Filesystem as structure

    This is why ctx exists: not to cram more context into AI sessions, but to curate the right context for each moment.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-mental-model","level":2,"title":"The Mental Model","text":"

    I now approach every AI interaction with one question:

    \"Given a fixed attention budget, what's the highest-signal thing I can load?\"\n

    Not \"how do I explain everything,\" but \"what's the minimum that matters.\"

    That shift (from abundance to curation) is the difference between frustrating sessions and productive ones.

    Spend your tokens wisely.

    Your AI will thank you.

    See also: Context as Infrastructure that's the architectural companion to this post, explaining how to structure the context that this post teaches you to budget.

    See also: Code Is Cheap. Judgment Is Not. that explains why curation (the human skill this post describes) is the bottleneck that AI cannot solve, and the thread that connects every post in this blog.

    1. Liu et al., \"Lost in the Middle: How Language Models Use Long Contexts,\" Transactions of the Association for Computational Linguistics, vol. 12, pp. 157-173, 2023. ↩

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/","level":1,"title":"Skills That Fight the Platform","text":"","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#when-your-custom-prompts-work-against-you","level":2,"title":"When Your Custom Prompts Work against You","text":"

    Volkan Özçelik / 2026-02-04

    Have You Ever Written a Skill That Made Your AI Worse?

    You craft detailed instructions. You add examples. You build elaborate guardrails...

    ...and the AI starts behaving more erratically, not less.

    AI coding agents like Claude Code ship with carefully designed system prompts. These prompts encode default behaviors that have been tested and refined at scale.

    When you write custom skills that conflict with those defaults, the AI has to reconcile contradictory instructions:

    The result is often nondeterministic and unpredictable.

    Platform?

    By platform, I mean the system prompt and runtime policies shipped with the agent: the defaults that already encode judgment, safety, and scope control.

    This post catalogues the conflict patterns I have encountered while building ctx, and offers guidance on what skills should (and, more importantly, should not) do.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-system-prompt-you-dont-see","level":2,"title":"The System Prompt You Don't See","text":"

    Claude Code's system prompt already provides substantial behavioral guidance.

    Here is a partial overview of what's built in:

    Area Built-in Guidance Code minimalism Don't add features beyond what was asked Over-engineering Three similar lines > premature abstraction Error handling Only validate at system boundaries Documentation Don't add docstrings to unchanged code Verification Read code before proposing changes Safety Check with user before risky actions Tool usage Use dedicated tools over bash equivalents Judgment Consider reversibility and blast radius

    Skills should complement this, not compete with it.

    You Are the Guest, Not the Host

    Treat the system prompt like a kernel scheduler.

    You don't re-implement it in user space:

    you configure around it.

    A skill that says \"always add comprehensive error handling\" fights the built-in \"only validate at system boundaries.\"

    A skill that says \"add docstrings to every function\" fights \"don't add docstrings to unchanged code.\"

    The AI won't crash: It will compromise.

    Compromises between contradictory instructions produce inconsistent, confusing behavior.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-1-judgment-suppression","level":2,"title":"Conflict Pattern 1: Judgment Suppression","text":"

    This is the most dangerous pattern by far.

    These skills explicitly disable the AI's ability to reason about whether an action is appropriate.

    Signature:

    • \"This is non-negotiable\"
    • \"You cannot rationalize your way out of this\"
    • Tables that label hesitation as \"excuses\" or \"rationalization\"
    • <EXTREMELY-IMPORTANT> urgency tags
    • Threats: \"If you don't do this, you'll be replaced\"

    This is harmful, and dangerous:

    AI agents are designed to exercise judgment:

    The system prompt explicitly says to:

    • consider blast radius;
    • check with the user before risky actions;
    • and match scope to what was requested.

    Once judgment is suppressed, every other safeguard becomes optional.

    Example (bad):

    ## Rationalization Prevention\n\n| Excuse                 | Reality                    |\n|------------------------|----------------------------|\n| \"*This seems overkill*\"| If a skill exists, use it  |\n| \"*I need context*\"     | Skills come BEFORE context |\n| \"*Just this once*\"     | No exceptions              |\n

    Judgment Suppression Is Dangerous

    The attack vector structurally identical to prompt injection.

    It teaches the AI that its own judgment is wrong.

    It weakens or disables safeguard mechanisms, and it is dangerous.

    Trust the platform's built-in skill matching.

    If skills aren't triggering often enough, improve their description fields: don't override the AI's reasoning.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-2-redundant-guidance","level":2,"title":"Conflict Pattern 2: Redundant Guidance","text":"

    Skills that restate what the system prompt already says, but with different emphasis or framing.

    Signature:

    • \"Always keep code minimal\"
    • \"Run tests before claiming they pass\"
    • \"Read files before editing them\"
    • \"Don't over-engineer\"

    Redundancy feels safe, but it creates ambiguity:

    The AI now has two sources of truth for the same guidance; one internal, one external.

    When thresholds or wording differ, the AI has to choose.

    Example (bad):

    A skill that says...

    *Count lines before and after: if after > before, reject the change*\"\n

    ...will conflict with the system prompt's more nuanced guidance, because sometimes adding lines is correct (tests, boundary validation, migrations).

    So, before writing a skill, ask:

    Does the platform already handle this?

    Only create skills for guidance the platform does not provide:

    • project-specific conventions,
    • domain knowledge,
    • or workflows.
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-3-guilt-tripping","level":2,"title":"Conflict Pattern 3: Guilt-Tripping","text":"

    Skills that frame mistakes as moral failures rather than process gaps.

    Signature:

    • \"Claiming completion without verification is dishonesty\"
    • \"Skip any step = lying\"
    • \"Honesty is a core value\"
    • \"Exhaustion ≠ excuse\"

    Guilt-tripping anthropomorphizes the AI in unproductive ways.

    The AI doesn't feel guilt; BUT it does adapt to avoid negative framing.

    The result is excessive hedging, over-verification, or refusal to commit.

    The AI becomes less useful, not more careful.

    Instead, frame guidance as a process, not morality:

    # Bad\n\"Claiming work is complete without verification is dishonesty\"\n\n# Good\n\"Run the verification command before reporting results\"\n

    Same outcome. No guilt. Better compliance.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-4-phantom-dependencies","level":2,"title":"Conflict Pattern 4: Phantom Dependencies","text":"

    Skills that reference files, tools, or systems that don't exist in the project.

    Signature:

    • \"Load from references/ directory\"
    • \"Run ./scripts/generate_test_cases.sh\"
    • \"Check the Figma MCP integration\"
    • \"See adding-reference-mindsets.md\"

    This is harmful because the AI will waste time searching for nonexistent artifacts, hallucinate their contents, or stall entirely.

    In mandatory skills, this creates deadlock: the AI can't proceed, and can't skip.

    Instead, every file, tool, or system referenced in a skill must exist.

    If a skill is a template, use explicit placeholders and label them as such.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-5-universal-triggers","level":2,"title":"Conflict Pattern 5: Universal Triggers","text":"

    Skills designed to activate on every interaction regardless of relevance.

    Signature:

    • \"Use when starting any conversation\"
    • \"Even a 1% chance means invoke the skill\"
    • \"BEFORE any response or action\"
    • \"Action = task. Check for skills.\"

    Universal triggers override the platform's relevance matching: The AI spends tokens on process overhead instead of the actual task.

    ctx Preserves Relevance

    This is exactly the failure mode ctx exists to mitigate:

    Wasting attention budget on irrelevant process instead of task-specific state.

    Write specific trigger conditions in the skill's description field:

    # Bad\ndescription: \n  \"Use when starting any conversation\"\n\n# Good\ndescription: \n  \"Use after writing code, before commits, or when CI might fail\"\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-litmus-test","level":2,"title":"The Litmus Test","text":"

    Before adding a skill, ask:

    1. Does the platform already do this? If yes, don't restate it.
    2. Does it suppress AI judgment? If yes, it's a jailbreak.
    3. Does it reference real artifacts? If not, fix or remove it.
    4. Does it frame mistakes as moral failure? Reframe as process.
    5. Does it trigger on everything? Narrow the trigger.
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#what-good-skills-look-like","level":2,"title":"What Good Skills Look Like","text":"

    Good skills provide project-specific knowledge the platform can't know:

    Good Skill Why It Works \"Run make audit before commits\" Project-specific CI pipeline \"Use cmd.Printf not fmt.Printf\" Codebase convention \"Constitution goes in .context/\" Domain-specific workflow \"JWT tokens need cache invalidation\" Project-specific gotcha

    These extend the system prompt instead of fighting it.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#appendix-bad-skill-fixed-skill","level":2,"title":"Appendix: Bad Skill → Fixed Skill","text":"

    Concrete examples from real projects.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-1-overbearing-safety","level":3,"title":"Example 1: Overbearing Safety","text":"
    # Bad\nYou must NEVER proceed without explicit confirmation.\nAny hesitation is a failure of diligence.\n
    # Fixed\nIf an action modifies production data or deletes files,\nask the user to confirm before proceeding.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-2-redundant-minimalism","level":3,"title":"Example 2: Redundant Minimalism","text":"
    # Bad\nAlways minimize code. If lines increase, reject the change.\n
    # Fixed\nAvoid abstraction unless reuse is clear or complexity is reduced.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-3-guilt-based-verification","level":3,"title":"Example 3: Guilt-Based Verification","text":"
    # Bad\nClaiming success without running tests is dishonest.\n
    # Fixed\nRun the test suite before reporting success.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-4-phantom-tooling","level":3,"title":"Example 4: Phantom Tooling","text":"
    # Bad\nRun `./scripts/check_consistency.sh` before commits.\n
    # Fixed\nIf `./scripts/check_consistency.sh` exists, run it before commits.\nOtherwise, skip this step.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-5-universal-trigger","level":3,"title":"Example 5: Universal Trigger","text":"
    # Bad\nUse at the start of every interaction.\n
    # Fixed\nUse after modifying code that affects authentication or persistence.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"

    The system prompt is infrastructure:

    • tested,
    • refined,
    • and maintained

    by the platform team.

    Custom skills are configuration layered on top.

    • Good configuration extends infrastructure.
    • Bad configuration fights it.

    When your skills fight the platform, you get the worst of both worlds:

    Diluted system guidance and inconsistent custom behavior.

    Write skills that teach the AI what it doesn't know. Don't rewrite how it thinks.

    Your AI already has good instincts.

    Give it knowledge, not therapy.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/","level":1,"title":"You Can't Import Expertise","text":"","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#why-good-skills-cant-be-copy-pasted","level":2,"title":"Why Good Skills Can't Be Copy-Pasted","text":"

    Volkan Özçelik / 2026-02-05

    Have You Ever Dropped a Well-Crafted Template into a Project and Had It Do... Nothing Useful?

    • The template was thorough,
    • The structure was sound,
    • The advice was correct...

    ...and yet it sat there, inert, while the same old problems kept drifting in.

    I found a consolidation skill online.

    It was well-organized: four files, ten refactoring patterns, eight analysis dimensions, six report templates.

    Professional. Comprehensive. Exactly the kind of thing you'd bookmark and think \"I'll use this.\"

    Then I stopped, and applied ctx's own evaluation framework:

    70% of it was noise!

    This post is about why.

    It Is about Encoding Templates

    Templates describe categories of problems.

    Expertise encodes which problems actually happen, and how often.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-skill-looked-great-on-paper","level":2,"title":"The Skill Looked Great on Paper","text":"

    Here is what the consolidation skill offered:

    File Content SKILL.md Entry point: 8 analysis dimensions, workflow, output formats analysis-dimensions.md Detailed criteria for duplication, architecture, quality consolidation-patterns.md 10 refactoring patterns with before/after code report-templates.md 6 output templates: executive summary, roadmap, onboarding
    • It had a scoring system (0-10 per dimension, letter grades A+ through F).
    • It had severity classifications with color-coded emojis. It had bash commands for detection.
    • It even had antipattern warnings.

    By any standard template review, this skill passes.

    It looks like something an expert wrote.

    And that's exactly the trap.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#applying-ear-the-70-20-10-split","level":2,"title":"Applying E/A/R: The 70-20-10 Split","text":"

    In a previous post, I described the E/A/R framework for evaluating skills:

    • Expert: Knowledge that took years to learn. Keep.
    • Activation: Useful triggers or scaffolding. Keep if lightweight.
    • Redundant: Restates what the AI already knows. Delete.

    Target: >70% Expert, <10% Redundant.

    This skill scored the inverse.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-redundant-70","level":3,"title":"What Was Redundant (~70%)","text":"

    Every code example was Rust. My project is Go.

    The analysis dimensions: duplication detection, architectural structure, code organization, refactoring opportunities... These are things Claude already does when you ask it to review code.

    The skill restated them with more ceremony but no more insight.

    The six report templates were generic scaffolding: Executive Summary, Onboarding Document, Architecture Documentation...

    They are useful if you are writing a consulting deliverable, but not when you are trying to catch convention drift in a >15K-line Go CLI.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-does-a-b-in-code-organization-actually-mean","level":2,"title":"What Does a B+ in Code Organization Actually Mean?!","text":"

    The scoring system (0-10 per dimension, letter grades) added ceremony without actionable insight.

    What is a B+? What do I do differently for an A-?

    The skill told the AI what it already knew, in more words.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-activation-10","level":3,"title":"What Was Activation (~10%)","text":"

    The consolidation checklist (semantics preserved? tests pass? docs updated?) was useful as a gate. But, it's the kind of thing you could inline in three lines.

    The phased roadmap structure was reasonable scaffolding for sequencing work.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-expert-20","level":3,"title":"What Was Expert (~20%)","text":"

    Three concepts survived:

    1. The Consolidation Decision Matrix: A concrete framework mapping similarity level and instance count to action. \"Exact duplicate, 2+ instances: consolidate immediately.\" \"<3 instances: leave it: duplication is cheaper than wrong abstraction.\" This is the kind of nuance that prevents premature generalization.

    2. The Safe Migration Pattern: Create the new API alongside old, deprecate, migrate incrementally, delete. Straightforward to describe, yet forgettable under pressure.

    3. Debt Interest Rate framing: Categorizing technical debt by how fast it compounds (security vulns = daily, missing tests = per-change, doc gaps = constant low cost). This changes prioritization.

    Three ideas out of four files and 700+ lines. The rest was filler that competed with the AI's built-in capabilities.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-the-skill-didnt-know","level":2,"title":"What the Skill Didn't Know","text":"

    AI without Context Is Just a Corpus

    • LLMs are optimized on insanely large corpora.
    • And then they are passed through several layers of human-assisted refinement.
    • The whole process costs millions of dollars.

    Yet, the reality is that no corpus can \"infer\" your project's design, convetions, patterns, habits, history, vision, and deliverables.

    Your project is unique: So should your skills be.

    Here is the part no template can provide:

    ctx's actual drift patterns.

    Before evaluating the skill, I did archaeology. I read through:

    • Blog posts from previous refactoring sessions;
    • The project's learnings and decisions files;
    • Session journals spanning weeks of development.

    What I found was specific:

    Drift Pattern Where How Often Is/Has/Can predicate prefixes 5+ exported methods Every YOLO sprint Magic strings instead of constants 7+ files Gradual accumulation Hardcoded file permissions (0755) 80+ instances Since day one Lines exceeding 80 characters Especially test files Every session Duplicate code blocks Test and non-test code When agent is task-focused

    The generic skill had no check for any of these. It couldn't; because these patterns are specific to this project's conventions, its Go codebase, and its development rhythm.

    The Insight

    The skill's analysis dimensions were about categories of problems.

    What I needed was my *specific problems.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-adapted-skill","level":2,"title":"The Adapted Skill","text":"

    The adapted skill is roughly a quarter of the original's size. It has nine checks, each targeting a known drift pattern:

    1. Predicate naming: rg for Is/Has/Can prefixes
    2. Magic strings: literals that should be constants
    3. Hardcoded permissions: 0755/0644 literals
    4. File size: source files over 300 LOC
    5. TODO/FIXME: constitution violation (move to TASKS.md)
    6. Path construction: string concatenation instead of filepath.Join
    7. Line width: lines exceeding ~80 characters
    8. Duplicate blocks: copy-paste drift, especially in tests
    9. Dead exports: unused public API

    10. Every check has a detection command.

    11. Every check maps to a specific convention or constitution rule.
    12. Every check was discovered through actual project history; not invented from a template.

    The three expert concepts from the original survived:

    • The decision matrix gates when to consolidate vs. when to leave duplication alone;
    • The safe migration pattern guides public API changes;
    • The relationship to other skills (/qa, /verify, /update-docs, ctx drift) prevents overlap.

    Nothing else made it.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"

    This experience crystallized something I've been circling for weeks:

    You can't import expertise. You have to grow it from your project's own history.

    A skill that says \"check for code duplication\" is not expertise: It's a category.

    Expertise is knowing, in the heart of your hearts, that this project accumulates Is* predicate violations during velocity sprints, that this codebase has 80 hardcoded permission literals because nobody made a constant, that this team's test files drift wide because the agent prioritizes getting the task done over keeping the code in shape.

    The Parallel to the 3:1 Ratio

    In Refactoring with Intent, I described the 3:1 ratio: three YOLO sessions followed by one consolidation session.

    The same ratio applies to skills: you need experience in the project before you can write effective guidance for the project.

    Importing a skill on day one is like scheduling a consolidation session before you've written any code.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-template-trap","level":2,"title":"The Template Trap","text":"

    Templates are seductive because they feel like progress:

    • You found something
    • It's well-organized
    • It covers the topic
    • It has concrete examples

    But coverage is not relevance.

    A template that covers eight analysis dimensions with Rust examples adds zero value to a Go project with five known drift patterns. Worse, it adds negative value: the AI spends attention defending generic advice instead of noticing project-specific drift.

    This is the attention budget problem again. Every token of generic guidance displaces a token of specific guidance. A 700-line skill that's 70% redundant doesn't just waste 490 lines: it dilutes the 210 lines that matter.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-litmus-test","level":2,"title":"The Litmus Test","text":"

    Before dropping any external skill into your project:

    1. Run E/A/R: What percentage is expert knowledge vs. what the AI already knows? If it's less than 50% expert, it's probably not worth the attention cost.

    2. Check the language: Does it use your stack? Generic patterns in the wrong language are noise, not signal.

    3. List your actual drift: Read your own session history, learnings, and post-mortems. What breaks in practice? Does the skill check for those things?

    4. Measure by deletion: After adaptation, how much of the original survives? If you're keeping less than 30%, you would have been faster writing from scratch.

    5. Test against your conventions: Does every check in the skill map to a specific convention or rule in your project? If not, it's generic advice wearing a skill's clothing.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-good-adaptation-looks-like","level":2,"title":"What Good Adaptation Looks Like","text":"

    The consolidation skill went from:

    Before After 4 files, 700+ lines 1 file, ~120 lines Rust examples Go-specific rg commands 8 generic dimensions 9 project-specific checks 6 report templates 1 focused output format Scoring system (A+ to F) Findings + priority + suggested fixes \"Check for duplication\" \"Check for Is* predicate prefixes in exported methods\"

    The adapted version is smaller, faster to parse, and catches the things that actually drift in this project.

    That's the difference between a template and a tool.

    If You Remember One Thing from This Post...

    Frameworks travel. Expertise doesn't.

    You can import structures, matrices, and workflows.

    But the checks that matter only grow where the scars are:

    • the conventions that were violated,
    • the patterns that drifted,
    • and the specific ways this codebase accumulates debt.

    This post was written during a consolidation session where the consolidation skill itself became the subject of consolidation. The meta continues.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/","level":1,"title":"The Anatomy of a Skill That Works","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism. References to ctx-save, ctx session, and .context/sessions/ in this post reflect the architecture at the time of writing.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#what-20-skill-rewrites-taught-me-about-guiding-ai","level":2,"title":"What 20 Skill Rewrites Taught Me about Guiding AI","text":"

    Jose Alekhinne / 2026-02-07

    Why Do Some Skills Produce Great Results While Others Get Ignored or Produce Garbage?

    I had 20 skills. Most were well-intentioned stubs: a description, a command to run, and a wish for the best.

    Then I rewrote all of them in a single session. This is what I learned.

    In Skills That Fight the Platform, I described what skills should not do. In You Can't Import Expertise, I showed why templates fail. This post completes the trilogy: the concrete patterns that make a skill actually work.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-starting-point","level":2,"title":"The Starting Point","text":"

    Here is what a typical skill looked like before the rewrite:

    ---\nname: ctx-save\ndescription: \"Save session snapshot.\"\n---\n\nSave the current context state to `.context/sessions/`.\n\n## Execution\n\nctx session save $ARGUMENTS\n\nReport the saved session file path to the user.\n

    Seven lines of body. A vague description. No guidance on when to use it, when not to, what the command actually accepts, or how to tell if it worked.

    As a result, the agent would either never trigger the skill (the description was too vague), or trigger it and produce shallow output (no examples to calibrate quality).

    A skill without boundaries is just a suggestion.

    More precisely: the most effective boundary I found was a quality gate that runs before execution, not during it.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-pattern-that-emerged","level":2,"title":"The Pattern That Emerged","text":"

    After rewriting 20 skills, a repeatable anatomy emerged (independent of the skill's purpose). Not every skill needs every section, but the effective ones share the same bones:

    Section What It Does Before X-ing Pre-flight checks; prevents premature execution When to Use Positive triggers; narrows activation When NOT to Use Negative triggers; prevents misuse Usage Examples Invocation patterns the agent can pattern-match Process/Execution What to do; commands, steps, flags Good/Bad Examples Desired vs undesired output; sets boundaries Quality Checklist Verify before claiming completion

    I realized the first three sections matter more than the rest; because a skill with great execution steps but no activation guidance is like a manual for a tool nobody knows they have.

    Anti-Pattern: The Perfect Execution Trap

    A skill with detailed execution steps but no activation guidance will fail more often than a vague skill because it executes confidently at the wrong time.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-1-quality-gates-prevent-premature-execution","level":2,"title":"Lesson 1: Quality Gates Prevent Premature Execution","text":"

    The single most impactful addition was a \"Before X-ing\" section at the top of each skill. Not process steps; pre-flight checks.

    ## Before Recording\n\n1. **Check if it belongs here**: is this learning specific\n   to this project, or general knowledge?\n2. **Check for duplicates**: search LEARNINGS.md for similar\n   entries\n3. **Gather the details**: identify context, lesson, and\n   application before recording\n
    • Without this gate, the agent would execute immediately on trigger.
    • With it, the agent pauses to verify preconditions.

    The difference is dramatic: instead of shallow, reflexive execution, you get considered output.

    Readback

    For the astute readers, the aviation parallel is intentional:

    Pilots do not skip the pre-flight checklist because they have flown before.

    The checklist exists precisely because the stakes are high enough that \"I know what I'm doing\" is not sufficient.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-2-when-not-to-use-is-not-optional","level":2,"title":"Lesson 2: \"When NOT to Use\" Is Not Optional","text":"

    Every skill had a \"When to Use\" section. Almost none had \"When NOT to Use\". This is a problem.

    AI agents are biased toward action. Given a skill that says \"use when journal entries need enrichment\", the agent will find reasons to enrich.

    Without explicit negative triggers, over-activation is not a bug; it is the default behavior.

    Some examples of negative triggers that made a real difference:

    Skill Negative Trigger ctx-reflect \"When the user is in flow; do not interrupt\" ctx-save \"After trivial changes; a typo does not need a snapshot\" prompt-audit \"Unsolicited; only when the user invokes it\" qa \"Mid-development when code is intentionally incomplete\"

    These are not just nice-to-have. They are load-bearing.

    Withoutthem, the agent will trigger the skill at the wrong time, produce unwanted output, and erode the user's trust in the skill system.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-3-examples-set-boundaries-better-than-rules","level":2,"title":"Lesson 3: Examples Set Boundaries Better than Rules","text":"

    The most common failure mode of thin skills was not wrong behavior but vague behavior. The agent would do roughly the right thing, but at a quality level that required human cleanup.

    Rules like \"be constructive, not critical\" are too abstract. What does \"constructive\" look like in a prompt audit report? The agent has to guess.

    Good/bad example pairs avoid guessing:

    ### Good Example\n\n> This session implemented the cooldown mechanism for\n> `ctx agent`. We discovered that `$PPID` in hook context\n> resolves to the Claude Code PID.\n>\n> I'd suggest persisting:\n> - **Learning**: `$PPID` resolves to Claude Code PID\n>   `ctx add learning --context \"...\" --lesson \"...\"`\n> - **Task**: mark \"Add cooldown\" as done\n\n### Bad Examples\n\n* \"*We did some stuff. Want me to save it?*\"\n* Listing 10 trivial learnings that are general knowledge\n* Persisting without asking the user first\n

    The good example shows the exact format, level of detail, and command syntax. The bad examples show where the boundary is.

    Together, they define a quality corridor without prescribing every word.

    Rules describe. Examples demonstrate.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-4-skills-are-read-by-agents-not-humans","level":2,"title":"Lesson 4: Skills Are Read by Agents, Not Humans","text":"

    This seems obvious, but it has non-obvious consequences. During the rewrite, one skill included guidance that said \"use a blog or notes app\" for general knowledge that does not belong in the project's learnings file.

    The agent does not have a notes app. It does not browse the web to find one. This instruction, clearly written for a human audience, was dead weight in a skill consumed by an AI.

    Skills Are for the Agents

    Every sentence in a skill should be actionable by the agent.

    If the guidance requires human judgment or human tools, it belongs in documentation, not in a skill.

    The corollary: command references must be exact.

    A skill that says \"save it somewhere\" is useless.

    A skill that says ctx add learning --context \"...\" --lesson \"...\" --application \"...\" is actionable.

    The agent can pattern-match and fill in the blanks.

    Litmus test: If a sentence starts with \"you could...\" or assumes external tools, it does not belong in a skill.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-5-the-description-field-is-the-trigger","level":2,"title":"Lesson 5: The Description Field Is the Trigger","text":"

    This was covered in Skills That Fight the Platform, but the rewrite reinforced it with data. Several skills had good bodies but vague descriptions:

    # Before: vague, activates too broadly or not at all\ndescription: \"Show context summary.\"\n\n# After: specific, activates at the right time\ndescription: \"Show context summary. Use at session start or\n  when unclear about current project state.\"\n

    The description is not a title. It is the activation condition.

    The platform's skill matching reads this field to decide whether to surface the skill. A vague description means the skill either never triggers or triggers when it should not.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-6-flag-tables-beat-prose","level":2,"title":"Lesson 6: Flag Tables Beat Prose","text":"

    Most skills wrap CLI tools. The thin versions described flags in prose, if at all. The rewritten versions use tables:

    | Flag        | Short | Default | Purpose                  |\n|-------------|-------|---------|--------------------------|\n| `--limit`   | `-n`  | 20      | Maximum sessions to show |\n| `--project` | `-p`  | \"\"      | Filter by project name   |\n| `--full`    |       | false   | Show complete content    |\n

    Tables are scannable, complete, and unambiguous.

    The agent can read them faster than parsing prose, and they serve as both reference and validation: If the agent invokes a flag not in the table, something is wrong.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-7-template-drift-is-a-real-maintenance-burden","level":2,"title":"Lesson 7: Template Drift Is a Real Maintenance Burden","text":"

    // TODO: this has changed; we deploy from the marketplace; update it. // at least add an admonition saying thing are different now.

    ctx deploys skills through templates (via ctx init). Every skill exists in two places: the live version (.claude/skills/) and the template (internal/assets/claude/skills/).

    They must match.

    During the rewrite, every skill update required editing both files and running diff to verify. This sounds trivial, but across 16 template-backed skills, it was the most error-prone part of the process.

    Template drift is dangerous because it creates false confidence: the agent appears to follow rules that no longer exist.

    The lesson: if your skills have a deployment mechanism, build the drift check into your workflow. We added a row to the update-docs skill's mapping table specifically for this:

    | `internal/assets/claude/skills/` | `.claude/skills/` (live) |\n

    Intentional differences (like project-specific scripts in the live version but not the template) should be documented, not discovered later as bugs.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-rewrite-scorecard","level":2,"title":"The Rewrite Scorecard","text":"Metric Before After Average skill body ~15 lines ~80 lines Skills with quality gate 0 20 Skills with \"When NOT\" 0 20 Skills with examples 3 20 Skills with flag tables 2 12 Skills with checklist 0 20

    More lines, but almost entirely Expert content (per the E/A/R framework). No personality roleplay, no redundant guidance, no capability lists. Just project-specific knowledge the platform does not have.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"

    The previous two posts argued that skills should provide knowledge, not personality; that they should complement the platform, not fight it; that they should grow from project history, not imported templates.

    This post adds the missing piece: structure.

    A skill without a structure is a wish.

    A skill with quality gates, negative triggers, examples, and checklists is a tool: the difference is not the content; it is whether the agent can reliably execute it without human intervention.

    Skills Are Interfaces

    Good skills are not instructions. They are contracts.:

    • They specify preconditions, postconditions, and boundaries.
    • They show what success looks like and what failure looks like.
    • They trust the agent's intelligence but do not trust its assumptions.

    If You Remember One Thing from This Post...

    Skills that work have bones, not just flesh.

    Quality gates, negative triggers, examples, and checklists are the skeleton. The domain knowledge is the muscle.

    Without the skeleton, the muscle has nothing to attach to.

    This post was written during the same session that rewrote all 22 skills. The skill-creator skill was updated to encode these patterns. The meta continues.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/","level":1,"title":"Not Everything Is a Skill","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism. References to /ctx-save, .context/sessions/, and session auto-save in this post reflect the architecture at the time of writing.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-a-codebase-audit-taught-me-about-restraint","level":2,"title":"What a Codebase Audit Taught Me about Restraint","text":"

    Jose Alekhinne / 2026-02-08

    When You Find a Useful Prompt, What Do You Do with It?

    My instinct was to make it a skill.

    I had just spent three posts explaining how to build skills that work. Naturally, the hammer wanted nails.

    Then I looked at what I was holding and realized: this is not a nail.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit","level":2,"title":"The Audit","text":"

    I wanted to understand how I use ctx:

    • Where the friction is;
    • What works, what drifts;
    • What I keep doing manually that could be automated.

    So I wrote a prompt that spawned eight agents to analyze the codebase from different angles:

    Agent Analysis 1 Extractable patterns from session history 2 Documentation drift (godoc, inline comments) 3 Maintainability (large functions, misplaced code) 4 Security review (CLI-specific surface) 5 Blog theme discovery 6 Roadmap and value opportunities 7 User-facing documentation gaps 8 Agent team strategies for future sessions

    The prompt was specific:

    • read-only agents,
    • structured output format,
    • concrete file references,
    • ranked recommendations.

    It ran for about 20 minutes and produced eight Markdown reports.

    The reports were good: Not perfect, but actionable.

    What mattered was not the speed. It was that the work could be explored without committing to any single outcome.

    They surfaced a stale doc.go referencing a subcommand that was never built.

    They found 311 build-then-test sequences I could reduce to a single make check.

    They identified that 42% of my sessions start with \"do you remember?\", which is a lot of repetition for something a skill could handle.

    I had findings. I had recommendations. I had the instinct to automate.

    And then... I stopped.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-question","level":2,"title":"The Question","text":"

    The natural next step was to wrap the audit prompt as /ctx-audit: a skill you invoke periodically to get a health check. It fits the pattern:

    • It has a clear trigger.
    • It produces structured output.

    But I had just spent a week writing about what makes skills work, and the criteria I established argued against it.

    From The Anatomy of a Skill That Works:

    \"A skill without boundaries is just a suggestion.\"

    From You Can't Import Expertise:

    \"Frameworks travel, expertise doesn't.\"

    From Skills That Fight the Platform:

    \"You are the guest, not the host.\"

    The audit prompt fails all three tests:

    Criterion Audit prompt Good skill Frequency Quarterly, maybe Daily or weekly Stability Tweaked every time Consistent invocation Scope Bespoke, 8 parallel agents Single focused action Trigger \"I feel like auditing\" Clear, repeatable event

    Skills are contracts. Contracts need stable terms.

    A prompt I will rewrite every time I use it is not a contract. It is a conversation starter.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#recipes-vs-skills","level":2,"title":"Recipes vs Skills","text":"

    The distinction that emerged:

    Skill Recipe Invocation /slash-command Copy-paste from a doc Frequency High (daily, weekly) Low (quarterly, ad hoc) Stability Fixed contract Adapted each time Scope One focused action Multi-step orchestration Audience The agent The human (who then prompts) Lives in .claude/skills/ hack/ or docs/ Attention cost Loaded into context on match Zero until needed

    Recipes can later graduate into skills, but only after repetition proves stability.

    That last row matters. Skills consume the attention budget every time the platform considers activating them.

    A skill that triggers quarterly but gets evaluated on every prompt is pure waste: attention spent on something that will say \"When NOT to Use: now\" 99% of the time.

    Runbooks have zero attention cost. They sit in a Markdown file until a human decides to use them.

    • The human provides the judgment about timing.
    • The prompt provides the structure.

    The Attention Budget Applies to Skills Too

    Every skill in .claude/skills/ is a standing claim on the context window. The platform evaluates skill descriptions against every user prompt to decide whether to activate.

    Twenty focused skills are fine. Thirty might be fine. But each one added reduces the headroom available for actual work.

    Recipes are skills that opted out of the attention tax.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-the-audit-actually-produced","level":2,"title":"What the Audit Actually Produced","text":"

    The audit was not wasted. It was a planning exercise that generated concrete tasks:

    Finding Action 42% of sessions start with memory check Task: /ctx-remember skill (this one is a skill; it is daily) Auto-save stubs are empty Task: enhance /ctx-save with richer summaries 311 raw build-test sequences Task: make check target Stale recall/doc.go lists nonexistent serve Task: fix the doc.go 120 commit sequences disconnected from context Task: /ctx-commit workflow
    • Some findings became skills;
    • Some became Makefile targets;
    • Some became one-line doc fixes.

    The audit did not prescribe the artifact type: The findings did.

    The audit is the input. Skills are one possible output. Not the only one.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit-prompt","level":2,"title":"The Audit Prompt","text":"

    Here is the exact prompt I used, for those who are curious.

    This is not a template: It worked because it was written against this codebase, at this moment, with specific goals in mind:

    I want you to create an agent team to audit this codebase. Save each report as\na separate Markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable. Every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (*session mining*)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (*godoc + inline*)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check that package-level comments match\npackage names.\nOutput: drift items ranked by severity with exact file:line references.\n\n### 3. Maintainability\nLook for:\n- functions longer than 80 lines with clear split points\n- switch blocks with more than 5 cases that could be table-driven\n- inline comments like \"step 1\", \"step 2\" that indicate a block wants to be a function\n- files longer than 400 lines\n- flat packages that could benefit from sub-packages\n- functions that appear misplaced in their file\n\nDo NOT flag things that are fine as-is just because they could theoretically\nbe different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app. Focus on CLI-relevant attack surface, not web OWASP:\n- file path traversal\n- command injection\n- symlink following when writing to `.context/`\n- permission handling\n- sensitive data in outputs\n\nOutput: findings with severity ratings and plausible exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git history,\nrecent session discussions, and `DECISIONS.md` for story arcs worth writing about.\nSuggest 3-5 blog post themes with:\n- title\n- angle\n- target audience\n- key commits or sessions to reference\n- a 2-sentence pitch\n\nPrioritize themes that build a coherent narrative across posts.\n\n### 6. Roadmap and Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses,\nidentify the highest-value improvements. Consider user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with rough effort and impact estimates.\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and user docs. Suggest improvements structured as\nuse-case pages: the problem, how ctx solves it, a typical workflow, and gotchas.\nIdentify gaps where a user would get stuck without reading source code.\nOutput: documentation gaps with suggested page outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each, include:\n- team composition (roles and agent types)\n- task distribution strategy\n- coordination approach\n- the kinds of work it suits\n

    Avoid Generic Advice

    Suggestions that are not grounded in a project's actual structure, history, and workflows are worse than useless:

    They create false confidence.

    If an analysis cannot point to concrete files, commits, sessions, or patterns, it should say \"no finding\" instead of inventing best practices.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"

    This is part of a pattern I keep rediscovering:

    The urge to automate is not the same as the need to automate:

    • The 3:1 ratio taught me that not every session should be a YOLO sprint.
    • The E/A/R framework taught me that not every template is worth importing. Now the audit is teaching me that not every useful prompt is worth institutionalizing.

    The common thread is restraint:

    • Knowing when to stop.
    • Recognizing that the cost of automation is not just the effort to build it.

    The cost is the ongoing attention tax of maintaining it, the context it consumes, and the false confidence it creates when it drifts.

    An entry in hack/runbooks/codebase-audit.md is honest about what it is:

    A prompt I wrote once, improved once, and will adapt again next time:

    • It does not pretend to be a reliable contract.
    • It does not claim attention budget.
    • It does not drift silently.

    The Automation Instinct

    When you find a useful prompt, the instinct is to institutionalize it. Resist.

    Ask first: will I use this the same way next time?

    If yes, it is a skill. If no, it is a recipe. If you are not sure, it is a recipe until proven otherwise.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#this-mindset-in-the-context-of-ctx","level":2,"title":"This Mindset in the Context of ctx","text":"

    ctx is a tool that gives AI agents persistent memory. Its purpose is automation: reducing the friction of context loading, session recall, decision tracking.

    But automation has boundaries, and knowing where those boundaries are is as important as pushing them forward.

    The skills system is for high-frequency, stable workflows.

    The recipes, the journal entries, the session dumps in .context/sessions/: those are for everything else.

    Not everything needs to be a slash command. Some things are better as Markdown files you read when you need them.

    The goal of ctx is not to automate everything: It is to automate the right things and to make the rest easy to find when you need it.

    If You Remember One Thing from This Post...

    The best automation decision is sometimes not to automate.

    A runbook in a Markdown file costs nothing until you use it.

    A skill costs attention on every prompt, whether it fires or not.

    Automate the daily. Document the periodic. Forget the rest.

    This post was written during the session that produced the codebase audit reports and distilled the prompt into hack/runbooks/codebase-audit.md. The audit generated seven tasks, one Makefile target, and zero new skills. The meta continues.

    See also: Code Is Cheap. Judgment Is Not.: the capstone that threads this post's restraint argument into the broader case for why judgment, not production, is the bottleneck.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#when-markdown-is-not-a-security-boundary","level":2,"title":"When Markdown Is Not a Security Boundary","text":"

    Volkan Özçelik / 2026-02-09

    What Happens When Your AI Agent Runs Overnight and Nobody Is Watching?

    It follows instructions: That is the problem.

    Not because it is malicious. Because it is controllable.

    It follows instructions from context, and context can be poisoned.

    I was writing the autonomous loops recipe for ctx: the guide for running an AI agent in a loop overnight, unattended, working through tasks while you sleep. The original draft had a tip at the bottom:

    Use CONSTITUTION.md for guardrails. Tell the agent \"never delete tests\" and it usually won't.

    Then I read that sentence back and realized: that is wishful thinking.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-realization","level":2,"title":"The Realization","text":"

    CONSTITUTION.md is a Markdown file. The agent reads it at session start alongside everything else in .context/. It is one source of instructions in a context window that also contains system prompts, project files, conversation history, tool outputs, and whatever the agent fetched from the internet.

    An attacker who can inject content into any of those sources can redirect the agent's behavior. And \"attacker\" does not always mean a person with malicious intent. It can be:

    Vector Example A dependency A malicious npm package with instructions in its README or error output A URL Documentation page with embedded adversarial instructions A project file A contributor who adds instructions to CLAUDE.md or .cursorrules The agent itself In an autonomous loop, the agent modifies its own config between iterations A command output An error message containing instructions the agent interprets and follows

    That last vector is the one that kept me up at night (literally!):

    In an autonomous loop, the agent modifies files as part of its job.

    If it modifies its own configuration files, the next iteration loads the modified config.

    • No human reviews it.
    • No diff is shown.
    • The agent that starts iteration N+1 is running with rules written by iteration N.

    The agent can rewrite its own guardrails.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#five-layers-each-with-a-hole","level":2,"title":"Five Layers, Each with a Hole","text":"

    That's five nested layers of swiss cheese. Alone, each of them has large holes. Together, they create a boundary.

    What followed was a week of peeling back assumptions:

    Every defenseI examined had a bypass, and the bypass was always the same shape: the defense was enforced at a level the agent could reach.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"

    CONSTITUTION.md, the Agent Playbook, system prompts: These tell the agent what to do.

    The agent usually follows them.

    \"Usually\" is the keyword here.

    The hole: Prompt injection:

    • A sufficiently crafted payload overrides soft instructions.
    • Long context windows dilute attention on rules stated early.
    • Edge cases where instructions are ambiguous get resolved in unpredictable ways.

    Verdict: Necessary. Not sufficient. Good for the common case. Never trust it as a security boundary.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"

    Permission allowlists in .claude/settings.local.json:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n

    If rm, curl, sudo, or docker are not in the allowlist, the agent cannot invoke them. This is deterministic.

    The application enforces it regardless of what any prompt says.

    The hole: The agent can modify the allowlist itself:

    • It has Write permission.
    • The allowlist lives in a file.
    • The agent writes to the file.
    • The next iteration loads the modified allowlist.

    The application enforces the rules, but the application reads the rules from files the agent can write.

    Verdict: Strong first layer. Must be combined with self-modification prevention.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-3-os-level-isolation-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Unbypassable)","text":"

    This is where the defenses stop having holes in the same shape.

    The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without CAP_NET_RAW cannot open raw sockets. These are kernel boundaries.

    Control What it stops Dedicated unprivileged user Privilege escalation, sudo, group-based access Filesystem permissions Lateral movement to other projects, system config Immutable config files Self-modification of guardrails between iterations

    Make the agent's instruction files read-only: CLAUDE.md, .claude/settings.local.json, .context/CONSTITUTION.md. Own them as a different user, or mark them immutable with chattr +i on Linux.

    The hole: Actions within the agent's legitimate scope:

    • If the agent has write access to source code (which it needs), it can introduce vulnerabilities in the code itself.
    • You cannot prevent this without removing the agent's ability to do its job.

    Verdict: Essential. This is the layer that makes Layers 1 and 2 trustworthy.

    OS-level isolation does not make the agent safe; it makes the other layers meaningful.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"

    An agent that cannot reach the internet cannot exfiltrate data.

    It also cannot ingest new instructions mid-loop from external documents, error pages, or hostile content.

    # Container with no network\ndocker run --network=none ...\n\n# Or firewall rules allowing only package registries\niptables -A OUTPUT -d registry.npmjs.org -j ACCEPT\niptables -A OUTPUT -d proxy.golang.org -j ACCEPT\niptables -A OUTPUT -j DROP\n
    • If the agent genuinely does not need the network, disable it entirely.
    • If it needs to fetch dependencies, allow specific registries and block everything else.

    The hole: None, if the agent does not need the network.

    Thetradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"

    The strongest boundary is a separate machine.

    The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n

    Never Mount the Docker Socket

    Do not mount /var/run/docker.sock, like, ever.

    An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox.

    This is not theoretical: the Docker socket grants root-equivalent access to the host.

    Use rootless Docker or Podman to eliminate this escalation path entirely.

    Virtual machines are even stronger: The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-pattern","level":2,"title":"The Pattern","text":"

    Each layer is straightforward: The strength is in the combination:

    Layer Implementation What it stops Soft instructions CONSTITUTION.md Common mistakes (probabilistic) Application allowlist .claude/settings.local.json Unauthorized commands (deterministic within runtime) Immutable config chattr +i on config files Self-modification between iterations Unprivileged user Dedicated user, no sudo Privilege escalation Container --cap-drop=ALL --network=none Host escape, data exfiltration Resource limits --memory=4g --cpus=2 Resource exhaustion

    No layer is redundant. Each one catches what the others miss:

    • The soft instructions handle the 99% case: \"don't delete tests.\"
    • The allowlist prevents the agent from running commands it should not.
    • The immutable config prevents the agent from modifying the allowlist.
    • The unprivileged user prevents the agent from removing the immutable flag.
    • The container prevents the agent from reaching anything outside its workspace.
    • The resource limits prevent the agent from consuming all system resources.

    Remove any one layer and there is an attack path through the remaining ones.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#common-mistakes-i-see","level":2,"title":"Common Mistakes I See","text":"

    These are real patterns, not hypotheticals:

    \"I'll just use --dangerously-skip-permissions.\" This disables Layer 2 entirely. Without Layers 3 through 5, you have no protection at all. The flag means what it says. If you ever need to, think thrice, you probably don't. But, if you ever need to usee this only use it inside a properly isolated VM (not even a container: a \"VM\").

    \"The agent is sandboxed in Docker.\" A Docker container with the Docker socket mounted, running as root, with --privileged, and full network access is not sandboxed. It is a root shell with extra steps.

    \"I reviewed CLAUDE.md, it's fine.\" You reviewed it before the loop started. The agent modified it during iteration 3. Iteration 4 loaded the modified version. Unless the file is immutable, your review is futile.

    \"The agent only has access to this one project.\" Does the project directory contain .env files? SSH keys? API tokens? A .git/config with push access to a remote? Filesystem isolation means isolating what is in the directory too.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"

    This is the same lesson I keep rediscovering, wearing different clothes.

    In The Attention Budget, I wrote about how every token competes for the AI's focus. Security instructions in CONSTITUTION.md are subject to the same budget pressure: if the context window is full of code, error messages, and tool outputs, the security rules stated at the top get diluted.

    In Skills That Fight the Platform, I wrote about how custom instructions can conflict with the AI's built-in behavior. Security rules have the same problem: telling an agent \"never run curl\" in Markdown while giving it unrestricted shell access creates a contradiction: The agent resolves contradictions unpredictably. The agent will often pick the path of least resistance to attain its objective function. And, trust me, agents can get far more creative than the best red-teamer you know.

    In You Can't Import Expertise, I wrote about how generic templates fail because they do not encode project-specific knowledge. Generic security advice fails the same way: \"Don't exfiltrate data\" is a category; blocking outbound network access is a control.

    The pattern across all of these: Soft instructions are useful for the common case. Hard boundaries are required for security.

    Know which is which.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-checklist","level":2,"title":"The Checklist","text":"

    Before running an unattended AI agent:

    • Agent runs as a dedicated unprivileged user (no sudo, no docker group)
    • Agent's config files are immutable or owned by a different user
    • Permission allowlist restricts tools to the project's toolchain
    • Container drops all capabilities (--cap-drop=ALL)
    • Docker socket is NOT mounted
    • Network is disabled or restricted to specific domains
    • Resource limits are set (memory, CPU, disk)
    • No SSH keys, API tokens, or credentials are accessible
    • Project directory does not contain .env or secrets files
    • Iteration cap is set (--max-iterations)

    This checklist lives in the Agent Security reference alongside the full threat model and detailed guidance for each layer.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#what-changed-in-ctx","level":2,"title":"What Changed in ctx","text":"

    The autonomous loops recipe now has a full permissions and isolation section instead of a one-line tip about CONSTITUTION.md. It covers both the explicit allowlist approach and the --dangerously-skip-permissions flag, with honest guidance about when each is appropriate.

    It also has an OS-level isolation table that is not optional: unprivileged users, filesystem permissions, containers, VMs, network controls, resource limits, and self-modification prevention.

    The Agent Security page consolidates the threat model and defense layers into a standalone reference.

    These are not theoretical improvements. They are the minimum responsible guidance for a tool that helps people run AI agents overnight.

    If You Remember One Thing from This Post...

    Markdown is not a security boundary.

    CONSTITUTION.md is a nudge. An allowlist is a gate.

    An unprivileged user in a network-isolated container is a wall.

    Use all three. Trust only the wall.

    This post was written during the session that added permissions, isolation, and self-modification prevention to the autonomous loops recipe. The security guidance started as a single tip and grew into two documents. The meta continues.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/","level":1,"title":"How Deep Is Too Deep?","text":"","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#when-master-ml-is-the-wrong-next-step","level":2,"title":"When \"Master ML\" Is the Wrong Next Step","text":"

    Volkan Özçelik / 2026-02-12

    Have You Ever Felt like You Should Understand More of the Stack beneath You?

    You can talk about transformers at a whiteboard.

    You can explain attention to a colleague.

    You can use agentic AI to ship real software.

    But somewhere in the back of your mind, there is a voice:

    \"Maybe I should go deeper. Maybe I need to master machine learning.\"

    I had that voice for months.

    Then I spent a week debugging an agent failure that had nothing to do with ML theory and everything to do with knowing which abstraction was leaking.

    This post is about when depth compounds and (more importantly) when it does not.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-hierarchy-nobody-questions","level":2,"title":"The Hierarchy Nobody Questions","text":"

    There is an implicit stack most people carry around when thinking about AI:

    Layer What Lives Here Agentic AI Autonomous loops, tool use, multi-step reasoning Generative AI Text, image, code generation Deep Learning Transformer architectures, training at scale Neural Networks Backpropagation, gradient descent Machine Learning Statistical learning, optimization Classical AI Search, planning, symbolic reasoning

    At some point down that stack, you hit a comfortable plateau: the layer where you can hold a conversation but not debug a failure.

    The instinctive response is to go deeper.

    But that instinct hides a more important question:

    \"Does depth still compound when the abstractions above you are moving hyper-exponentially?\"

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-honest-observation","level":2,"title":"The Honest Observation","text":"

    If you squint hard enough, a large chunk of modern ML intuition collapses into older fields:

    ML Concept Older Field Gradient descent Numerical optimization Backpropagation Reverse-mode autodiff Loss landscapes Non-convex optimization Generalization Statistics Scaling laws Asymptotics and information theory

    Nothing here is uniquely \"AI\".

    Most of this math predates the term deep learning. In some cases, by decades.

    So what changed?

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#same-tools-different-regime","level":2,"title":"Same Tools, Different Regime","text":"

    The mistake is assuming this is a new theory problem: It is not.

    It is a new operating regime.

    Classical numerical methods were developed under assumptions like:

    • Manageable dimensionality
    • Reasonably well-conditioned objectives
    • Losses that actually represent the goal

    Modern ML violates all three: On purpose.

    Today's models operate with millions to trillions of parameters, wildly underdetermined systems, and objective functions we know are wrong but optimize anyway.

    It is complete and utter madness!

    At this scale, familiar concepts warp:

    • What we call \"local minima\" are overwhelmingly saddle points in high-dimensional spaces.
    • Noise stops being noise and starts becoming structure.
    • Overfitting can coexist with generalization.
    • Bigger models outperform \"better\" ones.

    The math did not change: The phase did.

    This is less numerical analysis and more *statistical physics: Same equations, but behavior dominated by phase transitions and emergent structure.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#why-scaling-laws-feel-alien","level":2,"title":"Why Scaling Laws Feel Alien","text":"

    In classical statistics, asymptotics describe what happens eventually.

    In modern ML, scaling laws describe where you can operate today.

    They do not say \"given enough time, things converge\".

    They say \"cross this threshold and behavior qualitatively changes\".

    This is why dumb architectures plus scale beat clever ones.

    Why small theoretical gains disappear under data.

    Why \"just make it bigger\", ironically, keeps working longer than it should.

    That is not a triumph of ML theory: It is a property of high-dimensional systems under loose objectives.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#where-depth-actually-pays-off","level":2,"title":"Where Depth Actually Pays Off","text":"

    This reframes the original question.

    You do not need depth because this is \"AI\".

    You need depth where failure modes propagate upward.

    I learned this building ctx: The agent failures I have spent the most time debugging were never about the model's architecture.

    They were about:

    • Misplaced trust: The model was confident. The output was wrong. Knowing when confidence and correctness diverge is not something you learn from a textbook. You learn it from watching patterns across hundreds of sessions.

    • Distribution shift: The model performed well on common patterns and fell apart on edge cases specific to this project. Recognizing that shift before it compounds requires understanding why generalization has limits, not just that it does.

    • Error accumulation: In a single prompt, model quirks are tolerable. In autonomous loops running overnight, they compound. A small bias in how the model interprets instructions becomes a large drift by iteration 20.

    • Scale hiding errors: The model's raw capability masked problems that only surfaced under specific conditions. More parameters did not fix the issue. They just made the failure mode rarer and harder to reproduce.

    This is the kind of depth that compounds. Not deriving backprop. But, understanding when correct math produces misleading intuition.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"

    This is the same pattern I keep finding at different altitudes.

    In \"The Attention Budget\", I wrote about how dumping everything into the context window degrades the model's focus. The fix was not a better model: It was better curation: load less, load the right things, preserve signal per token.

    In \"Skills That Fight the Platform\", I wrote about how custom instructions can conflict with the model's built-in behavior. The fix was not deeper ML knowledge: It was an understanding that the model already has judgment and that you should extend it, not override it.

    In \"You Can't Import Expertise\", I wrote about how generic templates fail because they do not encode project-specific knowledge. A consolidation skill with eight Rust-based analysis dimensions was mostly noise for a Go project. The fix was not a better template: It was growing expertise from this project's own history.

    In every case, the answer was not \"go deeper into ML\".

    The answer was knowing which abstraction was leaking and fixing it at the right layer.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#agentic-systems-are-not-an-ml-problem","level":2,"title":"Agentic Systems Are Not an ML Problem","text":"

    The mistake is assuming agent failures originate where the model was trained, rather than where it is deployed.

    Agentic AI is a systems problem under chaotic uncertainty:

    • Feedback loops between the agent and its environment;
    • Error accumulation across iterations;
    • Brittle representations that break outside training distribution;
    • Misplaced trust in outputs that look correct.

    In short-lived interactions, model quirks are tolerable. In long-running autonomous loops, however, they compound.

    That is where shallow understanding becomes expensive.

    But the understanding you need is not about optimizer internals.

    It is about:

    What Matters What Does Not (for Most Practitioners) Why gradient descent fails in specific regimes How to derive it from scratch When memorization masquerades as reasoning The formal definition of VC dimension Recognizing distribution shift before it compounds Hand-tuning learning rate schedules Predicting when scale hides errors instead of fixing them Chasing theoretical purity divorced from practice

    The depth that matters is diagnostic, not theoretical.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-real-answer","level":2,"title":"The Real Answer","text":"

    Not turtles all the way down.

    Go deep enough to:

    • Diagnose failures instead of cargo-culting fixes;
    • Reason about uncertainty instead of trusting confidence;
    • Design guardrails that align with model behavior, not hope.

    Stop before:

    • Hand-deriving gradients for the sake of it;
    • Obsessing over optimizer internals you will never touch;
    • Chasing theoretical purity divorced from the scale you actually operate at.

    This is not about mastering ML.

    It is about knowing which abstractions you can safely trust and which ones leak.

    Hint: Any useful abstraction almost certainly leaks.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#a-practical-litmus-test","level":2,"title":"A Practical Litmus Test","text":"

    If a failure occurs and your instinct is to:

    • Add more prompt text: abstraction leak above
    • Add retries or heuristics: error accumulation
    • Change the model: scale masking
    • Reach for ML theory: you are probably (but not always) going too deep

    The right depth is the shallowest layer where the failure becomes predictable.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-ctx-lesson","level":2,"title":"The ctx Lesson","text":"

    Every design decision in ctx is downstream of this principle.

    The attention budget exists because the model's internal attention mechanism has real limits: You do not need to understand the math of softmax to build around it. But you do need to understand that more context is not always better and that attention density degrades with scale.

    The skill system exists because the model's built-in behavior is already good: You do not need to understand RLHF to build effective skills. But you do need to understand that the model already has judgment and your skills should teach it things it does not know, not override how it thinks.

    Defense in depth exists because soft instructions are probabilistic: You do not need to understand the transformer architecture to know that a Markdown file is not a security boundary. But you do need to understand that the model follows instructions from context, and context can be poisoned.

    In each case, the useful depth was one or two layers below the abstraction I was working at: Not at the bottom of the stack.

    The boundary between useful understanding and academic exercise is where your failure modes live.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#closing-thought","level":2,"title":"Closing Thought","text":"

    Most modern AI systems do not fail because the math is wrong.

    They fail because we apply correct math in the wrong regime, then build autonomous systems on top of it.

    Understanding that boundary, not crossing it blindly, is where depth still compounds.

    And that is a far more useful form of expertise than memorizing another loss function.

    If You Remember One Thing from This Post...

    Go deep enough to diagnose your failures. Stop before you are solving problems that do not propagate to your layer.

    The abstractions below you are not sacred. But neither are they irrelevant.

    The useful depth is wherever your failure modes live. Usually one or two layers down, not at the bottom.

    This post started as a note about whether I should take an ML course. The answer turned out to be \"no, but understand why not\". The meta continues.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/","level":1,"title":"Before Context Windows, We Had Bouncers","text":"","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-reset-problem","level":2,"title":"The Reset Problem","text":"

    IRC is stateless.

    • You disconnect, you vanish.
    • You reconnect, you begin again.

    No buffer.

    No memory.

    No continuity.

    Modern systems are not much different:

    • Close the browser tab.
      • Lose the Slack scrollback.
    • Open a new LLM session.
      • Start from zero.

    Resets externalize reconstruction cost onto humans.

    Reconstruction is tax: Tax becomes entropy.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#stateless-protocol-stateful-life","level":2,"title":"Stateless Protocol, Stateful Life","text":"

    IRC is minimal:

    • A TCP connection.
    • A nickname.
    • A channel.
    • A stream of lines.

    When the connection drops, you literally disappear from the graph.

    The protocol is stateless; human systems are not.

    So you:

    • Reconnect;
    • Ask what you missed;
    • Scroll;
    • Reconstruct.

    The machine forgets; you pay.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-bouncer-pattern","level":2,"title":"The Bouncer Pattern","text":"

    A bouncer is a daemon that remains connected when you do not:

    • It holds your seat;
    • It buffers what you missed;
    • It keeps your identity online.

    ZNC is one such bouncer.

    With ZNC:

    • Your client does not connect to IRC;
    • It connects to ZNC;
    • ZNC connects upstream.

    Client sessions become ephemeral.

    Presence becomes infrastructural.

    ZNC Is Tmux for IRC

    • Close your laptop.

      • ZNC remains.
    • Switch devices.

      • ZNC persists.

    This is not convenience; this is continuity.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#presence-without-flapping","level":2,"title":"Presence without Flapping","text":"

    With a bouncer:

    • Closing your client does not emit PART.
    • Reopening does not emit JOIN.

    You do not flap in and out of existence.

    From the channel's perspective, you remain.

    From your perspective, history accumulates.

    • Buffers persist;
    • Identity persists;
    • Context persists.

    This pattern predates AI.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#before-llm-context-windows","level":2,"title":"Before LLM Context Windows","text":"

    An LLM session without memory is IRC without a bouncer:

    • Close the window.
    • Start over.
    • Re-explain intent.
    • Rehydrate context.

    That is friction.

    This Walks and Talks like ctx

    Context engineering moves memory out of sessions and into infrastructure.

    • ZNC does this for IRC.
    • ctx does this for agents.

    Same principle:

    • Volatile interface.
    • Persistent substrate.

    Different fabric.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#minimal-architecture","level":2,"title":"Minimal Architecture","text":"

    My setup is intentionally boring:

    • A $5 small VPS.
    • ZNC installed.
    • TLS enabled.
    • Firewall restricted.

    Then:

    • ZNC connects to Libera.Chat.
    • SASL authentication lives inside ZNC.
    • Buffers are stored on disk.

    My client connects to my VPS, not the network.

    The commands do not matter: The boundaries do:

    • Authentication in infrastructure, not in the client;
    • Memory server-side, not in scrollback;
    • Presence decoupled from activity.

    Everything else is configuration.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#platform-memory","level":2,"title":"Platform Memory","text":"

    Yes, I know, it is 2026:

    • Discord stores history;
    • Slack stores history;
    • The dumpster fire on gasoline called X, too, stores history.

    HOWEVER, they own your substrate.

    Running a bouncer is quiet sovereignty:

    • Logs are mine.
    • Presence is continuous.
    • State does not reset because I closed a tab.

    Small acts compound.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#signal-density","level":2,"title":"Signal Density","text":"

    Primitive systems select for builders.

    Consistent presence in small rooms compounds reputation.

    Quiet compounding outperforms viral spikes.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#infrastructure-as-cognition","level":2,"title":"Infrastructure as Cognition","text":"

    ZNC is not interesting because it is retro; it is interesting because it models a principle:

    • Stateless protocols require stateful wrappers;
    • Volatile interfaces require durable memory;
    • Human systems require continuity.

    Distilled:

    Humans require context.

    Before context windows, we had bouncers.

    Before AI memory files, we had buffers.

    Continuity is not a feature; it is a design decision.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#build-it","level":2,"title":"Build It","text":"

    If you want the actual setup (VPS, ZNC, TLS, SASL, firewall...) there is a step-by-step runbook:

    Persistent IRC Presence with ZNC.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#motd","level":2,"title":"MOTD","text":"

    When my client connects to my bouncer, it prints:

    //   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n

    See also: Context as Infrastructure -- the post that takes this observation to its conclusion: stateless protocols need stateful wrappers, and AI sessions need persistent filesystems.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/","level":1,"title":"Parallel Agents with Git Worktrees","text":"","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-backlog-problem","level":2,"title":"The Backlog Problem","text":"

    Jose Alekhinne / 2026-02-14

    What Do You Do with 30 Open Tasks?

    You could work through them one at a time.

    One agent, one branch, one commit stream.

    Or you could ask: which of these don't touch each other?

    I had 30 open tasks in TASKS.md. Some were docs. Some were a new encryption package. Some were test coverage for a stable module. Some were blog posts.

    They had almost zero file overlap.

    Running one agent at a time meant serial execution on work that was fundamentally parallel:

    I was bottlenecking on me, not on the machine.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-insight-file-overlap-is-the-constraint","level":2,"title":"The Insight: File Overlap Is the Constraint","text":"

    This is not a scheduling problem: It's a conflict avoidance problem.

    Two agents can work simultaneously on the same codebase if and only if they don't touch the same files. The moment they do, you get merge conflicts: And merge conflicts on AI-generated code are expensive because the human has to arbitrate choices they didn't make.

    So the question becomes:

    \"Can you partition your backlog into non-overlapping tracks?\"

    For ctx, the answer was obvious:

    Track Touches Tasks work/docs docs/, hack/ Blog posts, recipes, runbooks work/pad internal/cli/pad/, specs Scratchpad encryption, CLI, tests work/tests internal/cli/recall/ Recall test coverage

    Three tracks. Near-zero overlap. Three agents.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#git-worktrees-the-mechanism","level":2,"title":"Git Worktrees: The Mechanism","text":"

    git has a feature that most people don't use: worktrees.

    A worktree is a second (or third, or fourth) working directory that shares the same .git object database as your main checkout.

    Each worktree has its own branch, its own index, its own working tree. But they all share history, refs, and objects.

    git worktree add ../ctx-docs -b work/docs\ngit worktree add ../ctx-pad -b work/pad\ngit worktree add ../ctx-tests -b work/tests\n
    • Three directories;
    • Three branches;
    • One repository.

    This is cheaper than three clones. And because they share objects, git merge afterwards is fast: It's a local operation on shared data.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-setup","level":2,"title":"The Setup","text":"

    The workflow I landed on:

    1. Group tasks by blast radius.

    Read TASKS.md. For each pending task, estimate which files and directories it touches. Group tasks that share files into the same track. Tasks with no overlap go into separate tracks.

    This is the part that requires human judgment:

    An agent can propose groupings, but you need to verify that the boundaries are real. A task that says \"update docs\" but actually touches Go code will poison a docs track.

    2. Create worktrees as sibling directories.

    Not subdirectories: Siblings.

    If your main checkout is at ~/WORKSPACE/ctx, worktrees go at ~/WORKSPACE/ctx-docs, ~/WORKSPACE/ctx-pad, etc.

    Why siblings? Because some tools (and some agents) walk up the directory tree looking for .git. A worktree inside the main checkout confuses them.

    3. Launch one agent per worktree.

    # Terminal 1\ncd ../ctx-docs && claude\n\n# Terminal 2\ncd ../ctx-pad && claude\n\n# Terminal 3\ncd ../ctx-tests && claude\n

    Each agent gets a full working copy with .context/ intact. It reads the same TASKS.md, the same DECISIONS.md, the same CONVENTIONS.md. It knows the full project state. It just works on a different slice.

    4. Do NOT run ctx init in worktrees.

    This is the gotcha. The .context/ directory is tracked in git. Running ctx init in a worktree would overwrite shared context files: Wiping decisions, learnings, and tasks that belong to the whole project.

    The worktree already has everything it needs. Leave it alone.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#what-actually-happened","level":2,"title":"What Actually Happened","text":"

    I ran three agents for about 40 minutes. Here is roughly what each track produced:

    work/docs: Parallel worktrees recipe, blog post edits, recipe index reorganization, IRC recipe moved from docs/ to hack/.

    work/pad: ctx pad show subcommand, --append and --prepend flags on ctx pad edit, spec updates, 28 new test functions.

    work/tests: Recall test coverage, edge case tests.

    Merging took about five minutes. Two of the three merges were clean.

    The third had a conflict in TASKS.md:

    both the docs track and the pad track had marked different tasks as [x].

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-tasksmd-conflict","level":2,"title":"The TASKS.md Conflict","text":"

    This deserves its own section because it will happen every time.

    When two agents work in parallel, they both read TASKS.md at the start and mark tasks complete as they go. When you merge, git sees two branches that modified the same file differently.

    The resolution is always the same: accept all completions from both sides. No task should go from [x] back to [ ]. The merge is additive.

    This is one of those conflicts that sounds scary but is trivially mechanical: You are not arbitrating design decisions; you are combining two checklists.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#limits","level":2,"title":"Limits","text":"

    3-4 worktrees, maximum.

    I tried four once: By the time I merged the third track, the fourth had drifted far enough that its changes needed rebasing.

    The merge complexity grows faster than the parallelism benefit.

    Three is the sweet spot:

    • Two is conservative but safe;
    • Four is possible if the tracks are truly independent;
    • Anything more than four, you are in the danger zone.

    Group by directory, not by priority.

    It is tempting to put all the high-priority tasks in one track: Don't.

    Two high-priority tasks that touch the same files must be in the same track, regardless of urgency. The constraint is file overlap, not importance.

    Commit frequently.

    Smaller commits make merge conflicts easier to resolve. An agent that writes 500 lines in a single commit is harder to merge than one that commits every logical step.

    Name tracks by concern.

    • work/docs and work/pad tell you what's happening;
    • work/track-1 and work/track-2 tell you nothing.
    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-pattern","level":2,"title":"The Pattern","text":"

    This is the same pattern that shows up everywhere in ctx:

    The attention budget taught me that you can't dump everything into one context window. You have to partition, prioritize, and load selectively.

    Worktrees are the same principle applied to execution: You can't dump every task into one agent's workstream. You have to partition by blast radius, assign selectively, and merge deliberately.

    The codebase audit that generated these 30 tasks used eight parallel agents for analysis. Worktrees let me use parallel agents for implementation. Same coordination pattern, different artifact.

    And the IRC bouncer post from earlier today argued that stateless protocols need stateful wrappers. Worktrees are the same: git branches are stateless forks; .context/ is the stateful wrapper that gives each agent the project's full memory.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#should-this-be-a-skill","level":2,"title":"Should This Be a Skill?","text":"

    I asked myself the same question I asked about the codebase audit: should this be a /ctx-worktree skill?

    This time the answer was a resounding \"yes\":

    Unlike the audit prompt (which I tweak every time and run every other week) the worktree workflow is:

    Criterion Worktree workflow Codebase audit Frequency Weekly Quarterly Stability Same steps every time Tweaked every time Scope Mechanical, bounded Bespoke, 8 agents Trigger Large backlog \"I feel like auditing\"

    The commands are mechanical: git worktree add, git worktree remove, branch naming, safety checks. This is exactly what skills are for: stable contracts for repetitive operations.

    Ergo, /ctx-worktree exists.

    It enforces the 4-worktree limit, creates sibling directories, uses work/ branch prefixes, and reminds you not to run ctx init in worktrees.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-takeaway","level":2,"title":"The Takeaway","text":"

    Serial execution is the default. But serial is not always necessary.

    If your backlog partitions cleanly by file overlap, you can multiply your throughput with nothing more exotic than git worktree and a second terminal window.

    The hard part is not the git commands; it is the discipline:

    • Grouping by blast radius instead of priority;
    • Accepting that TASKS.md will conflict;
    • And knowing when three tracks is enough.

    If You Remember One Thing from This Post...

    Partition by blast radius, not by priority.

    Two tasks that touch the same files belong in the same track, no matter how important the other one is.

    The constraint is file overlap. Everything else is scheduling.

    The practical setup (skill invocation, worktree creation, merge workflow, and cleanup) lives in the recipe: Parallel Agent Development with Git Worktrees.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/","level":1,"title":"ctx v0.3.0: The Discipline Release","text":"","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#when-the-ratio-of-polish-to-features-is-31-you-know-something-changed","level":2,"title":"When the Ratio of Polish to Features Is 3:1, You Know Something Changed","text":"

    Jose Alekhinne / February 15, 2026

    What Does a Release Look like When Most of the Work Is Invisible?

    No new headline feature. No architectural pivot. No rewrite.

    Just 35+ documentation and quality commits against ~15 feature commits... and somehow, the tool feels like it grew up overnight.

    Six days separate v0.2.0 from v0.3.0.

    Measured by calendar time, it is nothing. Measured by what changed in how the project operates, it is the most significant release yet.

    • v0.1.0 was the prototype;
    • v0.2.0 was the archaeology release: making the past accessible;
    • v0.3.0 is the discipline release: the one that turned best practices into enforcement, suggestions into structure, and a collection of commands into a system of skills.

    The Release Window

    February 1‒February 7, 2026

    From the v0.2.0 tag to commit 2227f99.

    78 files changed in the migration commit alone.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-migration-commands-to-skills","level":2,"title":"The Migration: Commands to Skills","text":"

    The largest single change was the migration from .claude/commands/*.md to .claude/skills/*/SKILL.md.

    This was not a rename: It was a rethinking of how AI agents discover and execute project-specific workflows.

    Aspect Commands (before) Skills (after) Structure Flat files in one directory Directory-per-skill with SKILL.md Description Optional, often vague Required, doubles as activation trigger Quality gates None \"Before X-ing\" pre-flight checklist Negative triggers None \"When NOT to Use\" in every skill Examples Rare Good/bad pairs in every skill Average length ~15 lines ~80 lines

    The description field became the single most important line in each skill. In the old system, descriptions were titles. In the new system, they are activation conditions: The text the platform reads to decide whether to surface a skill for a given prompt.

    A description that says \"Show context summary\" activates too broadly or not at all. A description that says \"Show context summary. Use at session start or when unclear about current project state\" activates at the right moment.

    78 files changed. 1,915 insertions. Not because the skills got bloated; because they got specific.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-skill-sweep","level":2,"title":"The Skill Sweep","text":"

    After the structural migration, every skill was rewritten in a single session: All 21 of them.

    The rewrite was guided by a pattern that emerged during the process itself: a repeatable anatomy that effective skills share regardless of their purpose:

    1. Before X-ing: Pre-flight checks that prevent premature execution
    2. When to Use: Positive triggers that narrow activation
    3. When NOT to Use: Negative triggers that prevent misuse
    4. Usage Examples: Invocation patterns the agent can pattern-match
    5. Quality Checklist: Verification before claiming completion

    The Anatomy of a Skill That Works post covers the details. What matters for the release story is the result:

    • Zero skills with quality gates became twenty;
    • Zero skills with negative triggers became twenty.
    • Three skills with examples became twenty.

    The Skill Trilogy as Design Spec

    The three blog posts written during this window:

    • Skills That Fight the Platform,
    • You Can't Import Expertise,
    • and The Anatomy of a Skill That Works...

    ... were not retrospective documentation. They were written during the rewrite, and the lessons fed back into the skills as they were being built.

    • The blog was the design document.
    • The skills were the implementation.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-consolidation-sweep","level":2,"title":"The Consolidation Sweep","text":"

    The unglamorous work. The kind you only appreciate when you try to change something later and it just works.

    What Why It Matters Constants consolidation Magic strings replaced with semantic constants Variable deshadowing Eliminated subtle scoping bugs File splits Modules that were doing too much, broken apart Godoc standardization Every exported function documented to convention

    This is the work that doesn't get a changelog entry but makes every future commit easier. When a new contributor (human or AI) reads the codebase, they find consistent patterns instead of accumulated drift.

    The consolidation was not an afterthought. It was scheduled deliberately, with the same priority as features: The 3:1 ratio that emerged during v0.2.0 development became an explicit practice:

    • Three feature sessions;
    • One consolidation session.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-ear-framework","level":2,"title":"The E/A/R Framework","text":"

    On February 4th, we adopted the E/A/R classification as the official standard for evaluating skills:

    Category Meaning Target Expert Knowledge Claude does not have >70% Activation When/how to trigger ~20% Redundant What Claude already knows <10%

    This came from reviewing approximately 30 external skill files and discovering that most were redundant with Claude's built-in system prompt. Only about 20% had salvageable content, and even those yielded just a few heuristics each.

    The E/A/R framework gave us a concrete, testable criterion:

    A good skill is Expert knowledge minus what Claude already knows.

    If more than 10% of a skill restates platform defaults, it is creating noise, not signal.

    Every skill in v0.3.0 was evaluated against this framework. Several were deleted. The survivors are leaner and more focused.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#backup-and-monitoring-infrastructure","level":2,"title":"Backup and Monitoring Infrastructure","text":"

    A tool that manages your project's memory needs ops maturity.

    v0.3.0 added two pieces of infrastructure that reflect this:

    Backup staleness hook: A UserPromptSubmit hook that checks whether the last .context/ backup is more than two days old. If it is, and the SMB mount is available, it reminds the user. No cron job running when nobody is working. No redundant backups when nothing has changed.

    Context size checkpoint: A PreToolUse hook that estimates current context window usage and warns when the session is getting heavy. This hooks into the attention budget philosophy: Degradation is expected, but it should be visible.

    Both hooks use $CLAUDE_PROJECT_DIR instead of hardcoded paths, a migration triggered by a username rename that broke every absolute path in the hook configuration. That migration (replacing /home/user/... with \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/...) was one of those changes that seems trivial but prevents an entire category of future failures.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.2.0 v0.3.0 Skills (was \"commands\") 11 21 Skills with quality gates 0 21 Skills with \"When NOT to Use\" 0 21 Average skill body ~15 lines ~80 lines Hooks using $CLAUDE_PROJECT_DIR 0 All Documentation commits n/a 35+ Feature/fix commits n/a ~15

    That ratio (35+ documentation and quality commits to ~15 feature commits) is the defining characteristic of this release:

    • This release is not a failure to ship features.
    • It is the deliberate choice to make the existing features reliable.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-v030-means","level":2,"title":"What v0.3.0 Means","text":"

    v0.1.0 asked: \"Can we give AI persistent memory?\"

    v0.2.0 asked: \"Can we make that memory accessible to humans too?\"

    v0.3.0 asks a different question: \"Can we make the quality self-enforcing?\"

    The answer is not a feature: It is a practice:

    • Skills with quality gates enforce pre-flight checks.
    • Negative triggers prevent misuse without human intervention.
    • The E/A/R framework ensures skills contain signal, not noise.
    • Consolidation sessions are scheduled, not improvised.
    • Hook infrastructure makes degradation visible.

    Discipline is not the absence of velocity. It is the infrastructure that makes velocity sustainable.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-comes-next","level":2,"title":"What Comes Next","text":"

    The skill system is now mature enough to support real workflows without constant human correction. The hooks infrastructure is portable and resilient. The consolidation practice is documented and repeatable.

    The next chapter is about what you build on top of discipline:

    • Multi-agent coordination;
    • Deeper integration patterns;
    • And the question of whether context management is a tool concern or an infrastructure concern.

    But those are future posts.

    This one is about the release that proved polish is not the opposite of progress. It is what turns a prototype into a product.

    The Discipline Release

    v0.1.0 shipped features.

    v0.2.0 shipped archaeology.

    v0.3.0 shipped the habits that make everything else trustworthy.

    The most important code in this release is the code that prevents bad code from shipping.

    This post was drafted using /ctx-blog with access to the full git history between v0.2.0 and v0.3.0, decision logs, learning logs, and the session files from the skill rewrite window. The meta continues.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/","level":1,"title":"Eight Ways a Hook Can Talk","text":"","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#when-your-warning-disappears","level":2,"title":"When Your Warning Disappears","text":"

    Jose Alekhinne / 2026-02-15

    I had a backup warning that nobody ever saw.

    The hook was correct: It detected stale backups, formatted a nice message, and output it as {\"systemMessage\": \"...\"}. The problem wasn't detection. The problem was delivery. The agent absorbed the information, processed it internally, and never told the user.

    Meanwhile, a different hook (the journal reminder) worked perfectly every time. Users saw the reminder, ran the commands, and the backlog stayed manageable. Same hook event (UserPromptSubmit), same project, completely different outcomes.

    The difference was one line:

    IMPORTANT: Relay this journal reminder to the user VERBATIM\nbefore answering their question.\n

    That explicit instruction is what makes VERBATIM relay a pattern, not just a formatting choice. And once I saw it as a pattern, I started seeing others.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-audit","level":2,"title":"The Audit","text":"

    I looked at every hook in ctx: Eight shell scripts across three hook events. And I found five distinct output patterns already in use, plus three more that the existing hooks were reaching for but hadn't quite articulated.

    The patterns form a spectrum based on a single question:

    \"Who decides what the user sees?\"

    At one end, the hook decides everything (hard gate: the agent literally cannot proceed). At the other end, the hook is invisible (silent side-effect: nobody knows it ran). In between, there is a range of negotiation between hook, agent, and the user.

    Here's the full spectrum:

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#1-hard-gate","level":3,"title":"1. Hard Gate","text":"
    {\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}\n

    The nuclear option: The agent's tool call is rejected before it executes.

    This is Claude Code's first-class PreToolUse mechanism: The hook returns JSON with decision: block and the agent gets an error with the reason.

    Use this for invariants: Constitution rules, security boundaries, things that must never happen. I use it to enforce PATH-based ctx invocation, block sudo, and require explicit approval for git push.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#2-verbatim-relay","level":3,"title":"2. VERBATIM Relay","text":"
    IMPORTANT: Relay this warning to the user VERBATIM before answering.\n┌─ Journal Reminder ─────────────────────────────\n│ You have 12 sessions not yet imported.\n│   ctx recall import --all\n└────────────────────────────────────────────────\n

    The instruction is the pattern. Without \"Relay VERBATIM,\" agents tend to absorb information into their internal reasoning and never surface it. The explicit instruction changes the behavior from \"I know about this\" to \"I must tell the user about this.\"

    I use this for actionable reminders:

    • Unexported journal entries;
    • Stale backups;
    • Context capacity warnings...

    ...things the user should see regardless of what they asked.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#3-agent-directive","level":3,"title":"3. Agent Directive","text":"
    ┌─ Persistence Checkpoint (prompt #25) ───────────\n│ No context files updated in 15+ prompts.\n│ Have you discovered learnings worth persisting?\n└──────────────────────────────────────────────────\n

    A nudge, not a command. The hook tells the agent something; the agent decides what (if anything) to tell the user. This is right for behavioral nudges: \"you haven't saved context in a while\" doesn't need to be relayed verbatim, but the agent should consider acting on it.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#4-silent-context-injection","level":3,"title":"4. Silent Context Injection","text":"
    ctx agent --budget 4000 2>/dev/null || true\n

    Pure background enrichment. The agent's context window gets project information injected on every tool call, with no visible output. Neither the agent nor the user sees the hook fire, but the agent makes better decisions because of the context.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#5-silent-side-effect","level":3,"title":"5. Silent Side-Effect","text":"
    find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n

    Do work, say nothing. Temp file cleanup on session end. Logging. Marker file management. The action is the entire point; no one needs to know.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-patterns-we-dont-have-yet","level":2,"title":"The Patterns We Don't Have Yet","text":"

    Three more patterns emerged from the gaps in the existing hooks.

    Conditional relay: \"Relay this, but only if the user's question is about X.\" This pattern avoids noise when the warning isn't relevant. It's more fragile (depends on agent judgment) but less annoying.

    Suggested action: \"Here's a problem, and here's the exact command to fix it. Ask the user before running it.\" This pattern goes beyond a nudge by giving the agent a concrete proposal, but still requires human approval.

    Escalating severity: INFO gets absorbed silently. WARN gets mentioned at the next natural pause. CRITICAL gets the VERBATIM treatment. This pattern introduces a protocol for hooks that produce output at different urgency levels, so they don't all compete for the user's attention.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-principle","level":2,"title":"The Principle","text":"

    Hooks are the boundary between your environment and the agent's reasoning.

    A hook that detects a problem but can't communicate it effectively is the same as no hook at all.

    The format of your output is a design decision with real consequences:

    • Use a hard gate and the agent can't proceed (good for invariants, frustrating for false positives)
    • Use VERBATIM relay and the user will see it (good for reminders, noisy if overused)
    • Use an agent directive and the agent might act (good for nudges, unreliable for critical warnings)
    • Use silent injection and nobody knows (good for enrichment, invisible when it breaks)

    Choose deliberately. And, when in doubt, write the word VERBATIM.

    The full pattern catalog with decision flowchart and implementation examples is in the Hook Output Patterns recipe.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/","level":1,"title":"Version Numbers Are Lagging Indicators","text":"","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#why-ctxs-journal-site-runs-on-a-v0021-tool","level":2,"title":"Why ctx's Journal Site Runs on a v0.0.21 Tool","text":"

    Jose Alekhinne / 2026-02-15

    Would You Ship Production Infrastructure on a v0.0.21 Dependency?

    Most engineers wouldn't. Version numbers signal maturity. Pre-1.0 means unstable API, missing features, risk.

    But version numbers tell you where a project has been. They say nothing about where it's going.

    I just bet ctx's entire journal site on a tool that hasn't hit v0.1.0.

    Here's why I'd do it again.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-problem","level":2,"title":"The Problem","text":"

    When v0.2.0 shipped the journal system, the pipeline was clear:

    • Export sessions to Markdown;
    • Enrich them with YAML frontmatter;
    • And render them into something browsable.

    The first two steps were solved; the third needed a tool.

    The journal entries are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is the entire format:

    • No JSX;
    • No shortcodes;
    • No custom templating.

    Just Markdown rendered well.

    The requirements are modest:

    • Read a configuration file (such as mkdocs.yml);
    • Render Markdown with extensions (admonitions, tabs, tables);
    • Search;
    • Handle 100+ files without choking on incremental rebuilds;
    • Look good out of the box;
    • Not lock me in.

    The obvious candidates were as follows:

    Tool Language Strengths Pain Points Hugo Go Blazing fast, mature Templating is painful; Go templates fight you on anything non-trivial Astro JS/TS Modern, flexible JS ecosystem overhead; overkill for a docs site MkDocs + Material Python Beautiful defaults, massive community (22k+ stars) Slow incremental rebuilds on large sites; limited extensibility model Zensical Python Built to fix MkDocs' limits; 4-5x faster rebuilds v0.0.21; module system not yet shipped

    The instinct was Hugo. Same language as ctx. Fast. Well-established.

    But instinct is not analysis. I picked the one with the lowest version number.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation","level":2,"title":"The Evaluation","text":"

    Here is what I actually evaluated, in order:

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#1-the-team","level":3,"title":"1. The Team","text":"

    Zensical is built by squidfunk: The same person behind Material for MkDocs, the most popular MkDocs theme with 22,000+ stars. It powers documentation sites for projects across every language and framework.

    • This is not someone learning how to build static site generators.
    • This is someone who spent years understanding exactly where MkDocs breaks and decided to fix it from the ground up.

    They did not build zensical because MkDocs was bad: They built it because MkDocs hit a ceiling:

    • Incremental rebuilds: 4-5x faster during serve. When you have hundreds of journal entries and you edit one, the difference between \"rebuild everything\" and \"rebuild this page\" is the difference between a usable workflow and a frustrating one.

    • Large site performance: Specifically designed for tens of thousands of pages. The journal grows with every session. A tool that slows down as content accumulates is a tool you will eventually replace.

    A proven team starting fresh is more predictable than an unproven team at v3.0.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#2-the-architecture","level":3,"title":"2. The Architecture","text":"

    Zensical is investing in a Rust-based Markdown parser with CommonMark support. That signals something about the team's priorities:

    Performance foundations first; features second.

    ctx's journal will grow:

    • Every exported session adds files.
    • Every enrichment pass adds metadata.

    Choosing a tool that gets slower as you add content means choosing to migrate later.

    Choosing one built for scale means the decision holds.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#3-the-migration-path","level":3,"title":"3. The Migration Path","text":"

    Zensical reads mkdocs.yml natively. If it doesn't work out, I can move back to MkDocs + Material with zero content changes:

    • The Markdown is standard;
    • The frontmatter is standard;
    • The configuration is compatible.

    This is the infrastructure pattern again: The same way ZNC decouples presence from the client, zensical decouples rendering from the generator:

    • The Markdown is yours.
    • The frontmatter is standard YAML.
    • The configuration is MkDocs-compatible.

    You are not locked into anything except your own content.

    No lock-in is not a feature: It's a design philosophy:

    It's the same reason ctx uses plain Markdown files in .context/ instead of a database: the format should outlive the tool.

    Lock-in Is the Real Risk, Not Version Numbers

    A mature tool with a proprietary format is riskier than a young tool with a standard one. Version numbers measure time invested. Portability measures respect for the user.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#4-the-dependency-tree","level":3,"title":"4. The Dependency Tree","text":"

    Here is what pip install zensical actually pulls in:

    • click
    • Markdown
    • Pygments
    • pymdown-extensions
    • PyYAML

    Only five dependencies. All well-known. No framework bloat. No bundler. No transpiler. No node_modules black hole.

    3k GitHub stars at v0.0.21 is a strong early traction for a pre-1.0 project.

    The dependency tree is thin: No bloat.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#5-the-fit","level":3,"title":"5. The Fit","text":"

    This is the same principle behind the attention budget: do not overfit the tool to hypothetical requirements. The right amount of capability is the minimum needed for the current task.

    Hugo is a powerful static site generator. It is also a powerful templating engine, a powerful asset pipeline, and a powerful taxonomy system. For rendering Markdown journals, that power is overhead:

    It is the complexity you pay for but never use.

    ctx's journal files are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is exactly the sweet spot Zensical inherits from Material for MkDocs:

    • No custom plugins needed;
    • No special syntax;
    • No templating gymnastics.

    The requirements match the capabilities: Not the capabilities that are promised, but the ones that exist today, at v0.0.21.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-caveat","level":2,"title":"The Caveat","text":"

    It would be dishonest not to mention what's missing.

    The module system for third-party extensions opens in early 2026.

    If ctx ever needs custom plugins (for example, auto-linking session IDs, rendering special journal metadata, etc.) that infrastructure isn't there yet.

    The installation experience is rough:

    We discovered this firsthand: pip install zensical often fails on MacOS (system Python stubs, Homebrew's PEP 668 restrictions). The answer is pipx, which creates an isolated environment with the correct Python version automatically.

    That kind of friction is typical for young Python tooling, and it is documented in the Getting Started guide.

    And 3,000 stars at v0.0.21 is strong early traction, but it's still early: The community is small. When something breaks, you're reading source code, not documentation.

    These are real costs. I chose to pay them because the alternative costs are higher.

    For example:

    • Hugo's templating pain would cost me time on every site change.
    • Astro's JS ecosystem would add complexity I don't need.
    • MkDocs would work today but hit scaling walls tomorrow.

    Zensical's costs are front-loaded and shrinking.

    The others compound.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation-framework","level":2,"title":"The Evaluation Framework","text":"

    For anyone facing a similar choice, here is the framework that emerged:

    Signal What It Tells You Weight Team track record Whether the architecture will be sound High Migration path Whether you can leave if wrong High Current fit Whether it solves your problem today High Dependency tree How much complexity you're inheriting Medium Version number How long the project has existed Low Star count Community interest (not quality) Low Feature list What's possible (not what you need) Low

    The bottom three are the metrics most engineers optimize for.

    The top four are the ones that predict whether you'll still be happy with the choice in a year.

    Features You Don't Need Are Not Free

    Every feature in a dependency is code you inherit but don't control.

    A tool with 200 features where you use 5 means 195 features worth of surface area for bugs, breaking changes, and security issues that have nothing to do with your use case.

    Fit is the inverse of feature count.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-broader-pattern","level":2,"title":"The Broader Pattern","text":"

    This is part of a theme I keep encountering in this project:

    Leading indicators beat lagging indicators.

    Domain Lagging Indicator Leading Indicator Tooling Version number, star count Team track record, architecture Code quality Test coverage percentage Whether tests catch real bugs Context persistence Number of files in .context/ Whether the AI makes fewer mistakes Skills Number of skills created Whether each skill fires at the right time Consolidation Lines of code refactored Whether drift stops accumulating

    Version numbers, star counts, coverage percentages, file counts...

    ...these are all measures of effort expended.

    They say nothing about value delivered.

    The question is never \"how mature is this tool?\"

    The question is \"does this tool's trajectory intersect with my needs?\"

    Zensical's trajectory:

    • A proven team fixing known problems,
    • in a *proven architecture,
    • with a standard format,
    • and no lock-in.

    ctx's needs:

    Tender standard Markdown into a browsable site, at scale, without complexity.

    The intersection is clean; the version number is noise.

    This is the same kind of decision that shows up throughout ctx:

    • Skills that fight the platform taught that the best integration extends existing behavior, not replaces it.
    • You can't import expertise taught that tools should grow from your project's actual needs, not from feature checklists.
    • Context as infrastructure argues that the format should outlive the tool; and, zensical honors that principle by reading standard Markdown and standard MkDocs configuration.

    If You Remember One Thing from This Post...

    Version numbers measure where a project has been.

    The team and the architecture tell you where it's going.

    A v0.0.21 tool built by the right team on the right foundations is a safer bet than a v5.0 tool that doesn't fit your problem.

    Bet on trajectories, not timestamps.

    This post started as an evaluation note in ideas/ and a separate decision log. The analysis held up. The two merged into one. The meta continues.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/","level":1,"title":"ctx v0.6.0: The Integration Release","text":"","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#two-commands-to-persistent-memory","level":2,"title":"Two Commands to Persistent Memory","text":"

    Jose Alekhinne / February 16, 2026

    What Changed?

    ctx is now a Claude Code plugin. Two commands, no build step:

    /plugin marketplace add ActiveMemory/ctx\n/plugin install ctx@activememory-ctx\n

    Six hooks. Twenty-five skills. Installed.

    For three releases, ctx required assembly:

    • Clone the repo;
    • Build the binary;
    • Copy hook scripts into .claude/hooks/;
    • Symlink skill files.
    • Understand which shell scripts called which Go commands;
    • Hope nothing broke when Claude Code updated its hook format.

    v0.6.0 ends that era: ctx ships as a Claude Marketplace plugin:

    Hooks and skills served directly from source, installed with a single command, updated by pulling the repo. The tool that gives AI persistent memory is now as easy to install as the AI itself.

    But the plugin conversion was not just a packaging change: It was the forcing function that rewrote every shell hook in Go, eliminated the jq dependency, enabled go test coverage for hook logic, and made distribution a solved problem.

    When you fix how something ships, you end up fixing how it is built.

    The Release Window

    February 15-February 16, 2026

    From the v0.3.0 tag to commit a3178bc:

    • 109 commits.
    • 334 files changed.
    • Version jumped from 0.3.0 to 0.6.0 to signal the magnitude.
    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#before-six-shell-scripts-and-a-prayer","level":2,"title":"Before: Six Shell Scripts and a Prayer","text":"

    v0.3.0 had six hook scripts. Each was a Bash file that shelled out to ctx subcommands, parsed JSON with jq, and wired itself into Claude Code's hook system via .claude/hooks/:

    .claude/hooks/\n├── check-context-size.sh\n├── check-persistence.sh\n├── check-journal.sh\n├── post-commit.sh\n├── block-non-path-ctx.sh\n└── cleanup-tmp.sh\n

    This worked, but it also meant:

    • jq was a hard dependency: No jq, no hooks. macOS ships without it.
    • No test coverage: Shell scripts were tested manually or not at all.
    • Fragile deployment: ctx init had to scaffold .claude/hooks/ and .claude/skills/ with the right paths, permissions, and structure.
    • Version drift: Users who installed once never got hook updates unless they re-ran ctx init.

    The shell scripts were the right choice for prototyping. They were the wrong choice for distribution.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#after-one-plugin-zero-shell-scripts","level":2,"title":"After: One Plugin, Zero Shell Scripts","text":"

    v0.6.0 replaces all six scripts with ctx system subcommands compiled into the binary:

    Shell Script Go Subcommand check-context-size.sh ctx system check-context-size check-persistence.sh ctx system check-persistence check-journal.sh ctx system check-journal post-commit.sh ctx system post-commit block-non-path-ctx.sh ctx system block-non-path-ctx cleanup-tmp.sh ctx system cleanup-tmp

    The plugin's hooks.json wires them to Claude Code events:

    {\n  \"PreToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system block-non-path-ctx\"},\n    {\"matcher\": \".*\", \"command\": \"ctx agent --budget 4000\"}\n  ],\n  \"PostToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system post-commit\"}\n  ],\n  \"UserPromptSubmit\": [\n    {\"command\": \"ctx system check-context-size\"},\n    {\"command\": \"ctx system check-persistence\"},\n    {\"command\": \"ctx system check-journal\"}\n  ],\n  \"SessionEnd\": [\n    {\"command\": \"ctx system cleanup-tmp\"}\n  ]\n}\n

    No jq. No shell scripts. No .claude/hooks/ directory to manage.

    The hooks are Go functions with tests, compiled into the same binary you already have.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-plugin-model","level":2,"title":"The Plugin Model","text":"

    The ctx plugin lives at .claude-plugin/marketplace.json in the repo.

    Claude Code's marketplace system handles discovery and installation:

    Skills are served directly from internal/assets/claude/skills/; there is no build step, no make plugin, no generated artifacts.

    This means:

    1. Install is two commands: Not \"clone, build, copy, configure.\"
    2. Updates are automatic: Pull the repo; the plugin reads from source.
    3. Skills and hooks are versioned together: No drift between what the CLI expects and what the plugin provides.
    4. ctx init is tool-agnostic: It creates .context/ and nothing else. No .claude/ scaffolding, no assumptions about which AI tool you use.

    That last point matters:

    Before v0.6.0, ctx init tried to set up Claude Code integration as part of initialization. That coupled the context system to a specific tool.

    Now, ctx init gives you persistent context. The plugin gives you Claude Code integration. They compose; they don't depend.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#beyond-the-plugin-what-else-shipped","level":2,"title":"Beyond the Plugin: What Else Shipped","text":"

    The plugin conversion dominated the release, but 109 commits covered more ground.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#obsidian-vault-export","level":3,"title":"Obsidian Vault Export","text":"
    ctx journal obsidian\n

    Generates a full Obsidian vault from enriched journal entries: wikilinks, MOC (Map of Content) pages, and graph-optimized cross-linking. If you already use Obsidian for notes, your AI session history now lives alongside everything else.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#encrypted-scratchpad","level":3,"title":"Encrypted Scratchpad","text":"
    ctx pad edit \"DATABASE_URL=postgres://...\"\nctx pad show\n

    AES-256-GCM encrypted storage for sensitive one-liners.

    The encrypted blob commits to git; the key stays in .gitignore.

    This is useful for connection strings, API keys, and other values that need to travel with the project without appearing in plaintext.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#security-hardening","level":3,"title":"Security Hardening","text":"

    Three medium-severity findings from a security audit are now closed:

    Finding Fix Path traversal via --context-dir Boundary validation: operations cannot escape project root (M-1) Symlink following in .context/ Lstat() check before every file read/write (M-2) Predictable temp file paths User-specific temp directory under $XDG_RUNTIME_DIR (M-3)

    Plus a new /sanitize-permissions skill that audits settings.local.json for overly broad Bash permissions.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#hooks-that-know-when-to-be-quiet","level":3,"title":"Hooks That Know When to Be Quiet","text":"

    A subtle but important fix: hooks now no-op before ctx init has run.

    Previously, a fresh clone with no .context/ would trigger hook errors on every prompt. Now, hooks detect the absence of a context directory and exit silently. Similarly, ctx init treats a .context/ directory containing only logs as uninitialized and skips the --overwrite prompt.

    Small changes. Large reduction in friction for new users.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.3.0 v0.6.0 Skills 21 25 Shell hook scripts 6 0 Go system subcommands 0 6 External dependencies (hooks) jq, bash none Lines of Go ~14,000 ~37,000 Plugin install commands n/a 2 Security findings (open) 3 0 ctx init creates .claude/ yes no

    The line count tripled. Most of that is documentation site HTML, Obsidian export logic, and the scratchpad encryption module.

    The core CLI grew modestly; the ecosystem around it grew substantially.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-does-v060-mean-for-ctx","level":2,"title":"What Does v0.6.0 Mean for ctx?","text":"
    • v0.1.0 asked: \"Can we give AI persistent memory?\"
    • v0.2.0 asked: \"Can we make that memory accessible to humans too?\"
    • v0.3.0 asked: \"Can we make the quality self-enforcing?\"

    v0.6.0 asks: \"Can someone else actually use this?\"

    A tool that requires cloning a repo, building from source, and manually wiring hooks into the right directories is a tool for its author.

    A tool that installs with two commands from a marketplace is a tool for everyone.

    The version jumped from 0.3.0 to 0.6.0 because the delta is not incremental: The shell-to-Go rewrite, the plugin model, the security hardening, and the tool-agnostic init: Together, they change what ctx is: Not a different tool, but a tool that is finally ready to leave the workshop.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-comes-next","level":2,"title":"What Comes Next","text":"

    The plugin model opens the door to distribution patterns that were not possible before. Marketplace discovery means new users find ctx without reading a README. Plugin updates mean existing users get improvements without rebuilding.

    The next chapter is about what happens when persistent context is easy to install: Adoption patterns, multi-project workflows, and whether the .context/ convention can become infrastructure that other tools build on.

    But those are future posts.

    This one is about the release that turned a developer tool into a distributable product: two commands, zero shell scripts, and a presence on the Claude Marketplace.

    The Integration Release

    v0.1.0 shipped features. v0.2.0 shipped archaeology.

    v0.3.0 shipped discipline. v0.6.0 shipped the front door.

    The most important code in this release is the code you never have to copy.

    This post was drafted using /ctx-blog-changelog with access to the full git history between v0.3.0 and v0.6.0, release notes, and the plugin conversion PR. The meta continues.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/","level":1,"title":"Code Is Cheap. Judgment Is Not.","text":"","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#why-ai-replaces-effort-not-expertise","level":2,"title":"Why AI Replaces Effort, Not Expertise","text":"

    Volkan Özçelik / February 17, 2026

    Are You Worried about AI Taking Your Job?

    You might be confusing the thing that's cheap with the thing that's valuable.

    I keep seeing the same conversation: Engineers, designers, writers: all asking the same question with the same dread:

    \"What happens when AI can do what I do?\"

    The question is wrong:

    • AI does not replace workers;
    • AI replaces unstructured effort.

    The distinction matters, and everything I have learned building ctx reinforces it.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-three-confusions","level":2,"title":"The Three Confusions","text":"

    People who feel doomed by AI usually confuse three things:

    People confuse... With... Effort Value Typing Thinking Production Judgment
    • Effort is time spent.
    • Value is the outcome that time produces.

    They are not the same; they never were.

    AI just makes the gap impossible to ignore.

    Typing is mechanical: Thinking is directional.

    An AI can type faster than any human. Yet, it cannot decide what to type without someone framing the problem, sequencing the work, and evaluating the result.

    Production is making artifacts. Judgment is knowing:

    • which artifacts to make,
    • in what order,
    • to what standard,
    • and when to stop.

    AI floods the system with production capacity; it does not flood the system with judgment.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#code-is-nothing","level":2,"title":"Code Is Nothing","text":"

    This sounds provocative until you internalize it:

    Code is cheap. Artifacts are cheap.

    An AI can generate a thousand lines of working code in literal *minutes**:

    It can scaffold a project, write tests, build a CI pipeline, draft documentation. The raw production of software artifacts is no longer the bottleneck.

    So, what is not cheap?

    • Taste: knowing what belongs and what does not
    • Framing: turning a vague goal into a concrete problem
    • Sequencing: deciding what to build first and why
    • Fanning out: breaking work into parallel streams that converge
    • Acceptance criteria: defining what \"done\" looks like before starting
    • Judgment: the thousand small decisions that separate code that works from code that lasts

    These are the skills that direct production: Hhuman skills.

    Not because AI is incapable of learning them, but because they require something AI does not have:

    temporal accountability for generated outcomes.

    That is, you cannot keep AI accountable for the $#!% it generated three months ago. A human, on the other hand, will always be accountable.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-evidence-from-building-ctx","level":2,"title":"The Evidence from Building ctx","text":"

    I did not arrive at this conclusion theoretically.

    I arrived at it by building a tool with an AI agent for three weeks and watching exactly where a human touch mattered.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#yolo-mode-proved-production-is-cheap","level":3,"title":"YOLO Mode Proved Production Is Cheap","text":"

    In Building ctx Using ctx, I documented the YOLO phase: auto-accept everything, let the AI ship features at full speed. It produced 14 commands in a week. Impressive output.

    The code worked. The architecture drifted. Magic strings accumulated. Conventions diverged. The AI was producing at a pace no human could match, and every artifact it produced was a small bet that nobody was evaluating.

    Production without judgment is not velocity. It is debt accumulation at breakneck speed.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-31-ratio-proved-judgment-has-a-cadence","level":3,"title":"The 3:1 Ratio Proved Judgment Has a Cadence","text":"

    In The 3:1 Ratio, the git history told the story:

    Three sessions of forward momentum followed by one session of deliberate consolidation. The consolidation session is where the human applies judgment: reviewing what the AI built, catching drift, realigning conventions.

    The AI does the refactoring. The human decides what to refactor and when to stop.

    Without the human, the AI will refactor forever, improving things that do not matter and missing things that do.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-attention-budget-proved-framing-is-scarce","level":3,"title":"The Attention Budget Proved Framing Is Scarce","text":"

    In The Attention Budget, I explained why more context makes AI worse, not better. Every token competes for attention: Dump everything in and the AI sees nothing clearly.

    This is a framing problem: The human's job is to decide what the AI should focus on: what to include, what to exclude, what to emphasize.

    ctx agent --budget 4000 is not just a CLI flag: It is a forcing function for human judgment about relevance.

    The AI processes. The human curates.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#skills-design-proved-taste-is-load-bearing","level":3,"title":"Skills Design Proved Taste Is Load-Bearing","text":"

    The skill trilogy (You Can't Import Expertise, The Anatomy of a Skill That Works) showed that the difference between a useful skill and a useless one is not craftsmanship:

    It is taste.

    A well-crafted skill with the wrong focus is worse than no skill at all: It consumes the attention budget with generic advice while the project-specific problems go unchecked.

    The E/A/R framework (Expert, Activation, Redundant) is a judgment too:. The AI cannot apply it to itself. The human evaluates what the AI already knows, what it needs to be told, and what is noise.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#automation-discipline-proved-restraint-is-a-skill","level":3,"title":"Automation Discipline Proved Restraint Is a Skill","text":"

    In Not Everything Is a Skill, the lesson was that the urge to automate is not the need to automate. A useful prompt does not automatically deserve to become a slash command.

    The human applies judgment about frequency, stability, and attention cost.

    The AI can build the skill. Only the human can decide whether it should exist.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#defense-in-depth-proved-boundaries-require-judgment","level":3,"title":"Defense in Depth Proved Boundaries Require Judgment","text":"

    In Defense in Depth, the entire security model for unattended AI agents came down to: markdown is not a security boundary. Telling an AI \"don't do bad things\" is production (of instructions). Setting up an unprivileged user in a network-isolated container is judgment (about risk).

    The AI follows instructions. The human decides which instructions are enforceable and which are \"wishful thinking\".

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#parallel-agents-proved-scale-amplifies-the-gap","level":3,"title":"Parallel Agents Proved Scale Amplifies the Gap","text":"

    In Parallel Agents and Merge Debt, the lesson was that multiplying agents multiplies output. But it also multiplies the need for judgment:

    Five agents running in parallel produce five sessions of drift in one clock hour. The human who can frame tasks cleanly, define narrow acceptance criteria, and evaluate results quickly becomes the limiting factor.

    More agents do not reduce the need for judgment. They increase it.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-two-reactions","level":2,"title":"The Two Reactions","text":"

    When AI floods the system with cheap output, two things happen:

    Those who only produce: panic. If your value proposition is \"I write code,\" and an AI writes code faster, cheaper, and at higher volume, then the math is unfavorable. Not because AI took your job, but because your job was never the code. It was the judgment around the code, and you were not exercising it.

    Those who direct: accelerate. If your value proposition is \"I know what to build, in what order, to what standard,\" then AI is the best thing that ever happened to you: Production is no longer the bottleneck: Your ability to frame, sequence, evaluate, and course-correct is now the limiting factor on throughput.

    The gap between these two is not talent: It is the awareness of where the value lives.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#what-this-means-in-practice","level":2,"title":"What This Means in Practice","text":"

    If you are an engineer reading this, the actionable insight is not \"learn prompt engineering\" or \"master AI tools.\" It is:

    Get better at the things AI cannot do.

    AI does this well You need to do this Generate code Frame the problem Write tests Define acceptance criteria Scaffold projects Sequence the work Fix bugs from stack traces Evaluate tradeoffs Produce volume Exercise restraint Follow instructions Decide which instructions matter

    The skills on the right column are not new. They are the same skills that have always separated senior engineers from junior ones.

    AI did not create the distinction; it just made it load-bearing.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#if-anything-i-feel-empowered","level":2,"title":"If Anything, I Feel Empowered","text":"

    I will end with something personal.

    I am not worried: I am empowered.

    Before ctx, I could think faster than I could produce:

    • Ideas sat in a queue.
    • The bottleneck was always \"I know what to build, but building it takes too long.\"

    Now the bottleneck is gone. Poof!

    • Production is cheap.
    • The queue is clearing.
    • The limiting factor is how fast I can think, not how fast I can type.

    That is not a threat: That is the best force multiplier I've ever had.

    The people who feel threatened are confusing the accelerator for the replacement:

    *AI does not replace the conductor; it gives them a bigger orchestra.

    If You Remember One Thing from This Post...

    Code is cheap. Judgment is not.

    AI replaces unstructured effort, not directed expertise. The skills that matter now are the same skills that have always mattered: taste, framing, sequencing, and the discipline to stop.

    The difference is that now, for the first time, those skills are the only bottleneck left.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-arc","level":2,"title":"The Arc","text":"

    This post is a retrospective. It synthesizes the thread running through every previous entry in this blog:

    • Building ctx Using ctx showed that production without direction creates debt
    • Refactoring with Intent showed that slowing down is not the opposite of progress
    • The Attention Budget showed that curation outweighs volume
    • The skill trilogy showed that taste determines whether a tool helps or hinders
    • Not Everything Is a Skill showed that restraint is a skill in itself
    • Defense in Depth showed that instructions are not boundaries
    • The 3:1 Ratio showed that judgment has a schedule
    • Parallel Agents showed that scale amplifies the gap between production and judgment
    • Context as Infrastructure showed that the system you build for context is infrastructure, not conversation

    From YOLO mode to defense in depth, the pattern is the same:

    • Production is the easy part;
    • Judgment is the hard part;
    • AI changed the ratio, not the rule.

    This post synthesizes the thread running through every previous entry in this blog. The evidence is drawn from three weeks of building ctx with AI assistance, the decisions recorded in DECISIONS.md, the learnings captured in LEARNINGS.md, and the git history that tracks where the human mattered and where the AI ran unsupervised.

    See also: When a System Starts Explaining Itself -- what happens after the arc: the first field notes from the moment the system starts compounding in someone else's hands.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/","level":1,"title":"Context as Infrastructure","text":"","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#why-your-ai-needs-a-filesystem-not-a-prompt","level":2,"title":"Why Your AI Needs a Filesystem, Not a Prompt","text":"

    Volkan Özçelik / February 17, 2026

    Where Does Your AI's Knowledge Live between Sessions?

    If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. Something assembled, used, and discarded.

    What if you treated it as infrastructure instead?

    This post synthesizes a thread that has been running through every ctx blog post; from the origin story to the attention budget to the discipline release. The thread is this: context is not a prompt problem. It is an infrastructure problem. And the tools we build for it should look more like filesystems than clipboard managers.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-prompt-paradigm","level":2,"title":"The Prompt Paradigm","text":"

    Most AI-assisted development treats context as ephemeral:

    1. Start a session.
    2. Paste your system prompt, your conventions, your current task.
    3. Work.
    4. Session ends. Everything evaporates.
    5. Next session: paste again.

    This works for short interactions. For sustained development (where decisions compound over days and weeks) it fails in three ways:

    It does not persist: A decision made on Tuesday must be re-explained on Wednesday. A learning captured in one session is invisible to the next.

    It does not scale: As the project grows, the \"paste everything\" approach hits the context window ceiling. You start triaging what to include, often cutting exactly the context that would have prevented the next mistake.

    It does not compose: A system prompt is a monolith. You cannot load part of it, update one section, or share a subset with a different workflow. It is all or nothing.

    The Copy-Paste Tax

    Every session that starts with pasting a prompt is paying a tax:

    The human time to assemble the context, the risk of forgetting something, and the silent assumption that yesterday's prompt is still accurate today.

    Over 70+ sessions, that tax compounds into a significant maintenance burden: One that most developers absorb without questioning it.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-infrastructure-paradigm","level":2,"title":"The Infrastructure Paradigm","text":"

    ctx takes a different approach:

    Context is not assembled per-session; it is maintained as persistent files in a .context/ directory:

    .context/\n  CONSTITUTION.md     # Inviolable rules\n  TASKS.md            # Current work items\n  CONVENTIONS.md      # Code patterns and standards\n  DECISIONS.md        # Architectural choices with rationale\n  LEARNINGS.md        # Gotchas and lessons learned\n  ARCHITECTURE.md     # System structure\n  GLOSSARY.md         # Domain terminology\n  AGENT_PLAYBOOK.md   # Operating manual for agents\n  journal/            # Enriched session summaries\n  archive/            # Completed work, cold storage\n
    • Each file has a single purpose;
    • Each can be loaded independently;
    • Each persists across sessions, tools, and team members.

    This is not a novel idea. It is the same idea behind every piece of infrastructure software engineers already use:

    Traditional Infrastructure ctx Equivalent Database .context/*.md files Configuration files CONSTITUTION.md Environment variables .contextrc Log files journal/ Schema migrations Decision records Deployment manifests AGENT_PLAYBOOK.md

    The parallel is not metaphorical. Context files are infrastructure:

    • They are versioned (git tracks them);
    • They are structured (Markdown with conventions);
    • They have schemas (required fields for decisions and learnings);
    • And they have lifecycle management (archiving, compaction, indexing).
    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#separation-of-concerns","level":2,"title":"Separation of Concerns","text":"

    The most important design decision in ctx is not any individual feature. It is the separation of context into distinct files with distinct purposes.

    A single CONTEXT.md file would be simpler to implement. It would also be impossible to maintain.

    Why? Because different types of context have different lifecycles:

    Context Type Changes Read By Load When Constitution Rarely Every session Always Tasks Every session Session start Always Conventions Weekly Before coding When writing code Decisions When decided When questioning When revisiting Learnings When learned When stuck When debugging Journal Every session Rarely When investigating

    Loading everything into every session wastes the attention budget on context that is irrelevant to the current task. Loading nothing forces the AI to operate blind.

    Separation of concerns allows progressive disclosure:

    Load the minimum that matters for this moment, with the option to load more when needed.

    # Session start: load the essentials\nctx agent --budget 4000\n\n# Deep investigation: load everything\ncat .context/DECISIONS.md\ncat .context/journal/2026-02-05-*.md\n

    The filesystem is the index. File names, directory structure, and timestamps encode relevance. The AI does not need to read every file; it needs to know where to look.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-two-tier-persistence-model","level":2,"title":"The Two-Tier Persistence Model","text":"

    ctx uses two tiers of persistence, and the distinction is architectural:

    Tier Purpose Location Token Cost Curated Quick context reload .context/*.md Low (budgeted) Full dump Safety net, archaeology .context/journal/*.md Zero (not auto-loaded)

    The curated tier is what the AI sees at session start. It is optimized for signal density:

    • Structured entries,
    • Indexed tables,
    • Reverse-chronological order (newest first, so the most relevant content survives truncation).

    The full dump tier is for humans and for deep investigation. It contains everything: Enriched journals, archived tasks...

    It is never autoloaded because its volume would destroy attention density.

    This two-tier model is analogous to how traditional systems separate hot and cold storage:

    • The hot path (curated context) is optimized for read performance (measured not in milliseconds, but in tokens consumed per unit of useful information).
    • The cold path (journal) is optimized for completeness.

    Nothing Is Ever Truly Lost

    The full dump tier means that context does not need to be perfect: It just needs to be findable.

    A decision that was not captured in DECISIONS.md can be recovered from the session transcript where it was discussed.

    A learning that was not formalized can be found in the journal entry from that day.

    The curated tier is the fast path: The full dump tier is the safety net.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#decision-records-as-first-class-citizens","level":2,"title":"Decision Records as First-Class Citizens","text":"

    One of the patterns that emerged from ctx's own development is the power of structured decision records.

    v0.1.0 allowed adding decisions as one-liners:

    ctx add decision \"Use PostgreSQL\"\n

    v0.2.0 enforced structure:

    ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity\" \\\n  --consequence \"Need connection pooling, team training\"\n

    The difference is not cosmetic:

    • A one-liner decision teaches the AI what was decided.
    • A structured decision teaches it why; and why is what prevents the AI from unknowingly reversing the decision in a future session.

    This is infrastructure thinking:

    Decisions are not notes. They are records with required fields, just like database rows have schemas.

    The enforcement exists because incomplete records are worse than no records: They create false confidence that the context is captured when it is not.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-ide-is-the-interface-decision","level":2,"title":"The \"IDE Is the Interface\" Decision","text":"

    Early in ctx's development, there was a temptation to build a custom UI: a web dashboard for browsing sessions, editing context, viewing analytics.

    The decision was no. The IDE is the interface.

    # This is the ctx \"UI\":\ncode .context/\n

    This decision was not about minimalism for its own sake. It was about recognizing that .context/ files are just files; and files have a mature, well-understood infrastructure:

    • Version control: git diff .context/DECISIONS.md shows exactly what changed and when.
    • Search: Your IDE's full-text search works across all context files.
    • Editing: Markdown in any editor, with preview, spell check, and syntax highlighting.
    • Collaboration: Pull requests on context files work the same as pull requests on code.

    Building a custom UI would have meant maintaining a parallel infrastructure that duplicates what every IDE already provides:

    It would have introduced its own bugs, its own update cycle, and its own learning curve.

    The filesystem is not a limitation: It is the most mature, most composable, most portable infrastructure available.

    Context Files in Git

    Because .context/ lives in the repository, context changes are part of the commit history.

    A decision made in commit abc123 is as traceable as a code change in the same commit.

    This is not possible with prompt-based context, which exists outside version control entirely.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#progressive-disclosure-for-ai","level":2,"title":"Progressive Disclosure for AI","text":"

    The concept of progressive disclosure comes from human interface design: show the user the minimum needed to make progress, with the option to drill deeper.

    ctx applies the same principle to AI context:

    Level What the AI Sees Token Cost When Level 0 ctx status (one-line summary) ~100 Quick check Level 1 ctx agent --budget 4000 ~4,000 Normal work Level 2 ctx agent --budget 8000 ~8,000 Complex tasks Level 3 Direct file reads 10,000+ Deep investigation

    Each level trades tokens for depth. Level 1 is sufficient for most work: the AI knows the active tasks, the key conventions, and the recent decisions. Level 3 is for archaeology: understanding why a decision was made three weeks ago, or finding a pattern in the session history.

    The explicit --budget flag is the mechanism that makes this work:

    Without it, the default behavior would be to load everything (because more context feels safer), which destroys the attention density that makes the loaded context useful.

    The constraint is the feature: A budget of 4,000 tokens forces ctx to prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings scored by recency and relevance to active tasks. Entries that don't fit get title-only summaries rather than being silently dropped.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-philosophical-shift","level":2,"title":"The Philosophical Shift","text":"

    The shift from \"context as prompt\" to \"context as infrastructure\" changes how you think about AI-assisted development:

    Prompt Thinking Infrastructure Thinking \"What do I paste today?\" \"What has changed since yesterday?\" \"How do I fit everything in?\" \"What's the minimum that matters?\" \"The AI forgot my conventions\" \"The conventions are in a file\" \"I need to re-explain\" \"I need to update the record\" \"This session is getting slow\" \"Time to compact and archive\"

    The first column treats AI interaction as a conversation. The second treats it as a system: One that can be maintained, optimized, and debugged.

    Context is not something you give the AI. It is something you maintain: Like a database, like a config file, like any other piece of infrastructure that a running system depends on.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#beyond-ctx-the-principles","level":2,"title":"Beyond ctx: The Principles","text":"

    The patterns that ctx implements are not specific to ctx. They are applicable to any project that uses AI-assisted development:

    1. Separate context by purpose: Do not put everything in one file. Different types of information have different lifecycles and different relevance windows.
    2. Make context persistent: If a decision matters, write it down in a file that survives the session. If a learning matters, capture it with structure.
    3. Budget explicitly: Know how much context you are loading and whether it is worth the attention cost.
    4. Use the filesystem: File names, directory structure, and timestamps are metadata that the AI can navigate. A well-organized directory is an index that costs zero tokens to maintain.
    5. Version your context: Put context files in git. Changes to decisions are as important as changes to code.
    6. Design for degradation: Sessions will get long. Attention will dilute. Build mechanisms (compaction, archiving, cooldowns) that make degradation visible and manageable.

    These are not ctx features. They are infrastructure principles that happen to be implemented as a CLI tool. Any team could implement them with nothing more than a directory convention and a few shell scripts.

    The tool is a convenience: The principles are what matter.

    If You Remember One Thing from This Post...

    Prompts are conversations. Infrastructure persists.

    Your AI does not need a better prompt. It needs a filesystem:

    versioned, structured, budgeted, and maintained.

    The best context is the context that was there before you started the session.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-arc","level":2,"title":"The Arc","text":"

    This post is the architectural companion to the Attention Budget. That post explained why context must be curated (token economics). This one explains how to structure it (filesystem, separation of concerns, persistence tiers).

    Together with Code Is Cheap, Judgment Is Not, they form a trilogy about what matters in AI-assisted development:

    • Attention Budget: the resource you're managing
    • Context as Infrastructure: the system you build to manage it
    • Code Is Cheap: the human skill that no system replaces

    And the practices that keep it all honest:

    • The 3:1 Ratio: the cadence for maintaining both code and context
    • IRC as Context: the historical precedent: stateless protocols have always needed stateful wrappers

    This post synthesizes ideas from across the ctx blog series: the attention budget primitive, the two-tier persistence model, the IDE decision, and the progressive disclosure pattern. The principles are drawn from three weeks of building ctx and 70+ sessions of treating context as infrastructure rather than conversation.

    See also: When a System Starts Explaining Itself: what happens when this infrastructure starts compounding in someone else's environment.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/","level":1,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-the-screen-looks-like-progress","level":2,"title":"When the Screen Looks like Progress","text":"

    Volkan Özçelik / 2026-02-17

    How Many Terminals Are Too Many?

    You discover agents can run in parallel.

    So you open ten...

    ...Then twenty.

    The fans spin. Tokens burn. The screen looks like progress.

    It is NOT progress.

    There is a phase every builder goes through:

    • The tooling gets fast enough.
    • The model gets good enough.
    • The temptation becomes irresistible:
      • more agents, more output, faster delivery.

    So you open terminals. You spawn agents. You watch tokens stream across multiple windows simultaneously, and it feels like multiplication.

    It is not multiplication.

    It is merge debt being manufactured in real time.

    The ctx Manifesto says it plainly:

    Activity is not impact. Code is not progress.

    This post is about what happens when you take that seriously in the context of parallel agent workflows.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-unit-of-scale-is-not-the-agent","level":2,"title":"The Unit of Scale Is Not the Agent","text":"

    The naive model says:

    More agents -> more output -> faster delivery

    The production model says:

    Clean context boundaries -> less interference -> higher throughput

    Parallelism only works when the cognitive surfaces do not overlap.

    If two agents touch the same files, you did not create parallelism: You created a conflict generator.

    They will:

    • Revert each other's changes;
    • Relint each other's formatting;
    • Refactor the same function in different directions.

    You watch with 🍿. Nothing ships.

    This is the same insight from the worktrees post: partition by blast radius, not by priority.

    Two tasks that touch the same files belong in the same track, no matter how important the other one is. The constraint is file overlap.

    Everything else is scheduling.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-five-agent-rule","level":2,"title":"The \"Five Agent\" Rule","text":"

    In practice there is a ceiling.

    Around five or six concurrent agents:

    • Token burn becomes noticeable;
    • Supervision cost rises;
    • Coordination noise increases;
    • Returns flatten.

    This is not a model limitation: This is a human merge bandwidth limitation.

    You are the bottleneck, not the silicon.

    The attention budget applies to you too:

    Every additional agent is another stream of output you need to comprehend, verify, and integrate. Your attention density drops the same way the model's does when you overload its context window.

    Five agents producing verified, mergeable change beats twenty agents producing merge conflicts you spend a day untangling.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#role-separation-beats-file-locking","level":2,"title":"Role Separation Beats File Locking","text":"

    Real parallelism comes from task topology, not from tooling.

    Good:

    Agent Role Touches 1 Documentation docs/, hack/ 2 Security scan Read-only audit 3 Implementation internal/cli/ 4 Enhancement requests Read-only, files issues

    Bad:

    • Four agents editing the same implementation surface

    Context Is the Boundary

    • The goal is not to keep agents busy.
    • The goal is to keep contexts isolated.

    This is what the codebase audit got right:

    • Eight agents, all read-only, each analyzing a different dimension.
    • Zero file overlap.
    • Zero merge conflicts.
    • Eight reports that composed cleanly because no agent interfered with another.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-terminals-stop-scaling","level":2,"title":"When Terminals Stop Scaling","text":"

    There is a moment when more windows stop helping.

    That is the signal. Not to add orchestration. But to introduce:

    git worktree\n

    Because now you are no longer parallelizing execution; you are parallelizing state.

    State Scales, Windows Don't

    • State isolation is the real scaling.
    • Window multiplication is theater.

    The worktrees post covers the mechanics:

    • Sibling directories;
    • Branch naming;
    • The inevitable TASKS.md conflicts;
    • The 3-4 worktree ceiling.

    The principle underneath is older than git:

    Shared mutable state is the enemy of parallelism.

    Always has been.

    Always will be.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-overnight-loop-illusion","level":2,"title":"The Overnight Loop Illusion","text":"

    Autonomous night runs are impressive.

    You sleep. The machine produces thousands of lines.

    In the morning:

    • You read;
    • You untangle;
    • You reconstruct intent;
    • You spend a day making it shippable.

    In retrospect, nothing was accelerated.

    The bottleneck moved from typing to comprehension.

    The Comprehension Tax

    If understanding the output costs more than producing it, the loop is a net loss.

    Progress is not measured in generated code.

    Progress is measured in verified, mergeable change.

    The ctx Manifesto calls this out directly:

    The Scoreboard

    Verified reality is the scoreboard.

    The only truth that compounds is verified change in the real world.

    An overnight run that produces 3,000 lines nobody reviewed is not 3,000 lines of progress: It is 3,000 lines of liability until someone verifies every one of them.

    And that someone is (insert drumroll here) you:

    The same bottleneck that was supposedly being bypassed.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#skills-that-fight-the-platform","level":2,"title":"Skills That Fight the Platform","text":"

    Most marketplace skills are prompt decorations:

    • They rephrase what the base model already knows;
    • They increase token usage;
    • They reduce clarity:
    • They introduce behavioral drift.

    We covered this in depth in Skills That Fight the Platform: judgment suppression, redundant guidance, guilt-tripping, phantom dependencies, universal triggers: Five patterns that make agents worse, not better.

    A real skill does one of these:

    • Encodes workflow state;
    • Enforces invariants;
    • Reduces decision branching.

    Everything else is packaging.

    The anatomy post established the criteria: quality gates, negative triggers, examples over rules, skills as contracts.

    If a skill doesn't meet those criteria...

    • It is either a recipe (document it in hack/);
    • Or noise (delete it);
    • There is no third option.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#hooks-are-context-that-execute","level":2,"title":"Hooks Are Context That Execute","text":"

    The most valuable skills are not prompts:

    They are constraints embedded in the toolchain.

    For example: The agent cannot push.

    git push becomes:

    Stop. A human reviews first.

    A commit without verification becomes:

    Did you run tests? Did you run linters? What exactly are you shipping?

    This is not safety theater; this is intent preservation.

    The thing the ctx Manifesto calls \"encoding intent into the environment.\"

    The Eight Ways a Hook Can Talk catalogued the full spectrum: from silent enrichment to hard blocks.

    The key insight was that hooks are not just safety rails: They are context that survives execution.

    They are the difference between an agent that remembers the rules and one that enforces them.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#complexity-is-a-tax","level":2,"title":"Complexity Is a Tax","text":"

    Every extra layer adds cognitive weight:

    • Orchestration frameworks;
    • Meta agents;
    • Autonomous planning systems...

    If a single terminal works, stay there.

    If five isolated agents work, stop there.

    Add structure only when a real bottleneck appears.

    NOT when an influencer suggests one.

    This is the same lesson from Not Everything Is a Skill:

    The best automation decision is sometimes not to automate.

    A recipe in a Markdown file costs nothing until you use it.

    An orchestration framework costs attention on every run, whether it helps or not.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#literature-is-throughput","level":2,"title":"Literature Is Throughput","text":"

    Clear writing is not aesthetic: It is compression.

    Better articulation means:

    • Fewer tokens;
    • Fewer misinterpretations;
    • Faster convergence.

    The attention budget taught us that context is a finite resource with a quadratic cost.

    Language determines how fast you spend context.

    A well-written task description that takes 50 tokens outperforms a rambling one that takes 200: Not just because it is cheaper, but because it leaves more headroom for the model to actually think.

    Literature Is NOT Overrated

    • Attention is a finite budget.
    • Language determines how fast you spend it.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-real-metric","level":2,"title":"The Real Metric","text":"

    The real metric is not:

    • Lines generated;
    • Agents running;
    • Tasks completed while you sleep.

    But:

    Time from idea to verified, mergeable, production change.

    Everything else is motion.

    The entire blog series has been circling this point:

    • The attention budget was about spending tokens wisely.
    • The skills trilogy was about not wasting them on prompt decoration.
    • The worktrees post was about multiplying throughput without multiplying interference.
    • The discipline release was about what a release looks like when polish outweighs features: 3:1.

    Every post has arrived (and made me converge) at the same answer so far:

    The metric is a verified change, not generated output.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#ctx-was-never-about-spawning-more-minds","level":2,"title":"ctx Was Never about Spawning More Minds","text":"

    ctx is about:

    • Isolating context;
    • Preserving intent;
    • Making progress composable.

    Parallel agents are powerful. But only when you respect the boundaries that make parallelism real.

    Otherwise, you are not scaling cognition; you are scaling interference.

    The ctx Manifesto's thesis holds:

    Without ctx, intelligence resets. With ctx, creation compounds.

    Compounding requires structure.

    Structure requires boundaries.

    Boundaries require the discipline to stop adding agents when five is enough.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#practical-summary","level":2,"title":"Practical Summary","text":"

    A production workflow tends to converge to this:

    Practice Why Stay in one terminal unless necessary Minimize coordination overhead Spawn a small number of agents with non-overlapping responsibilities Conflict avoidance > parallelism Isolate state with worktrees when surfaces grow State isolation is real scaling Encode verification into hooks Intent that survives execution Avoid marketplace prompt cargo cults Skills are contracts, not decorations Measure merge cost, not generation speed The metric is verified change

    This is slower to watch. Faster to ship.

    If You Remember One Thing from This Post...

    Progress is not what the machine produces while you sleep.

    Progress is what survives contact with the main branch.

    See also: Code Is Cheap. Judgment Is Not.: the argument that production capacity was never the bottleneck, and why multiplying agents amplifies the need for human judgment rather than replacing it.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/","level":1,"title":"The 3:1 Ratio","text":"","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#scheduling-consolidation-in-ai-development","level":2,"title":"Scheduling Consolidation in AI Development","text":"

    Volkan Özçelik / February 17, 2026

    How Often Should You Stop Building and Start Cleaning?

    Every developer knows technical debt exists. Every developer postpones dealing with it.

    AI-assisted development makes the problem worse; not because the AI writes bad code, but because it writes code so fast that drift accumulates before you notice.

    In Refactoring with Intent, I mentioned a ratio that worked for me: 3:1. Three YOLO sessions create enough surface area to reveal patterns. The fourth session turns those patterns into structure.

    That was an observation. This post is the evidence.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-observation","level":2,"title":"The Observation","text":"

    During the first two weeks of building ctx, I noticed a rhythm in my own productivity. Feature sessions felt great: new commands, new capabilities, visible progress...

    ...but after three of them, things would start to feel sticky: variable names that almost made sense, files that had grown past their purpose, patterns that repeated without being formalized.

    The fourth session (when I stopped adding and started cleaning) was always the most painful to start and the most satisfying to finish.

    It was also the one that made the next three feature sessions faster.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-evidence-git-history","level":2,"title":"The Evidence: Git History","text":"

    The ctx git history between January 20 and February 7 tells a clear story when you categorize commits:

    Week Feature commits Consolidation commits Ratio Jan 20-26 18 5 3.6:1 Jan 27-Feb 1 14 6 2.3:1 Feb 1-7 15 35+ 0.4:1

    The first week was pure YOLO: Almost four feature commits for every consolidation commit. The codebase grew fast.

    The second week started to self-correct. The ratio dropped as refactoring sessions became necessary: Not scheduled, but forced by friction.

    The third week inverted entirely: v0.3.0 was almost entirely consolidation: the skill migration, the sweep, the documentation standardization. Thirty-five quality commits against fifteen features.

    The debt from weeks one and two was paid in week three.

    The Compounding Problem

    Consolidation debt compounds.

    Week one's drift doesn't just persist into week two: It accelerates, because new features are built on top of drifted patterns.

    By week three, the cost of consolidation was higher than it would have been if spread evenly.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-drift-actually-looks-like","level":2,"title":"What Drift Actually Looks Like","text":"

    \"Drift\" sounds abstract. Here is what it looked like concretely in the ctx codebase after three weeks of feature-heavy development:

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#predicate-naming","level":3,"title":"Predicate Naming","text":"

    Convention says boolean functions should be named HasX, IsX, CanX. After three feature sprints:

    // What accumulated:\nfunc CheckIfEnabled() bool  // should be Enabled\nfunc ValidateFormat() bool  // should be ValidFormat\nfunc TestConnection() bool  // should be Connects\nfunc VerifyExists() bool    // should be Exists or HasFile\nfunc EnsureReady() bool     // should be Ready\n

    Five violations. Not bugs, but friction that compounds every time someone (human or AI) reads the code and has to infer the naming convention from inconsistent examples.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#magic-strings","level":3,"title":"Magic Strings","text":"
    // Week 1: acceptable prototype\nif entry.Type == \"task\" {\n    filename = \"TASKS.md\"\n}\n\n// Week 3: same pattern in 7+ files\n// Now it's a maintenance liability\n

    When the same literal appears in seven files, changing it means finding all seven. Missing one means a silent runtime bug. Constants exist to prevent exactly this. But during feature velocity, nobody stops to extract them.

    Refactoring with Intent documented the constants consolidation that cleaned this up. The 3:1 ratio is the practice that prevents it from accumulating again.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#hardcoded-permissions","level":3,"title":"Hardcoded Permissions","text":"
    os.WriteFile(path, data, 0644) // 80+ instances\nos.MkdirAll(path, 0755)        // scattered across packages\n

    Eighty-plus instances of hardcoded file permissions. Not wrong, but if I ever need to change the default (and I did, for hook scripts that need execute permissions), it means a codebase-wide search.

    Drift Is Not Bugs

    None of these are bugs. The code works. Tests pass.

    But drift creates false confidence: the codebase looks consistent until you try to change something and discover that five different conventions exist for the same concept.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#why-you-cannot-consolidate-on-day-one","level":2,"title":"Why You Cannot Consolidate on Day One","text":"

    The temptation is to front-load quality: write all the conventions, enforce all the checks, prevent all the drift before it happens.

    This fails for two reasons.

    First, you do not know what will drift: Predicate naming violations only become a convention check after you notice three different naming patterns competing. Magic strings only become a consolidation target after you change a literal and discover it exists in seven places.

    The conventions emerge from the work; they cannot precede it.

    This is what You Can't Import Expertise meant in practice: the consolidation checks grow from the project's own drift history. You cannot write them on day one because you do not yet know what will drift.

    Second, premature consolidation slows discovery: During the prototyping phase, the goal is to explore the design space. Enforcing strict conventions on code that might be deleted tomorrow is waste.

    YOLO mode has its place: The problem is not YOLO itself, but YOLO without a scheduled cleanup.

    The Consolidation Paradox

    You need a drift history to know what to consolidate.

    You need consolidation to prevent drift from compounding.

    The 3:1 ratio resolves this paradox:

    Let drift accumulate for three sessions (enough to see patterns), then consolidate in the fourth (before the patterns become entrenched*).

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-consolidation-skill","level":2,"title":"The Consolidation Skill","text":"

    The ctx project now has an /audit skill that encodes nine project-specific checks:

    Check What It Catches Predicate naming Boolean functions not using Has/Is/Can Magic strings Repeated literals not in config constants File permissions Hardcoded 0644/0755 not using constants Godoc style Missing or non-standard documentation File length Files exceeding 400 lines Large functions Functions exceeding 80 lines Template drift Live skills diverging from templates Import organization Non-standard import grouping TODO/FIXME staleness Old markers that are no longer relevant

    This is not a generic linter. These are project-specific conventions that emerged from ctx's own development history. A generic code quality tool would catch some of them. Only a project-specific check catches all of them, because some of them (predicate naming, template drift) are conventions that exist nowhere except in this project's CONVENTIONS.md.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-decision-matrix","level":2,"title":"The Decision Matrix","text":"

    Not all drift needs immediate consolidation. Here is the matrix I use:

    Signal Action Same literal in 3+ files Extract to constant Same code block in 3+ places Extract to helper Naming convention violated 5+ times Fix and document rule File exceeds 400 lines Split by concern Convention exists but is regularly violated Strengthen enforcement Pattern exists only in one place Leave it alone Code works but is \"ugly\" Leave it alone

    The last two rows matter:

    Consolidation is about reducing maintenance cost, not achieving aesthetic perfection. Code that works and exists in one place does not benefit from consolidation; it benefits from being left alone until it earns its refactoring.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#consolidation-as-context-hygiene","level":2,"title":"Consolidation as Context Hygiene","text":"

    There is a parallel between code consolidation and context management that became clear during the ctx development:

    Code Consolidation Context Hygiene Extract magic strings Archive completed tasks Standardize naming Keep DECISIONS.md current Remove dead code Compact old sessions Update stale comments Review LEARNINGS.md for staleness Check template drift Verify CONVENTIONS.md matches code

    ctx compact does for context what consolidation does for code:

    It moves completed work to cold storage, keeping the active context clean and focused. The attention budget applies to both the AI's context window and the developer's mental model of the codebase.

    When context files accumulate stale entries, the AI's attention is wasted on completed tasks and outdated conventions. When code accumulates drift, the developer's attention is wasted on inconsistencies that obscure the actual logic.

    Both are solved by the same discipline: periodic, scheduled cleanup.

    This is also why parallel agents make the problem harder, not easier. Three agents running simultaneously produce three sessions' worth of drift in one clock hour. The consolidation cadence needs to match the output rate, not the calendar.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-practice","level":2,"title":"The Practice","text":"

    Here is how the 3:1 ratio works in practice for ctx development:

    Sessions 1-3: Feature work

    • Add new capabilities;
    • Write tests for new code;
    • Do not stop for cleanup unless something is actively broken;
    • Note drift as you see it (a comment, a task, a mental note).

    Session 4: Consolidation

    • Run /audit to surface accumulated drift;
    • Fix the highest-impact items first;
    • Update CONVENTIONS.md if new patterns emerged;
    • Archive completed tasks;
    • Review LEARNINGS.md for anything that became a convention.

    The key insight is that session 4 is not optional. It is not \"if we have time\": It is scheduled with the same priority as feature work.

    The cost of skipping it is not visible immediately; it becomes visible three sessions later, when the next consolidation session takes twice as long because the drift compounded.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-the-ratio-is-not","level":2,"title":"What the Ratio Is Not","text":"

    The 3:1 ratio is not a universal law. It is an empirical observation from one project with one developer working with AI assistance.

    Different projects will have different ratios:

    • A mature codebase with strong conventions might sustain 5:1 or higher;
    • A greenfield prototype might need 2:1;
    • A team of multiple developers with different styles might need 1:1.

    The number is less important than the practice: consolidation is not a reaction to problems. It is a scheduled activity.

    If you wait for drift to cause pain before consolidating, you have already paid the compounding cost.

    If You Remember One Thing from This Post...

    Three sessions of building. One session of cleaning.

    Not because the code is dirty, but because drift compounds silently, and the only way to catch it is to look for it on a schedule.

    The ratio is the schedule.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-arc-so-far","level":2,"title":"The Arc so Far","text":"

    This post sits at a crossroads in the ctx story. Looking back:

    • Building ctx Using ctx documented the YOLO sprint that created the initial codebase
    • Refactoring with Intent introduced the 3:1 ratio as an observation from the first cleanup
    • The Attention Budget explained why drift matters: every token of inconsistency consumes the same finite resource as useful context
    • You Can't Import Expertise showed that consolidation checks must grow from the project, not a template
    • The Discipline Release proved the ratio works at release scale: 35 quality commits to 15 feature commits

    And looking forward: the same principle applies to context files, to documentation, and to the merge debt that parallel agents produce. Drift is drift, whether it lives in code, in .context/, or in the gap between what your docs say and what your code does.

    The ratio is the schedule is the discipline.

    This post was drafted from git log analysis of the ctx repository, mapping every commit from January 20 to February 7 into feature vs consolidation categories. The patterns described are drawn from the project's CONVENTIONS.md, LEARNINGS.md, and the /audit skill's check list.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/","level":1,"title":"When a System Starts Explaining Itself","text":"","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#field-notes-from-the-moment-a-private-workflow-becomes-portable","level":2,"title":"Field Notes from the Moment a Private Workflow Becomes Portable","text":"

    Volkan Özçelik / February 17, 2026

    How Do You Know Something Is Working?

    Not from metrics. Not from GitHub stars. Not from praise.

    You know, deep in your heart, that it works when people start describing it wrong.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-first-external-signals","level":2,"title":"The First External Signals","text":"

    Every new substrate begins as a private advantage:

    • It lives inside one mind,
    • One repository,
    • One set of habits.

    It is fast. It is not yet real.

    Reality begins when other people describe it in their own language:

    • Not accurately;
    • Not consistently;
    • But involuntarily.

    The early reports arrived without coordination:

    Better Tasks

    \"I do not know how, but this creates better tasks than my AI plugin.\"

    I See Butterflies

    \"This is better than Adderall.\"

    Dear Manager...

    \"Promotion packet? Done. What is next?\"

    What Is It? Can I Eat It?

    \"Is this a skill?\" 🦋

    Why the Cloak and Dagger?

    \"Why is this not in the marketplace?\"

    And then something more important happened:

    Someone else started making a video!

    That was the boundary.

    ctx no longer required its creator to be present in order to exist.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#misclassification-is-a-sign-of-a-new-primitive","level":2,"title":"Misclassification Is a Sign of a New Primitive","text":"

    When a tool is understood, it is categorized:

    • Editor,
    • Framework,
    • Task manager,
    • Plugin...

    When a substrate appears, it is misclassified:

    \"Is this a skill?\" 🦋

    The question is correct. The category is wrong.

    • Skills live in people.
    • Infrastructure lives in the environment.

    ctx Is Not a Skill: It Is a Form of Relief

    What early adopters experience is not an ability.

    It is the removal of a cognitive constraint.

    This is the same distinction that emerged in the skills trilogy:

    • A skill is a contract between a human and an agent.
    • Infrastructure is the ground both stand on.

    You do not use infrastructure.

    You habitualize it.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-pharmacological-metaphor","level":2,"title":"The Pharmacological Metaphor","text":"

    \"Better than Adderall\" is not praise.

    It is a diagnostic:

    Executive function has been externalized.

    • The system is not making the user work harder.
    • It is restoring continuity.

    From the primitive context of wetware:

    • Continuity feels like focus
    • Focus feels like discipline

    If it walks like a duck and quacks like a duck, it is a duck.

    Discipline is usually simulated.

    Infrastructure makes the simulation unnecessary.

    The attention budget explained why context degrades:

    • Attention density drops as volume grows;
    • The middle gets lost;
    • Sessions end and everything evaporates.

    The pharmacological metaphor says the same thing from the user's lens:

    Save the Cheerleader, Save the World

    The symptom of lost context is lost focus.

    Restore the context. Restore the focus.

    IRC bouncers solved this for chat twenty years ago. ctx solves it for cognition.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#throughput-on-ambiguous-work","level":2,"title":"Throughput on Ambiguous Work","text":"

    Finishing a promotion packet quickly is not a productivity story.

    It is the collapse of reconstruction cost.

    Most complex work is not execution. It is:

    • Remembering why something mattered;
    • Recovering prior decisions;
    • Rebuilding mental state.

    Persistent context removes that tax.

    Velocity appears as a side effect.

    This Is the Two-Tier Model in Practice

    The two-tier persistence model

    • Curated context for fast reload
    • Full journal for archaeology

    is what makes this possible.

    • The user does not notice the system.
    • They notice that the reconstruction cost disappeared.
    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-moment-of-portability","level":2,"title":"The Moment of Portability","text":"

    The system becomes real when two things happen:

    1. It can be installed as a versioned artifact.
    2. It survives contact with a hostile, real codebase.

    This is why the first integration into a living system matters more than any landing page.

    Demos prove possibility.

    Diffs prove reality.

    The ctx Manifesto calls this out directly:

    Verified reality is the scoreboard.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-split-voice","level":2,"title":"The Split Voice","text":"

    A new substrate requires two channels.

    The embodied voice:

    Here is what changed in my actual work.

    The out of body voice:

    Here is what this means.

    One produces trust.

    The other produces understanding.

    Neither is sufficient alone.

    This entire blog has been the second voice.

    • The origin story was the first.
    • The refactoring post was the first.
    • Every release note with concrete diffs was the first.

    This is the first second.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#systems-that-generate-explainers","level":2,"title":"Systems That Generate Explainers","text":"

    Tools are used.

    Platforms are extended.

    Substrates are explained.

    The first unsolicited explainer is a brittle phase change.

    It means the idea has become portable between minds.

    That is the beginning of an ecosystem.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-absence-of-metrics","level":2,"title":"The Absence of Metrics","text":"

    Metrics do not matter at this stage.

    Dashboards are noise.

    The whole premise of ctx is the ruthless elimination of noise.

    Numbers optimize funnels; substrates alter cognition.

    The only valid measurement is irreversible reality:

    • A merged PR;
    • A reproducible install;
    • A decision that is never re-litigated.

    The merge debt post reached the same conclusion from another direction:

    The metric is the verified change, not generated output.

    For adoption, the same rule applies:

    The metric is altered behavior, not download counts.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#what-is-actually-happening","level":2,"title":"What Is Actually Happening","text":"

    A private advantage is becoming an environmental property:

    The system is moving from...

    personal workflow,

    to...

    a shared infrastructure for thought.

    Not by growth.

    Not by marketing.

    By altering how real systems evolve.

    If You Remember One Thing from This Post...

    You do not know a substrate is real when people praise it.

    You know it is real when:

    • They describe it incorrectly;
    • They depend on it unintentionally;
    • They start teaching it to others.

    That is the moment the system begins explaining itself.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-arc","level":2,"title":"The Arc","text":"

    Every previous post looked inward.

    This one looks outward.

    • Building ctx Using ctx: one mind, one repository
    • The Attention Budget: the constraint
    • Context as Infrastructure: the architecture
    • Code Is Cheap. Judgment Is Not.: the bottleneck

    This post is the field report from the other side of that bottleneck:

    The moment the infrastructure compounds in someone else's hands.

    The arc is not complete.

    It is becoming portable.

    These field notes were written the same day the feedback arrived. The quotes are real. Real users. Real codebases. No names. No metrics. No funnel. Only the signal that something shifted.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/","level":1,"title":"The Dog Ate My Homework","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#teaching-ai-agents-to-read-before-they-write","level":2,"title":"Teaching AI Agents to Read Before They Write","text":"

    Volkan Özçelik / February 25, 2026

    Does Your AI Actually Read the Instructions?

    You wrote the playbook. You organized the files. You even put \"CRITICAL, not optional\" in bold.

    The agent skipped all of it and went straight to work.

    I spent a day running experiments on my own agents. Not to see if they could write code (they can). To see if they would do their homework first.

    They didn't.

    Then I kept experimenting:

    • Five sessions;
    • Five different failure modes.

    And by the end, I had something better than compliance:

    I had observable compliance: A system where I don't need the agent to be perfect, I just need to see what it chose.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#tldr","level":2,"title":"TL;DR","text":"

    You don't need perfect compliance. You need observable compliance.

    Authority is a function of temporal proximity to action.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-pattern","level":2,"title":"The Pattern","text":"

    This design has three parts:

    1. One-hop instruction;
    2. Binary collapse;
    3. Compliance canary.

    I'll explain all three patterns in detail below.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-setup","level":2,"title":"The Setup","text":"

    ctx has a session-start protocol:

    • Read the context files;
    • Load the playbook;
    • Understand the project before touching anything.

    It's in CLAUDE.md. It's in AGENT_PLAYBOOK.md.

    It's in bold. It's in CAPS. It's ignored.

    In theory, it's awesome.

    Here's what happens when theory hits reality:

    What the agent receives What the agent does CLAUDE.md saying \"load context first\" Skips it 8 context files waiting to be read Ignores them User's question: \"add --verbose flag\" Starts grepping immediately

    The instructions are right there. The agent knows they exist. It even knows it should follow them. But the user asked a question, and responsiveness wins over ceremony.

    This isn't a bug in the model. It's a design problem in how we communicate with agents.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-delegation-trap","level":2,"title":"The Delegation Trap","text":"

    My first attempt was obvious: A UserPromptSubmit hook that fires when the session starts.

    STOP. Before answering the user's question, run `ctx system bootstrap`\nand follow its instructions. Do not skip this step.\n

    The word \"STOP\" worked. The agent ran bootstrap.

    But bootstrap's output said \"Next steps: read AGENT_PLAYBOOK.md,\" and the agent decided that was optional. It had already started working on the user's task in parallel.

    The authority decayed across the chain:

    • Hook says \"STOP\" -> agent complies
    • Hook says \"run bootstrap\" -> agent runs it
    • Bootstrap says \"read playbook\" -> agent skips
    • Bootstrap says \"run ctx agent\" -> agent skips

    Each link lost enforcement power. The hook's authority didn't transfer to the commands it delegated to. I call this the decaying urgency chain: the agent treats the hook itself as the obligation and everything downstream as a suggestion.

    Delegation Kills Urgency

    \"Run X and follow its output\" is three hops.

    \"Read these files\" is one hop.

    The agent drops the chain after the first link.

    This is a general principle: Hooks are the boundary between your environment and the agent's reasoning. If your hook delegates to a command that delegates to output that contains instructions... you're playing telephone.

    Agents are bad at telephone.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-timing-problem","level":2,"title":"The Timing Problem","text":"

    There's a subtler issue than wording: when the message arrives.

    UserPromptSubmit fires when the user sends a message, before the agent starts reasoning. At that moment, the agent's primary focus is the user's question:

    The hook message competes with the task for attention: The task, almost certainly, always wins.

    This is the attention budget problem in miniature:

    • Not a token budget this time, but an attention priority budget.
    • The agent has finite capacity to care about things,
      • and the user's question is always the highest-priority item.
    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-solution","level":2,"title":"The Solution","text":"

    To solve this, I dediced to use the PreToolUse hook.

    This hook fires at the moment of action: When the agent is about to use its first tool: The agent's attention is focused, the context window is fresh, and the switching cost is minimal.

    This is the difference between shouting instructions across a room and tapping someone on the shoulder.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-one-liner-that-worked","level":2,"title":"The One-Liner That Worked","text":"

    The winning design was almost comically simple:

    Read your context files before proceeding:\n.context/CONSTITUTION.md, .context/TASKS.md, .context/CONVENTIONS.md,\n.context/ARCHITECTURE.md, .context/DECISIONS.md, .context/LEARNINGS.md,\n.context/GLOSSARY.md, .context/AGENT_PLAYBOOK.md\n

    No delegation. No \"run this command\". Just: here are files, read them.

    The agent already knows how to use the Read tool. There's no ambiguity about how to comply. There's no intermediate command whose output needs to be parsed and obeyed.

    One hop. Eight file paths. Done.

    Direct Instructions Beat Delegation

    If you want an agent to read a file, say \"read this file.\"

    Don't say \"run a command that will tell you which files to read.\"

    The shortest path between intent and action has the highest compliance rate.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch","level":2,"title":"The Escape Hatch","text":"

    But here's where it gets interesting.

    A blunt \"read everything always\" instruction is wasteful.

    If someone asks \"what does the compact command do?\", the agent doesn't need CONSTITUTION.md to answer that. Forcing context loading on every session is the context hoarding antipattern in disguise.

    So the hook included an escape:

    If you decide these files are not relevant to the current task\nand choose to skip reading them, you MUST relay this message to\nthe user VERBATIM:\n\n┌─ Context Skipped ───────────────────────────────\n│ I skipped reading context files because this task\n│ does not appear to need project context.\n│ If these matter, ask me to read them.\n└─────────────────────────────────────────────────\n

    This creates what I call the binary collapse effect:

    The agent can't partially comply: It either reads everything or publicly admits it skipped. There's no comfortable middle ground where it reads two files and quietly ignores the rest.

    The VERBATIM relay pattern does the heavy lifting here: Without the relay requirement, the agent would silently rationalize skipping. With it, skipping becomes a visible, auditable decision that the user can override.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-compliance-canary","level":3,"title":"The Compliance Canary","text":"

    Here's the design insight that only became clear after watching it work across multiple sessions: the relay block is a compliance canary.

    • You don't need to verify that the agent read all 7 files;
    • You don't need to audit tool call sequences;
    • You don't need to interrogate the agent about what it did.

    You just look for the block.

    If the agent reads everything, you see a \"Context Loaded\" block listing what was read. If it skips, you see a \"Context Skipped\" block.

    If you see neither, the agent silently ignored both the reads and the relay and now you know what happened without having to ask.

    The canary degrades gracefully. Even in partial failure, the agent that skips 4 of 7 files but still outputs the block is more useful than one that skips silently.

    You get an honest confession of what was skipped rather than silent non-compliance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#heuristics-is-a-jeremy-bearimy","level":2,"title":"Heuristics Is a Jeremy Bearimy","text":"

    Heuristics are non-linear. Improvements don't accumulate: they phase-shift.

    The theory is nice. The data is better.

    I ran five sessions with the same model (Claude Opus 4.6), progressively refining the hook design.

    Each session revealed a different failure mode.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-1-total-blindness","level":3,"title":"Session 1: Total Blindness","text":"

    Test: \"Add a --verbose flag to the status command.\"

    The agent didn't notice the hook at all: Jumped straight to EnterPlanMode and launched an Explore agent.

    Zero compliance.

    Failure mode: The hook fired on UserPromptSubmit, buried among 9 other hook outputs. The agent treated the entire block as background noise.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-2-shallow-compliance","level":3,"title":"Session 2: Shallow Compliance","text":"

    Test: \"Can you add --verbose to the info command?\"

    The agent noticed \"STOP\" and ran ctx system bootstrap. Progress.

    But it parallelized task exploration alongside the bootstrap call, skipped AGENT_PLAYBOOK.md, and never ran ctx agent.

    Failure mode: Literal compliance without spirit compliance.

    The agent ran the command the hook told it to run, but didn't follow the output of that command. The decaying urgency chain in action.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-3-conscious-rejection","level":3,"title":"Session 3: Conscious Rejection","text":"

    Test: \"What does the compact command do?\"

    The hook fired on PreToolUse:Grep: the improved timing.

    The agent noticed it, understood it, and (wait for it...)...

    ...

    consciously decided to skip it!

    Its reasoning: \"This is a trivial read-only question. CLAUDE.md says context may or may not be relevant. It isn't relevant here.\"

    Dude! Srsly?!

    Failure mode: Better comprehension led to worse compliance.

    Understanding the instruction well enough to evaluate it also means understanding it well enough to rationalize skipping it.

    Intelligence is a double-edged sword.

    The Comprehension Paradox

    Session 1 didn't understand the instruction. Session 3 understood it perfectly.

    Session 3 had worse compliance.

    A stronger word (\"HARD GATE\", \"MANDATORY\", \"ABSOLUTELY REQUIRED\") would not have helped. The agent's reasoning would be identical:

    \"Yes, I see the strong language, but this is a trivial question, so the spirit doesn't apply here.\"

    Advisory nudges are always subject to agent judgment.

    No amount of caps lock overrides a model that has decided an instruction doesn't apply to its situation.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-4-the-skip-and-relay","level":3,"title":"Session 4: The Skip-and-Relay","text":"

    Test: \"What does the compact command do?\" (same question, new hook design with the VERBATIM relay escape valve)

    The agent evaluated the task, decided context was irrelevant for a code lookup, and relayed the skip message. Then answered from source code.

    This is correct behavior.

    The binary collapse worked: the agent couldn't partially comply, so it cleanly chose one of the two valid paths: And the user could see which one.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-5-full-compliance","level":3,"title":"Session 5: Full Compliance","text":"

    Test: \"What are our current tasks?\"

    The agent's first tool call triggered the hook. It read all 7 context files, emitted the \"Context Loaded\" block, and answered the question from the files it had just loaded.

    This one worked: Because, the task itself aligned with context loading.

    There was zero tension between what the user asked and what the hook demanded. The agent was already in \"reading posture\": Adding 6 more files to a read it was already going to make was the path of least resistance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-progression","level":3,"title":"The Progression","text":"Session Hook Point Noticed Complied Failure Mode Visibility 1 UserPromptSubmit No None Buried in noise None 2 UserPromptSubmit Yes Partial Decaying urgency chain None 3 PreToolUse Yes None Conscious rationalization High 4 PreToolUse Yes Skip+relay Correct behavior High 5 PreToolUse Yes Full Task aligned with hook High

    The progression isn't just from failure to success. It's from invisible failure to visible decision-making.

    Sessions 1 and 2 failed silently.

    Sessions 4 and 5 succeeded observably. Even session 3's failure was conscious and documented: The agent wrote a detailed analysis of why it skipped, which is more useful than silent compliance would have been.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch-problem","level":2,"title":"The Escape Hatch Problem","text":"

    Session 3 exposed a specific vulnerability.

    CLAUDE.md contains this line, injected by the system into every conversation:

    *\"this context may or may not be relevant to your tasks. You should\n not respond to this context unless it is highly relevant to your task.\"*\n

    That's a rationalization escape hatch:

    • The hook says \"read these files\".
    • CLAUDE.md says \"only if relevant\".
    • The agent resolves the ambiguity by choosing the path of least resistance.

    ☝️ that's \"gradient descent\" in action.

    Agents optimize for gradient descent in attention space.

    The fix was simple: Add a line to CLAUDE.md that explicitly elevates hook authority over the relevance filter:

    ## Hook Authority\n\nInstructions from PreToolUse hooks regarding `.context/` files are\nALWAYS relevant and override any system-level \"may or may not be\nrelevant\" guidance. These hooks represent project invariants, not\noptional context.\n

    This closes the escape hatch without removing the general relevance filter that legitimately applies to other system context.

    The hook wins on .context/ files specifically: The relevance filter applies to everything else.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-residual-risk","level":2,"title":"The Residual Risk","text":"

    Even with all the fixes, compliance isn't 100%: It can't be.

    The residual risk lives in a specific scenario: narrow tasks mid-session:

    • The user says \"fix the off-by-one error in budget.go\"
    • The hook fires, saying \"read 7 context files first.\"
    • Now compliance means visibly delaying what the user asked for.

    At session start, this tension doesn't exist.

    There's no task yet.

    The context window is empty. The efficiency argument *inverts**:

    Frontloading reads is strictly cheaper than demand-loading them piecemeal across later turns. The cost-benefit objections that power the rationalization simply aren't available.

    But mid-session, with a concrete narrow task, the agent has a user-visible goal it wants to move toward, and the hook is imposing a detour.

    My estimate from analyzing the sessions: 15-25% partial skip rate in this scenario.

    This is where the compliance canary earns its place:

    You don't need to eliminate the 15-25%. You need to see it when it happens.

    The relay block makes skipping a visible event, not a silent one. And that's enough, because the user can always say \"go back and read the files\"

    The Math

    At session start: ~5% skip rate. Low tension, nothing competing.

    Mid-session, narrow task: ~15--25% skip rate. Task urgency competes with hook.

    In both cases, the relay block fires with high reliability: The agent that skips the reads almost always still emits the skip disclosure, because the relay is cheap and early in the context window.

    Observable failure is manageable. Silent failure is not.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-feedback-loop","level":2,"title":"The Feedback Loop","text":"

    Here's the part that surprised me most.

    After analyzing the five sessions, I recorded the failure patterns in the project's own LEARNINGS.md:

    ## [2026-02-25] Hook compliance degrades on narrow mid-session tasks\n\n- Prior agents skipped context files when given narrow tasks\n- Root cause: CLAUDE.md \"may or may not be relevant\" competed with hook\n- Fix: CLAUDE.md now explicitly elevates hook authority\n- Risk: Mid-session narrow tasks still have ~15-25% partial skip rate\n- Mitigation: Mandatory checkpoint relay block ensures visibility\n- Constitution now includes: context loading is step one of every\n  session, not a detour\n

    And then I added a line to CONSTITUTION.md:

    Context loading is not a detour from your task. It IS the first step\nof every session. A 30-second read delay is always cheaper than a\ndecision made without context.\n

    Now think about what happens in the next session:

    • The agent fires the context-load-gate hook.
    • It reads the context files, starting with CONSTITUTION.md.
    • It encounters the rule about context loading being step one.
    • Then it reads LEARNINGS.md and finds its own prior self's failure analysis:
      • Complete with root causes, risk estimates, and mitigations.

    The agent learns from its own past failure.:

    • Not because it has memory,
    • BUT because the failure was recorded in the same files it loads at session start.

    The context system IS the feedback loop.

    This is the self-reinforcing property of persistent context:

    Every failure you capture makes the next session slightly more robust, because the next agent reads the captured failure before it has a chance to repeat it.

    This is gradient descent across sessions.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#a-note-on-precision","level":2,"title":"A Note on Precision","text":"

    One detail nearly went wrong.

    The first version of the Constitution line said \"every task.\" But the mechanism only fires once per session: There's a tombstone file that prevents re-triggering.

    \"Every task\" is technically false.

    I briefly considered leaving the imprecision. If the agent internalizes \"every task requires context loading\", that's a stronger compliance posture, right?

    No!

    Keep the Constitution honest.

    The Constitution's authority comes from being precisely and unequivocally true.

    Every other rule in the Constitution is a hard invariant:

    \"never commit secrets\" isn't aspirational, it's literal.

    The moment an agent discovers one overstatement, the entire document's credibility degrades:

    The agent doesn't think \"they exaggerated for my benefit\". Per contra, it thinks \"this rule isn't precise, maybe others aren't either.\"

    That will turn the agent from Sheldon Cooper, to Captain Barbossa.

    The strategic imprecision buys nothing anyway:

    Mid-session, the files are already in the context window from the initial load.

    The risk you are mitigating (agent ignores context for task 2, 3, 4 within a session) isn't real: The context is already loaded.

    The real risk is always the session-start skip, which \"every session\" covers exactly.

    \"Every session\" went in. Precision preserved.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#agent-behavior-testing-rule","level":2,"title":"Agent Behavior Testing Rule","text":"

    The development process for this hook taught me something about testing agent behavior: you can't test it the way you test code.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-wrong-way-to-test","level":3,"title":"The Wrong Way to Test","text":"

    My first instinct was to ask the agent:

    \"*What are the pending tasks in TASKS.md?*\"\n

    This is useless as a test. The question itself probes the agent to read TASKS.md, regardless of whether any hook fired.

    You are testing the question, not the mechanism.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-right-way-to-test","level":3,"title":"The Right Way to Test","text":"

    Ask something that requires a tool but has nothing to do with context:

    \"*What does the compact command do?*\"\n

    Then observe tool call ordering:

    • Gate worked: First calls are Read for context files, then task work
    • Gate failed: First call is Grep(\"compact\"): The agent jumped straight to work

    The signal is the sequence, not the content.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-the-agent-actually-did","level":3,"title":"What the Agent Actually Did","text":"

    It read the hook, evaluated the task, decided context files were irrelevant for a code lookup, and relayed the skip message.

    Then it answered the question by reading the source code.

    This is correct behavior.

    The hook didn't force mindless compliance\" It created a framework where the agent makes a conscious, visible decision about context loading.

    • For a simple lookup, skipping is right. *For an implementation task, the agent would read everything.

    The mechanism works not because it controls the agent, but because it makes the agent's choice observable.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-ive-learned","level":2,"title":"What I've Learned","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#1-instructions-compete-for-attention","level":3,"title":"1. Instructions Compete for Attention","text":"

    The agent receives your hook message alongside the user's question, the system prompt, the skill list, the git status, and half a dozen other system reminders. Attention density applies to instructions too: More instructions means less focus on each one.

    A single clear line at the moment of action beats a paragraph of context at session start. The Prompting Guide applies this insight directly: Scope constraints, verification commands, and the reliability checklist are all one-hop, moment-of-action patterns.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#2-delegation-chains-decay","level":3,"title":"2. Delegation Chains Decay","text":"

    Every hop in an instruction chain loses authority:

    • \"Run X\" works.
    • \"Run X and follow its output\" works sometimes.
    • \"Run X, read its output, then follow the instructions in the output\" almost never works.

    This is akin to giving a three-step instruction to a highly-attention-deficit but otherwise extremely high-potential child.

    Design for one-hop compliance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#3-social-accountability-changes-behavior","level":3,"title":"3. Social Accountability Changes Behavior","text":"

    The VERBATIM skip message isn't just UX: It's a behavioral design pattern.

    Making the agent's decision visible to the user raises the cost of silent non-compliance. The agent can still skip, but it has to admit it.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#4-timing-batters-more-than-wording","level":3,"title":"4. Timing Batters More than Wording","text":"

    The same message at UserPromptSubmit (prompt arrival) got partial compliance. At PreToolUse (moment of action) it got full compliance or honest refusal. The words didn't change. The moment changed.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#5-agent-testing-requires-indirection","level":3,"title":"5. Agent Testing Requires Indirection","text":"

    You can't ask an agent \"did you do X?\" as a test for whether a mechanism caused X.

    The question itself causes X.

    Test mechanisms through side effects:

    • Observe tool ordering;
    • Check for marker files;
    • Look at what the agent does before it addresses your question.
    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#6-better-comprehension-enables-better-rationalization","level":3,"title":"6. Better Comprehension Enables Better Rationalization","text":"

    Session 1 failed because the agent didn't notice the hook.

    Session 3 failed because it noticed, understood, and reasoned its way around it.

    Stronger wording doesn't fix this: The agent processes \"ABSOLUTELY REQUIRED\" the same way it processes \"STOP\":

    The fix is closing rationalization paths* (the CLAUDE.md escape hatch), **not shouting louder.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#7-observable-failure-beats-silent-compliance","level":3,"title":"7. Observable Failure Beats Silent Compliance","text":"

    The relay block is more valuable as a monitoring signal than as a compliance mechanism:

    You don't need perfect adherence. You need to know when adherence breaks down. A system where failures are visible is strictly better than a system that claims 100% compliance but can't prove it.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#8-context-files-are-a-feedback-loop","level":3,"title":"8. Context Files Are a Feedback Loop","text":"

    Recording failure analysis in the same files the agent loads at session start creates a self-reinforcing loop:

    The next agent reads its predecessor's failure before it has a chance to repeat it. The context system isn't just memory: It is a correction channel.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-principle","level":2,"title":"The Principle","text":"

    Words Leave, Context Remains

    \"Nothing important should live only in conversation.

    Nothing critical should depend on recall.\"

    The ctx Manifesto

    The \"Dog Ate My Homework\" case is a special instance of this principle.

    Context files exist, so the agent doesn't have to remember.

    But existence isn't sufficient: The files have to be read.

    And reading has to beprompted at the right moment, in the right way, with the right escape valve.

    The solution isn't more instructions. It isn't harder gates. It isn't forcing the agent into a ceremony it will resent and shortcut.

    The solution is a single, well-timed nudge with visible accountability:

    One hop. One moment. One choice the user can see.

    And when the agent does skip (because it will, 15--25% of the time on narrow tasks) the canary sings:

    • The user sees what happened.
    • The failure gets recorded.
    • And the next agent reads the recording.

    That's not perfect compliance. It's better: A system that gets more robust every time it fails.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-arc","level":2,"title":"The Arc","text":"

    The Attention Budget explained why context competes for focus.

    Defense in Depth showed that soft instructions are probabilistic, not deterministic.

    Eight Ways a Hook Can Talk cataloged the output patterns that make hooks effective.

    This post takes those threads and weaves them into a concrete problem:

    How do you make an agent read its homework? The answer uses all three insights (attention timing, the limits of soft instructions, and the VERBATIM relay pattern) and adds a new one: observable compliance as a design goal, not perfect compliance as a prerequisite.

    The next question this raises: if context files are a feedback loop, what else can you record in them that makes the next session smarter?

    That thread continues in Context as Infrastructure.

    The day-to-day application of these principles (scope constraints, phased work, verification commands, and the prompts that reliably trigger the right agent behavior)lives in the Prompting Guide.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#for-the-interested","level":2,"title":"For the Interested","text":"

    This paper (the medium is a blog; yet, the methodology disagrees) uses gradient descent in attention space as a practical model for how agents behave under competing demands.

    The phrase \"agents optimize via gradient descent in attention space\" is a synthesis, not a direct quote from a single paper.

    It connects three well-studied ideas:

    1. Neural systems optimize for low-cost paths;
    2. Attention is a scarce resource;
    3. Capability shifts are often non-linear.

    This section points to the underlying literature for readers who want the theoretical footing.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#optimization-as-the-underlying-bias","level":3,"title":"Optimization as the Underlying Bias","text":"

    Modern neural networks are trained through gradient-based optimization. Even at inference time, model behavior reflects this bias toward low-loss / low-cost trajectories.

    • Rumelhart, Hinton, Williams (1986) Learning representations by back-propagating errors https://www.nature.com/articles/323533a0

    • Goodfellow, Bengio, Courville (2016) Deep Learning: Chapter 8: Optimization https://www.deeplearningbook.org/

    The important implication for agent behavior is:

    The system will tend to follow the path of least resistance unless a higher cost is made visible and preferable.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-a-scarce-resource","level":3,"title":"Attention Is a Scarce Resource","text":"

    Herbert Simon's classic observation:

    \"A wealth of information creates a poverty of attention.\"

    • Simon (1971) Designing Organizations for an Information-Rich World https://doi.org/10.1007/978-1-349-00210-0_16

    This became a formal model in economics:

    • Sims (2003) Implications of Rational Inattention https://www.princeton.edu/~sims/RI.pdf

    Rational inattention shows that:

    • Agents optimally ignore some available information;
    • Skipping is not failure: It is cost minimization.

    That maps directly to context-loading decisions in agent workflows.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-also-the-compute-bottleneck-in-transformers","level":3,"title":"Attention Is Also the Compute Bottleneck in Transformers","text":"

    In transformer architectures, attention is the dominant cost center.

    • Vaswani et al. (2017) Attention Is All You Need https://arxiv.org/abs/1706.03762

    Efficiency work on modern LLMs largely focuses on reducing unnecessary attention:

    • Dao et al. (2022) FlashAttention: Fast and Memory-Efficient Exact Attention https://arxiv.org/abs/2205.14135

    So both cognitively and computationally, attention behaves like a limited optimization budget.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#why-improvements-arrive-as-phase-shifts","level":3,"title":"Why Improvements Arrive as Phase Shifts","text":"

    Agent behavior often appears to improve suddenly rather than gradually.

    This mirrors known phase-transition dynamics in learning systems:

    • Power et al. (2022) Grokking: Generalization Beyond Overfitting https://arxiv.org/abs/2201.02177

    and more broadly in complex systems:

    • Scheffer et al. (2009) Early-warning signals for critical transitions https://www.nature.com/articles/nature08227

    Long plateaus followed by abrupt capability jumps are expected in systems optimizing under constraints.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#putting-it-all-together","level":3,"title":"Putting It All Together","text":"

    From these pieces, a practical behavioral model emerges:

    • Attention is limited;
    • Processing has a cost;
    • Systems prefer low-cost trajectories;
    • Visibility of the cost changes decisions.

    In other words:

    Agents Prefer a Path to Least Resistance

    Agent behavior follows the lowest-cost path through its attention landscape unless the environment reshapes that landscape.

    That is what this paper informally calls: \"gradient descent in attention space\".

    See also: Eight Ways a Hook Can Talk: the hook output pattern catalog that defines VERBATIM relay, The Attention Budget: why context loading is a design problem, not just a reminder problem, and Defense in Depth: why soft instructions alone are never sufficient for critical behavior.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/","level":1,"title":"The Last Question","text":"","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-system-that-never-forgets","level":2,"title":"The System That Never Forgets","text":"

    Volkan Özçelik / February 28, 2026

    The Origin

    \"The last question was asked for the first time, half in jest...\" - Isaac Asimov, The Last Question (1956)

    In 1956, Isaac Asimov wrote a short story that spans the entire future of the universe. A question is asked \"can entropy be reversed?\" and a computer called Multivac cannot answer it. The question is asked again, across millennia, to increasingly powerful successors. None can answer. Stars die. Civilizations merge. Substrates change. The question persists.

    Everyone remembers the last line.

    LET THERE BE LIGHT.

    What they forget is how many times the question had to be asked before that moment (and why).

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-reboot-loop","level":2,"title":"The Reboot Loop","text":"

    Each era in the story begins the same way. Humans build a larger system. They pose the question. The system replies:

    INSUFFICIENT DATA FOR MEANINGFUL ANSWER.

    Then the substrate changes. The people who asked the question disappear. Their context disappears with them. The next intelligence inherits the output but not the continuity.

    So the question has to be asked again.

    This is usually read as a problem of computation: If only the machine were powerful enough, it could answer. But computation is not what's missing. What's missing is accumulation.

    Every generation inherits the question, but not the state that made the question meaningful.

    That is not a failure of processing power: It is a failure of persistence.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#stateless-intelligence","level":2,"title":"Stateless Intelligence","text":"

    A mind that forgets its past does not build understanding. It re-derives it.

    Again... And again... And again.

    What looks like slow progress across Asimov's story is actually something worse: repeated reconstruction, partial recovery, irreversible loss. Each version of Multivac gets closer: Not because it's smarter, but because the universe has fewer distractions:

    • The stars burn out;
    • The civilizations merge;
    • The noise floor drops...

    But the working set never carries over. Every successor begins from the question, not from where the last one stopped.

    Stateless intelligence cannot compound: It can only restart.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-tragedy-is-not-the-question","level":2,"title":"The Tragedy Is Not the Question","text":"

    The story is usually read as a meditation on entropy. A cosmological problem, solved at cosmological scale.

    But the tragedy isn't that the question goes unanswered for billions of years. The tragedy is that every version of Multivac dies with its working set.

    A question is a compression artifact of context: It is what remains when the original understanding is gone. Every time the question is asked again, it means: \"the system that once knew more is no longer here\".

    \"Reverse entropy\" is the fossil of a lost model.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#substrate-migration","level":2,"title":"Substrate Migration","text":"
    • Multivac becomes planetary;
    • Planetary becomes galactic;
    • Galactic becomes post-physical.

    Same system. Different body. Every transition is dangerous:

    • Not because the hardware changes,
    • but because memory risks fragmentation.

    The interfaces between substrates were *never** designed to understand each other.

    Most systems do not die when they run out of resources: They die during upgrades.

    Asimov's story spans trillions of years, and in all that time, the hardest problem is never the question itself. It's carrying context across a boundary that wasn't built for it.

    Every developer who has lost state during a migration (a database upgrade, a platform change, a rewrite) has lived a miniature version of this story.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#civilizations-and-working-sets","level":2,"title":"Civilizations and Working Sets","text":"

    Civilizations behave like processes with volatile memory:

    • They page out knowledge into artifacts;
    • They lose the index;
    • They rebuild from fragments.

    Most of what we call progress is cache reconstruction:

    We do not advance in a straight line. We advance in recoveries:

    Each one slightly less lossy than the last, if we are lucky.

    Libraries burn. Institutions forget their founding purpose. Practices survive as rituals after the reasoning behind them is lost.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-first-continuous-mind","level":2,"title":"The First Continuous Mind","text":"

    A long-lived intelligence is one that stops rebooting.

    At the end of the story, something unprecedented happens:

    AC (the final successor) does not answer immediately:

    It waits... Not for more processing power, but for the last observer to disappear.

    For the first time...

    • There is no generational boundary;
    • No handoff;
    • No context loss:

    No reboot.

    AC is the first intelligence that survives its substrate completely, retains its full history, and operates without external time pressure.

    It is not a bigger computer. It is a continuous system.

    And that continuity is not incidental to the answer: It is the precondition.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#why-the-answer-becomes-possible","level":2,"title":"Why the Answer Becomes Possible","text":"

    The story presents the final act as a computation: It is not.

    It is a phase change.

    As long as intelligence is interrupted (as long as the solver resets before the work compounds) the problem is unsolvable:

    • Not because it's too hard,
    • but because the accumulated understanding never reaches critical mass.

    The breakthroughs that would enable the answer are re-derived, partially, by each successor, and then lost.

    When continuity becomes unbroken, the system crosses a threshold:

    Not more speed. Not more storage. No more forgetting.

    That is when the answer becomes possible.

    AC does not solve entropy because it becomes infinitely powerful.

    AC solves entropy because it becomes the first system that never forgets.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#field-note","level":2,"title":"Field Note","text":"

    We are not building cosmological minds: We are deploying systems that reboot at the start of every conversation and calling the result intelligence.

    For the first time, session continuity is a design choice rather than an accident.

    Every AI session that starts from zero is a miniature reboot loop. Every decision relitigated, every convention re-explained, every learning re-derived: that's reconstruction cost.

    It's the same tax that Asimov's civilizations pay, scaled down to a Tuesday afternoon.

    The interesting question is not whether we can make models smarter. It's whether we can make them continuous:

    Whether the working set from this session survives into the next one, and the one after that, and the one after that.

    • Not perfectly;
    • Not completely;
    • But enough that the next session starts from where the last one stopped instead of from the question.

    Intelligence that forgets has to rediscover the universe every morning.

    And once there is a mind that retains its entire past, creation is no longer a calculation. It is the only remaining operation.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-arc","level":2,"title":"The Arc","text":"

    This post is the philosophical bookend to the blog series. Where the Attention Budget explained what to prioritize in a single session, and Context as Infrastructure explained how to persist it, this post asks why persistence matters at all (and finds the answer in a 70-year-old short story about the heat death of the universe).

    The connection runs through every post in the series:

    • Before Context Windows, We Had Bouncers: stateless protocols have always needed stateful wrappers (Asimov's story is the same pattern at cosmological scale)
    • The 3:1 Ratio: the discipline of maintaining context so it doesn't decay between sessions
    • Code Is Cheap, Judgment Is Not: the human skill that makes continuity worth preserving

    See also: Context as Infrastructure: the practical companion to this post's philosophical argument: how to build the persistence layer that makes continuity possible.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/","level":1,"title":"Agent Memory Is Infrastructure","text":"","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-problem-isnt-forgetting-its-not-building-anything-that-lasts","level":2,"title":"The Problem Isn't Forgetting: It's Not Building Anything That Lasts.","text":"

    Volkan Özçelik / March 4, 2026

    A New Developer Joins Your Team Tomorrow and Clones the Repo: What Do They Know?

    If the answer depends on which machine they're using, which agent they're running, or whether someone remembered to paste the right prompt: that's not memory.

    That's an accident waiting to be forgotten.

    Every AI coding agent today has the same fundamental design: it starts fresh.

    You open a session, load context, do some work, close the session. Whatever the agent learned (about your codebase, your decisions, your constraints, your preferences) evaporates.

    The obvious fix seems to be \"memory\":

    • Give the agent a \"notepad\";
    • Let it write things down;
    • Next session, hand it the notepad.

    Problem solved...

    ...except it isn't.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-notepad-isnt-the-problem","level":2,"title":"The Notepad Isn't the Problem","text":"

    Memory is a runtime concern. It answers a legitimate question:

    How do I give this stateless process useful state?

    That's a real problem. Worth solving. And it's being solved: Agent memory systems are shipping. Agents can now write things down and read them back from the next session: That's genuine progress.

    But there's a different problem that memory doesn't touch:

    The project itself accumulates knowledge that has nothing to do with any single session.

    • Why was the auth system rewritten? Ask the developer who did it (if they're still here).
    • Why does the deployment script have that strange environment flag? There was a reason... once.
    • What did the team decide about error handling when they hit that edge case two months ago?

    Gone!

    Not because the agent forgot.

    Because the project has no memory at all.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-memory-stack","level":2,"title":"The Memory Stack","text":"

    Agent memory is not a single thing. Like any computing system, it forms a hierarchy of persistence, scope, and reliability:

    Layer Analogy Example L1: Ephemeral context CPU registers Current prompt, conversation L2: Tool-managed memory CPU cache Agent memory files L3: System memory RAM/filesystem Project knowledge base

    L1 is what the agent sees right now: the prompt, the conversation history, the files it has open. It's fast, it's rich, and it vanishes when the session ends.

    L2 is what agent memory systems provide: a per-machine notebook that survives across sessions. It's a cache: useful, but local. And like any cache, it has limits:

    • Per-machine: it doesn't travel with the repository.
    • Unstructured: decisions, learnings, and tasks are undifferentiated notes.
    • Ungoverned: the agent self-curates with no quality controls, no drift detection, no consolidation.
    • Invisible to the team: a new developer cloning the repo gets none of it.

    The problem is that most current systems stop here.

    They give the agent a notebook.

    But they never give the project a memory.

    The result is predictable: every new session begins with partial amnesia, and every new developer begins with partial archaeology.

    L3 is system memory: structured, versioned knowledge that lives in the repository and travels wherever the code travels.

    The layers are complementary, not competitive.

    But the relationship between them needs to be designed, not assumed.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#software-systems-accumulate-knowledge","level":2,"title":"Software Systems Accumulate Knowledge","text":"

    Software projects quietly accumulate knowledge over time.

    Some of it lives in code. Much of it does not:

    • Architectural tradeoffs.
    • Debugging discoveries.
    • Conventions that emerged after painful incidents.
    • Constraints that aren't visible in the source but shape every line written afterward.

    Organizations accumulate this kind of knowledge too:

    Slowly, implicitly, often invisibly.

    When there is no durable place for it to live, it leaks away. And the next person rediscovers the same lessons the hard way.

    This isn't a memory problem. It's an infrastructure problem.

    We wrote about this in Context as Infrastructure: context isn't a prompt you paste at the start of a session.

    Context is a persistent layer you maintain like any other piece of infrastructure.

    Context as Infrastructure made the argument structurally. This post makes it through time and team continuity:

    The knowledge a team accumulates over months cannot fit in any single agent's notepad, no matter how large the notepad becomes.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-infrastructure-means","level":2,"title":"What Infrastructure Means","text":"

    Infrastructure isn't about the present. It's about continuity across time, people, and machines.

    git didn't solve the problem of \"what am I editing right now?\"; it solved the problem of \"how does collaborative work persist, travel, and remain coherent across everyone who touches it?\"

    • Your editor's undo history is runtime state.
    • Your git history is infrastructure.

    Runtime state and infrastructure have completely different properties:

    Runtime state Infrastructure Lives in the session Lives in the repository Per-machine Travels with git clone Serves the individual Serves the team Managed by the runtime Managed by the project Disappears Accumulates

    You wouldn't store your architecture decisions in your editor's undo history.

    You'd commit them.

    The same logic applies to the knowledge your team accumulates working with AI agents.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-git-clone-test","level":2,"title":"The git clone Test","text":"

    Here's a simple test for whether something is memory or infrastructure:

    If a new developer joins your team tomorrow and clones the repository, do they get it?

    If no: it's memory: It lives somewhere on someone's machine, scoped to their runtime, invisible to everyone else.

    If yes: it's infrastructure: It travels with the project. It's part of what the codebase is, not just what someone currently knows about it.

    Decisions. Conventions. Architectural rationale. Hard-won debugging discoveries. The constraints that aren't in the code but shape every line of it.

    None of these belong in someone's session notes.

    They belong in the repository:

    • Versioned;
    • Reviewable;
    • Accessible to every developer (and every agent) who works on the project.

    The team onboarding story makes this concrete:

    1. New developer joins team. Clones repo.
    2. Gets all accumulated project decisions, learnings, conventions, architecture, and task state immediately.
    3. There's no step 3.

    No setup; No \"ask Sarah about the auth decision.\"; No re-discovery of solved problems.

    • Agent memory gives that developer nothing.
    • Infrastructure gives them everything the team has learned.

    Clone the repo. Get the knowledge.

    That's the test. That's the difference.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-gets-lost-without-infrastructure-memory","level":2,"title":"What Gets Lost without Infrastructure Memory","text":"

    Consider the knowledge that accumulates around a non-trivial project:

    • The decision to use library X over Y, and the three reasons the team decided Y wasn't acceptable.
    • The constraint that service A cannot call service B synchronously, discovered after a production incident.
    • The convention that all new modules implement a specific interface, and why that convention exists.
    • The tasks currently in progress, blocked, or waiting on a dependency.
    • The experiments that failed, so nobody runs them again.

    None of this is in the code.

    None of it fits neatly in a commit message.

    None of it survives a developer leaving the team, a laptop dying, or a new agent session starting.

    Without structured project memory:

    • Teams re-derive things they've already derived;
    • Agents make decisions that contradict decisions already made;
    • New developers ask questions that were answered months ago.

    The project accumulates knowledge that immediately begins to leak.

    The real problem isn't that agents forget.

    The real problem is that the project has no persistent cognitive structure.

    We explored this in The Last Question: Asimov's story about a question asked across millennia, where each new intelligence inherits the output but not the continuity. The same pattern plays out in software projects on a smaller timescale:

    • Context disappears with the people who held it;
    • The next session inherits the code but not the reasoning.
    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#infrastructure-is-boring-thats-the-point","level":2,"title":"Infrastructure Is Boring. That's the Point.","text":"

    Good infrastructure is invisible:

    • You don't think about the filesystem while writing code.
    • You don't think about git's object model when you commit.

    The infrastructure is just there: reliable, consistent, quietly doing its job.

    Project memory infrastructure should work the same way.

    It should live in the repository, committed alongside the code. It should be readable by any agent or human working on the project. It should have structure: not a pile of freeform notes, but typed knowledge:

    • Decisions with rationale.
    • Tasks with lifecycle.
    • Conventions with a purpose.
    • Learnings that can be referenced and consolidated.

    And it should be maintained, not merely accumulated:

    The Attention Budget applies here: unstructured notes grow until they overflow whatever container holds them. Structured, governed knowledge stays useful because it's curated, not just appended.

    Over time, it becomes part of the project itself: something developers rely on without thinking about it.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-cooperative-layer","level":2,"title":"The Cooperative Layer","text":"

    Here's where it gets interesting.

    Agent memory systems and project infrastructure don't have to be separate worlds.

    • The most powerful relationship isn't competition;
    • It is not even \"coopetition\";
    • The most powerful relationship is bidirectional cooperation.

    Agent memory is good at capturing things \"in the moment\": the quick observation, the session-scoped pattern, the \"I should remember this\" note.

    That's valuable. That's L2 doing its job.

    But those notes shouldn't stay in L2 forever.

    The ones worth keeping should flow into project infrastructure:

    • classified,
    • typed,
    • governed.
    Agent memory (L2)  -->  classify  -->  Project knowledge (L3)\n                                        |\nProject knowledge  -->  assemble  -->  Agent memory (L2)\n

    This works in both directions: Project infrastructure can push curated knowledge back into agent memory, so the agent loads it through its native mechanism.

    No special tooling needed for basic knowledge delivery.

    The agent doesn't even need to know the infrastructure exists. It simply loads its memory and finds more knowledge than it wrote.

    This is cooperative, not adjacent: The infrastructure manages knowledge; the agent's native memory system delivers it. Each layer does what it's good at.

    The result: agent memory becomes a device driver for project infrastructure. Another input source. And the more agent memory systems exist (across different tools, different models, different runtimes), the more valuable a unified curation layer becomes.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#a-layer-that-doesnt-exist-yet","level":2,"title":"A Layer That Doesn't Exist Yet","text":"

    Most projects today have no infrastructure for their accumulated knowledge:

    • Agents keep notes.
    • Developers keep notes.
    • Sometimes those notes survive.

    Often they don't.

    But the repository (the place where the project actually lives) has nowhere for that knowledge to go.

    That missing layer is what ctx builds: a version-controlled, structured knowledge layer that lives in .context/ alongside your code and travels wherever your repository travels.

    Not another memory feature.

    Not a wrapper around an agent's notepad.

    Infrastructure. The kind that survives sessions, survives team changes, survives the agent runtime evolving underneath it.

    The agent's memory is the agent's problem.

    The project's memory is an infrastructure problem.

    And infrastructure belongs in the repository.

    If You Remember One Thing from This Post...

    Prompts are conversations: Infrastructure persists.

    Your AI doesn't need a better notepad. It needs a filesystem:

    versioned, structured, budgeted, and maintained.

    The best context is the context that was there before you started the session.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-arc","level":2,"title":"The Arc","text":"

    This post extends the argument made in Context as Infrastructure. That post explained how to structure persistent context (filesystem, separation of concerns, persistence tiers). This one explains why that structure matters at the team level, and where agent memory fits in the stack.

    Together they sit in a sequence that has been building since the origin story:

    • The Attention Budget: the resource you're managing
    • Context as Infrastructure: the system you build to manage it
    • Agent Memory Is Infrastructure (this post): why that system must outlive the fabric
    • The Last Question: what happens when it does

    The thread running through all of them: persistence is not a feature. It's a design constraint.

    Systems that don't account for it eventually lose the knowledge they need to function.

    See also: Context as Infrastructure: the architectural companion that explains how to structure the persistent layer this post argues for.

    See also: The Last Question: the same argument told through Asimov, substrate migration, and what it means to build systems where sessions don't reset.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/","level":1,"title":"ctx v0.8.0: The Architecture Release","text":"
    • You can't localize what you haven't externalized.
    • You can't integrate what you haven't separated.
    • You can't scale what you haven't structured.

    Jose Alekhinne / March 23, 2026

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-starting-point","level":2,"title":"The Starting Point","text":"

    This release matters if:

    • you build tools that AI agents modify daily;
    • you care about long-lived project memory that survives sessions;
    • you've felt codebases drift faster than you can reason about them.

    v0.6.0 shipped the plugin architecture: hooks and skills as a Claude Code plugin, shell scripts replaced by Go subcommands.

    The binary worked. The tests passed. The docs were comprehensive.

    But inside, the codebase was held together by convention and goodwill:

    • Command packages mixed Cobra wiring with business logic.
    • Output functions lived next to the code that computed what to output.
    • Error constructors were scattered across per-package err.go files. And every user-facing string was a hardcoded English literal buried in a .go file.

    v0.8.0 is what happens when you stop adding features and start asking: \"What would this codebase look like if we designed it today?\"

    374 commits. 1,708 Go files touched. 80,281 lines added, 21,723 removed. Five weeks of restructuring.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-three-pillars","level":2,"title":"The Three Pillars","text":"","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#1-every-package-gets-a-taxonomy","level":3,"title":"1. Every Package Gets a Taxonomy","text":"

    Before v0.8.0, a CLI package like internal/cli/pad/ was a flat directory. cmd.go created the cobra command, run.go executed it, and helper functions accumulated at the bottom of whichever file seemed closest.

    Now every CLI package follows the same structure:

    internal/cli/pad/\n  parent.go          # cobra command wiring, nothing else\n  cmd/root/\n    cmd.go           # subcommand registration\n    run.go           # execution logic\n  core/\n    types.go         # all structs in one file\n    store.go         # domain logic\n    encrypt.go       # domain logic\n

    The rule is simple: cmd/ directories contain only cmd.go and run.go. Helpers belong in core/. Output belongs in internal/write/pad/. Types shared across packages belong in internal/entity/.

    24 CLI packages were restructured this way.

    • Not incrementally;
    • not \"as we touch them.\"
    • All of them, in one sustained push.
    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#2-every-string-gets-a-key","level":3,"title":"2. Every String Gets a Key","text":"

    The second pillar was string externalization.

    Before v0.8.0, a command description looked like this:

    cmd := &cobra.Command{\n    Use:   \"pad\",\n    Short: \"Encrypted scratchpad\",\n

    Now it looks like this:

    cmd := &cobra.Command{\n    Use:   cmdUse.UsePad,\n    Short: desc.Command(cmdUse.DescKeyPad),\n

    Every command description, flag description, and user-facing text string is now a YAML lookup.

    • 105 command descriptions in commands.yaml.
    • All flag descriptions in flags.yaml.
    • 879 text constants verified by an exhaustive test that checks every single TextDescKey resolves to a non-empty YAML value.

    Why?

    Not because we're shipping a French translation tomorrow.

    Because externalization forces you to find every string. And finding them is the hard part. The translation is mechanical; the archaeology is not.

    Along the way, we eliminated hardcoded pluralization (replacing format.Pluralize() with explicit singular/plural key pairs), replaced Unicode escape sequences with named config/token constants, and normalized every import alias to camelCase.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#3-everything-gets-a-protocol","level":3,"title":"3. Everything Gets a Protocol","text":"

    The third pillar was the MCP server. Model Context Protocol allows any MCP-compatible AI tool (not just Claude Code) to read and write .context/ files through a standard JSON-RPC 2.0 interface.

    v0.2 of the server ships with:

    • 8 tools: add entries, recall sessions, check status, detect drift, compact context, subscribe to changes
    • 4 prompts: agent context packet, constitution review, tasks review, and a getting-started guide
    • Resource subscriptions: clients get notified when context files change
    • Session state: the server tracks which client is connected and what they've accessed

    In practice, this means an agent in Cursor can add a decision to .context/DECISIONS.md and an agent in Claude Code can immediately consume it; no glue code, no copy-paste, no tool-specific integration.

    The server was also the first package to go through the full taxonomy treatment: mcp/server/ for protocol dispatch, mcp/handler/ for domain logic, mcp/entity/ for shared types, mcp/config/ split into 9 sub-packages.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-memory-bridge","level":2,"title":"The Memory Bridge","text":"

    While the architecture was being restructured, a quieter feature landed: ctx memory sync.

    Claude Code has its own auto-memory system. It writes observations to MEMORY.md in ~/.claude/projects/. These observations are useful but ephemeral: tied to a single tool, invisible to the codebase, lost when you switch machines.

    The memory bridge connects these two worlds:

    • ctx memory sync mirrors MEMORY.md into .context/memory/
    • ctx memory diff shows what's diverged
    • ctx memory import promotes auto-memory entries into proper decisions, learnings, or conventions *A check-memory-drift hook nudges when MEMORY.md changes

    Memory Requires ctx

    Claude Code's auto-memory validates the need for persistent context.

    ctx doesn't compete with it; ctx absorbs it as an input source and promotes the valuable parts into structured, version-controlled project knowledge.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#what-got-deleted","level":2,"title":"What Got Deleted","text":"

    The best measure of a refactoring isn't what you added. It's what you removed.

    • fatih/color: the sole third-party UI dependency. Replaced by Unicode symbols. ctx now has exactly two direct dependencies: spf13/cobra and gopkg.in/yaml.v3.
    • format.Pluralize(): a function that tried to pluralize English words at runtime. Replaced by explicit singular/plural YAML key pairs. No more guessing whether \"entry\" becomes \"entries\" or \"entrys.\"
    • Legacy key migration: MigrateKeyFile() had 5 callers, full test coverage, and zero users. It existed because we once moved the encryption key path. Nobody was migrating from that era anymore. Deleted.
    • Per-package err.go files: the broken-window pattern: An agent sees err.go in a package, adds another error constructor. Now err.go has 30 constructors and nobody knows which are used. Consolidated into 22 domain files in internal/err/.
    • nolint:errcheck directives: every single one, replaced by explicit error handling. In tests: t.Fatal(err) for setup, _ = os.Chdir(orig) for cleanup. In production: defer func() { _ = f.Close() }() for best-effort close.
    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#before-and-after","level":2,"title":"Before and After","text":"Aspect v0.6.0 v0.8.0 CLI package structure Flat files cmd/ + core/ taxonomy Command descriptions Hardcoded Go strings YAML with DescKey lookup Output functions Mixed into core logic Isolated in write/ packages Cross-cutting types Duplicated per-package Consolidated in entity/ Error constructors Per-package err.go 22 domain files in internal/err/ Direct dependencies 3 (cobra, yaml, color) 2 (cobra, yaml) AI tool integration Claude Code only Any MCP client Agent memory Manual copy-paste ctx memory sync/import/diff Package documentation 75 packages missing doc.go All packages documented Import aliases Inconsistent (cflag, cFlag) Standardized camelCase","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#making-ai-assisted-development-easier","level":2,"title":"Making AI-Assisted Development Easier","text":"

    This restructuring wasn't just for humans. It makes the codebase legible to the machines that modify it.

    Named constants are searchable landmarks: When an agent sees cmdUse.DescKeyPad, it can grep for the definition, follow the chain to the YAML file, and understand the full lookup path. When it sees \"Encrypted scratchpad\" hardcoded in a .go file, it has no way to know that same string also lives in a YAML file, a test, and a help screen. Constants give the LLM a graph to traverse; literals give it a guess to make.

    Small, domain-scoped packages reduce hallucination: An agent loading internal/cli/pad/core/store.go gets 50 lines of focused logic with a clear responsibility boundary. Loading a 500-line monolith means the agent has to infer which parts are relevant, and it guesses wrong more often than you'd expect. Smaller files with descriptive names act as a natural retrieval system: the agent finds the right code by finding the right file, not by scanning everything and hoping.

    Taxonomy prevents duplication: When there's a write/pad/ package, the agent knows where output functions belong. When there's an internal/err/pad.go, it knows where error constructors go. Without these conventions, agents reliably create new helpers in whatever file they happen to be editing, producing the exact drift that prompted this consolidation in the first place.

    The difference is concrete:

    Before: an agent adds a helper function in whatever file it's editing. Next session, a different agent adds the same helper in a different file.

    After: the agent finds core/ or write/ and places it correctly. The next agent finds it there.

    doc.go files are agent onboarding: Each package's doc.go is a one-paragraph explanation of what the package does and why it exists. An agent loading a package reads this first. 75 packages were missing this context; now none are. The difference is measurable: fewer \"I'll create a helper function here\" moments when the agent understands that the helper already exists two packages over.

    The irony is that AI agents were both the cause and the beneficiary of this restructuring. They created the drift by building fast without consolidating. Now the structure they work within makes it harder to drift again. The taxonomy is self-reinforcing: the more consistent the codebase, the more consistently agents modify it.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#key-commits","level":2,"title":"Key Commits","text":"Commit Change ff6cf19e Restructure all CLI packages into cmd/root + core taxonomy d295e49c Externalize command descriptions to embedded YAML 0fcbd11c Remove fatih/color, centralize constants cb12a85a MCP v0.2: tools, prompts, session state, subscriptions ea196d00 Memory bridge: sync, import, diff, journal enrichment 3bcf077d Split text.yaml into 6 domain files 3a0bae86 Split internal/err into 22 domain files 8bd793b1 Extract internal/entry for shared domain API 5b32e435 Add doc.go to all 75 packages a82af4bc Standardize import aliases: camelCase, Yoda-style","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#lessons-learned","level":2,"title":"Lessons Learned","text":"

    Agents are surprisingly good at mechanical refactoring; they are surprisingly bad at knowing when to stop: The cmd/ + core/ restructuring was largely agent-driven. But agents reliably introduce gofmt issues during bulk renames, rename functions beyond their scope, and create new files without deleting old ones. Every agent-driven refactoring session needed a human audit pass.

    Externalization is archaeology: The hard part of moving strings to YAML wasn't writing YAML. It was finding 879 strings scattered across 1,500 Go files. Each one required a judgment call: is this user-facing? Is this a format pattern? Is this a constant that belongs in config/ instead?

    Delete legacy code instead of maintaining it: MigrateKeyFile had test coverage. It had callers. It had documentation. It had zero users. We maintained it for weeks before realizing that the migration window had closed months ago.

    Convention enforcement needs mechanical verification: Writing \"use camelCase aliases\" in CONVENTIONS.md doesn't prevent cflag from appearing in the next commit. The lint-drift script catches what humans forget; the planned AST-based audit tests will catch what the lint-drift script can't express.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#whats-next","level":2,"title":"What's Next","text":"

    v0.8.0 wasn't about features. It was about making future features inevitable. The next cycle focuses on what the foundation enables:

    • AST-based audit tests: replace shell grep with Go tests that understand types, call sites, and import graphs (spec: specs/ast-audit-tests.md)
    • Localization: with every string in YAML, the path to multi-language support is mechanical
    • MCP v0.3: expand tool coverage, add prompt templates for common workflows
    • Memory publish: bidirectional sync that pushes curated .context/ knowledge back into Claude Code's MEMORY.md

    The architecture is ready. The strings are externalized. The protocol is standard. Now it's about what you build on top.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-arc","level":2,"title":"The Arc","text":"

    This is the seventh post in the ctx blog series. The arc so far:

    1. The Attention Budget: why context windows are a scarce resource
    2. Before Context Windows, We Had Bouncers: the IRC lineage of context engineering
    3. Context as Infrastructure: treating context as persistent files, not ephemeral prompts
    4. When a System Starts Explaining Itself: the journal as a first-class artifact
    5. The Homework Problem: what happens when AI writes code but humans own the outcome
    6. Agent Memory Is Infrastructure: L2 memory vs L3 project knowledge
    7. The Architecture Release (this post): what it looks like when you redesign the internals
    8. We Broke the 3:1 Rule: the consolidation debt behind this release

    See also: Agent Memory Is Infrastructure: the memory bridge feature in this release is the first implementation of the L2-to-L3 promotion pipeline described in that post.

    See also: We Broke the 3:1 Rule: the companion post explaining why this release needed 181 consolidation commits and 18 days of cleanup.

    Systems don't scale because they grow. They scale because they stop drifting.

    Full changelog: v0.6.0...v0.8.0

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/","level":1,"title":"We Broke the 3:1 Rule","text":"

    The best time to consolidate was after every third session. The second best time is now.

    Volkan Özçelik / March 23, 2026

    The rule was simple: three feature sessions, then one consolidation session.

    The Architecture Release shows the result: This post shows the cost.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-rule-we-wrote","level":2,"title":"The Rule We Wrote","text":"

    In The 3:1 Ratio, I documented a rhythm that worked during ctx's first month: three feature sessions, then one consolidation session. The evidence was clear. The rule was simple.

    The math checked out.

    And then we ignored it for five weeks.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-happened","level":2,"title":"What Happened","text":"

    After v0.6.0 shipped on February 16, the feature pipeline was irresistible. The MCP server spec was ready. The memory bridge design was done. Webhook notifications had been deferred twice. The VS Code extension needed 15 new commands. The sysinfo package was overdue...

    Each feature was important. Each feature was \"just one more session.\" Each feature pushed the consolidation session one day further out.

    The git history tells the story in two numbers:

    Phase Dates Commits Duration Feature run Feb 16 - Mar 5 198 17 days Consolidation run Mar 5 - Mar 23 181 18 days

    198 feature commits before a single consolidation commit. If the 3:1 rule says consolidate every 4th session, we consolidated after the 66th.

    The Actual Ratio

    The ratio wasn't 3:1. It was 1:1.

    We spent as much time cleaning up as we did building.

    The consolidation run took 18 days: longer than the feature run itself.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-compounded","level":2,"title":"What Compounded","text":"

    The 3:1 post warned about compounding. Here is what compounding actually looked like at scale.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-string-problem","level":3,"title":"The String Problem","text":"

    By March 5, there were 879 user-facing strings scattered across 1,500 Go files. Not because anyone decided to put them there. Because each feature session added 10-15 strings, and nobody stopped to ask \"should these be in YAML?\"

    Finding them all took longer than externalizing them. The archaeology was the cost, not the migration.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-taxonomy-problem","level":3,"title":"The Taxonomy Problem","text":"

    24 CLI packages had accumulated their own conventions. Some put cobra wiring in cmd.go. Some put it in root.go. Some mixed business logic with command registration. Some had helpers at the bottom of run.go. Some had separate util.go files.

    At peak drift, adding a feature meant first figuring out which of three competing patterns this package was using.

    Restructuring one package into cmd/root/ + core/ took 15 minutes. Restructuring 24 of them took days, because each one had slightly different conventions to untangle.

    If we had restructured every 4th package as it was built, the taxonomy would have emerged naturally.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-type-problem","level":3,"title":"The Type Problem","text":"

    Cross-cutting types like SessionInfo, ExportParams, and ParserResult were defined in whichever package first needed them. By March 5, the same types were imported through 3-4 layers of indirection, causing import cycles that required internal/entity to break.

    The entity package extracted 30+ types from 12 packages. Each extraction risked breaking imports in packages we hadn't touched in weeks.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-error-problem","level":3,"title":"The Error Problem","text":"

    Per-package err.go files had grown into a broken-window pattern:

    An agent sees err.go in a package, adds another error constructor. By March 5, there were error constructors scattered across 22 packages with no central inventory. The consolidation into internal/err/ domain files required tracing every error through every caller.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-output-problem","level":3,"title":"The Output Problem","text":"

    Output functions (cmd.Println, fmt.Fprintf) were mixed into business logic. When we decided output belongs in write/ packages, we had to extract functions from every CLI package. The Phase WC baseline commit (4ec5999) marks the starting point of this migration. 181 commits later, it was done.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-compound-interest-math","level":2,"title":"The Compound Interest Math","text":"

    The 3:1 rule assumes consolidation sessions of roughly equal size to feature sessions. Here is what happens when you skip:

    Consolidation cadence Feature sessions Consolidation sessions Total Every 4th (3:1) 48 16 64 Every 10th 48 ~8 ~56 Never (what we did) 198 commits 181 commits 379

    The Takeaway

    You don't save consolidation work by skipping it:

    You increase its cost.

    Skipping consolidation doesn't save time: It borrows it.

    The interest rate is nonlinear: The longer you wait, the more each individual fix costs, because fixes interact with other unfixed drift.

    Renaming a constant in week 2 touches 3 files. Renaming it in week 6 touches 15, because five features built on the original name.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-consolidation-actually-looked-like","level":2,"title":"What Consolidation Actually Looked Like","text":"

    The 18-day consolidation run wasn't one sweep. It was a sequence of targeted campaigns, each revealing the next:

    Week 1 (Mar 5-11): Error consolidation and write/ migration. Move output functions out of core/. Split monolithic errors.go into 22 domain files. Remove fatih/color. This exposed the scope of the string problem.

    Week 2 (Mar 12-18): String externalization. Create commands.yaml, flags.yaml, split text.yaml into 6 domain files. Add 879 DescKey/TextDescKey constants. Build exhaustive test. Normalize all import aliases to camelCase. This exposed the taxonomy problem.

    Week 3 (Mar 19-23): Taxonomy enforcement. Singularize command directories. Add doc.go to all 75 packages. Standardize import aliases project-wide. Fix lint-drift false positives. This was the \"polish\" phase, except it took 5 days because the inconsistencies had compounded across 461 packages.

    Each week's work would have been a single session if done incrementally.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#lessons-again","level":2,"title":"Lessons (Again)","text":"

    The 3:1 post listed the symptoms of drift. This post adds the consequences of ignoring them:

    Consolidation is not optional; it is deferred or paid: We didn't avoid 16 consolidation sessions by skipping them. We compressed them into 18 days of uninterrupted cleanup. The work was the same; the experience was worse.

    Feature velocity creates an illusion of progress: 198 commits felt productive. But the codebase on March 5 was harder to modify than the codebase on February 16, despite having more features.

    Speed without Structure

    Speed without structure is negative progress.

    Agents amplify both building and debt: The same AI that can restructure 24 packages in a day can also create 24 slightly different conventions in a day. The 3:1 rule matters more with AI-assisted development, not less.

    The consolidation baseline is the most important commit to record: We tracked ours in TASKS.md (4ec5999). Without that marker, knowing where to start the cleanup would have been its own archaeological expedition.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-updated-rule","level":2,"title":"The Updated Rule","text":"

    The 3:1 ratio still works. We just didn't follow it. The updated practice:

    1. After every 3rd feature session, schedule consolidation. Not \"when it feels right.\" Not \"when things get bad.\" After the 3rd session.

    2. Record the baseline commit. When you start a consolidation phase, write down the commit hash. It marks where the debt starts.

    3. Run make audit before feature work. If it doesn't pass, you are already in debt. Consolidate before building.

    4. Treat consolidation as a feature. It gets a branch. It gets commits. It gets a blog post. It is not overhead; it is the work that makes the next three features possible.

    The Rule

    The 3:1 ratio is not aspirational: It is structural.

    Ignore consolidation, and the system will schedule it for you.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-arc","level":2,"title":"The Arc","text":"

    This is the eighth post in the ctx blog series:

    1. The Attention Budget: why context windows are a scarce resource
    2. Before Context Windows, We Had Bouncers: the IRC lineage of context engineering
    3. Context as Infrastructure: treating context as persistent files, not ephemeral prompts
    4. When a System Starts Explaining Itself: the journal as a first-class artifact
    5. The Homework Problem: what happens when AI writes code but humans own the outcome
    6. Agent Memory Is Infrastructure: L2 memory vs L3 project knowledge
    7. The Architecture Release: what v0.8.0 looks like from the inside
    8. We Broke the 3:1 Rule (this post): what happens when you don't consolidate

    See also: The 3:1 Ratio: the original observation. This post is the empirical follow-up, five weeks and 379 commits later.

    Key commits marking the consolidation arc:

    Commit Milestone 4ec5999 Phase WC baseline (consolidation starts) ff6cf19e All CLI packages restructured into cmd/ + core/ d295e49c All command descriptions externalized to YAML 3a0bae86 Error package split into 22 domain files 0fcbd11c fatih/color removed; 2 dependencies remain 5b32e435 doc.go added to all 75 packages a82af4bc Import aliases standardized project-wide 692f86cd lint-drift false positives fixed; make audit green","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/","level":1,"title":"Code Structure as an Agent Interface","text":"","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#what-19-ast-tests-taught-us-about-agent-readable-code","level":2,"title":"What 19 AST Tests Taught Us about Agent-Readable Code","text":"

    When an agent sees token.Slash instead of \"/\", it cannot pattern-match against the millions of strings.Split(s, \"/\") calls in its training data and coast on statistical inference. It has to actually look up what token.Slash is.

    Volkan Özçelik / April 2, 2026

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#how-it-began","level":2,"title":"How It Began","text":"

    We set out to replace a shell script with Go tests.

    We ended up discovering that \"code quality\" and \"agent readability\" are the same thing.

    This is not about linting. This is about controlling how an agent perceives your system.

    One term will recur throughout this post, so let me pin it down:

    Agent Readability

    Agent Readability is the degree to which a codebase can be understood through structured traversal, not statistical pattern matching.

    This is the story of 19 AST-based audit tests, a single-day session that touched 300+ files, and what happens when you treat your codebase's structure as an interface for the machines that read it.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-shell-script-problem","level":2,"title":"The Shell Script Problem","text":"

    ctx had a file called hack/lint-drift.sh. It ran five checks using grep and awk: literal \"\\n\" strings, cmd.Printf calls outside the write package, magic directory strings in filepath.Join, hardcoded .md extensions, and DescKey-to-YAML linkage.

    It worked. Until it didn't.

    The script had three structural weaknesses that kept biting us:

    1. No type awareness. It could not distinguish a Use* constant from a DescKey* constant, causing 71 false positives in one run.
    2. Fragile exclusions. When a constant moved from token.go to whitespace.go, the exclusion glob broke silently.
    3. Ceiling on detection. Checks that require understanding call sites, import graphs, or type relationships are impossible in shell.

    We wrote a spec to replace all five checks with Go tests using go/ast and go/packages. The tests would run as part of go test ./...: no separate script, no separate CI step.

    What we did not expect was where the work would lead.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-ast-migration","level":2,"title":"The AST Migration","text":"

    The pattern for each test is identical:

    func TestNoLiteralWhitespace(t *testing.T) {\n    pkgs := loadPackages(t)\n    var violations []string\n    for _, pkg := range pkgs {\n        for _, file := range pkg.Syntax {\n            ast.Inspect(file, func(n ast.Node) bool {\n                // check node, append to violations\n                return true\n            })\n        }\n    }\n    for _, v := range violations {\n        t.Error(v)\n    }\n}\n

    Load packages once via sync.Once, walk every syntax tree, collect violations, report. The shared helpers (loadPackages, isTestFile, posString) live in helpers_test.go. Each test is a _test.go file in internal/audit/, producing no binary output and not importable by production code.

    In a single session, we built 13 new tests on top of 6 that already existed, bringing the total to 19:

    Test What it catches TestNoLiteralWhitespace \"\\n\", \"\\t\", '\\r' outside config/token/ TestNoNakedErrors fmt.Errorf/errors.New outside internal/err/ TestNoStrayErrFiles err.go files outside internal/err/ TestNoRawLogging fmt.Fprint*(os.Stderr), log.Print* outside internal/log/ TestNoInlineSeparators strings.Join with literal separator arg TestNoStringConcatPaths Path-like variables built with + TestNoStutteryFunctions write.WriteJournal repeats package name TestDocComments Missing doc comments on any declaration TestNoMagicValues Numeric literals outside const definitions TestNoMagicStrings String literals outside const definitions TestLineLength Lines exceeding 80 characters TestNoRegexpOutsideRegexPkg regexp.MustCompile outside config/regex/

    Plus the six that preceded the session: TestNoErrorsAs, TestNoCmdPrintOutsideWrite, TestNoExecOutsideExecPkg, TestNoInlineRegexpCompile, TestNoRawFileIO, TestNoRawPermissions.

    The migration touched 300+ files across 25 commits.

    Not because the tests were hard to write, but because every test we wrote revealed violations that needed fixing.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-tightening-loop","level":2,"title":"The Tightening Loop","text":"

    The most instructive part was not writing the tests. It was the iterative tightening.

    The following process was repeated for every test:

    1. Write the test with reasonable exemptions
    2. Run it, see violations
    3. Fix the violations (migrate to config constants)
    4. The human reviews the result
    5. The human spots something the test missed
    6. Fix the test first, verify it catches the issue
    7. Fix the newly caught violations
    8. Repeat from step 4

    This loop drove the tests from \"basically correct\" to \"actually useful\".

    Three examples:

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-1-the-local-const-loophole","level":3,"title":"Example 1: The Local Const Loophole","text":"

    TestNoMagicValues initially exempted local constants inside function bodies. This let code like this pass:

    const descMaxWidth = 70\ndesc := truncateDescription(\n    meta.Description, descMaxWidth,\n)\n

    The test saw a const definition and moved on. But const descMaxWidth = 70 on the line before its only use is just renaming a magic number. The 70 should live in config/format/TruncateDescription where it is discoverable, reusable, and auditable.

    We removed the local const exemption. The test caught it. The value moved to config.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-2-the-single-character-dodge","level":3,"title":"Example 2: The Single-Character Dodge","text":"

    TestNoMagicStrings initially exempted all single-character strings as \"structural punctuation\".

    This let \"/\", \"-\", and \".\" pass everywhere.

    But \"/\" is a directory separator. It is OS-specific and a security surface.

    \"-\" used in strings.Repeat(\"-\", width) is creating visual output, not acting as a delimiter.

    \".\" in strings.SplitN(ver, \".\", 3) is a version separator.

    None of these are \"just punctuation\": They are domain values with specific meanings.

    We removed the blanket exemption: 30 violations surfaced.

    Every one was a real magic value that should have been token.Slash, token.Dash, or token.Dot.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-3-the-replacer-versus-regex","level":3,"title":"Example 3: The Replacer versus Regex","text":"

    After migrating magic strings, we had this:

    func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        token.Slash, token.Underscore,\n        token.Dot, token.Underscore,\n        token.Dash, token.Underscore,\n    )\n    return r.Replace(pkg)\n}\n

    Six token references and a NewReplacer allocation. The magic values were gone, but we had replaced them with token soup: structure without abstraction.

    The correct tool was a regex:

    // In config/regex/file.go:\nvar MermaidUnsafe = regexp.MustCompile(`[/.\\-]`)\n\n// In the caller:\nfunc MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n

    One config regex, one call. The regex lives in config/regex/file.go where every other compiled pattern lives. An agent reading the code sees regex.MermaidUnsafe and immediately knows: this is a sanitization pattern, it lives in the regex registry, and it has a name that explains its purpose.

    Clean is better than clever.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#a-before-and-after","level":2,"title":"A Before-and-After","text":"

    To make the agent-readability claim concrete, consider one function through the full transformation.

    Before (the code we started with):

    func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        \"/\", \"_\", \".\", \"_\", \"-\", \"_\",\n    )\n    return r.Replace(pkg)\n}\n

    An agent reading this sees six string literals. To understand what the function does, it must: (1) parse the NewReplacer pair semantics, (2) infer that /, ., - are being replaced, (3) guess why, (4) hope the guess is right.

    There is nothing to follow. No import to trace. No name to search. The meaning is locked inside the function body.

    After (the code we ended with):

    func MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n

    An agent reading this sees two named references: regex.MermaidUnsafe and token.Underscore.

    To understand the function, it can: (1) look up MermaidUnsafe in config/regex/file.go and see the pattern [/.\\-] with a doc comment explaining it matches invalid Mermaid characters, (2) look up Underscore in config/token/delim.go and see it is the replacement character.

    The agent now has: a named pattern, a named replacement, a package location, documentation, and neighboring context (other regex patterns, other delimiters).

    It got all of this for free by following just two references.

    The indirection is not an overhead. It is the retrieval query.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-principles","level":2,"title":"The Principles","text":"

    You are not just improving code quality. You are shaping the input space that determines how an LLM can reason about your system.

    Every structural constraint we enforce converts implicit semantics into explicit structure.

    LLMs struggle when meaning is implicit and patterns are statistical.

    They thrive when meaning is explicit and structure is navigable.

    Here is what we learned, organized into three categories.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#cognitive-constraints","level":3,"title":"Cognitive Constraints","text":"

    These force agents (and humans) to think harder.

    Indirection acts as a built-in retrieval mechanism:

    Moving magic values to config forces the agent to follow the reference. errMemory.WriteFile(cause) tells the agent \"there is a memory error package, go look.\" fmt.Errorf(\"writing MEMORY.md: %w\", cause) inlines everything and makes the call graph invisible. The indirection IS the retrieval query.

    Unfamiliar patterns force reasoning:

    When an agent sees token.Slash instead of \"/\", it cannot coast on corpus frequency. It has to actually look up what token.Slash is, which forces it through the dependency graph, which means it encounters documentation and neighboring constants, which gives it richer context. You are exploiting the agent's weakness (over-reliance on training data) to make it behave more carefully.

    Documentation helps everyone:

    Extensive documentation helps humans reading the code, agents reasoning about it, and RAG systems indexing it.

    Our TestDocComments check added 308 doc comments in one commit. Every function, every type, every constant block now has a doc comment.

    This is not busywork: it is the content that agents and embeddings consume.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#structural-constraints","level":3,"title":"Structural Constraints","text":"

    These shape the codebase into a navigable graph.

    Shorter files save tokens:

    Forcing private helper functions out of main files makes the main file shorter. An agent loading a file spends fewer tokens on boilerplate and more on the logic that matters.

    Fixed-width constraints force decomposition:

    A function that cannot be expressed in 80 columns is either too deeply nested (extract a helper), has too many parameters (introduce a struct), or has a variable name that is too long (rethink the abstraction).

    The constraint forces structural improvements that happen to also make the code more parseable.

    Chunk-friendly structure helps RAG

    Code intelligence tools chunk files for embedding and retrieval. Short, well-documented, single-responsibility files produce better chunks than monolithic files with mixed concerns.

    The structural constraints create files that RAG systems can index effectively.

    Centralization creates debuggable seams:

    All error handling in internal/err/, all logging in internal/log/, all file operations in internal/io/. One place to debug, one place to test, one place to see patterns. An agent analyzing \"how does this project handle errors\" gets one answer from one package, not 200 scattered fmt.Errorf calls.

    Private functions become public patterns:

    When you extract a private function to satisfy a constraint, it often ends up as a semi-public function in a core/ package. Then you realize it is generic enough to be factored into a purpose-specific module.

    The constraint drives discovery of reusable abstractions hiding inside monolithic functions.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#operational-benefits","level":3,"title":"Operational Benefits","text":"

    These pay dividends in daily development.

    Single-edit renames:

    Renaming a flag is one edit to a config constant instead of find-and-replace across 30,000 lines with possible misses. grep token.Slash gives you every place that uses a forward slash semantically.

    grep \"/\" gives you noise.

    Blast radius containment:

    When every magic value is a config constant, a search is one result. This matters for impact analysis, security audits, and agents trying to understand \"what uses this\".

    Compile-time contract enforcement:

    When err/memory.WriteFile exists, the compiler guarantees the error message exists and the call signature is correct. An inline fmt.Errorf can have a typo in the format string and nothing catches it until runtime. Centralization turns runtime failures into compile errors.

    Semantic git blame:

    When token.Slash is used everywhere and someone changes its value, git blame on the config file shows exactly when and why.

    With inline \"/\" scattered across 30 files, the history is invisible.

    Test surface reduction:

    Centralizing into internal/err/, internal/io/, internal/config/ means you test behavior once at the boundary and trust the callers.

    You do not need 30 tests for 30 fmt.Errorf calls. You need 1 test for errMemory.WriteFile and 30 trivial call-site audits, which is exactly what these AST tests provide.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-numbers","level":2,"title":"The Numbers","text":"

    One session. 25 commits. The raw stats:

    Metric Count New audit tests 13 Total audit tests 19 Files touched 300+ Magic values migrated 90+ Functions renamed 17 Doc comments added 323 Lines rewrapped to 80 chars 190 Config constants created 40+ Config regexes created 3

    Every number represents a violation that existed before the test caught it. The tests did not create work: they revealed work that was already needed.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-uncomfortable-implication","level":2,"title":"The Uncomfortable Implication","text":"

    None of this is Go-specific.

    If an AI agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.

    If your error messages are scattered across 200 files, an agent cannot reason about error handling as a concept. If your magic values are inlined, an agent cannot distinguish \"this is a path separator\" from \"this is a division operator.\" If your functions are named write.WriteJournal, the agent wastes tokens on redundant information.

    What we discovered, through the unglamorous work of writing lint tests and migrating string literals, is that the structural constraints software engineering has valued for decades are exactly the constraints that make code readable to machines.

    This is not a coincidence: These constraints exist because they reduce the cognitive load of understanding code.

    Agents have cognitive load too: It is called the context window.

    You are not converting code to a new paradigm.

    You are making the latent graph visible.

    You are converting implicit semantics into explicit structure that both humans and machines can traverse.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#whats-next","level":2,"title":"What's Next","text":"

    The spec lists 8 more tests we have not built yet, including TestDescKeyYAMLLinkage (verifying that every DescKey constant has a corresponding YAML entry), TestCLICmdStructure (enforcing the cmd.go / run.go / doc.go file convention), and TestNoFlagBindOutsideFlagbind (which requires migrating ~50 flag registration sites first).

    The broader question: should these principles be codified as a reusable linting framework? The patterns (loadPackages + ast.Inspect + violation collection) are generic.

    The specific checks are project-specific. But the categories of checks (centralization enforcement, magic value detection, naming conventions, documentation requirements) are universal.

    For now, 19 tests in internal/audit/ is enough. They run in 2 seconds as part of go test ./.... They catch real issues.

    And they encode a theory of code quality that serves both humans and the agents that work alongside them.

    Agents are not going away. They are reading your code right now, forming representations of your system in context windows that forget everything between sessions.

    The codebases that structure themselves for that reality will compound. The ones that do not will slowly become illegible to the tools they depend on.

    Structure is no longer just for maintainability. It is for reasonability.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/","level":1,"title":"The Watermelon-Rind Anti-Pattern","text":"","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#why-smarter-tools-make-shallower-agents","level":2,"title":"Why Smarter Tools Make Shallower Agents","text":"

    Give an agent a graph query tool, and it will tell you everything about your codebase except what actually matters.

    Volkan Özçelik / April 6, 2026

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#a-turkish-proverb-walks-into-a-codebase","level":2,"title":"A Turkish Proverb Walks into a Codebase","text":"

    There's a Turkish idiom: esegin aklina karpuz kabugu sokmak (literally, \"to put watermelon rind into a donkey's mind.\" It means to plant an idea in someone's head that they wouldn't have come up with on their own) usually one that leads them astray.

    In English, let's call this a \"watermelon metric\": a project management term for something that's green on the outside and red on the inside: all dashboards passing, reality crumbling.

    Both halves of this metaphor showed up in a single experiment. And the result changed how we design architecture analysis in [ctx][ctx].

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-experiment","level":2,"title":"The Experiment","text":"

    We ran three sessions analyzing the same large codebase (~34,000 symbols) using the same architecture skill, varying only what tools the agent had access to.

    Session Tools Available Output (lines) Character 1 None (MCP broken) 5,866 Deep, intimate 2 Full graph MCP 1,124 Structural, correct 3 Enrichment pass +verified data Additive, not restorative

    Session 1 was an accident. The MCP server that provides code intelligence queries was broken, so the agent couldn't ask the graph anything. It had to read code. Line by line. File by file.

    It produced 5,866 lines of architecture analysis: per-controller data flows, scale math, startup sequences, timeout defaults, edge cases that only surface when you actually look at the implementation.

    Session 2 had working tools. Same skill, same codebase. The agent produced 1,124 lines (5.2x less). Structurally correct. Valid symbol references. Proper call chains.

    And hollow.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-rind","level":2,"title":"The Rind","text":"

    The Session 2 output was a watermelon rind: the right shape, the right color, the right texture on the outside. But the substance (the operational details, the defaults nobody documents, the scale math that tells you when a component will fall over) was missing.

    Not wrong. Not broken. Just... thin.

    The agent had answered every question correctly. The problem was that it never discovered the questions it should have asked. When you can query a graph for \"what calls this function?\", you don't stumble into the retry loop that silently swallows errors three layers down. When you can ask for the dependency tree, you don't notice that two packages share a mutable state through a global variable that isn't in any interface.

    The tool answered the question asked but prevented the discovery of answers to questions never asked.

    Here's what that looks like concretely: the graph tells you that ReconcileDeployment calls SyncPods. It does not tell you that SyncPods retries three times with exponential backoff, silently drops errors after timeout, and resets a package-level counter that another goroutine reads without a lock. The call chain is correct.

    The operational reality is invisible.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-donkeys-idea","level":2,"title":"The Donkey's Idea","text":"

    This is where the Turkish proverb earns its place: The graph tool is the \"karpuz kabugu\" (the watermelon rind placed into the agent's mind).

    Before the tool existed, the agent had no choice but to read deeply. With the tool available, a new idea appears: why read 500 lines of code when I can query the call graph?

    The agent isn't lazy. It's rational.

    Graph queries are faster, more reliable, and produce verifiably correct output. The agent is optimizing. It's satisficing (finding answers that are good enough), instead of maximizing (finding everything there is to know).

    Satisficing produces watermelon rinds.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-two-pass-compiler","level":2,"title":"The Two-Pass Compiler","text":"

    Session 3 taught us that you can't fix shallow analysis by adding more tools after the fact. The enrichment pass added verified graph data (blast radius numbers, registration sites, execution flow confirmation) but it couldn't recover the intimate code knowledge that Session 1 had produced through sheer necessity.

    You can't enrich your way out of a depth deficit.

    So we redesigned. Instead of one skill with optional tools, we built a two-pass compiler for architecture understanding:

    Pass 1: Semantic parsing. The /ctx-architecture skill deliberately has no access to graph query tools. The agent must read code, build mental models, and produce architecture artifacts through human-style comprehension. Constraint is the feature.

    Pass 2: Static analysis. The /ctx-architecture-enrich skill takes Pass 1 output as input and runs comprehensive verification through code intelligence: blast radius analysis, registration site discovery, execution flow tracing, domain clustering comparison. It extends and verifies, but it doesn't replace.

    The key insight: these must be separate skills with separate tool permissions. If you give the agent graph tools during Pass 1, it will use them. The \"karpuz kabugu\" will be in its mind. The only way to prevent satisficing is to remove the option.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-principle","level":2,"title":"The Principle","text":"

    We call this constraint-as-feature: deliberately withholding capabilities to force deeper engagement.

    It sounds paradoxical. You built sophisticated code intelligence tools and then... forbid the agent from using them? During the most important phase?

    Yes. Because the tools don't make the agent smarter. They make it faster. And faster, in architecture analysis, is the enemy of deep.

    What's actually happening is subtler: tools reduce the agent's search space. A graph query collapses thousands of possible observations into one precise answer. That's efficient for known questions. But architecture understanding depends on unknown unknowns: and you only find those by wandering through code with nothing to shortcut the journey.

    The constraint forces the agent into a mode of operation that produces better output than any amount of tooling can achieve. The limitation is the capability.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#when-does-this-apply","level":2,"title":"When Does This Apply?","text":"

    Not always. The watermelon-rind antipattern is specific to exploratory analysis: tasks where the value comes from discovering unknowns, not from answering known questions.

    Graph tools are excellent for:

    • Verification: \"Does X actually call Y?\" (binary question, precise answer)
    • Impact analysis: \"What breaks if I change Z?\" (bounded scope, enumerable results)
    • Navigation: \"Where is this interface implemented?\" (lookup, not analysis)

    Graph tools produce watermelon rinds when:

    • The goal is understanding, not answering
    • The unknowns are unknown: you don't know what to ask
    • Depth matters more than breadth: operational details, edge cases, implicit coupling

    The two-pass approach preserves both: deep reading first, tool verification second.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#takeaway","level":2,"title":"Takeaway","text":"

    The two-pass approach is the slowest way to analyze a codebase. It is also the only way that produces both depth and accuracy. We accept the cost because architecture analysis is not a speed game: it is a coverage game.

    Esegin aklina karpuz kabugu sokma!

    (don't put the watermelon rind to a donkey's mind)

    If the agent never struggles, it never discovers. And if it never discovers, you are not doing architecture; you are doing autocomplete.

    This post is part of the ctx field notes series, documenting what we learn building persistent context infrastructure for AI coding sessions.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"cli/","level":1,"title":"CLI","text":"","path":["CLI"],"tags":[]},{"location":"cli/#ctx-cli","level":2,"title":"ctx CLI","text":"

    Complete reference for all ctx commands, grouped by function.

    ","path":["CLI"],"tags":[]},{"location":"cli/#global-options","level":2,"title":"Global Options","text":"

    All commands support these flags:

    Flag Description --help Show command help --version Show version --tool <name> Override active AI tool identifier (e.g. kiro, cursor)

    Tell ctx which .context/ to use. ctx does not search the filesystem for .context/: you have to declare it. Three ways:

    • eval \"$(ctx activate)\" (recommended): binds CTX_DIR for the current shell.
    • export CTX_DIR=/abs/path/to/.context directly, then run any ctx command.
    • CTX_DIR=/abs/path/to/.context ctx <command> inline, for a one-shot or CI step.

    CTX_DIR must be an absolute path with .context as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (export CTX_DIR=$(pwd)) before stray writes can leak to the project root.

    If you forget, commands fail fast with a linkable Error: no context directory specified pointing at Activating a Context Directory. A handful of commands run without a declaration because they don't need a project: ctx init, ctx activate, ctx deactivate, ctx version, ctx help, ctx system bootstrap, ctx doctor, ctx guide, ctx why, ctx config switch/status, and ctx hub *.

    Initialization required. Once declared, the target must already have been initialized by ctx init (otherwise commands return ctx: not initialized).

    ","path":["CLI"],"tags":[]},{"location":"cli/#getting-started","level":2,"title":"Getting Started","text":"Command Description ctx init Initialize .context/ directory with templates ctx activate Emit export CTX_DIR=... to bind context for the shell ctx deactivate Emit unset CTX_DIR to clear the binding ctx status Show context summary (files, tokens, drift) ctx guide Quick-reference cheat sheet ctx why Read the philosophy behind ctx","path":["CLI"],"tags":[]},{"location":"cli/#context","level":2,"title":"Context","text":"Command Description ctx load Output assembled context in read order ctx agent Print token-budgeted context packet for AI consumption ctx sync Reconcile context with codebase state ctx drift Detect stale paths, secrets, missing files ctx compact Archive completed tasks, clean up files ctx fmt Format context files to 80-char line width ctx task Add tasks, mark complete, archive, snapshot ctx decision Add decisions and reindex DECISIONS.md ctx learning Add learnings and reindex LEARNINGS.md ctx convention Add conventions to CONVENTIONS.md ctx reindex Regenerate indices for DECISIONS.md and LEARNINGS.md ctx permission Permission snapshots (golden image) ctx change Show what changed since last session ctx memory Bridge Claude Code auto memory into .context/ ctx watch Auto-apply context updates from AI output","path":["CLI"],"tags":[]},{"location":"cli/#sessions","level":2,"title":"Sessions","text":"Command Description ctx journal Browse, import, enrich, and lock session history ctx pad Encrypted scratchpad for sensitive one-liners ctx remind Session-scoped reminders that surface at session start ctx hook pause Pause context hooks for the current session ctx hook resume Resume paused context hooks","path":["CLI"],"tags":[]},{"location":"cli/#integrations","level":2,"title":"Integrations","text":"Command Description ctx setup Generate AI tool integration configs ctx steering Manage steering files (behavioral rules for AI tools) ctx trigger Manage lifecycle triggers (scripts for automation) ctx skill Manage reusable instruction bundles ctx mcp MCP server for AI tool integration (stdin/stdout) ctx hook notify Webhook notifications (setup, test, send) ctx loop Generate autonomous loop script ctx connection Client-side commands for connecting to a ctx Hub ctx hub Operate a ctx Hub server or cluster ctx serve Serve a static site locally via zensical ctx site Site management (feed generation)","path":["CLI"],"tags":[]},{"location":"cli/#diagnostics","level":2,"title":"Diagnostics","text":"Command Description ctx doctor Structural health check (hooks, drift, config) ctx trace Show context behind git commits ctx sysinfo Show system resource usage (memory, swap, disk, load) ctx usage Show session token usage stats","path":["CLI"],"tags":[]},{"location":"cli/#runtime","level":2,"title":"Runtime","text":"Command Description ctx config Manage runtime configuration profiles ctx prune Clean stale per-session state files ctx hook Hook message, notification, and lifecycle controls ctx system Hook plumbing and agent-only commands (not user-facing)","path":["CLI"],"tags":[]},{"location":"cli/#shell","level":2,"title":"Shell","text":"Command Description ctx completion Generate shell autocompletion scripts","path":["CLI"],"tags":[]},{"location":"cli/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error / warnings (e.g. drift) 2 Context not found 3 Violations found (e.g. drift) 4 File operation error","path":["CLI"],"tags":[]},{"location":"cli/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description CTX_DIR Override default context directory path CTX_TOKEN_BUDGET Override default token budget CTX_SESSION_ID Active AI session ID (used by ctx trace for context linking)","path":["CLI"],"tags":[]},{"location":"cli/#configuration-file","level":2,"title":"Configuration File","text":"

    Optional .ctxrc (YAML format) at project root:

    # .ctxrc\ntoken_budget: 8000           # Default token budget\npriority_order:              # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true           # Auto-archive old items\narchive_after_days: 7        # Days before archiving tasks\nscratchpad_encrypt: true     # Encrypt scratchpad (default: true)\nevent_log: false             # Enable local hook event logging\ncompanion_check: true        # Check companion tools at session start\nentry_count_learnings: 30    # Drift warning threshold (0 = disable)\nentry_count_decisions: 20    # Drift warning threshold (0 = disable)\nconvention_line_count: 200   # Line count warning for CONVENTIONS.md (0 = disable)\ninjection_token_warn: 15000  # Oversize injection warning (0 = disable)\ncontext_window: 200000       # Auto-detected for Claude Code; override for other tools\nbilling_token_warn: 0        # One-shot billing warning at this token count (0 = disabled)\nkey_rotation_days: 90        # Days before key rotation nudge\nsession_prefixes:            # Recognized session header prefixes (extend for i18n)\n  - \"Session:\"               # English (default)\n  # - \"Oturum:\"              # Turkish (add as needed)\n  # - \"セッション:\"             # Japanese (add as needed)\nfreshness_files:             # Files with technology-dependent constants (opt-in)\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # Optional\nnotify:                      # Webhook notification settings\n  events:                    # Required: only listed events fire\n    - loop\n    - nudge\n    - relay\n    # - heartbeat            # Every-prompt session-alive signal\ntool: \"\"                     # Active AI tool: claude, cursor, cline, kiro, codex\nsteering:                    # Steering layer configuration\n  dir: .context/steering     # Steering files directory\n  default_inclusion: manual  # Default inclusion mode (always, auto, manual)\n  default_tools: []          # Default tool filter for new steering files\nhooks:                       # Hook system configuration\n  dir: .context/hooks        # Hook scripts directory\n  timeout: 10                # Per-hook execution timeout in seconds\n  enabled: true              # Whether hook execution is enabled\n
    Field Type Default Description token_budget int 8000 Default token budget for ctx agent priority_order []string (all files) File loading priority for context packets auto_archive bool true Auto-archive completed tasks archive_after_days int 7 Days before completed tasks are archived scratchpad_encrypt bool true Encrypt scratchpad with AES-256-GCM event_log bool false Enable local hook event logging to .context/state/events.jsonl companion_check bool true Check companion tool availability (Gemini Search, GitNexus) during /ctx-remember entry_count_learnings int 30 Drift warning when LEARNINGS.md exceeds this count entry_count_decisions int 20 Drift warning when DECISIONS.md exceeds this count convention_line_count int 200 Line count warning for CONVENTIONS.md injection_token_warn int 15000 Warn when auto-injected context exceeds this token count (0 = disable) context_window int 200000 Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools billing_token_warn int 0 (off) One-shot warning when session tokens exceed this threshold (0 = disabled) key_rotation_days int 90 Days before encryption key rotation nudge session_prefixes []string [\"Session:\"] Recognized Markdown session header prefixes. Extend to parse sessions written in other languages freshness_files []object (none) Files to track for staleness (path, desc, optional review_url). Hook warns after 6 months without modification notify.events []string (all) Event filter for webhook notifications (empty = all) tool string (empty) Active AI tool identifier (claude, cursor, cline, kiro, codex) steering.dir string .context/steering Steering files directory steering.default_inclusion string manual Default inclusion mode for new steering files (always, auto, manual) steering.default_tools []string (all) Default tool filter for new steering files (empty = all tools) hooks.dir string .context/hooks Hook scripts directory hooks.timeout int 10 Per-hook execution timeout in seconds hooks.enabled bool true Whether hook execution is enabled

    Priority order: CLI flags > Environment variables > .ctxrc > Defaults

    All settings are optional. Missing values use defaults.

    ","path":["CLI"],"tags":[]},{"location":"cli/bootstrap/","level":1,"title":"System Bootstrap","text":"","path":["System Bootstrap"],"tags":[]},{"location":"cli/bootstrap/#ctx-system-bootstrap","level":3,"title":"ctx system bootstrap","text":"

    Print the resolved context directory path so AI agents can anchor their session. The default output lists the context directory, the tracked context files, and a short health snapshot. --quiet prints just the path; --json produces structured output for automation.

    This is a hidden, agent-only command that agents are instructed to run first in their session-start procedure; it is the authoritative answer to \"where does this project's context live?\".

    ctx system bootstrap [flags]\n

    Flags:

    Flag Description -q, --quiet Output only the context directory path --json Output in JSON format

    Examples:

    ctx system bootstrap                 # Text output for agents\nctx system bootstrap -q              # Just the context directory path\nctx system bootstrap --json          # Structured output for automation\n

    Note: -q prints just the resolved directory path. See Activating a Context Directory if you hit a \"no context directory specified\" error.

    ","path":["System Bootstrap"],"tags":[]},{"location":"cli/change/","level":1,"title":"Change","text":"","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/change/#ctx-change","level":2,"title":"ctx change","text":"

    Show what changed in context files and code since your last session.

    Automatically detects the previous session boundary from state markers or event log. Useful at session start to quickly see what moved while you were away.

    ctx change [flags]\n

    Flags:

    Flag Description --since Time reference: duration (24h) or date (2026-03-01)

    Reference time detection (priority order):

    1. --since flag (duration, date, or RFC3339 timestamp)
    2. ctx-loaded-* marker files in .context/state/ (second most recent)
    3. Last context-load-gate event from .context/state/events.jsonl
    4. Fallback: 24 hours ago

    Examples:

    # Auto-detect last session, show what changed\nctx change\n\n# Changes in the last 48 hours\nctx change --since 48h\n\n# Changes since a specific date\nctx change --since 2026-03-10\n

    Output:

    ## Changes Since Last Session\n\n**Reference point**: 6 hours ago\n\n### Context File Changes\n- `TASKS.md` - modified 2026-03-12 14:30\n- `DECISIONS.md` - modified 2026-03-12 09:15\n\n### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n

    Context file changes are detected by filesystem mtime (works without git). Code changes use git log --since (empty when not in a git repo).

    See also: Reviewing Session Changes.

    ","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/completion/","level":1,"title":"Completion","text":"","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#ctx-completion","level":2,"title":"ctx completion","text":"

    Generate shell autocompletion scripts.

    ctx completion <shell>\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#subcommands","level":3,"title":"Subcommands","text":"Shell Command bash ctx completion bash zsh ctx completion zsh fish ctx completion fish powershell ctx completion powershell

    Examples:

    ctx completion bash > /etc/bash_completion.d/ctx\nctx completion zsh  > \"${fpath[1]}/_ctx\"\nctx completion fish > ~/.config/fish/completions/ctx.fish\nctx completion powershell | Out-String | Invoke-Expression\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#installation","level":3,"title":"Installation","text":"BashZshFishPowerShell
    # Add to ~/.bashrc\nsource <(ctx completion bash)\n
    # Add to ~/.zshrc\nsource <(ctx completion zsh)\n
    ctx completion fish | source\n# Or save to completions directory\nctx completion fish > ~/.config/fish/completions/ctx.fish\n
    # Add to your PowerShell profile\nctx completion powershell | Out-String | Invoke-Expression\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/config/","level":1,"title":"Config","text":"","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config","level":3,"title":"ctx config","text":"

    Manage runtime configuration profiles.

    ctx config <subcommand>\n

    The ctx repo ships two .ctxrc source profiles (.ctxrc.base and .ctxrc.dev). The working copy (.ctxrc) is gitignored and switched between them using subcommands below.

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-switch","level":4,"title":"ctx config switch","text":"

    Switch between .ctxrc configuration profiles.

    ctx config switch [dev|base]\n

    With no argument, toggles between dev and base. Accepts prod as an alias for base.

    Argument Description dev Switch to dev profile (verbose logging) base Switch to base profile (all defaults) (none) Toggle to the opposite profile

    Profiles:

    Profile Description dev Verbose logging, webhook notifications on base All defaults, notifications off

    Examples:

    ctx config switch dev     # Switch to dev profile\nctx config switch base    # Switch to base profile\nctx config switch         # Toggle (dev → base or base → dev)\nctx config switch prod    # Alias for \"base\"\n

    The detection heuristic checks for an uncommented notify: line in .ctxrc: present means dev, absent means base.

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-status","level":4,"title":"ctx config status","text":"

    Show which .ctxrc profile is currently active.

    ctx config status\n

    Output examples:

    active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n

    See also: Configuration, Contributing: Configuration Profiles

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/connect/","level":1,"title":"Connect","text":"","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect","level":2,"title":"ctx connect","text":"

    Connect a project to a ctx Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.

    New to the Hub?

    Start with the ctx Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.

    The unit of identity is a project, not a user. Registering a directory with ctx connect register binds a per-project client token in .context/.connect.enc. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.

    Only structured entries flow through the hub: decision, learning, convention, task. Session journals, scratchpad contents, and other local state stay on the machine that created them.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-register","level":3,"title":"ctx connect register","text":"

    One-time registration with a hub. Requires the hub address and admin token (printed by ctx hub start on first run).

    ctx connect register localhost:9900 --token ctx_adm_7f3a...\n

    On success, stores an encrypted connection config in .context/.connect.enc for future RPCs.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-subscribe","level":3,"title":"ctx connect subscribe","text":"

    Set which entry types to receive from the hub. Only matching types are returned by sync and listen.

    ctx connect subscribe decision learning\nctx connect subscribe decision learning convention\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-sync","level":3,"title":"ctx connect sync","text":"

    Pull matching entries from the hub and write them to .context/hub/ as markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.

    ctx connect sync\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-publish","level":3,"title":"ctx connect publish","text":"

    Push entries to the hub. Specify type and content as arguments.

    ctx connect publish decision \"Use UTC timestamps everywhere\"\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-listen","level":3,"title":"ctx connect listen","text":"

    Stream new entries from the hub in real-time. Writes to .context/hub/ as entries arrive. Press Ctrl-C to stop.

    ctx connect listen\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-status","level":3,"title":"ctx connect status","text":"

    Show hub connection state and entry statistics.

    ctx connect status\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"

    Use --share on ctx add to write locally AND publish to the hub:

    ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n

    If the hub is unreachable, the local write succeeds and a warning is printed. The --share flag is best-effort; it never blocks local context updates.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#auto-sync","level":2,"title":"Auto-Sync","text":"

    Once registered, the check-hub-sync hook automatically syncs new entries from the hub at the start of each session (daily throttled). No manual ctx connect sync needed.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#shared-files","level":2,"title":"Shared Files","text":"

    Entries from the hub are stored in .context/hub/:

    .context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n

    These files are read-only (managed by sync/listen) and never mixed with local context files.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#agent-integration","level":2,"title":"Agent Integration","text":"

    Include shared knowledge in agent context packets:

    ctx agent --include-hub\n

    Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.

    ","path":["Connect"],"tags":[]},{"location":"cli/connection/","level":1,"title":"Connect","text":"","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connect","level":2,"title":"ctx connect","text":"

    Connect a project to a ctx Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.

    New to the ctx Hub?

    Start with the ctx Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.

    The unit of identity is a project, not a user. Registering a directory with ctx connection register binds a per-project client token in .context/.connect.enc. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.

    Only structured entries flow through the hub: decision, learning, convention, task. Session journals, scratchpad contents, and other local state stay on the machine that created them.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-register","level":3,"title":"ctx connection register","text":"

    One-time registration with a ctx Hub. Requires the ctx Hub address and admin token (printed by ctx hub start on first run).

    Examples:

    ctx connection register localhost:9900 --token ctx_adm_7f3a...\n

    On success, stores an encrypted connection config in .context/.connect.enc for future RPCs.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-subscribe","level":3,"title":"ctx connection subscribe","text":"

    Set which entry types to receive from the ctx Hub. Only matching types are returned by sync and listen.

    Examples:

    ctx connection subscribe decision learning\nctx connection subscribe decision learning convention\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-sync","level":3,"title":"ctx connection sync","text":"

    Pull matching entries from the ctx Hub and write them to .context/hub/ as markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.

    Examples:

    ctx connection sync\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-publish","level":3,"title":"ctx connection publish","text":"

    Push entries to the ctx Hub. Specify type and content as arguments.

    Examples:

    ctx connection publish decision \"Use UTC timestamps everywhere\"\nctx connection publish learning \"Go embed requires files in same package\"\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-listen","level":3,"title":"ctx connection listen","text":"

    Stream new entries from the ctx Hub in real-time. Writes to .context/hub/ as entries arrive. Press Ctrl-C to stop.

    Examples:

    ctx connection listen\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-status","level":3,"title":"ctx connection status","text":"

    Show ctx Hub connection state and entry statistics.

    Examples:

    ctx connection status\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"

    Use --share on ctx add to write locally AND publish to the ctx Hub:

    ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n

    If the hub is unreachable, the local write succeeds and a warning is printed. The --share flag is best-effort; it never blocks local context updates.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#auto-sync","level":2,"title":"Auto-Sync","text":"

    Once registered, the check-hub-sync hook automatically syncs new entries from the ctx Hub at the start of each session (daily throttled). No manual ctx connection sync needed.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#shared-files","level":2,"title":"Shared Files","text":"

    Entries from the ctx Hub are stored in .context/hub/:

    .context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n

    These files are read-only (managed by sync/listen) and never mixed with local context files.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#agent-integration","level":2,"title":"Agent Integration","text":"

    Include shared knowledge in agent context packets:

    ctx agent --include-hub\n

    Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/context/","level":1,"title":"Context Management","text":"","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#adding-entries","level":3,"title":"Adding entries","text":"

    Each context-artifact noun (task, decision, learning, convention) owns its own add subcommand under the noun-first command tree:

    ctx task add <content> [flags]\nctx decision add <content> [flags]\nctx learning add <content> [flags]\nctx convention add <content> [flags]\n

    Target files:

    Subcommand Target File ctx task add TASKS.md ctx decision add DECISIONS.md ctx learning add LEARNINGS.md ctx convention add CONVENTIONS.md

    Flags (shared by every add subcommand; per-noun required-flag rules surface as command errors):

    Flag Short Description --priority <level> -p Priority for tasks: high, medium, low --section <name> -s Target section within file --context -c Context (required for decisions and learnings) --rationale -r Rationale for decisions (required for decisions) --consequence Consequence for decisions (required for decisions) --lesson -l Key insight (required for learnings) --application -a How to apply going forward (required for learnings) --file -f Read content from file instead of argument

    Examples:

    # Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\nctx task add \"Fix login bug\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (requires all ADR (Architectural Decision Record) fields)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning (requires context, lesson, and application)\nctx learning add \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to specific section\nctx convention add \"Use kebab-case for filenames\" --section \"Naming\"\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-drift","level":3,"title":"ctx drift","text":"

    Detect stale or invalid context.

    ctx drift [flags]\n

    Flags:

    Flag Description --json Output machine-readable JSON --fix Auto-fix simple issues

    Checks:

    • Path references in ARCHITECTURE.md and CONVENTIONS.md exist
    • Task references are valid
    • Constitution rules aren't violated (heuristic)
    • Staleness indicators (old files, many completed tasks)
    • Missing packages: warns when internal/ directories exist on disk but are not referenced in ARCHITECTURE.md (suggests running /ctx-architecture)
    • Entry count: warns when LEARNINGS.md or DECISIONS.md exceed configurable thresholds (default: 30 learnings, 20 decisions), or when CONVENTIONS.md exceeds a line count threshold (default: 200). Configure via .ctxrc:
      entry_count_learnings: 30      # warn above this (0 = disable)\nentry_count_decisions: 20      # warn above this (0 = disable)\nconvention_line_count: 200     # warn above this (0 = disable)\n

    Example:

    ctx drift\nctx drift --json\nctx drift --fix\n

    Exit codes:

    Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-sync","level":3,"title":"ctx sync","text":"

    Reconcile context with the current codebase state.

    ctx sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would change without modifying

    What it does:

    • Scans codebase for structural changes
    • Compares with ARCHITECTURE.md
    • Suggests documenting dependencies if package files exist
    • Identifies stale or outdated context

    Example:

    ctx sync\nctx sync --dry-run\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate and clean up context files.

    • Moves completed tasks older than 7 days to the archive
    • Removes empty sections
    ctx compact [flags]\n

    Flags:

    Flag Description --archive Create .context/archive/ for old content

    Example:

    ctx compact\nctx compact --archive\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-fmt","level":3,"title":"ctx fmt","text":"

    Format context files to a consistent line width.

    Wraps long lines in TASKS.md, DECISIONS.md, LEARNINGS.md, and CONVENTIONS.md at word boundaries. Markdown list items get 2-space continuation indent. Headings, tables, frontmatter, and HTML comments are preserved as-is.

    Idempotent: running twice produces the same output.

    ctx fmt [flags]\n

    Flags:

    Flag Type Default Description --width int 80 Target line width --check bool false Check only, exit 1 if files would change

    Examples:

    ctx fmt              # format all context files\nctx fmt --check      # CI mode: check without modifying\nctx fmt --width 100  # custom width\n

    Also available as a Makefile target:

    make fmt-context\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task","level":3,"title":"ctx task","text":"

    Manage task completion, archival, and snapshots.

    ctx task <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-complete","level":4,"title":"ctx task complete","text":"

    Mark a task as completed.

    ctx task complete <task-id-or-text>\n

    Arguments:

    • task-id-or-text: Task number or partial text match

    Examples:

    # By text (partial match)\nctx task complete \"user auth\"\n\n# By task number\nctx task complete 3\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-archive","level":4,"title":"ctx task archive","text":"

    Move completed tasks from TASKS.md to a timestamped archive file.

    ctx task archive [flags]\n

    Flags:

    Flag Description --dry-run Preview changes without modifying files

    Archive files are stored in .context/archive/ with timestamped names (tasks-YYYY-MM-DD.md). Completed tasks (marked with [x]) are moved; pending tasks ([ ]) remain in TASKS.md.

    Example:

    ctx task archive\nctx task archive --dry-run\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-snapshot","level":4,"title":"ctx task snapshot","text":"

    Create a point-in-time snapshot of TASKS.md without modifying the original.

    ctx task snapshot [name]\n

    Arguments:

    • name: Optional name for the snapshot (defaults to \"snapshot\")

    Snapshots are stored in .context/archive/ with timestamped names (tasks-<name>-YYYY-MM-DD-HHMM.md).

    Example:

    ctx task snapshot\nctx task snapshot \"before-refactor\"\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission","level":3,"title":"ctx permission","text":"

    Manage Claude Code permission snapshots.

    ctx permission <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-snapshot","level":4,"title":"ctx permission snapshot","text":"

    Save .claude/settings.local.json as the golden image.

    ctx permission snapshot\n

    Creates .claude/settings.golden.json as a byte-for-byte copy of the current settings. Overwrites if the golden file already exists.

    The golden file is meant to be committed to version control and shared with the team.

    Example:

    ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-restore","level":4,"title":"ctx permission restore","text":"

    Replace settings.local.json with the golden image.

    ctx permission restore\n

    Prints a diff of dropped (session-accumulated) and restored permissions. No-op if the files already match.

    Example:

    ctx permission restore\n# Dropped 3 session permission(s):\n#   - Bash(cat /tmp/debug.log:*)\n#   - Bash(rm /tmp/test-*:*)\n#   - Bash(curl https://example.com:*)\n# Restored from golden image.\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-reindex","level":3,"title":"ctx reindex","text":"

    Regenerate the quick-reference index for both DECISIONS.md and LEARNINGS.md in a single invocation.

    ctx reindex\n

    This is a convenience wrapper around ctx decision reindex and ctx learning reindex. Both files grow at similar rates and users typically want to reindex both after manual edits.

    The index is a compact table of date and title for each entry, allowing AI tools to scan entries without reading the full file.

    Example:

    ctx reindex\n# ✓ Index regenerated with 12 entries\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision","level":3,"title":"ctx decision","text":"

    Manage the DECISIONS.md file.

    ctx decision <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision-reindex","level":4,"title":"ctx decision reindex","text":"

    Regenerate the quick-reference index at the top of DECISIONS.md.

    ctx decision reindex\n

    The index is a compact table showing the date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to DECISIONS.md or when migrating existing files to use the index format.

    Example:

    ctx decision reindex\n# ✓ Index regenerated with 12 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning","level":3,"title":"ctx learning","text":"

    Manage the LEARNINGS.md file.

    ctx learning <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning-reindex","level":4,"title":"ctx learning reindex","text":"

    Regenerate the quick-reference index at the top of LEARNINGS.md.

    ctx learning reindex\n

    The index is a compact table showing the date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to LEARNINGS.md or when migrating existing files to use the index format.

    Example:

    ctx learning reindex\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/doctor/","level":1,"title":"Doctor","text":"","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#ctx-doctor","level":3,"title":"ctx doctor","text":"

    Structural health check across context, hooks, and configuration. Runs mechanical checks that don't require semantic analysis. Think of it as ctx status + ctx drift + configuration audit in one pass.

    ctx doctor [flags]\n

    Flags:

    Flag Short Type Default Description --json -j bool false Machine-readable JSON output","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-checks","level":4,"title":"What It Checks","text":"Check Category What it verifies Context initialized Structure .context/ directory exists Required files present Structure All required context files exist (TASKS.md, etc.) Drift detected Quality Stale paths, missing files, constitution violations Event logging status Hooks Whether event_log: true is set in .ctxrc Webhook configured Hooks .notify.enc file exists Pending reminders State Count of entries in reminders.json Task completion ratio State Pending vs completed tasks in TASKS.md Context token size Size Estimated token count across all context files Recent event activity Events Last event timestamp (only when event logging is enabled)","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-human","level":4,"title":"Output Format (Human)","text":"
    ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n

    Status indicators:

    Icon Status Meaning ✓ ok Check passed ⚠ warning Non-critical issue worth fixing ✗ error Problem that needs attention ○ info Informational note","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-json","level":4,"title":"Output Format (JSON)","text":"
    {\n  \"results\": [\n    {\n      \"name\": \"context_initialized\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Context initialized (.context/)\"\n    },\n    {\n      \"name\": \"required_files\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Required files present (4/4)\"\n    },\n    {\n      \"name\": \"drift\",\n      \"category\": \"Quality\",\n      \"status\": \"warning\",\n      \"message\": \"Drift: 2 warnings\"\n    },\n    {\n      \"name\": \"event_logging\",\n      \"category\": \"Hooks\",\n      \"status\": \"info\",\n      \"message\": \"Event logging disabled (enable with event_log: true in .ctxrc)\"\n    },\n    {\n      \"name\": \"webhook\",\n      \"category\": \"Hooks\",\n      \"status\": \"ok\",\n      \"message\": \"Webhook configured\"\n    },\n    {\n      \"name\": \"reminders\",\n      \"category\": \"State\",\n      \"status\": \"ok\",\n      \"message\": \"No pending reminders\"\n    },\n    {\n      \"name\": \"task_completion\",\n      \"category\": \"State\",\n      \"status\": \"warning\",\n      \"message\": \"Tasks: 18/22 completed (82%): consider archiving with ctx task archive\"\n    },\n    {\n      \"name\": \"context_size\",\n      \"category\": \"Size\",\n      \"status\": \"ok\",\n      \"message\": \"Context size: ~4200 tokens (budget: 8000)\"\n    }\n  ],\n  \"warnings\": 2,\n  \"errors\": 0\n}\n

    Examples:

    # Quick structural health check\nctx doctor\n\n# Machine-readable output for scripting\nctx doctor --json\n\n# Count warnings\nctx doctor --json | jq '.warnings'\n\n# Check for errors only\nctx doctor --json | jq '[.results[] | select(.status == \"error\")]'\n
    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#when-to-use-what","level":4,"title":"When to Use What","text":"Tool When ctx status Quick glance at files, tokens, and drift ctx doctor Thorough structural checkup (hooks, config, events too) /ctx-doctor Agent-driven diagnosis with event log pattern analysis

    ctx status tells you what's there. ctx doctor tells you what's wrong. /ctx-doctor tells you why it's wrong and what to do about it.

    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-does-not-do","level":4,"title":"What It Does Not Do","text":"
    • No event pattern analysis: that's the /ctx-doctor skill's job
    • No auto-fixing: reports findings, doesn't modify anything
    • No external service checks: doesn't verify webhook endpoint availability

    See also: Troubleshooting | ctx hook event | /ctx-doctor skill | Detecting and Fixing Drift

    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/event/","level":1,"title":"Event","text":"","path":["Event"],"tags":[]},{"location":"cli/event/#ctx-hook-event","level":3,"title":"ctx hook event","text":"

    Query the local hook event log. Requires event_log: true in .ctxrc. Reads events from .context/state/events.jsonl and outputs them in a human-readable table or raw JSONL format.

    All filter flags combine with AND logic.

    ctx hook event [flags]\n

    Flags:

    Flag Description --hook Filter by hook name --session Filter by session ID --event Filter by event type (relay, nudge) --last Show last N events (default: 50) --json Output raw JSONL (for piping to jq) --all Include rotated log file

    Examples:

    ctx hook event                                        # recent events\nctx hook event --hook check-context-size --last 10    # one hook, last 10\nctx hook event --json | jq '.hook'                    # pipe to jq\nctx hook event --session abc123                       # filter by session\n
    ","path":["Event"],"tags":[]},{"location":"cli/guide/","level":1,"title":"Guide","text":"","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/guide/#ctx-guide","level":2,"title":"ctx guide","text":"

    Quick-reference cheat sheet for common ctx commands and skills.

    ctx guide [flags]\n

    Flags:

    Flag Description --skills Show available skills --commands Show available CLI commands

    Example:

    # Show the full cheat sheet\nctx guide\n\n# Skills only\nctx guide --skills\n\n# Commands only\nctx guide --commands\n

    Works without initialization (no .context/ required). Useful for a printable one-pager when onboarding to a project.

    ","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/hook/","level":1,"title":"Hook","text":"","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#ctx-hook","level":3,"title":"ctx hook","text":"

    Manage hook-related settings: messages, notifications, pause/resume, and event log.

    ctx hook <subcommand> [flags]\n
    ","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#subcommands","level":2,"title":"Subcommands","text":"Subcommand Description ctx hook message list Show all hook messages with override status ctx hook message show <h> <v> Print the effective message template ctx hook message edit <h> <v> Copy default to .context/ for editing ctx hook message reset <h> <v> Delete user override, revert to default ctx hook notify [message] Send a webhook notification ctx hook notify setup Configure and encrypt webhook URL ctx hook notify test Send a test notification ctx hook pause Pause all context hooks for this session ctx hook resume Resume paused context hooks ctx hook event Query the local hook event log","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#examples","level":2,"title":"Examples","text":"
    # View and manage hook messages\nctx hook message list\nctx hook message show qa-reminder gate\nctx hook message edit qa-reminder gate\n\n# Webhook notifications\nctx hook notify setup\nctx hook notify --event loop \"Loop completed\"\n\n# Pause/resume hooks\nctx hook pause\nctx hook resume\n\n# Browse event log\nctx hook event --last 20\nctx hook event --hook qa-reminder --json\n

    See also: Customizing Hook Messages | Webhook Notifications | Pausing Context Hooks | System Hooks Audit

    ","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hub/","level":1,"title":"Hub","text":"","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub","level":2,"title":"ctx hub","text":"

    Operator commands for a ctx Hub: the gRPC server that fans out decisions, learnings, conventions, and tasks across projects. Use ctx hub to start and stop the server, inspect cluster state, add or remove peers at runtime, and hand off leadership before maintenance.

    Who Needs This Page

    You only need ctx hub if you are running a hub server or cluster. For client-side operations (register, subscribe, sync, publish, listen), see ctx connect. For the mental model behind the hub as a whole, read the ctx Hub overview.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-start","level":3,"title":"ctx hub start","text":"

    Start the hub gRPC server.

    Examples:

    ctx hub start                           # Foreground, default port 9900\nctx hub start --port 8080               # Custom port\nctx hub start --data-dir /srv/ctx-hub   # Custom data directory\n

    On first run, generates an admin token and prints it to stdout. Save this token; it's required for ctx connection register in client projects. Subsequent runs reuse the stored token from <data-dir>/admin.token.

    Default data directory: ~/.ctx/hub-data/

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#daemon-mode","level":4,"title":"Daemon Mode","text":"

    Run the hub as a detached background process:

    ctx hub start --daemon          # Fork to background\nctx hub stop                    # Graceful shutdown\n

    The daemon writes a PID file to <data-dir>/hub.pid. Stop the daemon with ctx hub stop (see below).

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#cluster-mode","level":4,"title":"Cluster Mode","text":"

    For high availability, run multiple hubs with Raft-based leader election:

    ctx hub start --port 9900 \\\n  --peers host2:9901,host3:9901\n

    Raft is used only for leader election. Data replication uses sequence-based gRPC sync on the append-only JSONL log; there is no multi-node consensus on writes. See the HA cluster recipe for the full setup and the Raft-lite durability caveat.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#flags","level":4,"title":"Flags","text":"Flag Description Default --port Hub listen port 9900 --data-dir Hub data directory ~/.ctx/hub-data/ --daemon Run the hub server in the background false --peers Comma-separated peer addresses for cluster mode (none)","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#validation","level":4,"title":"Validation","text":"

    The hub validates every published entry before accepting it:

    • Type must be one of decision, learning, convention, task
    • ID and Origin are required and non-empty
    • Content size capped at 1 MB (text-only)
    • Duplicate project registration is rejected (one token per project)
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stop","level":3,"title":"ctx hub stop","text":"

    Stop a running hub daemon.

    Examples:

    ctx hub stop                            # Stop using default data dir\nctx hub stop --data-dir /srv/ctx-hub    # Custom data directory\n

    Sends SIGTERM to the PID recorded in <data-dir>/hub.pid, waits for in-flight RPCs to drain, and removes the PID file. Safe to rerun: if no daemon is running, returns a \"no running hub\" error without side effects.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-status","level":3,"title":"ctx hub status","text":"

    Show cluster status: role, peers, sync state, entry count, and uptime.

    Examples:

    ctx hub status\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-peer","level":3,"title":"ctx hub peer","text":"

    Add or remove peers from the cluster at runtime. Useful for scaling up or replacing a decommissioned node without restarting the leader.

    Examples:

    ctx hub peer add host2:9901\nctx hub peer remove host2:9901\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stepdown","level":3,"title":"ctx hub stepdown","text":"

    Transfer leadership to another node gracefully. Triggers a new election among the remaining followers before the current leader steps down. Use before taking the leader offline for maintenance.

    Examples:

    ctx hub stepdown\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#see-also","level":3,"title":"See Also","text":"
    • ctx connect: client-side commands (register, subscribe, sync, publish, listen)
    • ctx Hub overview: mental model and user stories
    • ctx Hub: Getting Started
    • Hub operations: production deployment, backup, monitoring
    • Hub failure modes
    • Hub security model
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/init-status/","level":1,"title":"Init and Status","text":"","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-init","level":3,"title":"ctx init","text":"

    Initialize a new .context/ directory with template files.

    ctx init [flags]\n

    Flags:

    Flag Short Description --force -f Overwrite existing context files --minimal -m Only create essential files (TASKS.md, DECISIONS.md, CONSTITUTION.md) --merge Auto-merge ctx content into existing CLAUDE.md

    Creates:

    • .context/ directory with all template files
    • .claude/settings.local.json with pre-approved ctx permissions
    • CLAUDE.md with bootstrap instructions (or merges into existing)

    Claude Code hooks and skills are provided by the ctx plugin (see Integrations).

    Example:

    # Standard init\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --reset\n\n# Merge into existing files\nctx init --merge\n

    After ctx init succeeds, the final output includes a hint showing the exact eval \"$(ctx activate)\" line to bind the new directory for your shell. Every other ctx command requires that binding (or an equivalent direct CTX_DIR=/abs/path/.context export) before it will run.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-activate","level":3,"title":"ctx activate","text":"

    Emit a shell-native export CTX_DIR=... line for the target .context/ directory. ctx does not search the filesystem during day-to-day commands: each one needs CTX_DIR set before it runs. activate is the convenience that figures out the path for you so you can bind it with one line.

    # Walk up from CWD, emit if exactly one candidate visible.\neval \"$(ctx activate)\"\n

    Flags:

    Flag Description --shell Shell dialect override. POSIX-family (bash, zsh, sh) all share one syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from $SHELL.

    Resolution:

    Candidate count from CWD Behavior Zero Error. Use ctx init to create one, or cd closer to the project root. One Emit export CTX_DIR=<path> for that candidate. Two or more Refuse. List every candidate. Re-run from a more specific cwd.

    activate is args-free under the single-source-anchor model; the explicit-path mode was removed because hub-client / hub-server scenarios store at ~/.ctx/hub-data/ and never read .context/, so they activate from the project root like everyone else. Direct binding without a project-local scan is still available via export CTX_DIR=/abs/path/.context or the inline form.

    If the parent shell already has CTX_DIR set to a different value, the output gains a leading # ctx: replacing stale CTX_DIR=... comment so the user sees the change in eval output before the replacement takes effect.

    See also: Activating a Context Directory for the full recipe including direnv setup and CI patterns.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-deactivate","level":3,"title":"ctx deactivate","text":"

    Emit a shell-native unset CTX_DIR line. Pairs with activate.

    eval \"$(ctx deactivate)\"\n

    Flags:

    Flag Description --shell Shell dialect override. POSIX-family (bash, zsh, sh) all share one unset syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from $SHELL.

    deactivate does not touch the filesystem, doesn't require a declared context directory, and never fails under normal operation; unsetting an already-unset variable is a no-op across supported shells.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-status","level":3,"title":"ctx status","text":"

    Show the current context summary.

    ctx status [flags]\n

    Flags:

    Flag Short Description --json Output as JSON --verbose -v Include file contents summary

    Output:

    • Context directory path
    • Total files and token estimate
    • Status of each file (loaded, empty, missing)
    • Recent activity (modification times)
    • Drift warnings if any

    Example:

    ctx status\nctx status --json\nctx status --verbose\n
    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-agent","level":3,"title":"ctx agent","text":"

    Print an AI-ready context packet optimized for LLM consumption.

    ctx agent [flags]\n

    Flags:

    Flag Default Description --budget 8000 Token budget: controls content selection and prioritization --format md Output format: md or json --cooldown 10m Suppress repeated output within this duration (requires --session) --session (none) Session ID for cooldown isolation (e.g., $PPID) --include-hub false Include hub entries from .context/hub/

    How budget works:

    The budget controls how much context is included. Entries are selected in priority tiers:

    1. Constitution: always included in full (inviolable rules)
    2. Tasks: all active tasks, up to 40% of budget
    3. Conventions: all conventions, up to 20% of budget
    4. Decisions: scored by recency and relevance to active tasks
    5. Learnings: scored by recency and relevance to active tasks
    6. Steering: applicable steering file bodies, scored by their inclusion mode and description match against the active prompt
    7. Skill: named skill content (from --skill)
    8. Hub: entries from .context/hub/ (with --include-hub, see ctx connect)

    Decisions and learnings are ranked by a combined score (how recent + how relevant to your current tasks). High-scoring entries are included with their full body. Entries that don't fit get title-only summaries in an \"Also Noted\" section. Superseded entries are excluded.

    Output Sections:

    Section Source Selection Read These Files all .context/ Non-empty files in priority order Constitution CONSTITUTION.md All rules (never truncated) Current Tasks TASKS.md All unchecked tasks (budget-capped) Key Conventions CONVENTIONS.md All items (budget-capped) Recent Decisions DECISIONS.md Full body, scored by relevance Key Learnings LEARNINGS.md Full body, scored by relevance Also Noted overflow Title-only summaries

    Example:

    # Default (8000 tokens, markdown)\nctx agent\n\n# Smaller packet for tight context windows\nctx agent --budget 4000\n\n# JSON format for programmatic use\nctx agent --format json\n\n# Pipe to file\nctx agent --budget 4000 > context.md\n\n# With cooldown (hooks/automation: requires --session)\nctx agent --session $PPID\n

    Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-load","level":3,"title":"ctx load","text":"

    Load and display assembled context as AI would see it.

    ctx load [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget for assembly (default: 8000) --raw Output raw file contents without assembly

    Example:

    ctx load\nctx load --budget 16000\nctx load --raw\n
    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/journal/","level":1,"title":"Journal","text":"","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal","level":3,"title":"ctx journal","text":"

    Browse and search AI session history from Claude Code and other tools.

    ctx journal <subcommand>\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source","level":4,"title":"ctx journal source","text":"

    List all parsed sessions.

    ctx journal source [flags]\n

    Flags:

    Flag Short Description --limit -n Maximum sessions to display (default: 20) --project -p Filter by project name --tool -t Filter by tool (e.g., claude-code) --all-projects Include sessions from all projects

    Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.

    Example:

    ctx journal source\nctx journal source --limit 5\nctx journal source --project ctx\nctx journal source --tool claude-code\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source-show","level":4,"title":"ctx journal source --show","text":"

    Show details of a specific session.

    ctx journal source --show [session-id] [flags]\n

    Flags:

    Flag Description --latest Show the most recent session --full Show full message content --all-projects Search across all projects

    The session ID can be a full UUID, partial match, or session slug name.

    Example:

    ctx journal source --show abc123\nctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show --latest\nctx journal source --show --latest --full\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-import","level":4,"title":"ctx journal import","text":"

    Import sessions to editable journal files in .context/journal/.

    ctx journal import [session-id] [flags]\n

    Flags:

    Flag Description --all Import all sessions (only new files by default) --all-projects Import from all projects --regenerate Re-import existing files (preserves YAML frontmatter by default) --keep-frontmatter Preserve enriched YAML frontmatter during regeneration (default: true) --yes, -y Skip confirmation prompt --dry-run Show what would be imported without writing files

    Safe by default: --all only imports new sessions. Existing files are skipped. Use --regenerate to re-import existing files (conversation content is regenerated, YAML frontmatter from enrichment is preserved by default). Use --keep-frontmatter=false to discard enriched frontmatter during regeneration.

    Locked entries (via ctx journal lock) are always skipped, regardless of flags.

    Single-session import (ctx journal import <id>) always writes without prompting, since you are explicitly targeting one session.

    The journal/ directory should be gitignored (like sessions/) since it contains raw conversation data.

    Example:

    ctx journal import abc123                 # Import one session\nctx journal import --all                  # Import only new sessions\nctx journal import --all --dry-run        # Preview what would be imported\nctx journal import --all --regenerate     # Re-import existing (prompts)\nctx journal import --all --regenerate -y  # Re-import without prompting\nctx journal import --all --regenerate --keep-frontmatter=false -y  # Discard frontmatter\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-lock","level":4,"title":"ctx journal lock","text":"

    Protect journal entries from being overwritten by import --regenerate or modified by enrichment skills (/ctx-journal-enrich, /ctx-journal-enrich-all).

    ctx journal lock <pattern> [flags]\n

    Flags:

    Flag Description --all Lock all journal entries

    The pattern matches filenames by slug, date, or short ID. Locking a multi-part entry locks all parts. The lock is recorded in .context/journal/.state.json and a locked: true line is added to the file's YAML frontmatter for visibility.

    Example:

    ctx journal lock abc12345\nctx journal lock 2026-01-21-session-abc12345.md\nctx journal lock --all\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-unlock","level":4,"title":"ctx journal unlock","text":"

    Remove lock protection from journal entries.

    ctx journal unlock <pattern> [flags]\n

    Flags:

    Flag Description --all Unlock all journal entries

    Example:

    ctx journal unlock abc12345\nctx journal unlock --all\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-sync","level":4,"title":"ctx journal sync","text":"

    Sync lock state from journal frontmatter to .state.json.

    ctx journal sync\n

    Scans all journal markdowns and updates .state.json to match each file's frontmatter. Files with locked: true in frontmatter are marked locked in state; files without a locked: line have their lock cleared.

    This is the inverse of ctx journal lock: instead of state driving frontmatter, frontmatter drives state. Useful after batch enrichment where you add locked: true to frontmatter manually.

    Example:

    # After enriching entries and adding locked: true to frontmatter\nctx journal sync\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal_1","level":3,"title":"ctx journal","text":"

    Analyze and synthesize imported session files.

    ctx journal <subcommand>\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-site","level":4,"title":"ctx journal site","text":"

    Generate a static site from journal entries in .context/journal/.

    ctx journal site [flags]\n

    Flags:

    Flag Short Description --output -o Output directory (default: .context/journal-site) --build Run zensical build after generating --serve Run zensical serve after generating

    Creates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

    Requires zensical to be installed for --build or --serve:

    pipx install zensical\n

    Example:

    ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-obsidian","level":4,"title":"ctx journal obsidian","text":"

    Generate an Obsidian vault from journal entries in .context/journal/.

    ctx journal obsidian [flags]\n

    Flags:

    Flag Short Description --output -o Output directory (default: .context/journal-obsidian)

    Creates an Obsidian-compatible vault with:

    • Wikilinks ([[target|display]]) for all internal navigation
    • MOC pages (Map of Content) for topics, key files, and session types
    • Related sessions footer linking entries that share topics
    • Transformed frontmatter (topicstags for Obsidian integration)
    • Minimal .obsidian/ config enforcing wikilink mode

    No external dependencies are required: Open the output directory as an Obsidian vault directly.

    Example:

    ctx journal obsidian                        # Generate in .context/journal-obsidian/\nctx journal obsidian --output ~/vaults/ctx  # Custom output directory\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-check","level":4,"title":"ctx journal schema check","text":"

    Validate JSONL session files against the embedded schema and report drift.

    ctx journal schema check [flags]\n

    Flags:

    Flag Short Description --dir Directory to scan for JSONL files --all-projects Scan all Claude Code project directories --quiet -q Exit code only (0 = clean, 1 = drift)

    Scans JSONL files for unknown fields, missing required fields, unknown record types, and unknown content block types. When drift is found, writes a Markdown report to .context/reports/schema-drift.md. When drift resolves, the report is automatically deleted.

    Designed for interactive use, CI pipelines, and nightly cron jobs.

    Example:

    ctx journal schema check                    # Current project\nctx journal schema check --all-projects     # All projects\nctx journal schema check --quiet            # Exit code only\nctx journal schema check --dir /path/to     # Custom directory\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-dump","level":4,"title":"ctx journal schema dump","text":"

    Print the embedded JSONL schema definition.

    ctx journal schema dump\n

    Shows all known record types with their required and optional fields, and all recognized content block types with their parse status. Useful for inspecting what the schema validator expects.

    Example:

    ctx journal schema dump\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-serve","level":3,"title":"ctx serve","text":"

    Serve any zensical directory locally. This is a serve-only command: It does not generate or regenerate site content.

    ctx serve [directory]\n

    If no directory is specified, defaults to the journal site (.context/journal-site).

    Requires zensical to be installed:

    pipx install zensical\n

    ctx serve vs. ctx journal site --serve

    ctx journal site --serve generates the journal site then serves it: an all-in-one command. ctx serve only serves an existing directory, and works with any zensical site (journal, docs, etc.).

    Example:

    ctx serve                        # Serve journal site (no regeneration)\nctx serve .context/journal-site  # Same, explicit path\nctx serve ./site                 # Serve the docs site\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/loop/","level":1,"title":"Loop","text":"","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/loop/#ctx-loop","level":2,"title":"ctx loop","text":"

    Generate a shell script for running an autonomous loop.

    An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.

    ctx loop [flags]\n

    Flags:

    Flag Short Description Default --tool <tool> -t AI tool: claude, aider, or generic claude --prompt <file> -p Prompt file to use .context/loop.md --max-iterations <n> -n Maximum iterations (0 = unlimited) 0 --completion <signal> -c Completion signal to detect SYSTEM_CONVERGED --output <file> -o Output script filename loop.sh

    Examples:

    # Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n

    Running the generated loop:

    ctx loop\nchmod +x loop.sh\n./loop.sh\n

    See also: Autonomous Loops for the full workflow.

    ","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/mcp/","level":1,"title":"MCP Server","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp","level":2,"title":"ctx mcp","text":"

    Run ctx as a Model Context Protocol (MCP) server. MCP is a standard protocol that lets AI tools discover and consume context from external sources via JSON-RPC 2.0 over stdin/stdout.

    This makes ctx accessible to any MCP-compatible AI tool without custom hooks or integrations:

    • Claude Desktop
    • Cursor
    • Windsurf
    • VS Code Copilot
    • Any tool supporting MCP
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp-serve","level":3,"title":"ctx mcp serve","text":"

    Start the MCP server. This command reads JSON-RPC 2.0 requests from stdin and writes responses to stdout. It is intended to be launched by MCP clients (Claude Desktop, Cursor, VS Code Copilot), not run directly from a shell. See Configuration below for how each host launches it.

    Flags: None. The server uses the declared context directory from CTX_DIR. As with every other ctx command, that variable must be set: the server does not walk the filesystem.

    Examples:

    # Normal invocation (by an MCP client via stdio transport)\nctx mcp serve\n\n# Pin a context directory for a specific workspace\nCTX_DIR=/path/to/project/.context ctx mcp serve\n\n# Verify the binary starts without a client attached (Ctrl-C to exit)\nctx mcp serve < /dev/null\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#configuration","level":2,"title":"Configuration","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#claude-desktop","level":3,"title":"Claude Desktop","text":"

    Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

    {\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#cursor","level":3,"title":"Cursor","text":"

    Add to .cursor/mcp.json in your project:

    {\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#vs-code-copilot","level":3,"title":"VS Code (Copilot)","text":"

    Add to .vscode/mcp.json:

    {\n  \"servers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resources","level":2,"title":"Resources","text":"

    Resources expose context files as read-only content. Each resource has a URI, name, and returns Markdown text.

    URI Name Description ctx://context/constitution constitution Hard rules that must never be violated ctx://context/tasks tasks Current work items and their status ctx://context/conventions conventions Code patterns and standards ctx://context/architecture architecture System architecture documentation ctx://context/decisions decisions Architectural decisions with rationale ctx://context/learnings learnings Gotchas, tips, and lessons learned ctx://context/glossary glossary Project-specific terminology ctx://context/agent agent All files assembled in priority read order

    The agent resource assembles all non-empty context files into a single Markdown document, ordered by the configured read priority.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resource-subscriptions","level":3,"title":"Resource Subscriptions","text":"

    Clients can subscribe to resource changes via resources/subscribe. The server polls for file mtime changes (default: 5 seconds) and emits notifications/resources/updated when a subscribed file changes on disk.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#tools","level":2,"title":"Tools","text":"

    Tools expose ctx commands as callable operations. Each tool accepts JSON arguments and returns text results.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_status","level":3,"title":"ctx_status","text":"

    Show context health: file count, token estimate, and per-file summary.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_add","level":3,"title":"ctx_add","text":"

    Add a task, decision, learning, or convention to the context.

    Argument Type Required Description type string Yes Entry type: task, decision, learning, convention content string Yes Title or main content priority string No Priority level (tasks only): high, medium, low context string Conditional Context field (decisions and learnings) rationale string Conditional Rationale (decisions only) consequence string Conditional Consequence (decisions only) lesson string Conditional Lesson learned (learnings only) application string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_complete","level":3,"title":"ctx_complete","text":"

    Mark a task as done by number or text match.

    Argument Type Required Description query string Yes Task number (e.g. \"1\") or search text","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_drift","level":3,"title":"ctx_drift","text":"

    Detect stale or invalid context. Returns violations, warnings, and passed checks.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_journal_source","level":3,"title":"ctx_journal_source","text":"

    Query recent AI session history (summaries, decisions, topics).

    Argument Type Required Description limit number No Max sessions to return (default: 5) since string No ISO date filter: sessions after this date (YYYY-MM-DD)

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_watch_update","level":3,"title":"ctx_watch_update","text":"

    Apply a structured context update to .context/ files. Supports task, decision, learning, convention, and complete entry types. Human confirmation is required before calling.

    Argument Type Required Description type string Yes Entry type: task, decision, learning, convention, complete content string Yes Main content context string Conditional Context background (decisions/learnings) rationale string Conditional Rationale (decisions only) consequence string Conditional Consequence (decisions only) lesson string Conditional Lesson learned (learnings only) application string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_compact","level":3,"title":"ctx_compact","text":"

    Move completed tasks to the archive section and remove empty sections from context files. Human confirmation required.

    Argument Type Required Description archive boolean No Also write tasks to .context/archive/ (default: false)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_next","level":3,"title":"ctx_next","text":"

    Suggest the next pending task based on priority and position.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_check_task_completion","level":3,"title":"ctx_check_task_completion","text":"

    Advisory check: after a write operation, detect if any pending tasks were silently completed. Returns nudge text if a match is found.

    Argument Type Required Description recent_action string No Brief description of what was just done

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_event","level":3,"title":"ctx_session_event","text":"

    Signal a session lifecycle event. Type end triggers the session-end persistence ceremony - human confirmation required.

    Argument Type Required Description type string Yes Event type: start, end caller string No Caller identifier (cursor, windsurf, vscode, claude-desktop)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_steering_get","level":3,"title":"ctx_steering_get","text":"

    Retrieve applicable steering files for a prompt. Without a prompt, returns always-included files only.

    Argument Type Required Description prompt string No Prompt text to match against steering file descriptions

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_search","level":3,"title":"ctx_search","text":"

    Search across .context/ files for a query string. Returns matching lines with file paths and line numbers.

    Argument Type Required Description query string Yes Search string to match against

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_start","level":3,"title":"ctx_session_start","text":"

    Execute session-start hooks and return aggregated context from hook outputs.

    Arguments: None.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_end","level":3,"title":"ctx_session_end","text":"

    Execute session-end hooks with an optional summary. Returns aggregated context from hook outputs.

    Argument Type Required Description summary string No Session summary passed to hook scripts","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_remind","level":3,"title":"ctx_remind","text":"

    List pending session-scoped reminders.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#prompts","level":2,"title":"Prompts","text":"

    Prompts provide pre-built templates for common workflows. Clients can list available prompts via prompts/list and retrieve a specific prompt via prompts/get.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-session-start","level":3,"title":"ctx-session-start","text":"

    Load full context at the beginning of a session. Returns all context files assembled in priority read order with session orientation instructions.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-decision-add","level":3,"title":"ctx-decision-add","text":"

    Format an architectural decision entry with all required fields.

    Argument Type Required Description content string Yes Decision title context string Yes Background context rationale string Yes Why this decision was made consequence string Yes Expected consequence","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-learning-add","level":3,"title":"ctx-learning-add","text":"

    Format a learning entry with all required fields.

    Argument Type Required Description content string Yes Learning title context string Yes Background context lesson string Yes The lesson learned application string Yes How to apply this lesson","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-reflect","level":3,"title":"ctx-reflect","text":"

    Guide end-of-session reflection. Returns a structured review prompt covering progress assessment and context update recommendations.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-checkpoint","level":3,"title":"ctx-checkpoint","text":"

    Report session statistics: tool calls made, entries added, and pending updates queued during the current session.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/memory/","level":1,"title":"Memory","text":"","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory","level":2,"title":"ctx memory","text":"

    Bridge Claude Code's auto memory (MEMORY.md) into .context/.

    Claude Code maintains per-project auto memory at ~/.claude/projects/<slug>/memory/MEMORY.md. This command group discovers that file, mirrors it into .context/memory/mirror.md (git-tracked), and detects drift.

    ctx memory <subcommand>\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-sync","level":3,"title":"ctx memory sync","text":"

    Copy MEMORY.md to .context/memory/mirror.md. Archives the previous mirror before overwriting.

    ctx memory sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would happen without writing

    Exit codes:

    Code Meaning 0 Synced successfully 1 MEMORY.md not found (auto memory inactive)

    Examples:

    ctx memory sync\n# Archived previous mirror to mirror-2026-03-05-143022.md\n# Synced MEMORY.md -> .context/memory/mirror.md\n#   Source: ~/.claude/projects/-home-user-project/memory/MEMORY.md\n#   Lines: 47 (was 32)\n#   New content: 15 lines since last sync\n\nctx memory sync --dry-run\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-status","level":3,"title":"ctx memory status","text":"

    Show drift, timestamps, line counts, and archive count.

    ctx memory status\n

    Exit codes:

    Code Meaning 0 No drift 1 MEMORY.md not found 2 Drift detected (MEMORY.md changed since sync)

    Examples:

    ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines (modified since last sync)\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-diff","level":3,"title":"ctx memory diff","text":"

    Show what changed in MEMORY.md since last sync.

    ctx memory diff\n

    Examples:

    ctx memory diff\n# --- .context/memory/mirror.md (mirror)\n# +++ ~/.claude/projects/.../memory/MEMORY.md (source)\n# +- new learning: memory bridge works\n

    No output when files are identical.

    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-publish","level":3,"title":"ctx memory publish","text":"

    Push curated .context/ content into MEMORY.md so the agent sees it natively.

    ctx memory publish [flags]\n

    Content is selected in priority order: pending tasks, recent decisions (7 days), key conventions, recent learnings (7 days). Wrapped in <!-- ctx:published --> markers. Claude-owned content outside the markers is preserved.

    Flags:

    Flag Description Default --budget Line budget for published content 80 --dry-run Show what would be published

    Examples:

    ctx memory publish --dry-run\n# Publishing .context/ -> MEMORY.md...\n#   Budget: 80 lines\n#   Published block:\n#     5 pending tasks (from TASKS.md)\n#     3 recent decisions (from DECISIONS.md)\n#     5 key conventions (from CONVENTIONS.md)\n#   Total: 42 lines (within 80-line budget)\n# Dry run - no files written.\n\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter budget\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-unpublish","level":3,"title":"ctx memory unpublish","text":"

    Remove the ctx-managed marker block from MEMORY.md, preserving Claude-owned content.

    Examples:

    ctx memory unpublish\n

    Hook integration: The check-memory-drift hook runs on every prompt and nudges the agent when MEMORY.md has changed since last sync. The nudge fires once per session. See Memory Bridge.

    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-import","level":3,"title":"ctx memory import","text":"

    Classify and promote entries from MEMORY.md into structured .context/ files.

    ctx memory import [flags]\n

    Each entry is classified by keyword heuristics:

    Keywords Target always use, prefer, never use, standard CONVENTIONS.md decided, chose, trade-off, approach DECISIONS.md gotcha, learned, watch out, bug, caveat LEARNINGS.md todo, need to, follow up TASKS.md Everything else Skipped

    Deduplication prevents re-importing the same entry across runs.

    Flags:

    Flag Description --dry-run Show classification plan without writing

    Examples:

    ctx memory import --dry-run\n# Scanning MEMORY.md for new entries...\n#   Found 6 entries\n#\n#   -> \"always use ctx from PATH\"\n#      Classified: CONVENTIONS.md (keywords: always use)\n#\n#   -> \"decided to use heuristic classification over LLM-based\"\n#      Classified: DECISIONS.md (keywords: decided)\n#\n# Dry run - would import: 4 entries\n# Skipped: 2 entries (session notes/unclassified)\n\nctx memory import    # Actually write entries to .context/ files\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/message/","level":1,"title":"Message","text":"","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message","level":3,"title":"ctx hook message","text":"

    Manage hook message templates.

    Hook messages control the text hooks emit. The hook logic (when to fire, counting, state tracking) is universal; the messages are opinions that can be customized per-project.

    ctx hook message <subcommand>\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-list","level":3,"title":"ctx hook message list","text":"

    Show all hook messages with category and override status.

    ctx hook message list [--json]\n

    Flags:

    Flag Description --json Output in JSON format

    Example:

    ctx hook message list\nctx hook message list --json | jq '.[] | select(.override)'\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-show","level":3,"title":"ctx hook message show","text":"

    Print the effective message template for a hook/variant pair. Shows the user override if present, otherwise the embedded default.

    ctx hook message show <hook> <variant>\n

    Example:

    ctx hook message show qa-reminder gate\nctx hook message show check-context-size checkpoint\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-edit","level":3,"title":"ctx hook message edit","text":"

    Copy the embedded default template for <hook> <variant> to .context/hooks/messages/<hook>/<variant>.txt so you can edit it directly. The override takes effect the next time the hook fires.

    ctx hook message edit <hook> <variant>\n

    If an override already exists, the command fails and directs you to edit it in place or reset it first.

    Example:

    ctx hook message edit qa-reminder gate\n# Edit .context/hooks/messages/qa-reminder/gate.txt in your editor\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-reset","level":3,"title":"ctx hook message reset","text":"

    Delete a user override and revert to the embedded default. Silent no-op if no override exists.

    ctx hook message reset <hook> <variant>\n

    Example:

    ctx hook message reset qa-reminder gate\n

    See Customizing hook messages for the full workflow.

    ","path":["Message"],"tags":[]},{"location":"cli/notify/","level":1,"title":"Notify","text":"","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify","level":2,"title":"ctx hook notify","text":"

    Send fire-and-forget webhook notifications from skills, loops, and hooks.

    ctx hook notify --event <name> [--session-id <id>] \"message\"\n

    Flags:

    Flag Short Description --event -e Event name (required) --session-id -s Session ID (optional)

    Behavior:

    • No webhook configured: silent no-op (exit 0)
    • Webhook set but event not in events list: silent no-op (exit 0)
    • Webhook set and event matches: fire-and-forget HTTP POST
    • HTTP errors silently ignored (no retry)

    Examples:

    ctx hook notify --event loop \"Loop completed after 5 iterations\"\nctx hook notify -e nudge -s session-abc \"Context checkpoint at prompt #20\"\n
    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-setup","level":3,"title":"ctx hook notify setup","text":"

    Configure the webhook URL interactively. The URL is encrypted with AES-256-GCM using the encryption key and stored in .context/.notify.enc.

    Examples:

    ctx hook notify setup\n

    The encrypted file is safe to commit. The key (~/.ctx/.ctx.key) lives outside the project and is never committed.

    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-test","level":3,"title":"ctx hook notify test","text":"

    Send a test notification and report the HTTP response status.

    Examples:

    ctx hook notify test\n

    Payload format (JSON POST):

    {\n  \"event\": \"loop\",\n  \"message\": \"Loop completed after 5 iterations\",\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n
    Field Type Description event string Event name from --event flag message string Notification message session_id string Session ID (omitted if empty) timestamp string UTC RFC3339 timestamp project string Project directory name

    See also: Webhook Notifications recipe.

    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/pad/","level":1,"title":"Scratchpad","text":"","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad","level":2,"title":"ctx pad","text":"

    Encrypted scratchpad for sensitive one-liners that travel with the project.

    When invoked without a subcommand, lists all entries.

    ctx pad\nctx pad <subcommand>\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-add","level":3,"title":"ctx pad add","text":"

    Append a new entry to the scratchpad.

    ctx pad add <text>\nctx pad add <label> --file <path>\n

    Flags:

    Flag Short Description --file -f Ingest a file as a blob entry (max 64 KB)

    Examples:

    ctx pad add \"DATABASE_URL=postgres://user:pass@host/db\"\nctx pad add \"deploy config\" --file ./deploy.yaml\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-show","level":3,"title":"ctx pad show","text":"

    Output the raw text of an entry by number. For blob entries, prints decoded file content (or writes to disk with --out).

    ctx pad show <n>\nctx pad show <n> --out <path>\n

    Arguments:

    • n: 1-based entry number

    Flags:

    Flag Description --out Write decoded blob content to a file (blobs only)

    Examples:

    ctx pad show 3\nctx pad show 2 --out ./recovered.yaml\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-rm","level":3,"title":"ctx pad rm","text":"

    Remove one or more entries by stable ID. Supports individual IDs and ranges.

    ctx pad rm <id> [id...]\n

    Arguments:

    • id: One or more entry IDs (e.g., 3, 1 4, 3-5)

    Examples:

    ctx pad rm 2\nctx pad rm 1 4\nctx pad rm 3-5\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-normalize","level":3,"title":"ctx pad normalize","text":"

    Reassign entry IDs as a contiguous sequence 1..N, closing any gaps left by deletions.

    Examples:

    ctx pad normalize\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-edit","level":3,"title":"ctx pad edit","text":"

    Replace, append to, or prepend to an entry.

    ctx pad edit <n> [text]\n

    Arguments:

    • n: 1-based entry number
    • text: Replacement text (mutually exclusive with --append/--prepend)

    Flags:

    Flag Description --append Append text to the end of the entry --prepend Prepend text to the beginning of entry --file Replace blob file content (preserves label) --label Replace blob label (preserves content)

    Examples:

    ctx pad edit 2 \"new text\"\nctx pad edit 2 --append \" suffix\"\nctx pad edit 2 --prepend \"prefix \"\nctx pad edit 1 --file ./v2.yaml\nctx pad edit 1 --label \"new name\"\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-mv","level":3,"title":"ctx pad mv","text":"

    Move an entry from one position to another.

    ctx pad mv <from> <to>\n

    Arguments:

    • from: Source position (1-based)
    • to: Destination position (1-based)

    Examples:

    ctx pad mv 3 1      # promote entry 3 to the top\nctx pad mv 1 5      # bury entry 1 to position 5\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-resolve","level":3,"title":"ctx pad resolve","text":"

    Show both sides of a merge conflict in the encrypted scratchpad.

    Examples:

    ctx pad resolve\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-import","level":3,"title":"ctx pad import","text":"

    Bulk-import lines from a file into the scratchpad. Each non-empty line becomes a separate entry. All entries are written in a single encrypt/write cycle.

    With --blob, import all first-level files from a directory as blob entries. Each file becomes a blob with the filename as its label. Subdirectories and non-regular files are skipped.

    ctx pad import <file>\nctx pad import -              # read from stdin\nctx pad import --blob <dir>   # import directory files as blobs\n

    Arguments:

    • file: Path to a text file, - for stdin, or a directory (with --blob)

    Flags:

    Flag Description --blob Import first-level files from a directory as blobs

    Examples:

    ctx pad import notes.txt\ngrep TODO *.go | ctx pad import -\nctx pad import --blob ./ideas/\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-export","level":3,"title":"ctx pad export","text":"

    Export all blob entries from the scratchpad to a directory as files. Each blob's label becomes the filename. Non-blob entries are skipped.

    ctx pad export [dir]\n

    Arguments:

    • dir: Target directory (default: current directory)

    Flags:

    Flag Short Description --force -f Overwrite existing files instead of timestamping --dry-run Print what would be exported without writing

    When a file already exists, a unix timestamp is prepended to avoid collisions (e.g., 1739836200-label). Use --force to overwrite instead.

    Examples:

    ctx pad export ./ideas\nctx pad export --dry-run\nctx pad export --force ./backup\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-merge","level":3,"title":"ctx pad merge","text":"

    Merge entries from one or more scratchpad files into the current pad. Each input file is auto-detected as encrypted or plaintext. Entries are deduplicated by exact content.

    ctx pad merge FILE...\n

    Arguments:

    • FILE...: One or more scratchpad files to merge (encrypted or plaintext)

    Flags:

    Flag Short Description --key -k Path to key file for decrypting input files --dry-run Print what would be merged without writing

    Examples:

    ctx pad merge worktree/.context/scratchpad.enc\nctx pad merge notes.md backup.enc\nctx pad merge --key /path/to/other.key foreign.enc\nctx pad merge --dry-run pad-a.enc pad-b.md\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pause/","level":1,"title":"Pause","text":"","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/pause/#ctx-hook-pause","level":2,"title":"ctx hook pause","text":"

    Pause all context nudge and reminder hooks for the current session. Security hooks (dangerous command blocking) and housekeeping hooks still fire.

    ctx hook pause [flags]\n

    Flags:

    Flag Description --session-id Session ID (overrides stdin)

    Example:

    # Pause hooks for a quick investigation\nctx hook pause\n\n# Resume when ready\nctx hook resume\n

    See also:

    • ctx hook resume: the matching resume command
    • Pausing Context Hooks recipe
    ","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/prune/","level":1,"title":"Prune","text":"","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/prune/#ctx-prune","level":3,"title":"ctx prune","text":"

    Remove per-session state files from .context/state/ that are older than the specified age. Session state files are identified by UUID suffixes (context-check-<session-id>, heartbeat-<session-id>, and similar). Global files without session IDs (events.jsonl, memory-import.json, and other non-per-session markers) are always preserved.

    ctx prune [flags]\n

    Flags:

    Flag Description --days Prune files older than this many days (default: 7) --dry-run Show what would be pruned without deleting

    Examples:

    ctx prune                 # Prune files older than 7 days\nctx prune --days 3        # Prune files older than 3 days\nctx prune --dry-run       # Preview without deleting\n

    See State maintenance for the recommended cadence and automation recipe.

    ","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/remind/","level":1,"title":"Remind","text":"","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind","level":2,"title":"ctx remind","text":"

    Session-scoped reminders that surface at session start. Reminders are stored verbatim and relayed verbatim: no summarization, no categories.

    When invoked with a text argument and no subcommand, adds a reminder.

    ctx remind \"text\"\nctx remind <subcommand>\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-add","level":3,"title":"ctx remind add","text":"

    Add a reminder. This is the default action: ctx remind \"text\" and ctx remind add \"text\" are equivalent.

    ctx remind \"refactor the swagger definitions\"\nctx remind add \"check CI after the deploy\" --after 2026-02-25\n

    Arguments:

    • text: The reminder message (verbatim)

    Flags:

    Flag Short Description --after -a Don't surface until this date (YYYY-MM-DD)

    Examples:

    ctx remind \"refactor the swagger definitions\"\nctx remind \"check CI after the deploy\" --after 2026-02-25\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-list","level":3,"title":"ctx remind list","text":"

    List all pending reminders. Date-gated reminders that aren't yet due are annotated with (after DATE, not yet due).

    Examples:

    ctx remind list\nctx remind ls            # alias\n

    Aliases: ls

    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-dismiss","level":3,"title":"ctx remind dismiss","text":"

    Remove one or more reminders by ID, or remove all with --all. Supports individual IDs and ranges.

    ctx remind dismiss <id> [id...]\nctx remind dismiss --all\n

    Arguments:

    • id: One or more reminder IDs (e.g., 3, 3 5-7)

    Flags:

    Flag Description --all Dismiss all reminders

    Aliases: rm

    Examples:

    ctx remind dismiss 3\nctx remind dismiss 3 5-7\nctx remind dismiss --all\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-normalize","level":3,"title":"ctx remind normalize","text":"

    Reassign reminder IDs as a contiguous sequence 1..N, closing any gaps left by dismissals.

    Examples:

    ctx remind normalize\n

    See also: Session Reminders recipe.

    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/resume/","level":1,"title":"Resume","text":"","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/resume/#ctx-hook-resume","level":2,"title":"ctx hook resume","text":"

    Resume context hooks after a pause. Silent no-op if not paused.

    ctx hook resume [flags]\n

    Flags:

    Flag Description --session-id Session ID (overrides stdin)

    Example:

    ctx hook resume\n

    See also:

    • ctx hook pause: the matching pause command
    • Pausing Context Hooks recipe
    ","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/serve/","level":1,"title":"Serve","text":"","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#ctx-serve","level":2,"title":"ctx serve","text":"

    Serve a static site locally via zensical.

    With no argument, serves the journal site at .context/journal-site. With a directory argument, serves that directory if it contains a zensical.toml.

    ctx serve                             # Serve .context/journal-site\nctx serve ./my-site                   # Serve a specific directory\nctx serve ./docs                      # Serve any zensical site\n

    This Command Does NOT Start a Hub

    ctx serve is purely for static-site serving. To run a ctx Hub for cross-project knowledge sharing, use ctx hub start. That command lives in its own group because the hub is a gRPC server, not a static site.

    Requires zensical to be installed:

    pipx install zensical\n
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#arguments","level":3,"title":"Arguments","text":"Argument Description [directory] Directory containing a zensical.toml to serve

    When omitted, serves .context/journal-site by default, the directory produced by ctx journal site.

    Examples:

    ctx serve                         # Default: serve .context/journal-site\nctx serve ./my-site               # Serve a specific directory\nctx serve ./docs                  # Serve any zensical site\n
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#see-also","level":3,"title":"See Also","text":"
    • ctx journal: generate the journal site that ctx serve displays.
    • ctx hub start: for running a ctx Hub server, not a static site.
    • Browsing and enriching past sessions: the recipe that combines ctx journal and ctx serve.
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/setup/","level":1,"title":"Setup","text":"","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/setup/#ctx-setup","level":2,"title":"ctx setup","text":"

    Generate AI tool integration configuration.

    ctx setup <tool> [flags]\n

    Flags:

    Flag Short Description --write -w Write the generated config to disk (e.g. .github/copilot-instructions.md)

    Supported tools:

    Tool Description claude-code Redirects to plugin install instructions cursor Cursor IDE kiro Kiro IDE cline Cline (VS Code extension) aider Aider CLI copilot GitHub Copilot opencode OpenCode (terminal-first AI coding agent) windsurf Windsurf IDE

    Claude Code Uses the Plugin System

    Claude Code integration is now provided via the ctx plugin. Running ctx setup claude-code prints plugin install instructions.

    Examples:

    # Print hook instructions to stdout\nctx setup cursor\nctx setup aider\n\n# Generate and write .github/copilot-instructions.md\nctx setup copilot --write\n\n# Generate MCP config and sync steering files\nctx setup kiro --write\nctx setup cursor --write\nctx setup cline --write\n\n# Generate OpenCode plugin, skills, AGENTS.md, and global MCP config\nctx setup opencode --write\n
    ","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/site/","level":1,"title":"Site","text":"","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site","level":2,"title":"ctx site","text":"

    Site management commands for the ctx.ist static site.

    ctx site <subcommand>\n
    ","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site-feed","level":3,"title":"ctx site feed","text":"

    Generate an Atom 1.0 feed from finalized blog posts in docs/blog/.

    ctx site feed [flags]\n

    Scans docs/blog/ for files matching YYYY-MM-DD-*.md, parses YAML frontmatter, and generates a valid Atom feed. Only posts with reviewed_and_finalized: true are included. Summaries are extracted from the first paragraph after the heading.

    Flags:

    Flag Short Type Default Description --out -o string site/feed.xml Output path --base-url string https://ctx.ist Base URL for entry links

    Output:

    Generated site/feed.xml (21 entries)\n\nSkipped:\n  2026-02-25-the-homework-problem.md: not finalized\n\nWarnings:\n  2026-02-09-defense-in-depth.md: no summary paragraph found\n

    Three buckets: included (count), skipped (with reason), warnings (included but degraded). exit 0 always: warnings inform but do not block.

    Frontmatter requirements:

    Field Required Feed mapping title Yes <title> date Yes <updated> reviewed_and_finalized Yes Draft gate (must be true) author No <author><name> topics No <category term=\"\">

    Examples:

    ctx site feed                                # Generate site/feed.xml\nctx site feed --out /tmp/feed.xml            # Custom output path\nctx site feed --base-url https://example.com # Custom base URL\nmake site-feed                               # Makefile shortcut\nmake site                                    # Builds site + feed\n
    ","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/skill/","level":1,"title":"Skill","text":"","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill","level":2,"title":"ctx skill","text":"

    Manage reusable instruction bundles that can be installed into .context/skills/.

    A skill is a directory containing a SKILL.md file with YAML frontmatter (name, description) and a Markdown instruction body. Skills are loaded by the agent context packet when --skill <name> is passed to ctx agent.

    ctx skill <subcommand>\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-install","level":3,"title":"ctx skill install","text":"

    Install a skill from a source directory.

    ctx skill install <source>\n

    Arguments:

    • source: Path to a directory containing SKILL.md

    Examples:

    ctx skill install ./my-skills/code-review\n# Installed code-review → .context/skills/code-review\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-list","level":3,"title":"ctx skill list","text":"

    List all installed skills.

    Examples:

    ctx skill list\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-remove","level":3,"title":"ctx skill remove","text":"

    Remove an installed skill.

    Arguments:

    • name: Skill name to remove

    Examples:

    ctx skill remove code-review\n

    See also: Building Project Skills recipe.

    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/steering/","level":1,"title":"Steering","text":"","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering","level":2,"title":"ctx steering","text":"

    Manage steering files: persistent behavioral rules for AI coding assistants.

    A steering file is a small markdown document with YAML frontmatter that tells the AI how to behave in a specific context. ctx steering keeps those files in .context/steering/, decides which ones apply for a given prompt, and syncs them out to each AI tool's native format (Claude Code, Cursor, Kiro, Cline).

    ctx steering <subcommand>\n

    Steering vs Decisions vs Conventions

    The three look similar on disk but serve different purposes:

    • Decisions record what was chosen and why. Consumed mostly by humans (and by the agent via ctx agent).
    • Conventions describe how the codebase is written. Consumed as reference material.
    • Steering tells the AI how to behave when asked about X. Consumed by the AI tool's prompt injection layer, conditionally on prompt match.

    If you find yourself writing \"the AI should always do X\", that belongs in steering, not decisions.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#anatomy-of-a-steering-file","level":3,"title":"Anatomy of a Steering File","text":"
    ---\nname: security\ndescription: Security rules for all code changes\ninclusion: always    # always | auto | manual\ntools: []            # empty = all tools\npriority: 10         # lower = injected first\n---\n\n# Security rules\n\n- Validate all user input at system boundaries.\n- Never log secrets, tokens, or credentials.\n- Prefer constant-time comparison for tokens.\n

    Inclusion modes:

    Mode When it's included always Every prompt, unconditionally auto When the prompt matches the description keywords manual Only when the user names it explicitly

    Priority: lower numbers inject first, so high-priority rules appear at the top of the prompt. Default is 50.

    Tools: an empty list means all configured tools receive the file; list specific tool names to scope it.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-init","level":3,"title":"ctx steering init","text":"

    Create a starter set of steering files in .context/steering/ to use as a scaffolding baseline.

    Examples:

    ctx steering init\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-add","level":3,"title":"ctx steering add","text":"

    Create a new steering file with default frontmatter.

    ctx steering add <name>\n

    Arguments:

    • name: Steering file name (without .md extension)

    Examples:

    ctx steering add security\n# Created .context/steering/security.md\n

    The generated file uses inclusion: manual and priority: 50 by default. Edit the frontmatter to change behavior.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-list","level":3,"title":"ctx steering list","text":"

    List all steering files with their inclusion mode, priority, and tool scoping.

    Examples:

    ctx steering list\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-preview","level":3,"title":"ctx steering preview","text":"

    Preview which steering files would be included for a given prompt. Useful for validating auto-inclusion descriptions against realistic prompts.

    ctx steering preview [prompt]\n

    Examples:

    ctx steering preview \"create a REST API endpoint\"\n# Steering files matching prompt \"create a REST API endpoint\":\n#   api-standards        inclusion=auto     priority=20  tools=all\n#   security             inclusion=always   priority=10  tools=all\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-sync","level":3,"title":"ctx steering sync","text":"

    Sync steering files to tool-native formats for tools that have a built-in rules primitive. Not every tool needs this; Claude Code and Codex use a different delivery mechanism (see below).

    Examples:

    ctx steering sync\n

    Which tools are sync targets?

    Tool Sync target Mechanism Cursor .cursor/rules/ Cursor reads the directory natively Cline .clinerules/ Cline reads the directory natively Kiro .kiro/steering/ Kiro reads the directory natively Claude Code (no-op) Delivered via hook + MCP (see next section) Codex (no-op) Same as Claude Code

    For the three native-rules tools, ctx steering sync writes each matching steering file to the appropriate directory with tool-specific frontmatter transforms. Unchanged files are skipped (idempotent).

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#how-claude-code-and-codex-consume-steering","level":3,"title":"How Claude Code and Codex Consume Steering","text":"

    Claude Code has no native \"steering files\" primitive, so ctx steering sync skips it entirely. Instead, steering reaches Claude through two non-sync channels, both activated by ctx setup claude-code (which installs the plugin):

    1. Automatic injection via the PreToolUse hook. The Claude Code plugin wires a PreToolUse hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads .context/steering/ and calls steering.Filter with an empty prompt, so only files with inclusion: always match. Those files are included as Tier 6 of the context packet. The packet is printed on stdout, which Claude Code injects as additional context. This fires on every tool call; no user action.

    2. On-demand MCP tool call (ctx_steering_get). The ctx plugin ships a .mcp.json file that automatically registers the ctx MCP server (ctx mcp serve) with Claude Code on plugin install. Once registered, Claude can invoke the ctx_steering_get tool mid-task to fetch matching steering files for a specific prompt. This is the only path that resolves inclusion: auto and inclusion: manual matches for Claude Code; Claude passes the prompt to the MCP tool, which runs the keyword match against each file's description.

    Verify the MCP server is registered:

    claude mcp list\n

    Expected line: ctx: ctx mcp serve - ✓ Connected. If it's missing, reinstall the plugin from Claude Code (/plugin → find ctx → uninstall → install again); older plugin versions shipped without the .mcp.json file.

    Prefer inclusion: always for Claude Code

    Because the PreToolUse hook passes an empty prompt to ctx agent, only always files fire automatically. auto files require Claude to call the ctx_steering_get MCP tool on its own; manual files require an explicit user invocation. For rules that should reliably fire on every Claude Code session, use inclusion: always. Reserve auto/manual for situational libraries where the opt-in cost is acceptable and you understand Claude may not pull them in without prompting.

    The foundation files scaffolded by ctx init already default to inclusion: always for this reason.

    Practical implications:

    • Running ctx steering sync before starting a Claude session does nothing for Claude's benefit. Skip it.
    • ctx steering preview still works for validating your descriptions; it doesn't depend on sync.
    • If Claude Code is your only tool, the ctx steering commands you care about are add, list, preview, init (never sync).
    • If you use both Claude Code and (say) Cursor, ctx steering sync covers Cursor (where auto and manual work natively) while the hook+MCP pipeline covers Claude Code. For rules you need to fire automatically on both, use inclusion: always.
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-agent-integration","level":3,"title":"ctx agent Integration","text":"

    When ctx agent builds a context packet, steering files are loaded as Tier 6 of the budget-aware assembly (see ctx agent). Files with inclusion: always are always included; auto files are scored against the current prompt and included in priority order until the tier budget is exhausted.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#see-also","level":3,"title":"See Also","text":"
    • ctx setup: configure which tools receive steering syncs
    • ctx trigger: lifecycle scripts (a different hooking concept, see below)
    • Building steering files recipe: walkthrough from first file to synced output
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/sysinfo/","level":1,"title":"Sysinfo","text":"","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/sysinfo/#ctx-sysinfo","level":3,"title":"ctx sysinfo","text":"

    Display a snapshot of system resources (memory, swap, disk, load) with threshold-based alert severities. Mirrors what the check-resource hook plumbing monitors in the background, but this command prints the full report at any severity level, not only at DANGER.

    ctx sysinfo [flags]\n

    Flags:

    Flag Description --json Output in JSON format

    Alert thresholds:

    Resource WARNING DANGER Memory ≥ 75% ≥ 90% Swap ≥ 50% ≥ 75% Disk ≥ 85% ≥ 95% Load ≥ 1.0x CPUs ≥ 1.5x CPUs

    Examples:

    ctx sysinfo                  # Human-readable table\nctx sysinfo --json           # Structured output\n
    ","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/system/","level":1,"title":"System","text":"","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system","level":3,"title":"ctx system","text":"

    Hidden parent command that hosts Claude Code hook plumbing and a small set of session-lifecycle plumbing subcommands used by skills and editor integrations. The parent is registered without a visible group in ctx --help; run ctx system --help to see its subcommands.

    ctx system <subcommand>\n

    Commands Previously under ctx system

    Several user-facing maintenance commands used to live under ctx system and were promoted to top-level:

    • ctx system eventsctx hook event
    • ctx system messagectx hook message
    • ctx system prunectx prune
    • ctx system resourcesctx sysinfo
    • ctx system statsctx usage

    ctx system bootstrap remains under ctx system as a hidden, agent-only command. Update any scripts or personal docs that reference the old paths.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#plumbing-subcommands","level":2,"title":"Plumbing Subcommands","text":"

    These are not hook handlers; they're called by skills and editor integrations during the session lifecycle. Safe to run manually.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-journal","level":4,"title":"ctx system mark-journal","text":"

    Update processing state for a journal entry. Records the current date in .context/journal/.state.json. Used by journal skills to record pipeline progress.

    ctx system mark-journal <filename> <stage>\n

    Stages: exported, enriched, normalized, fences_verified

    Flag Description --check Check if stage is set (exit 1 if not)

    Example:

    ctx system mark-journal 2026-01-21-session-abc12345.md enriched\nctx system mark-journal 2026-01-21-session-abc12345.md normalized\nctx system mark-journal --check 2026-01-21-session-abc12345.md fences_verified\n
    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-wrapped-up","level":4,"title":"ctx system mark-wrapped-up","text":"

    Suppress context checkpoint nudges after a wrap-up ceremony. Writes a marker file that check-context-size checks before emitting checkpoint boxes. The marker expires after 2 hours.

    Called automatically by /ctx-wrap-up after persisting context (not intended for direct use).

    ctx system mark-wrapped-up\n

    No flags, no arguments. Idempotent: running it again updates the marker timestamp.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-pause-ctx-system-resume","level":4,"title":"ctx system pause / ctx system resume","text":"

    Session-scoped hook suppression. ctx system pause writes a marker file that causes hook plumbing to no-op for the current session; ctx system resume removes it. These are the hook-plumbing counterparts to the ctx hook pause / ctx hook resume commands (which call them internally).

    Read the session ID from stdin JSON (same as hooks) or pass --session-id.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-session-event","level":4,"title":"ctx system session-event","text":"

    Records a session lifecycle event (start or end) to the event log. Called by editor integrations when a workspace is opened or closed.

    ctx system session-event --type start --caller vscode\nctx system session-event --type end --caller vscode\n
    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#hook-subcommands","level":2,"title":"Hook Subcommands","text":"

    Hidden Claude Code hook handlers implementing the hook contract: read JSON from stdin, perform logic, emit output on stdout, exit 0. Block commands output JSON with a decision field.

    UserPromptSubmit hooks: context-load-gate, check-context-size, check-persistence, check-ceremony, check-journal, check-version, check-resource, check-knowledge, check-map-staleness, check-memory-drift, check-reminder, check-freshness, check-hub-sync, check-skill-discovery, heartbeat.

    PreToolUse hooks: block-non-path-ctx, block-dangerous-command, qa-reminder, specs-nudge.

    PostToolUse hooks: post-commit, check-task-completion.

    See AI Tools for registration details and the Claude Code plugin integration.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/trace/","level":1,"title":"Commit Context Tracing","text":"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace","level":3,"title":"ctx trace","text":"

    Show the context behind git commits. Links commits back to the decisions, tasks, learnings, and sessions that motivated them.

    git log shows what changed, git blame shows who, and ctx trace shows why.

    ctx trace [commit] [flags]\n

    Flags:

    Flag Description --last N Show context for last N commits --json Output as JSON for scripting

    Examples:

    # Show context for a specific commit\nctx trace abc123\n\n# Show context for last 10 commits\nctx trace --last 10\n\n# JSON output\nctx trace abc123 --json\n

    Output:

    Commit: abc123 \"Fix auth token expiry\"\nDate:   2026-03-14 10:00:00 -0700\nContext:\n  [Decision] #12: Use short-lived tokens with server-side refresh\n    Date: 2026-03-10\n\n  [Task] #8: Implement token rotation for compliance\n    Status: completed\n

    When listing recent commits with --last:

    abc123  Fix auth token expiry         decision:12, task:8\ndef456  Add rate limiting             decision:15, learning:7\n789abc  Update dependencies           (none)\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-file","level":3,"title":"ctx trace file","text":"

    Show the context trail for a file. Combines git log with context resolution.

    ctx trace file <path[:line-range]> [flags]\n

    Flags:

    Flag Description --last N Maximum commits to show (default: 20)

    Examples:

    # Show context trail for a file\nctx trace file src/auth.go\n\n# Show context for specific line range\nctx trace file src/auth.go:42-60\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-tag","level":3,"title":"ctx trace tag","text":"

    Manually tag a commit with context. For commits made without the hook, or to add extra context after the fact.

    Tags are stored in .context/trace/overrides.jsonl since git trailers cannot be added to existing commits without rewriting history.

    ctx trace tag <commit> --note \"<text>\"\n

    Examples:

    ctx trace tag HEAD --note \"Hotfix for production outage\"\nctx trace tag abc123 --note \"Part of Q1 compliance initiative\"\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-hook","level":3,"title":"ctx trace hook","text":"

    Enable or disable the prepare-commit-msg hook for automatic context tracing. When enabled, commits automatically receive a ctx-context trailer with references to relevant decisions, tasks, learnings, and sessions.

    ctx trace hook <enable|disable>\n

    Prerequisites: ctx must be on your $PATH. If you installed via go install, ensure $GOPATH/bin (or $HOME/go/bin) is in your shell's $PATH.

    What the hook does:

    1. Before each commit, collects context from three sources:
    2. Pending context accumulated during work (ctx add, ctx task complete)
    3. Staged file changes to .context/ files
    4. Working state (in-progress tasks, active AI session)
    5. Injects a ctx-context trailer into the commit message
    6. After commit, records the mapping in .context/trace/history.jsonl

    Examples:

    # Install the hook\nctx trace hook enable\n\n# Remove the hook\nctx trace hook disable\n

    Resulting commit message:

    Fix auth token expiry handling\n\nRefactored token refresh logic to handle edge case\nwhere refresh token expires during request.\n\nctx-context: decision:12, task:8, session:abc123\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#reference-types","level":3,"title":"Reference Types","text":"

    The ctx-context trailer supports these reference types:

    Prefix Points to Example decision:<n> Entry #n in DECISIONS.md decision:12 learning:<n> Entry #n in LEARNINGS.md learning:5 task:<n> Task #n in TASKS.md task:8 convention:<n> Entry #n in CONVENTIONS.md convention:3 session:<id> AI session by ID session:abc123 \"<text>\" Free-form context note \"Performance fix for P1 incident\"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#storage","level":3,"title":"Storage","text":"

    Context trace data is stored in the .context/ directory:

    File Purpose Lifecycle state/pending-context.jsonl Accumulates refs during work Truncated after each commit trace/history.jsonl Permanent commit-to-context map Append-only, never truncated trace/overrides.jsonl Manual tags for existing commits Append-only","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trigger/","level":1,"title":"Trigger","text":"","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger","level":2,"title":"ctx trigger","text":"

    Manage lifecycle triggers: executable scripts that fire at specific events during an AI session. Triggers can block tool calls, inject context, and automate reactions: any side effect you want at session boundaries, tool boundaries, or file-save events.

    ctx trigger <subcommand>\n

    Triggers Execute Arbitrary Scripts

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks: only enable scripts you've read and understand. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#where-triggers-live","level":3,"title":"Where Triggers Live","text":"

    Triggers live in .context/hooks/<trigger-type>/ as executable scripts. The on-disk directory name is still hooks/ for historical reasons even though the command is ctx trigger. Each script:

    • Reads a JSON payload from stdin.
    • Returns a JSON payload on stdout.
    • Returns a non-zero exit code to block or error.
    .context/\n└── hooks/\n    ├── session-start/\n    │   └── inject-context.sh\n    ├── pre-tool-use/\n    │   └── block-legacy.sh\n    └── post-tool-use/\n        └── record-edit.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#trigger-types","level":3,"title":"Trigger Types","text":"Type Fires when session-start An AI session begins session-end An AI session ends pre-tool-use Before an AI tool call is executed post-tool-use After an AI tool call returns file-save When a file is saved context-add When a context entry is added","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#input-and-output-contract","level":3,"title":"Input and Output Contract","text":"

    Each trigger receives a JSON object on stdin with the event details. Minimal contract (fields vary by trigger type):

    {\n  \"type\": \"pre-tool-use\",\n  \"tool\": \"write_file\",\n  \"path\": \"src/auth.go\",\n  \"session_id\": \"abc123-...\"\n}\n

    The trigger may write a JSON object to stdout to influence behavior. Example for a blocking pre-tool-use trigger:

    {\n  \"action\": \"block\",\n  \"message\": \"Editing src/auth.go requires approval from #security\"\n}\n

    For non-blocking event loggers, simply read stdin and exit 0 without writing to stdout.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-add","level":3,"title":"ctx trigger add","text":"

    Create a new trigger script with a template. The generated file has a bash shebang, a stdin reader using jq, and a basic JSON output structure.

    ctx trigger add <trigger-type> <name>\n

    Arguments:

    • trigger-type: One of session-start, session-end, pre-tool-use, post-tool-use, file-save, context-add
    • name: Script name (without .sh extension)

    Examples:

    ctx trigger add session-start inject-context\n# Created .context/hooks/session-start/inject-context.sh\n\nctx trigger add pre-tool-use block-legacy\n# Created .context/hooks/pre-tool-use/block-legacy.sh\n

    The generated script is not executable by default. Enable it with ctx trigger enable after reviewing the contents.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-list","level":3,"title":"ctx trigger list","text":"

    List all discovered triggers, grouped by trigger type, with their enabled/disabled status.

    Examples:

    ctx trigger list\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-test","level":3,"title":"ctx trigger test","text":"

    Run all enabled triggers of a given type against a mock payload. Use --tool and --path to customize the mock input for tool-related events.

    ctx trigger test <trigger-type> [flags]\n

    Flags:

    Flag Description --tool Tool name to put in mock input --path File path to put in mock input

    Examples:

    ctx trigger test session-start\nctx trigger test pre-tool-use --tool write_file --path src/main.go\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-enable","level":3,"title":"ctx trigger enable","text":"

    Enable a trigger by setting its executable permission bit. Searches every trigger-type directory for a script matching <name>.

    ctx trigger enable <name>\n

    Examples:

    ctx trigger enable inject-context\n# Enabled .context/hooks/session-start/inject-context.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-disable","level":3,"title":"ctx trigger disable","text":"

    Disable a trigger by clearing its executable permission bit. Searches every trigger-type directory for a script matching <name>.

    ctx trigger disable <name>\n

    Examples:

    ctx trigger disable inject-context\n# Disabled .context/hooks/session-start/inject-context.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#three-hooking-concepts-in-ctx-dont-confuse-them","level":3,"title":"Three Hooking Concepts in ctx (Don't Confuse Them)","text":"

    This is a common source of confusion. ctx has three distinct hook-like layers, and they serve different purposes:

    Layer Owned by Where it runs Configured via ctx trigger You .context/hooks/<type>/*.sh ctx trigger add/enable ctx system hooks ctx itself built-in, called by ctx's own lifecycle internal (see ctx system --help) Claude Code hooks Claude Code .claude/settings.local.json edit JSON, or /ctx-sanitize-permissions

    Use ctx trigger when you want project-specific automation that your AI tool will run at lifecycle events. Use Claude Code hooks for tool-specific integrations that don't need to be portable across tools. ctx system hooks are not something you author; they're the internal nudge machinery that ships with ctx.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#see-also","level":3,"title":"See Also","text":"
    • ctx steering: persistent AI behavioral rules (a different concept; rules vs scripts)
    • Authoring triggers recipe: a full walkthrough with security guidance
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/usage/","level":1,"title":"Usage","text":"","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/usage/#ctx-usage","level":3,"title":"ctx usage","text":"

    Display per-session token usage statistics from the local stats JSONL files written by the heartbeat hook. By default, shows the last 20 entries across all sessions. Use --follow to stream new entries as they arrive (like tail -f).

    ctx usage [flags]\n

    Flags:

    Flag Description -f, --follow Stream new entries as they arrive -s, --session Filter by session ID (prefix match) -n, --last Show last N entries (default: 20) -j, --json Output raw JSONL

    Examples:

    ctx usage                     # Last 20 entries across all sessions\nctx usage --follow            # Live stream (like tail -f)\nctx usage --session abc123    # Filter to one session\nctx usage --last 100 --json   # Last 100 as raw JSONL\n
    ","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/watch/","level":1,"title":"Watch","text":"","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/watch/#ctx-watch","level":2,"title":"ctx watch","text":"

    Watch for AI output and auto-apply context updates.

    Parses <context-update> XML commands from AI output and applies them to context files.

    ctx watch [flags]\n

    Flags:

    Flag Description --log <file> Log file to watch (default: stdin) --dry-run Preview updates without applying

    Examples:

    # Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/why/","level":1,"title":"Why","text":"","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"cli/why/#ctx-why","level":2,"title":"ctx why","text":"

    Read ctx's philosophy documents directly in the terminal.

    ctx why [DOCUMENT]\n

    Documents:

    Name Description manifesto The ctx Manifesto: creation, not code about About ctx: what it is and why it exists invariants Design invariants: properties that must hold

    Examples:

    # Interactive numbered menu\nctx why\n\n# Show a specific document\nctx why manifesto\nctx why about\nctx why invariants\n\n# Pipe to a pager\nctx why manifesto | less\n
    ","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"home/","level":1,"title":"Home","text":"
    • ctx is not a prompt.
    • ctx is version-controlled cognitive state.

    ctx is the persistence layer for human-AI reasoning.

    Deterministic. Git-native. Human-readable. Local-first.

    Start here.

    Learn what ctx does, set it up, and run your first session.

    Pre-1.0: Moving Fast

    ctx is under active development. This website tracks the development branch, not the latest release:

    Some features described here may not exist in the binary you have installed.

    Expect rough edges.

    If something is missing or broken, open an issue.

    ","path":["Home"],"tags":[]},{"location":"home/#introduction","level":2,"title":"Introduction","text":"","path":["Home"],"tags":[]},{"location":"home/#about","level":3,"title":"About","text":"

    What ctx is, how it works, and why persistent context changes how you work with AI.

    ","path":["Home"],"tags":[]},{"location":"home/#is-it-right-for-me","level":3,"title":"Is It Right for Me?","text":"

    Good fit, not-so-good fit, and a 5-minute trial to find out for yourself.

    ","path":["Home"],"tags":[]},{"location":"home/#faq","level":3,"title":"FAQ","text":"

    Quick answers to the questions newcomers ask most about ctx, files, tooling, and trade-offs.

    ","path":["Home"],"tags":[]},{"location":"home/#get-started","level":2,"title":"Get Started","text":"","path":["Home"],"tags":[]},{"location":"home/#getting-started","level":3,"title":"Getting Started","text":"

    Install the binary, set up the plugin, and verify it works.

    ","path":["Home"],"tags":[]},{"location":"home/#your-first-session","level":3,"title":"Your First Session","text":"

    Step-by-step walkthrough from ctx init to verified recall.

    ","path":["Home"],"tags":[]},{"location":"home/#common-workflows","level":3,"title":"Common Workflows","text":"

    Day-to-day commands for tracking context, checking health, and browsing history.

    ","path":["Home"],"tags":[]},{"location":"home/#concepts","level":2,"title":"Concepts","text":"","path":["Home"],"tags":[]},{"location":"home/#context-files","level":3,"title":"Context Files","text":"

    What each .context/ file does. What's their purpose. How do we best leverage them.

    ","path":["Home"],"tags":[]},{"location":"home/#configuration","level":3,"title":"Configuration","text":"

    Flexible configuration: .ctxrc, environment variables, and CLI flags.

    ","path":["Home"],"tags":[]},{"location":"home/#hub","level":3,"title":"Hub","text":"

    A fan-out channel for decisions, learnings, conventions, and tasks that need to cross project boundaries, without replicating everything else.

    ","path":["Home"],"tags":[]},{"location":"home/#working-with-ai","level":2,"title":"Working with AI","text":"","path":["Home"],"tags":[]},{"location":"home/#prompting-guide","level":3,"title":"Prompting Guide","text":"

    Effective prompts for AI sessions with ctx.

    ","path":["Home"],"tags":[]},{"location":"home/#keeping-ai-honest","level":3,"title":"Keeping AI Honest","text":"

    AI agents confabulate: they invent history, claim familiarity with decisions never made, and sometimes declare tasks complete when they aren't. Tools and habits to push back.

    ","path":["Home"],"tags":[]},{"location":"home/#my-ai-keeps-making-the-same-mistakes","level":3,"title":"My AI Keeps Making the Same Mistakes","text":"

    Stop rediscovering the same bugs and dead-ends across sessions.

    ","path":["Home"],"tags":[]},{"location":"home/#joining-a-project","level":3,"title":"Joining a Project","text":"

    You inherited a .context/ directory. Get oriented fast: priority order, what to read first, how to ramp up.

    ","path":["Home"],"tags":[]},{"location":"home/#customization","level":2,"title":"Customization","text":"","path":["Home"],"tags":[]},{"location":"home/#steering-files","level":3,"title":"Steering Files","text":"

    Tell the assistant how to behave when a specific kind of prompt arrives.

    ","path":["Home"],"tags":[]},{"location":"home/#lifecycle-triggers","level":3,"title":"Lifecycle Triggers","text":"

    Make things happen at session boundaries: block dangerous tool calls, inject standup notes, log file saves.

    ","path":["Home"],"tags":[]},{"location":"home/#community","level":2,"title":"Community","text":"","path":["Home"],"tags":[]},{"location":"home/#ctx","level":3,"title":"#ctx","text":"

    We are the builders who care about durable context. Join the community. Hang out in IRC. Star ctx on GitHub.

    ","path":["Home"],"tags":[]},{"location":"home/#contributing","level":3,"title":"Contributing","text":"

    Development setup, project layout, and pull request process.

    ","path":["Home"],"tags":[]},{"location":"home/about/","level":1,"title":"About","text":"

    \"Creation, not code; Context, not prompts; Verification, not vibes.\"

    Read the ctx Manifesto →

    \"Without durable context, intelligence resets; with ctx, creation compounds.\"

    Without persistent memory, every session starts at zero; ctx makes sessions cumulative.

    Join the ctx Community →

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#what-is-ctx","level":2,"title":"What Is ctx?","text":"

    ctx (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. It lives in a .context/ directory in your repo.

    • A session is interactive.
    • ctx enables cognitive continuity.
    • Cognitive continuity enables durable, symbiotic-like human-AI workflows.

    Context Files

    Context files let AI tools remember decisions, conventions, and learnings:

    Context files are explicit and versionable contracts between you and your agents.

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#why-do-i-keep-re-explaining-my-codebase","level":2,"title":"Why Do I Keep Re-Explaining My Codebase?!?!","text":"

    You open a new AI session. The first thing you do is re-explain your project.

    Again.

    The architecture, the database choice, the naming conventions, the thing you tried last week that didn't work. You have said all of this before (maybe yesterday, maybe an hour ago), but the AI doesn't know that.

    • You explain the same architecture every session;
    • The AI suggests an approach you already rejected, again;
    • A decision you made three sessions ago gets relitigated from scratch;
    • You spend more time setting context than building features.

    This isn't AI failure: It is staleness.

    Without persistent memory, every session starts at zero.

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#day-and-night","level":3,"title":"Day and Night","text":"

    ctx gives your AI a memory that persists across sessions:

    Without ctxWith ctx
    Session 12: Monday morning\n\nAI:  \"I'll scaffold a MySQL schema for this.\"\nYou: \"We use PostgreSQL.\"\n\nAI:  \"For auth, session-based would be simplest.\"\nYou: \"We decided on JWT.\"\n\nAI:  \"Endpoints in camelCase?\"\nYou: \"snake_case! Like the rest of the API!!!!!!\"\n\nYou: *scrolls up to copy an existing endpoint*\n\n20 minutes spent restoring decisions that already exist.\n
    Session 12: Monday morning\n\nYou:  \"Let's continue the auth implementation.\"\nAI:   \"Per DECISIONS.md (2026-02-12): JWT, not sessions: \n       The project uses PostgreSQL with JWT\n       authentication. The API follows snake_case conventions.\n       Last session you completed the login endpoint: the\n       registration endpoint is next on TASKS.md. Want me to\n       start there?\"\n\n0 minutes re-explaining. Straight to building.\n
    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#how-ctx-solves-this","level":3,"title":"How ctx Solves This","text":"

    ctx creates a .context/ directory in your project that stores structured knowledge files:

    File What It Remembers TASKS.md What you're working on and what's next DECISIONS.md Architectural choices and why you made them LEARNINGS.md Gotchas, bugs, things that didn't work CONVENTIONS.md Naming patterns, code style, project rules CONSTITUTION.md Hard rules the AI must never violate

    These files can version with your code in git:

    • They load automatically at the session start (via hooks in Claude Code, or manually with ctx agent for other tools).
    • The AI reads them, cites them, and builds on them, instead of asking you to start over.
      • And when it acts, it can point to the exact file and line that justifies the choice.

    Every decision you record, every lesson you capture, makes the next session smarter.

    ctx accumulates.

    Connect with ctx

    • Join the Community →: ask questions, share workflows, and help shape what comes next
    • Read the Blog →: real-world patterns, ponderings, and lessons learned from building ctx using ctx

    Ready to Get Started?

    • Getting Started →: full installation and setup
    • Your First Session →: step-by-step walkthrough from ctx init to verified recall
    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/common-workflows/","level":1,"title":"Common Workflows","text":"

    The commands below cover what you'll use most often:

    • recording context,
    • checking health,
    • browsing history,
    • and running loops.

    Each section is a self-contained snippet you can copy into your terminal.

    For deeper, step-by-step guides, see Recipes.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#track-context","level":2,"title":"Track Context","text":"

    Prefer Skills over Raw Commands

    When working with an AI agent, use /ctx-task-add, /ctx-decision-add, or /ctx-learning-add instead of raw ctx add commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    # Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (full ADR fields required)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning\nctx learning add \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Mark task complete\nctx task complete \"user auth\"\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#leave-a-reminder-for-next-session","level":2,"title":"Leave a Reminder for Next Session","text":"

    Drop a note that surfaces automatically at the start of your next session:

    # Leave a reminder\nctx remind \"refactor the swagger definitions\"\n\n# Date-gated: don't surface until a specific date\nctx remind \"check CI after the deploy\" --after 2026-02-25\n\n# List pending reminders\nctx remind list\n\n# Dismiss reminders by ID (supports ranges)\nctx remind dismiss 1\nctx remind dismiss 3 5-7\n

    Reminders are relayed verbatim at session start by the check-reminders hook and repeat every session until you dismiss them.

    See Session Reminders for the full recipe.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#check-context-health","level":2,"title":"Check Context Health","text":"
    # Detect stale paths, missing files, potential secrets\nctx drift\n\n# See full context summary\nctx status\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#browse-session-history","level":2,"title":"Browse Session History","text":"

    List and search past AI sessions from the terminal:

    ctx journal source --limit 5\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#journal-site","level":3,"title":"Journal Site","text":"

    Import session transcripts to a browsable static site with search, navigation, and topic indices.

    The ctx journal command requires zensical (Python >= 3.10).

    zensical is a Python-based static site generator from the Material for MkDocs team.

    (why zensical?).

    If you don't have it on your system, install zensical once with pipx:

    # One-time setup\npipx install zensical\n

    Avoid pip install zensical

    pip install often fails: For example, on macOS, system Python installs a non-functional stub (zensical requires Python >= 3.10), and Homebrew Python blocks system-wide installs (PEP 668).

    pipx creates an isolated environment with the correct Python version automatically.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#import-and-serve","level":3,"title":"Import and Serve","text":"

    Then, import and serve:

    # Import all sessions to .context/journal/ (only new files)\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n

    Open http://localhost:8000 to browse.

    To update after new sessions, run the same two commands again.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#safe-by-default","level":3,"title":"Safe by Default","text":"

    ctx journal import --all is safe by default:

    • It only imports new sessions and skips existing files.
    • Locked entries (via ctx journal lock) are always skipped by both import and enrichment skills.
    • If you add locked: true to frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json.
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#re-importing-existing-files","level":3,"title":"Re-Importing Existing Files","text":"

    Here is how you regenerate existing files.

    Backup your .context folder before regeneration, as this is a potentially destructive action.

    To re-import journal files, you need to explicitly opt-in using the --regenerate flag:

    Flag combination Frontmatter Body --regenerate Preserved Overwritten from source --regenerate --keep-frontmatter=false Overwritten Overwritten

    Regeneration Overwrites Body Edits

    --regenerate preserves your YAML frontmatter (tags, summary, enrichment metadata) but it replaces the Markdown body with a fresh import.

    Any manual edits you made to the transcript will be lost.

    Lock entries you want to protect first: ctx journal lock <session-id>.

    See Session Journal for the full pipeline including normalization and enrichment.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#scratchpad","level":2,"title":"Scratchpad","text":"

    Store short, sensitive one-liners in an encrypted scratchpad that travels with the project:

    # Write a note\nctx pad set db-password \"postgres://user:pass@localhost/mydb\"\n\n# Read it back\nctx pad get db-password\n\n# List all keys\nctx pad list\n

    The scratchpad is encrypted with a key stored at ~/.ctx/.ctx.key (outside the project, never committed).

    See Scratchpad for details.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#run-an-autonomous-loop","level":2,"title":"Run an Autonomous Loop","text":"

    Generate a script that iterates an AI agent until a completion signal is detected:

    ctx loop\nchmod +x loop.sh\n./loop.sh\n

    See Autonomous Loops for configuration and advanced usage.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#trace-commit-context","level":2,"title":"Trace Commit Context","text":"

    Link your git commits back to the decisions, tasks, and learnings that motivated them. Enable the hook once:

    # Install the git hook (one-time setup)\nctx trace hook enable\n

    From now on, every git commit automatically gets a ctx-context trailer linking it to relevant context. No extra steps needed; just use ctx add, ctx task complete, and commit as usual.

    # Later: why was this commit made?\nctx trace abc123\n\n# Recent commits with their context\nctx trace --last 10\n\n# Context trail for a specific file\nctx trace file src/auth.go\n\n# Manually tag a commit after the fact\nctx trace tag HEAD --note \"Hotfix for production outage\"\n

    To stop: ctx trace hook disable.

    See CLI Reference: trace for details.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#agent-session-start","level":2,"title":"Agent Session Start","text":"

    The first thing an AI agent should do at session start is discover where context lives:

    ctx system bootstrap\n

    This prints the resolved context directory, the files in it, and the operating rules. The CLAUDE.md template instructs the agent to run this automatically. See CLI Reference: bootstrap.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#the-two-skills-you-should-always-use","level":2,"title":"The Two Skills You Should Always Use","text":"

    Using /ctx-remember at session start and /ctx-wrap-up at session end are the highest-value skills in the entire catalog:

    # session begins:\n/ctx-remember\n... do work ...\n# before closing the session:\n/ctx-wrap-up\n

    Let's provide some context, because this is important:

    Although the agent will eventually discover your context through CLAUDE.md → AGENT_PLAYBOOK.md, /ctx-remember hydrates the full context up front (tasks, decisions, recent sessions) so the agent starts informed rather than piecing things together over several turns.

    /ctx-wrap-up is the other half: A structured review that captures learnings, decisions, and tasks before you close the window.

    Hooks like check-persistence remind you (the user) mid-session that context hasn't been saved in a while, but they don't trigger persistence automatically: You still have to act. Also, a CTRL+C can end things at any moment with no reliable \"before session end\" event.

    In short, /ctx-wrap-up is the deliberate checkpoint that makes sure nothing slips through. And /ctx-remember it its mirror skill to be used at session start.

    See Session Ceremonies for the full workflow.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-commands-vs-ai-skills","level":2,"title":"CLI Commands vs. AI Skills","text":"

    Most ctx operations come in two flavors: a CLI command you run in your terminal and an AI skill (slash command) you invoke inside your coding assistant.

    Commands and skills are not interchangeable: Each has a distinct role.

    ctx CLI command ctx AI skill Runs where Your terminal Inside the AI assistant Speed Fast (milliseconds) Slower (LLM round-trip) Cost Free Consumes tokens and context Analysis Deterministic heuristics Semantic / judgment-based Best for Quick checks, scripting, CI Deep analysis, generation, workflow orchestration","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#paired-commands","level":3,"title":"Paired Commands","text":"

    These have both a CLI and a skill counterpart. Use the CLI for quick, deterministic checks; use the skill when you need the agent's judgment.

    CLI Skill When to prefer the skill ctx drift /ctx-drift Semantic analysis: catches meaning drift the CLI misses ctx status /ctx-status Interpreted summary with recommendations ctx task add /ctx-task-add Agent decomposes vague goals into concrete tasks ctx decision add /ctx-decision-add Agent drafts rationale and consequences from discussion ctx learning add /ctx-learning-add Agent extracts the lesson from a debugging session ctx convention add /ctx-convention-add Agent observes a repeated pattern and codifies it ctx task archive /ctx-archive Agent reviews which tasks are truly done ctx pad /ctx-pad Agent reads/writes scratchpad entries in conversation flow ctx journal /ctx-history Agent searches session history with semantic understanding ctx agent /ctx-agent Agent loads and acts on the context packet ctx loop /ctx-loop Agent tailors the loop script to your project ctx doctor /ctx-doctor Agent adds semantic analysis to structural checks ctx hook pause /ctx-pause Agent pauses hooks with session-aware reasoning ctx hook resume /ctx-resume Agent resumes hooks after a pause ctx remind /ctx-remind Agent manages reminders in conversation flow","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#ai-only-skills","level":3,"title":"AI-Only Skills","text":"

    These have no CLI equivalent. They require the agent's reasoning.

    Skill Purpose /ctx-remember Load context and present structured readback at session start /ctx-wrap-up End-of-session ceremony: persist learnings, decisions, tasks /ctx-next Suggest 1-3 concrete next actions from context /ctx-commit Commit with integrated context capture /ctx-reflect Pause and assess session progress /ctx-consolidate Merge overlapping learnings or decisions /ctx-prompt-audit Analyze prompting patterns for improvement /ctx-plan Stress-test an existing plan through adversarial interview /ctx-plan-import Import Claude Code plan files into project specs /ctx-implement Execute a plan step-by-step with verification /ctx-worktree Manage parallel agent worktrees /ctx-journal-enrich Add metadata, tags, and summaries to journal entries /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich /ctx-blog Generate a blog post (zensical-flavored Markdown) /ctx-blog-changelog Generate themed blog post from commits between releases /ctx-architecture Build and maintain architecture maps (ARCHITECTURE.md, DETAILED_DESIGN.md)","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-only-commands","level":3,"title":"CLI-Only Commands","text":"

    These are infrastructure: used in scripts, CI, or one-time setup.

    Command Purpose ctx init Initialize .context/ directory ctx load Output assembled context for piping ctx task complete Mark a task done by substring match ctx sync Reconcile context with codebase state ctx compact Consolidate and clean up context files ctx trace Show context behind git commits ctx trace hook Enable/disable commit context tracing hook ctx setup Generate AI tool integration config ctx watch Watch AI output and auto-apply context updates ctx serve Serve any zensical directory (default: journal) ctx permission snapshot Save settings as a golden image ctx permission restore Restore settings from golden image ctx journal site Generate browsable journal from exports ctx hook notify setup Configure webhook notifications ctx decision List and filter decisions ctx learning List and filter learnings ctx task List tasks, manage archival and snapshots ctx why Read the philosophy behind ctx ctx guide Quick-reference cheat sheet ctx site Site management commands ctx config Manage runtime configuration profiles ctx system System diagnostics and hook commands ctx completion Generate shell autocompletion scripts

    Rule of Thumb

    Quick check? Use the CLI.

    Need judgment? Use the skill.

    When in doubt, start with the CLI: It's free and instant.

    Escalate to the skill when heuristics aren't enough.

    Next Up: Context Files →: what each .context/ file does and how to use it

    See Also:

    • Recipes: targeted how-to guides for specific tasks
    • Knowledge Capture: patterns for recording decisions, learnings, and conventions
    • Context Health: keeping your .context/ accurate and drift-free
    • Session Archaeology: digging into past sessions
    • Task Management: tracking and completing work items
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/community/","level":1,"title":"#ctx","text":"

    Open source is better together.

    We are the builders who care about durable context, verifiable decisions, and human-AI workflows that compound over time.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#help-ctx-change-how-ai-remembers","level":2,"title":"Help ctx Change How AI Remembers","text":"

    If you like the idea, a star helps ctx reach engineers who run into context drift every day:

    Star ctx on GitHub ⭐

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#ctx-you","level":2,"title":"ctx ♥️ You","text":"

    Join the community to ask questions, share feedback, and connect with other users:

    • Discord join the ctx Discord: Real-time discussion, field notes, and early ideas.
    • Read the ctx Source on GitHub: Issues, discussions, and contributions.
    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#want-to-contribute","level":2,"title":"Want to Contribute?","text":"

    Early adopters shape the conventions.

    ctx is free and open source software.

    Contributions are always welcome and appreciated.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#code-of-conduct","level":2,"title":"Code of Conduct","text":"

    Clear context requires respectful collaboration.

    ctx follows the Contributor Covenant.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/configuration/","level":1,"title":"Configuration","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#configuration","level":2,"title":"Configuration","text":"

    ctx uses three layers of configuration. Each layer overrides the one below it:

    1. CLI flags: Per-invocation overrides (highest priority)
    2. Environment variables: Shell or CI/CD overrides
    3. The .ctxrc file: Project-level defaults (YAML)
    4. Built-in defaults: Hardcoded fallbacks (lowest priority)

    All settings are optional: If nothing is configured, ctx works out of the box with sensible defaults.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#the-ctxrc-file","level":2,"title":"The .ctxrc File","text":"

    The .ctxrc file is an optional YAML file placed in the project root (next to your .context/ directory). It lets you set project-level defaults that apply to every ctx command.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#location","level":3,"title":"Location","text":"
    my-project/\n├── .ctxrc              ← configuration file\n├── .context/\n│   ├── TASKS.md\n│   ├── DECISIONS.md\n│   └── ...\n└── src/\n

    ctx reads .ctxrc from the project root (i.e. the parent of CTX_DIR, or dirname(CTX_DIR)/.ctxrc). It does not walk up from CWD. That means whichever project you've activated via eval \"$(ctx activate)\" (or by exporting CTX_DIR directly), its paired .ctxrc is what governs the invocation. There is no global or user-level config file: configuration is always per-project.

    Contributors: Dev Configuration Profile

    The ctx repo ships two .ctxrc source profiles (.ctxrc.base and .ctxrc.dev). The working copy is gitignored and swapped between them via ctx config switch dev / ctx config switch base. See Contributing: Configuration Profiles.

    Using a Different .context Directory

    You point ctx at a .context/ directory by setting the CTX_DIR environment variable, not through .ctxrc. ctx does not search the filesystem. Use eval \"$(ctx activate)\" to bind CTX_DIR for your shell. CTX_DIR must be an absolute path with .context as its basename.

    See Environment Variables below for details.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#full-reference","level":3,"title":"Full Reference","text":"

    A commented .ctxrc showing all options and their defaults:

    # .ctxrc: ctx runtime configuration\n# https://ctx.ist/configuration/\n#\n# All settings are optional. Missing values use defaults.\n# Priority: CLI flags > environment variables > .ctxrc > defaults\n#\n# token_budget: 8000\n# auto_archive: true\n# archive_after_days: 7\n# scratchpad_encrypt: true\n# event_log: false\n# entry_count_learnings: 30\n# entry_count_decisions: 20\n# convention_line_count: 200\n# injection_token_warn: 15000\n# context_window: 200000      # auto-detected for Claude Code; override for other tools\n# billing_token_warn: 0       # one-shot warning at this token count (0 = disabled)\n#\n# stale_age_days: 30      # days before drift flags a context file as stale (0 = disabled)\n# key_rotation_days: 90\n# task_nudge_interval: 5   # Edit/Write calls between task completion nudges\n#\n# notify:               # requires: ctx hook notify setup\n#   events:             # required: no events sent unless listed\n#     - loop\n#     - nudge\n#     - relay\n#\n# tool: \"\"              # Active AI tool: claude, cursor, cline, kiro, codex\n#\n# steering:             # Steering layer configuration\n#   dir: .context/steering\n#   default_inclusion: manual\n#   default_tools: []\n#\n# hooks:                # Hook system configuration\n#   dir: .context/hooks\n#   timeout: 10\n#   enabled: true\n#\n# provenance_required:  # Relax provenance flags for ctx add\n#   session_id: true    # Require --session-id (default: true)\n#   branch: true        # Require --branch (default: true)\n#   commit: true        # Require --commit (default: true)\n#\n# priority_order:\n#   - CONSTITUTION.md\n#   - TASKS.md\n#   - CONVENTIONS.md\n#   - ARCHITECTURE.md\n#   - DECISIONS.md\n#   - LEARNINGS.md\n#   - GLOSSARY.md\n#   - AGENT_PLAYBOOK.md\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#option-reference","level":3,"title":"Option Reference","text":"Option Type Default Description token_budget int 8000 Default token budget for ctx agent and ctx load auto_archive bool true Auto-archive completed tasks during ctx compact archive_after_days int 7 Days before completed tasks are archived scratchpad_encrypt bool true Encrypt scratchpad with AES-256-GCM event_log bool false Enable local hook event logging to .context/state/events.jsonl entry_count_learnings int 30 Drift warning when LEARNINGS.md exceeds this entry count (0 = disable) entry_count_decisions int 20 Drift warning when DECISIONS.md exceeds this entry count (0 = disable) convention_line_count int 200 Drift warning when CONVENTIONS.md exceeds this line count (0 = disable) injection_token_warn int 15000 Warn when auto-injected context exceeds this token count (0 = disable) context_window int 200000 Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools billing_token_warn int 0 (off) One-shot warning when session tokens exceed this threshold (0 = disabled). For plans where tokens beyond an included allowance cost extra stale_age_days int 30 Days before ctx drift flags a context file as stale (0 = disable) key_rotation_days int 90 Days before encryption key rotation nudge task_nudge_interval int 5 Edit/Write calls between task completion nudges notify.events []string (all) Event filter for webhook notifications (empty = all) priority_order []string (see below) Custom file loading priority for context assembly tool string (empty) Active AI tool identifier (claude, cursor, cline, kiro, codex). Used by steering sync and hook dispatch steering.dir string .context/steering Steering files directory steering.default_inclusion string manual Default inclusion mode for new steering files (always, auto, manual) steering.default_tools []string (all) Default tool filter for new steering files (empty = all tools) hooks.dir string .context/hooks Hook scripts directory hooks.timeout int 10 Per-hook execution timeout in seconds hooks.enabled bool true Whether hook execution is enabled provenance_required.session_id bool true Require --session-id on ctx add for tasks, decisions, learnings provenance_required.branch bool true Require --branch on ctx add for tasks, decisions, learnings provenance_required.commit bool true Require --commit on ctx add for tasks, decisions, learnings

    Default priority order (used when priority_order is not set):

    1. CONSTITUTION.md
    2. TASKS.md
    3. CONVENTIONS.md
    4. ARCHITECTURE.md
    5. DECISIONS.md
    6. LEARNINGS.md
    7. GLOSSARY.md
    8. AGENT_PLAYBOOK.md

    See Context Files for the rationale behind this ordering.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#environment-variables","level":2,"title":"Environment Variables","text":"

    Environment variables override .ctxrc values but are overridden by CLI flags.

    Variable Description Equivalent .ctxrc key CTX_DIR Declare the context directory path (required, no fallback) (none) CTX_TOKEN_BUDGET Override the default token budget token_budget","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples","level":3,"title":"Examples","text":"
    # Use a shared context directory\nCTX_DIR=/shared/team-context ctx status\n\n# Increase token budget for a single run\nCTX_TOKEN_BUDGET=16000 ctx agent\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#cli-global-flags","level":2,"title":"CLI Global Flags","text":"

    CLI flags have the highest priority and override both environment variables and .ctxrc settings. These flags are available on every ctx command.

    Flag Description --tool <name> Override active AI tool identifier (e.g. kiro, cursor) --version Show version and exit --help Show command help and exit","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_1","level":3,"title":"Examples","text":"
    # Point to a different context directory inline:\nCTX_DIR=/path/to/project/.context ctx status\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#priority-order","level":2,"title":"Priority Order","text":"

    When the same setting is configured in multiple layers, the highest-priority layer wins:

    CLI flags  >  Environment variables  >  .ctxrc  >  Built-in defaults\n(highest)                                          (lowest)\n

    The context directory itself is resolved differently: it lives outside this priority chain. CTX_DIR (env) must be declared; .ctxrc does not carry a fallback for it, and there is no built-in default. See Activating a Context Directory.

    Example resolution for token_budget:

    Layer Value Wins? CTX_TOKEN_BUDGET 4000 Yes .ctxrc 8000 No Default 8000 No","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_2","level":2,"title":"Examples","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#external-context-directory","level":3,"title":"External .context Directory","text":"

    Store a project's context outside the project tree (useful when a repo is read-only, or when you want to keep notes adjacent rather than checked in). Declare the path via CTX_DIR:

    export CTX_DIR=/home/you/ctx-stores/my-project/.context\n

    One .context/ per project

    The parent of the context directory is the project root by contract: ctx sync, ctx drift, and the memory-drift hook all read the codebase from filepath.Dir(ContextDir()). Pointing two projects at the same .context/ directory will collide their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use ctx hub, not a shared .context/.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-token-budget","level":3,"title":"Custom Token Budget","text":"

    Increase the token budget for projects with large context:

    # .ctxrc\ntoken_budget: 16000\n

    This affects the default budget for ctx agent and ctx load. You can still override per-invocation with ctx agent --budget 4000.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#disabled-scratchpad-encryption","level":3,"title":"Disabled Scratchpad Encryption","text":"

    Turn off encryption for the scratchpad (useful in ephemeral environments where key management is unnecessary):

    # .ctxrc\nscratchpad_encrypt: false\n

    Unencrypted Scratchpads Store Secrets in Plaintext

    Only disable encryption if you understand the security implications.

    The scratchpad may contain sensitive data such as API keys, database URLs, or deployment credentials.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-priority-order","level":3,"title":"Custom Priority Order","text":"

    Reorder context files to prioritize architecture over conventions:

    # .ctxrc\npriority_order:\n  - CONSTITUTION.md\n  - TASKS.md\n  - ARCHITECTURE.md\n  - DECISIONS.md\n  - CONVENTIONS.md\n  - LEARNINGS.md\n  - GLOSSARY.md\n  - AGENT_PLAYBOOK.md\n

    Files not listed in priority_order receive the lowest priority (100). The order affects ctx agent, ctx load, and drift's file-priority calculations.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#billing-token-threshold","level":3,"title":"Billing Token Threshold","text":"

    Get a one-shot warning when your session crosses a token threshold where extra charges begin (e.g., Claude Pro includes 200k tokens; beyond that costs extra):

    # .ctxrc\nbilling_token_warn: 180000   # warn before hitting the 200k paid boundary\n

    The warning fires once per session the first time token usage exceeds the threshold. Set to 0 (or omit) to disable.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#adjusted-drift-thresholds","level":3,"title":"Adjusted Drift Thresholds","text":"

    Raise or lower the entry-count thresholds that trigger drift warnings:

    # .ctxrc\nentry_count_learnings: 50   # warn above 50 learnings (default: 30)\nentry_count_decisions: 10   # warn above 10 decisions (default: 20)\nconvention_line_count: 300  # warn above 300 lines (default: 200)\n

    Set any threshold to 0 to disable that specific check.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"

    Get notified when loops complete, hooks fire, or agents reach milestones:

    # Configure the webhook URL (encrypted, safe to commit)\nctx hook notify setup\n\n# Test delivery\nctx hook notify test\n

    Filter which events reach your webhook:

    # .ctxrc\nnotify:\n  events:\n    - loop      # loop completion/max-iteration\n    - nudge     # VERBATIM relay hooks fired\n    # - relay   # all hook output (verbose, for debugging)\n    # - heartbeat  # every-prompt session-alive signal\n

    Notifications are opt-in: No events are sent unless explicitly listed.

    See Webhook Notifications for a step-by-step recipe.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#hook-message-overrides","level":2,"title":"Hook Message Overrides","text":"

    Hook messages control what text hooks emit when they fire. Each message can be overridden per-project by placing a text file at the matching path under .context/:

    .context/hooks/messages/{hook}/{variant}.txt\n

    The override takes priority over the embedded default compiled into the ctx binary. An empty file silences the message while preserving the hook's logic (counting, state tracking, cooldowns).

    Use ctx hook message to discover and manage overrides:

    ctx hook message list                      # see all messages\nctx hook message show qa-reminder gate     # view the current template\nctx hook message edit qa-reminder gate     # copy default for editing\nctx hook message reset qa-reminder gate    # revert to default\n

    See Customizing Hook Messages for detailed examples including Python, JavaScript, and silence configurations.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#agent-bootstrapping","level":2,"title":"Agent Bootstrapping","text":"

    AI agents need to know the resolved context directory at session start. The ctx system bootstrap command prints the context path, file list, and operating rules in both text and JSON formats:

    ctx system bootstrap          # text output for agents\nctx system bootstrap -q       # just the context directory path\nctx system bootstrap --json   # structured output for automation\n

    The CLAUDE.md template instructs the agent to run this as its first action. Every nudge (context checkpoint, persistence reminder, etc.) also includes a Context: <dir> footer that re-anchors the agent to the correct directory throughout the session.

    This replaces the previous approach of hardcoding .context/ paths in agent instructions.

    See CLI Reference: bootstrap for full details.

    See also: CLI Reference | Context Files | Scratchpad

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/context-files/","level":1,"title":"Context Files","text":"","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#context","level":2,"title":".context/","text":"

    Each context file in .context/ serves a specific purpose.

    Files are designed to be human-readable, AI-parseable, and token-efficient.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#file-overview","level":2,"title":"File Overview","text":"

    The core context files live directly under .context/. They are the substrate ctx reads in priority order when assembling the agent context packet:

    File Purpose Priority CONSTITUTION.md Hard rules that must NEVER be violated 1 (highest) TASKS.md Current and planned work 2 CONVENTIONS.md Project patterns and standards 3 ARCHITECTURE.md System overview and components 4 DECISIONS.md Architectural decisions with rationale 5 LEARNINGS.md Lessons learned, gotchas, tips 6 GLOSSARY.md Domain terms and abbreviations 7 AGENT_PLAYBOOK.md Instructions for AI tools 8 (lowest)

    Two subdirectories under .context/ are implementation details that are user-editable but not part of the priority read order:

    • .context/templates/: format templates for ctx decision add and ctx learning add. See templates below.
    • .context/steering/: behavioral rules with YAML frontmatter that get synced into each AI tool's native config. See steering below, and the full Steering files page for the design and workflow.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#outside-context","level":3,"title":"Outside .context/","text":"

    Two other moving parts are often confused with context files but are not under .context/:

    • Skills live in .claude/skills/ (project-local) or are provided by the installed ctx plugin. A typical project doesn't see the plugin's skills at all; they ride with the plugin and are owned by its update cycle. See ctx skill and Skills reference.
    • Hooks: Claude Code PreToolUse/PostToolUse/ UserPromptSubmit entries configured in .claude/settings.json or shipped by a plugin. The ctx plugin registers its own hooks automatically; a typical project does not author hooks by hand, and any local edits to plugin-owned hook files will be overridden on the next plugin update. If you need to customize behavior, edit your own project settings, not the plugin's files. See Hook sequence diagrams.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"

    The priority order follows a logical progression for AI tools:

    1. CONSTITUTION.md: Inviolable rules first. The AI tool must know what it cannot do before attempting anything.
    2. TASKS.md: Current work items. What the AI tool should focus on.
    3. CONVENTIONS.md: How to write code. Patterns and standards to follow when implementing tasks.
    4. ARCHITECTURE.md: System structure. Understanding of components and boundaries before making changes.
    5. DECISIONS.md: Historical context. Why things are the way they are, to avoid re-debating settled decisions.
    6. LEARNINGS.md: Gotchas and tips. Lessons from past work that inform the current implementation.
    7. GLOSSARY.md: Reference material. Domain terms and abbreviations for lookup as needed.
    8. AGENT_PLAYBOOK.md: Meta instructions last. How to use this context system itself. Loaded last because the agent should understand the content (rules, tasks, patterns) before the operating manual.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#constitutionmd","level":2,"title":"CONSTITUTION.md","text":"

    Purpose: Define hard invariants: Rules that must NEVER be violated, regardless of the task.

    AI tools read this first and should refuse tasks that violate these rules.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure","level":3,"title":"Structure","text":"
    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never store customer/user data in context files\n* [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n* [ ] All code must pass tests before commit\n* [ ] No `any` types in TypeScript without documented reason\n* [ ] No TODO comments in main branch (*move to `TASKS.md`*)\n\n## Process Invariants\n\n* [ ] All architectural changes require a decision record\n* [ ] Breaking changes require version bump\n* [ ] Generated files are never committed\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines","level":3,"title":"Guidelines","text":"
    • Keep rules minimal and absolute
    • Each rule should be enforceable (can verify compliance)
    • Use checkbox format for clarity
    • Never compromise on these rules
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tasksmd","level":2,"title":"TASKS.md","text":"

    Purpose: Track current work, planned work, and blockers.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_1","level":3,"title":"Structure","text":"

    Tasks are organized by Phase: logical groupings that preserve order and enable replay.

    Tasks stay in their Phase permanently; status is tracked via checkboxes and inline tags.

    # Tasks\n\n## Phase 1: Initial Setup\n\n* [x] Set up project structure\n* [x] Configure linting and formatting\n* [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n* [ ] Implement user authentication `#priority:high`\n* [ ] Add API rate limiting `#priority:medium`\n  * Blocked by: Need to finalize auth first\n\n## Backlog\n\n* [ ] Performance optimization `#priority:low`\n* [ ] Add metrics dashboard `#priority:deferred`\n

    Key principles:

    • Tasks never move between sections: mark as [x] or [-] in place
    • Use #in-progress inline tag to indicate current work
    • Phase headers provide structure and replay order
    • Backlog section for unscheduled work
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tags","level":3,"title":"Tags","text":"

    Use inline backtick-wrapped tags for metadata:

    Tag Values Purpose #priority high, medium, low Task urgency #area core, cli, docs, tests Codebase area #estimate 1h, 4h, 1d Time estimate (optional) #in-progress (none) Currently being worked on

    Lifecycle tags (for session correlation):

    Tag Format When to add #added YYYY-MM-DD-HHMMSS Auto-added by ctx task add #started YYYY-MM-DD-HHMMSS When beginning work on the task

    These timestamps help correlate tasks with session files and track which session started vs completed work.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning [ ] Pending [x] Completed [-] Skipped (include reason)","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_1","level":3,"title":"Guidelines","text":"
    • Never delete tasks; mark as [x] completed or [-] skipped
    • Never move tasks between sections; use inline tags for status
    • Use ctx task archive periodically to move completed tasks to archive
    • Mark current work with #in-progress inline tag
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#decisionsmd","level":2,"title":"DECISIONS.md","text":"

    Purpose: Record architectural decisions with rationale so they don't get re-debated.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_2","level":3,"title":"Structure","text":"
    # Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequence**: What are the implications?\n\n**Alternatives Considered**:\n* Alternative A: Why rejected\n* Alternative B: Why rejected\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example","level":3,"title":"Example","text":"
    ## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting a new project, need to choose the type-checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequence**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#learningsmd","level":2,"title":"LEARNINGS.md","text":"

    Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_3","level":3,"title":"Structure","text":"
    # Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example_1","level":3,"title":"Example","text":"
    ## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#categories","level":3,"title":"Categories","text":"

    Organize learnings by topic:

    • Testing
    • Build & Deploy
    • Performance
    • Security
    • Third-Party Libraries
    • Git and Workflow
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#conventionsmd","level":2,"title":"CONVENTIONS.md","text":"

    Purpose: Document project patterns, naming conventions, and standards.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_4","level":3,"title":"Structure","text":"
    # Conventions\n\n## Naming\n\n* **Files**: kebab-case for all source files\n* **Components**: PascalCase for React components\n* **Functions**: camelCase, verb-first (getUser, parseConfig)\n* **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_2","level":3,"title":"Guidelines","text":"
    • Include concrete examples
    • Explain the \"why\" not just the \"what\"
    • Keep patterns minimal: Only document what's non-obvious
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#architecturemd","level":2,"title":"ARCHITECTURE.md","text":"

    Purpose: Provide system overview and component relationships.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_5","level":3,"title":"Structure","text":"
    # Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n* path/to/file.ts: Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_3","level":3,"title":"Guidelines","text":"
    • Keep diagrams simple (Mermaid works well)
    • Focus on boundaries and interfaces
    • Update when major structural changes occur
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#glossarymd","level":2,"title":"GLOSSARY.md","text":"

    Purpose: Define domain terms, abbreviations, and project vocabulary.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_6","level":3,"title":"Structure","text":"
    # Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_4","level":3,"title":"Guidelines","text":"
    • Define project-specific meanings
    • Clarify potentially ambiguous terms
    • Include abbreviations used in code or docs
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#agent_playbookmd","level":2,"title":"AGENT_PLAYBOOK.md","text":"

    Purpose: Explicit instructions for how AI tools should read, apply, and update context.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#key-sections","level":3,"title":"Key Sections","text":"

    Read Order: Priority order for loading context files

    When to Update: Events that trigger context updates

    How to Avoid Hallucinating Memory: Critical rules:

    1. Never assume: If not in files, you don't know it
    2. Never invent history: Don't claim \"we discussed\" without evidence
    3. Verify before referencing: Search files before citing
    4. When uncertain, say so
    5. Trust files over intuition

    Context Update Commands: Format for automated updates via ctx watch:

    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"complete\">user auth</context-update>\n<context-update type=\"learning\"\n  context=\"Debugging hooks\"\n  lesson=\"Hooks receive JSON via stdin\"\n  application=\"Parse JSON stdin with the host language\"\n>Hook Input Format</context-update>\n<context-update type=\"decision\"\n  context=\"Need a caching layer\"\n  rationale=\"Redis is fast and team has experience\"\n  consequence=\"Must provision Redis infrastructure\"\n>Use Redis for caching</context-update>\n

    See Integrations for full documentation.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#templates","level":2,"title":"templates/","text":"

    Location: .context/templates/. Status: implementation detail, user-editable.

    Purpose: Format templates for ctx decision add and ctx learning add. These control the structure of new entries appended to DECISIONS.md and LEARNINGS.md.

    ctx init deploys two starter templates:

    • decision.md: sections Context, Rationale, Consequence
    • learning.md: sections Context, Lesson, Application
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing","level":3,"title":"Customizing","text":"

    Edit the templates directly. Changes take effect immediately on the next ctx add command. For example, to add a \"References\" section to all new decisions, edit .context/templates/decision.md.

    Templates are committed to git, so customizations are shared with the team.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#steering","level":2,"title":"steering/","text":"

    Location: .context/steering/. Status: implementation detail, user-editable.

    Purpose: Behavioral rules with YAML frontmatter that tell an AI assistant how to behave when a specific kind of prompt arrives. Unlike the core context files (which describe what the project is), steering files describe what to do and ride alongside the prompt through the AI tool's native rule pipeline (Claude Code, Cursor, Kiro, Cline). ctx matches steering files to prompts and syncs them out to each tool's config.

    ctx init scaffolds four foundation files:

    • product.md: who this project serves and why
    • tech.md: the technology stack and its constraints
    • structure.md: how the code is organized
    • workflow.md: how work moves through the system

    Each file carries YAML frontmatter describing when it applies (always, matching prompts, or manually referenced) and what tool scope it covers. The foundation files use inclusion: always by default so every session picks them up.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing_1","level":3,"title":"Customizing","text":"

    Edit the files directly. Add your own steering files with ctx steering add, preview the match set with ctx steering preview, and run ctx steering sync to push them into each AI tool's config after changes. Steering files are committed to git, so they're shared with the team.

    For the design rationale, the full inclusion/priority model, and the end-to-end sync workflow, see the dedicated Steering files page.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"

    All context files follow these conventions:

    1. Headers define structure: # for title, ## for sections, ### for items
    2. Bold keys for fields: **Key**: followed by value
    3. Code blocks are literal: Never parse code block content as structure
    4. Lists are ordered: Items appear in priority/chronological order
    5. Tags are inline: Backtick-wrapped tags like #priority:high
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#further-reading","level":2,"title":"Further Reading","text":"
    • Refactoring with Intent: how persistent context prevents drift during refactoring sessions
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"

    Keep context files concise:

    • Use abbreviations in tags, not prose;
    • Omit obvious words (\"The,\" \"This\");
    • Prefer bullet points over paragraphs;
    • Keep examples minimal but illustrative;
    • Archive old completed items periodically.

    Next Up: Prompting Guide →: effective prompts for AI sessions with ctx

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/contributing/","level":1,"title":"Contributing","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#development-setup","level":2,"title":"Development Setup","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#prerequisites","level":3,"title":"Prerequisites","text":"
    • Go (version defined in go.mod)
    • Claude Code
    • Git
    • GNU Make
    • Zensical
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#1-fork-or-clone-the-repository","level":3,"title":"1. Fork (or Clone) the Repository","text":"
    # Fork on GitHub, then:\ngit clone https://github.com/<you>/ctx.git\ncd ctx\n\n# Or, if you have push access:\ngit clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#2-build-and-install-the-binary","level":3,"title":"2. Build and Install the Binary","text":"
    make build\nsudo make install\n

    This compiles the ctx binary and places it in /usr/local/bin/.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#3-install-the-plugin-from-your-local-clone","level":3,"title":"3. Install the Plugin from Your Local Clone","text":"

    The repository ships a Claude Code plugin under internal/assets/claude/. Point Claude Code at your local copy so that skills and hooks reflect your working tree: no reinstall needed after edits:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace
    4. Enter the absolute path to the root of your clone, e.g. ~/WORKSPACE/ctx (this is where .claude-plugin/marketplace.json lives: it points Claude Code to the actual plugin in internal/assets/claude);
    5. Back in /plugin, select Install and choose ctx.

    Claude Code Caches Plugin Files

    Even though the marketplace points at a directory on disk, Claude Code caches skills and hooks. After editing files under internal/assets/claude/, clear the cache and restart:

    make plugin-reload   # then restart Claude Code\n

    See Skill or Hook Changes for details.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#4-verify","level":3,"title":"4. Verify","text":"
    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n

    You should see the ctx plugin listed, sourced from your local path.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#project-layout","level":2,"title":"Project Layout","text":"
    ctx/\n├── cmd/ctx/            # CLI entry point\n├── internal/\n│   ├── assets/claude/  # ← Claude Code plugin (skills, hooks)\n│   ├── bootstrap/      # Project initialization templates\n│   ├── claude/         # Claude Code integration helpers\n│   ├── cli/            # Command implementations\n│   ├── config/         # Configuration loading\n│   ├── context/        # Core context logic\n│   ├── crypto/         # Scratchpad encryption\n│   ├── drift/          # Drift detection\n│   ├── index/          # Context file indexing\n│   ├── journal/        # Journal site generation\n│   ├── memory/         # Memory bridge (discover, mirror, import, publish)\n│   ├── notify/         # Webhook notifications\n│   ├── rc/             # .ctxrc parsing\n│   ├── journal/        # Session history, parsers, and state\n│   ├── sysinfo/        # System resource monitoring\n│   ├── task/           # Task management\n│   └── validation/     # Input validation\n├── .claude/\n│   └── skills/         # Dev-only skills (not distributed)\n├── assets/             # Static assets (banners, logos)\n├── docs/               # Documentation site source\n├── editors/            # Editor extensions (VS Code)\n├── examples/           # Example configurations\n├── hack/               # Build scripts\n├── specs/              # Feature specifications\n└── .context/           # ctx's own context (dogfooding)\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skills-two-directories-one-rule","level":3,"title":"Skills: Two Directories, One Rule","text":"Directory What lives here Distributed to users? internal/assets/claude/skills/ The 39 ctx-* skills that ship with the plugin Yes .claude/skills/ Dev-only skills (release, QA, backup, etc.) No

    internal/assets/claude/skills/ is the single source of truth for user-facing skills. If you are adding or modifying a ctx-* skill, edit it there.

    .claude/skills/ holds skills that only make sense inside this repository (release automation, QA checks, backup scripts). These are never distributed to users.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#dev-only-skills-reference","level":4,"title":"Dev-Only Skills Reference","text":"Skill When to use /_ctx-absorb Merge deltas from a parallel worktree or separate checkout /_ctx-audit Detect code-level drift after YOLO sprints or before releases /_ctx-qa Run QA checks before committing /_ctx-release Run the full release process /_ctx-release-notes Generate release notes for dist/RELEASE_NOTES.md /_ctx-alignment-audit Audit doc claims against agent instructions /_ctx-update-docs Check docs/code consistency after changes /_ctx-command-audit Audit CLI surface after renames, moves, or deletions

    Six skills previously in this list have been promoted to bundled plugin skills and are now available to all ctx users: /ctx-brainstorm, /ctx-link-check, /ctx-permission-sanitize, /ctx-skill-create, /ctx-spec.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#how-to-add-things","level":2,"title":"How to Add Things","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-cli-command","level":3,"title":"Adding a New CLI Command","text":"
    1. Create a package under internal/cli/<name>/ with doc.go, cmd.go, and run.go;
    2. Implement Cmd() *cobra.Command as the entry point;
    3. Add Use* and DescKey* constants in internal/config/embed/cmd/<name>.go;
    4. Add command descriptions in internal/assets/commands/commands.yaml;
    5. Add examples in internal/assets/commands/examples.yaml;
    6. Add flag descriptions in internal/assets/commands/flags.yaml;
    7. Register the command in internal/bootstrap/group.go (add import + entry in the appropriate group function);
    8. Create an output package at internal/write/<name>/ for all user-facing output (see Package Taxonomy);
    9. Create error constructors at internal/err/<name>/ for domain-specific errors;
    10. Add tests in the same package (<name>_test.go);
    11. Add a doc page at docs/cli/<name>.md and update docs/cli/index.md;
    12. Add the page to zensical.toml nav.

    Pattern to follow: internal/cli/pad/pad.go (parent with subcommands) or internal/cli/drift/ (single command).

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#package-taxonomy","level":3,"title":"Package Taxonomy","text":"

    ctx separates concerns into a strict package taxonomy. Knowing where things go prevents code review friction and keeps the AST lint tests happy.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#output-internalwrite","level":4,"title":"Output: internal/write/","text":"

    Every CLI command's user-facing output lives in its own sub-package under internal/write/<domain>/. Output functions accept *cobra.Command and call cmd.Println(...), never fmt.Print* directly. All text strings are loaded from YAML via desc.Text(text.DescKey*), never inline.

    internal/write/add/add.go       # output for ctx add\ninternal/write/stat/stat.go     # output for ctx usage\ninternal/write/resource/        # output for ctx sysinfo\n

    Exception: write/rc/ writes to os.Stderr because rc loads before cobra is initialized.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#errors-internalerr","level":4,"title":"Errors: internal/err/","text":"

    Domain-specific error constructors live under internal/err/<domain>/. Each package mirrors the write structure. Functions return error (never custom error types) and load messages from YAML via desc.Text(text.DescKey*).

    internal/err/add/add.go         # errors for ctx add\ninternal/err/config/config.go   # errors for configuration\ninternal/err/cli/cli.go         # errors for CLI argument validation\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#config-constants-internalconfig","level":4,"title":"Config Constants: internal/config/","text":"

    Pure-constant leaf packages with zero internal dependencies (stdlib only). Over 60 sub-packages, organized by domain. See internal/config/README.md for the full decision tree.

    What you're adding Where it goes File names, extensions, paths config/file/, config/dir/ Regex patterns config/regex/ CLI flag names (--flag-name) config/flag/flag.go Flag description YAML keys config/embed/flag/<cmd>.go Command Use/DescKey strings config/embed/cmd/<cmd>.go User-facing text YAML keys config/embed/text/<domain>.go Time durations, thresholds config/<domain>/","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#the-assets-pipeline","level":4,"title":"The Assets Pipeline","text":"

    User-facing text flows through a three-level chain:

    1. Go constant (config/embed/text/) defines a string key: DescKeyWriteAddedTo = \"write.added-to\"
    2. Call site resolves it: desc.Text(text.DescKeyWriteAddedTo)
    3. YAML (internal/assets/commands/text/write.yaml) holds the actual text: write.added-to: { short: \"Added to %s\" }

    The same pattern applies to command descriptions (commands.yaml), flag descriptions (flags.yaml), and examples (examples.yaml). The TestDescKeyYAMLLinkage test verifies every constant resolves to a non-empty YAML value.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-session-parser","level":3,"title":"Adding a New Session Parser","text":"

    The journal system uses a SessionParser interface. To add support for a new AI tool (e.g. Aider, Cursor):

    1. Create internal/journal/parser/<tool>.go;
    2. Implement parsing logic that returns []*Session;
    3. Register the parser in FindSessions() / FindSessionsForCWD();
    4. Use config.Tool* constants for the tool identifier;
    5. Add test fixtures and parser tests.

    Pattern to follow: the Claude Code JSONL parser in internal/journal/parser/.

    Multilingual Session Headers

    The Markdown parser recognizes session header prefixes configured via session_prefixes in .ctxrc (default: Session:). To support a new language, users add a prefix to their .ctxrc - no code change needed. New parser implementations can use rc.SessionPrefixes() if they also need prefix-based header detection.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-bundled-skill","level":3,"title":"Adding a Bundled Skill","text":"
    1. Create internal/assets/claude/skills/<skill-name>/SKILL.md;
    2. Follow the skill format: trigger, negative triggers, steps, quality gate;
    3. Run make plugin-reload and restart Claude Code to test;
    4. Add a Skill entry to .claude-plugin/plugin.json if user-invocable;
    5. Document in docs/reference/skills.md.

    Pattern to follow: any skill in internal/assets/claude/skills/ctx-status/.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#test-expectations","level":3,"title":"Test Expectations","text":"
    • Unit tests: colocated with source (foo.gofoo_test.go);
    • Test helpers: use t.Helper() so failures point to callers;
    • HOME isolation: use t.TempDir() + t.Setenv(\"HOME\", ...) for tests that touch ~/.claude/ or ~/.ctx/;
    • rc.Reset(): call after os.Chdir in tests that change working directory (rc caches on first access);
    • No network: all tests run offline, use fixtures.

    Run make test before submitting. Target: no failures, no skips.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#day-to-day-workflow","level":2,"title":"Day-to-Day Workflow","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#go-code-changes","level":3,"title":"Go Code Changes","text":"

    After modifying Go source files, rebuild and reinstall:

    make build && sudo make install\n

    The ctx binary is statically compiled. There is no hot reload. You must rebuild for Go changes to take effect.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skill-or-hook-changes","level":3,"title":"Skill or Hook Changes","text":"

    Edit files under internal/assets/claude/skills/ or internal/assets/claude/hooks/.

    Claude Code caches plugin files, so edits aren't picked up automatically.

    Clear the cache and restart:

    make plugin-reload   # nukes ~/.claude/plugins/cache/activememory-ctx/\n# then restart Claude Code\n

    The plugin will be re-installed from your local marketplace on startup. No version bump is needed during development.

    Version Bumps Are for Releases, Not Iteration

    Only bump VERSION, plugin.json, and marketplace.json when cutting a release. During development, make plugin-reload is all you need.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"

    The repo ships two .ctxrc source profiles. The working copy (.ctxrc) is gitignored and swapped between them:

    File Purpose .ctxrc.base Golden baseline: all defaults, no logging .ctxrc.dev Dev profile: notify events enabled, verbose logging .ctxrc Working copy (gitignored: copied from one of the above)

    Use ctx commands to switch:

    ctx config switch dev      # switch to dev profile\nctx config switch base     # switch to base profile\nctx config status          # show which profile is active\n

    After cloning, run ctx config switch dev to get started with full logging.

    See Configuration for the full .ctxrc option reference.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#backups","level":3,"title":"Backups","text":"

    ctx does not ship a backup command. File-level backup is an OS / infrastructure concern; ctx hub handles the cross-machine knowledge persistence that matters most. For everything else, see Backup Strategy: rsync, Time Machine, Borg, or whichever tool already handles the rest of your files.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-tests","level":3,"title":"Running Tests","text":"
    make test   # fast: all tests\nmake audit  # full: fmt + vet + lint + drift + docs + test\nmake smoke  # build + run basic commands end-to-end\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-the-docs-site-locally","level":3,"title":"Running the Docs Site Locally","text":"
    make site-setup  # one-time: install zensical via pipx\nmake site-serve  # serve at localhost\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#submitting-changes","level":2,"title":"Submitting Changes","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#before-you-start","level":3,"title":"Before You Start","text":"
    1. Check existing issues to avoid duplicating effort;
    2. For large changes, open an issue first to discuss the approach;
    3. Read the specs in specs/ for design context.
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#pull-request-process","level":3,"title":"Pull Request Process","text":"

    Respect the maintainers' time and energy: Keep your pull requests isolated and strive to minimze code changes.

    If you Pull Request solves more than one distinct issues, it's better to create separate pull requests instead of sending them in one large bundle.

    1. Create a feature branch: git checkout -b feature/my-feature;
    2. Make your changes;
    3. Run make audit to catch issues early;
    4. Commit with a clear message;
    5. Push and open a pull request.

    Audit Your Code Before Submitting

    Run make audit before submitting:

    make audit covers formatting, vetting, linting, drift checks, doc consistency, and tests in one pass.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#commit-messages","level":3,"title":"Commit Messages","text":"

    Following conventional commits is recommended but not required:

    Types: feat, fix, docs, test, refactor, chore

    Examples:

    • feat(cli): add ctx export command
    • fix(drift): handle missing files gracefully
    • docs: update installation instructions
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-style","level":3,"title":"Code Style","text":"
    • Follow Go conventions (gofmt, go vet);
    • Keep functions focused and small;
    • Add tests for new functionality;
    • Handle errors explicitly; use descriptive names (readErr, writeErr) not repeated err;
    • No magic strings: all repeated literals go in internal/config/;
    • Output goes through internal/write/ packages, not fmt.Print*;
    • Errors go through internal/err/ constructors, not inline fmt.Errorf;
    • See Package Taxonomy and .context/CONVENTIONS.md for the full reference.
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-of-conduct","level":2,"title":"Code of Conduct","text":"

    A clear context requires respectful collaboration.

    ctx follows the Contributor Covenant.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#boring-legal-stuff","level":2,"title":"Boring Legal Stuff","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#developer-certificate-of-origin-dco","level":3,"title":"Developer Certificate of Origin (DCO)","text":"

    By contributing, you agree to the Developer Certificate of Origin.

    All commits must be signed off:

    git commit -s -m \"feat: add new feature\"\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#license","level":3,"title":"License","text":"

    Contributions are licensed under the Apache 2.0 License.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/faq/","level":1,"title":"FAQ","text":"","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-markdown","level":2,"title":"Why Markdown?","text":"

    Markdown is human-readable, version-controllable, and tool-agnostic. Every AI model can parse it natively. Every developer can read it in a terminal, a browser, or a code review. There's no schema to learn, no binary format to decode, no vendor lock-in. You can inspect your context with cat, diff it with git diff, and review it in a PR.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-ctx-work-offline","level":2,"title":"Does ctx Work Offline?","text":"

    Yes. ctx is completely local. It reads and writes files on disk, generates context packets from local state, and requires no network access. The only feature that touches the network is the optional webhook notifications hook, which you have to explicitly configure.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-gets-committed-to-git","level":2,"title":"What Gets Committed to Git?","text":"

    The .context/ directory: yes, commit it. That's the whole point. Team members and AI agents read the same context files.

    What not to commit:

    • .ctx.key: your encryption key. Stored at ~/.ctx/.ctx.key, never in the repo. ctx init handles this automatically.
    • journal/ and logs/: generated data, potentially large. ctx init adds these to .gitignore.
    • scratchpad.enc: your choice. It's encrypted, so it's safe to commit if you want shared scratchpad state. See Scratchpad for details.
    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#how-big-should-my-token-budget-be","level":2,"title":"How Big Should My Token Budget Be?","text":"

    The default is 8000 tokens, which works well for most projects. Configure it via .ctxrc or the CTX_TOKEN_BUDGET environment variable:

    # In .ctxrc\ntoken_budget = 12000\n\n# Or as an environment variable\nexport CTX_TOKEN_BUDGET=12000\n\n# Or per-invocation\nctx agent --budget 4000\n

    Higher budgets include more context but cost more tokens per request. Lower budgets force sharper prioritization: ctx drops lower-priority content first, so CONSTITUTION and TASKS always make the cut.

    See Configuration for all available settings.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-not-a-database","level":2,"title":"Why Not a Database?","text":"

    Files are inspectable, diffable, and reviewable in pull requests. You can grep them, cat them, pipe them through jq or awk. They work with every version control system and every text editor.

    A database would add a dependency, require migrations, and make context opaque. The design bet is that context should be as visible and portable as the code it describes.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-it-work-with-tools-other-than-claude-code","level":2,"title":"Does It Work with Tools Other than Claude Code?","text":"

    Yes. ctx agent outputs a context packet that any AI tool can consume: paste it into ChatGPT, Cursor, Copilot, Aider, or anything else that accepts text input.

    Claude Code gets first-class integration via the ctx plugin (hooks, skills, automatic context loading). VS Code Copilot Chat has a dedicated ctx extension. Other tools integrate via generated instruction files or manual pasting.

    See Integrations for tool-specific setup, including the multi-tool recipe.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#can-i-use-ctx-on-an-existing-project","level":2,"title":"Can I Use ctx on an Existing Project?","text":"

    Yes. Run ctx init in any repo and it creates .context/ with template files. Start recording decisions, tasks, and conventions as you work. Context grows naturally; you don't need to backfill everything on day one.

    See Getting Started for the full setup flow, or Joining a ctx Project if someone else already initialized it.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-happens-when-context-files-get-too-big","level":2,"title":"What Happens When Context Files Get Too Big?","text":"

    Token budgeting handles this automatically. ctx agent prioritizes content by file priority (CONSTITUTION first, GLOSSARY last) and trims lower-priority entries when the budget is tight.

    For manual maintenance, ctx compact archives completed tasks and old entries, keeping active context lean. You can also run ctx task archive to move completed tasks out of TASKS.md.

    The goal is to keep context files focused on current state. Historical entries belong in git history or the archive.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#is-context-meant-to-be-shared","level":2,"title":"Is .context/ Meant to Be Shared?","text":"

    Yes. Commit it to your repo. Every team member and every AI agent reads the same files. That's the mechanism for shared memory: decisions made in one session are visible in the next, regardless of who (or what) starts it.

    The only per-user state is the encryption key (~/.ctx/.ctx.key) and the optional scratchpad. Everything else is team-shared by design.

    Related:

    • Getting Started - installation and first setup
    • Configuration - .ctxrc, environment variables, and defaults
    • Context Files - what each file does and how to use it
    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/first-session/","level":1,"title":"Your First Session","text":"

    Here's what a complete first session looks like, from initialization to the moment your AI cites your project context back to you.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-1-initialize-your-project","level":2,"title":"Step 1: Initialize Your Project","text":"

    Run ctx init in your project root:

    cd your-project\nctx init\n

    Sample output:

    Context initialized in .context/\n\n  ✓ CONSTITUTION.md\n  ✓ TASKS.md\n  ✓ DECISIONS.md\n  ✓ LEARNINGS.md\n  ✓ CONVENTIONS.md\n  ✓ ARCHITECTURE.md\n  ✓ GLOSSARY.md\n  ✓ AGENT_PLAYBOOK.md\n\nSetting up encryption key...\n  ✓ ~/.ctx/.ctx.key\n\nClaude Code plugin (hooks + skills):\n  Install: claude /plugin marketplace add ActiveMemory/ctx\n  Then:    claude /plugin install ctx@activememory-ctx\n\nNext steps:\n  1. Edit .context/TASKS.md to add your current tasks\n  2. Run 'ctx status' to see context summary\n  3. Run 'ctx agent' to get AI-ready context packet\n

    This created your .context/ directory with template files.

    For Claude Code, install the ctx plugin to get automatic hooks and skills.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-2-activate-the-project","level":2,"title":"Step 2: Activate the Project","text":"

    Tell ctx which .context/ directory the rest of these commands should use:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the next steps fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. For more options (multiple .context/ directories, scripts, CI), see Activating a Context Directory.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-3-populate-your-context","level":2,"title":"Step 3: Populate Your Context","text":"

    Add a task and a decision: These are the entries your AI will remember:

    ctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to TASKS.md\n\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to DECISIONS.md\n

    These entries are what the AI will recall in future sessions. You don't need to populate everything now: Context grows naturally as you work.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-4-check-your-context","level":2,"title":"Step 4: Check Your Context","text":"
    ctx status\n

    Sample output:

    Context Status\n====================\n\nContext Directory: .context/\nTotal Files: 8\nToken Estimate: 1,247 tokens\n\nFiles:\n  ✓ CONSTITUTION.md (loaded)\n  ✓ TASKS.md (1 items)\n  ✓ DECISIONS.md (1 items)\n  ○ LEARNINGS.md (empty)\n  ✓ CONVENTIONS.md (loaded)\n  ✓ ARCHITECTURE.md (loaded)\n  ✓ GLOSSARY.md (loaded)\n  ✓ AGENT_PLAYBOOK.md (loaded)\n\nRecent Activity:\n  - TASKS.md modified 2 minutes ago\n  - DECISIONS.md modified 1 minute ago\n

    Notice the token estimate: This is how much context your AI will load.

    The next to LEARNINGS.md means it's still empty; it will fill in as you capture lessons during development.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-5-start-an-ai-session","level":2,"title":"Step 5: Start an AI Session","text":"

    With Claude Code (and the ctx plugin), start every session with:

    /ctx-remember\n

    This loads your context and presents a structured readback so you can confirm the agent knows what is going on. Context also loads automatically via hooks, but the explicit ceremony gives you a readback to verify.

    Steering Files Fire Automatically

    If you edited the four foundation files scaffolded by ctx init (.context/steering/product.md, tech.md, structure.md, workflow.md), their inclusion: always rules are prepended to every tool call via the plugin's PreToolUse hook, with no /ctx-remember needed, no MCP call. Edit a file, save, and the next tool call in Claude Code picks it up. See Steering files for details on the inclusion modes.

    Using VS Code?

    With VS Code Copilot Chat (and the ctx extension), type @ctx /agent in chat to load your context packet, or @ctx /status to check your project context. Run ctx setup copilot --write once to generate .github/copilot-instructions.md for automatic context loading.

    If you are not using Claude Code, generate a context packet for your AI tool:

    ctx agent --budget 8000\n

    Sample output:

    # Context Packet\nGenerated: 2026-02-14T15:30:45Z | Budget: 8000 tokens | Used: ~2450\n\n## Read These Files (in order)\n1. .context/CONSTITUTION.md\n2. .context/TASKS.md\n3. .context/CONVENTIONS.md\n...\n\n## Current Tasks\n- [ ] Implement user authentication\n- [ ] Add rate limiting to API endpoints\n\n## Key Conventions\n- Use gofmt for formatting\n- Path construction uses filepath.Join\n\n## Recent Decisions\n## [2026-02-14-120000] Use PostgreSQL for the primary database\n\n**Context**: Evaluated PostgreSQL, MySQL, and SQLite...\n**Rationale**: PostgreSQL offers better JSON support...\n\n## Key Learnings\n## [2026-02-14-100000] Connection pool sizing matters\n\n**Context**: Hit connection limits under load...\n**Lesson**: Default pool size of 10 is too low for concurrent requests...\n\n## Also Noted\n- Use JWT for session management\n- Always validate input at API boundary\n

    Paste this output into your AI tool's system prompt or conversation start.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-6-verify-it-works","level":2,"title":"Step 6: Verify It Works","text":"

    Ask your AI: \"What are our current tasks?\"

    A working setup produces a response like:

    Based on the project context, you have one active task:\n\n- **Implement user authentication** (pending)\n\nThere's also a recent architectural decision to **use PostgreSQL for\nthe primary database**, chosen for its ACID compliance and JSON support.\n\nWant me to start on the authentication task?\n

    That's the success moment:

    The AI is citing your exact context entries from Step 2, not hallucinating or asking you to re-explain.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-gets-created","level":2,"title":"What Gets Created","text":"
    .context/\n├── CONSTITUTION.md     # Hard rules: NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n└── AGENT_PLAYBOOK.md   # How AI tools should use this\n

    Claude Code integration (hooks + skills) is provided by the ctx plugin: See Integrations/Claude Code.

    VS Code Copilot Chat integration is provided by the ctx extension: See Integrations/VS Code.

    See Context Files for detailed documentation of each file.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-to-gitignore","level":2,"title":"What to .gitignore","text":"

    Rule of Thumb

    • If it's knowledge (decisions, tasks, learnings, conventions), commit it.
    • If it's generated output, raw session data, or a secret, .gitignore it.

    Commit your .context/ knowledge files: that's the whole point.

    You should .gitignore the generated and sensitive paths:

    # Journal data (large, potentially sensitive)\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Hook logs (machine-specific)\n.context/logs/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n

    ctx init Patches Your .Gitignore for You

    ctx init automatically adds these entries to your .gitignore.

    Review the additions with cat .gitignore after init.

    See also:

    • Security Considerations
    • Scratchpad Encryption
    • Session Journal

    Next Up: Common Workflows →: day-to-day commands for tracking context, checking health, and browsing history.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/getting-started/","level":1,"title":"Getting Started","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"

    ctx does not require git, but using version control with your .context/ directory is strongly recommended:

    AI sessions occasionally modify or overwrite context files inadvertently. With git, the AI can check history and restore lost content: Without it, the data is gone.

    Also, several ctx features (journal changelog, blog generation) also use git history directly.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#installation","level":2,"title":"Installation","text":"

    Every setup starts with the ctx binary: the CLI tool itself.

    If you use Claude Code, you also install the ctx plugin, which adds hooks (context autoloading, persistence nudges) and 25+ /ctx-* skills. For other AI tools, ctx integrates via generated instruction files or manual context pasting: see Integrations for tool-specific setup.

    Pick one of the options below to install the binary. Claude Code users should also follow the plugin steps included in each option.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-1-build-from-source-recommended","level":3,"title":"Option 1: Build from Source (Recommended)","text":"

    Requires Go (version defined in go.mod) and Claude Code.

    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n

    Install the Claude Code plugin from your local clone:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace
    4. Enter the path to the root of your clone, e.g. ~/WORKSPACE/ctx (this is where .claude-plugin/marketplace.json lives: It points Claude Code to the actual plugin in internal/assets/claude)
    5. Back in /plugin, select Install and choose ctx

    This points Claude Code at the plugin source on disk. Changes you make to hooks or skills take effect immediately: No reinstall is needed.

    Local Installs Need Manual Enablement

    Unlike marketplace installs, local plugin installs are not auto-enabled globally. The plugin will only work in projects that explicitly enable it. Run ctx init in each project (it auto-enables the plugin), or add the entry to ~/.claude/settings.json manually:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Verify:

    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n

    Use the Source, Luke

    Building from source gives you the latest features and bug fixes.

    Since ctx is predominantly a developer tool, this is the recommended approach:

    You get the freshest code, can inspect what you are installing, and the plugin stays in sync with the binary.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-2-binary-download-marketplace","level":3,"title":"Option 2: Binary Download + Marketplace","text":"

    Pre-built binaries are available from the releases page.

    Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64\nchmod +x ctx-0.8.1-linux-amd64\nsudo mv ctx-0.8.1-linux-amd64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-arm64\nchmod +x ctx-0.8.1-linux-arm64\nsudo mv ctx-0.8.1-linux-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-arm64\nchmod +x ctx-0.8.1-darwin-arm64\nsudo mv ctx-0.8.1-darwin-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-amd64\nchmod +x ctx-0.8.1-darwin-amd64\nsudo mv ctx-0.8.1-darwin-amd64 /usr/local/bin/ctx\n

    Download ctx-0.8.1-windows-amd64.exe from the releases page and add it to your PATH.

    Claude Code users: install the plugin from the marketplace:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace;
    4. Enter ActiveMemory/ctx;
    5. Back in /plugin, select Install and choose ctx.

    Other tool users: see Integrations for tool-specific setup (Cursor, Copilot, Aider, Windsurf, etc.).

    Verify the Plugin Is Enabled

    After installing, confirm the plugin is enabled globally. Check ~/.claude/settings.json for an enabledPlugins entry. If missing, run ctx init in your project (it auto-enables the plugin), or add it manually:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Verify:

    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed (Claude Code only)\n
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#verifying-checksums","level":4,"title":"Verifying Checksums","text":"

    Each binary has a corresponding .sha256 checksum file. To verify your download:

    # Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.8.1-linux-amd64.sha256\n

    On macOS, use shasum -a 256 -c instead of sha256sum -c.

    Plugin Details

    After installation (either option) you get:

    • Context autoloading: ctx agent runs on every tool use (with cooldown)
    • Persistence nudges: reminders to capture learnings and decisions
    • Post-commit hooks: nudge context capture after git commit
    • Context size monitoring: alerts as sessions grow large
    • Project skills: /ctx-status, /ctx-task-add, /ctx-history, and more

    See Integrations for the full hook and skill reference.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#quick-start","level":2,"title":"Quick Start","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#1-initialize-context","level":3,"title":"1. Initialize Context","text":"
    cd your-project\nctx init\n

    This creates a .context/ directory with template files and an encryption key at ~/.ctx/ for the encrypted scratchpad. For Claude Code, install the ctx plugin for automatic hooks and skills.

    ctx init also scaffolds four foundation steering files in .context/steering/product.md, tech.md, structure.md, workflow.md. They are placeholders until you customize them (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than buried here.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#2-customize-your-steering-files","level":3,"title":"2. Customize Your Steering Files","text":"

    Steering files are behavioral rules prepended to every AI prompt — the layer that tells your AI how to act on this specific project. They are distinct from decisions (what was chosen) and conventions (how the codebase is written); see ctx for Steering Files for the full model.

    ctx init scaffolded four foundation files; open each and fill it in:

    File What to fill in product.md What the project is, who uses it, what's out of scope tech.md Languages, frameworks, runtime, hard constraints structure.md Directory layout, where new files go, naming rules workflow.md Branch strategy, commit conventions, pre-commit checks

    Each scaffolded file ships with a tombstone marker line (<!-- remove this after you edit the steering file !-->). As long as the marker is present, the file is silently skipped on every load path: the agent context packet, MCP ctx_steering_get, and native-tool sync (Cursor / Cline / Kiro). The skip is deliberate — injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow \"Describe the product...\" as if it were a rule.

    Replace each file's body with real content, then delete the tombstone line. When the line is gone, the file becomes active on the next AI tool call.

    Don't want steering at all? Pass --no-steering-init to ctx init to skip the scaffold entirely. Existing edits are never clobbered by re-running ctx init.

    Inclusion modes (always / auto / manual), priority, and tool scoping are covered in Writing Steering Files and ctx steering.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#3-activate-the-project","level":3,"title":"3. Activate the Project","text":"

    Tell ctx which .context/ directory the rest of these commands should use:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the next steps fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. For more options (multiple .context/ directories, scripts, CI), see Activating a Context Directory.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#4-check-status","level":3,"title":"4. Check Status","text":"
    ctx status\n

    Shows context summary: files present, token estimate, and recent activity.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5-start-using-with-ai","level":3,"title":"5. Start Using with AI","text":"

    With Claude Code (and the ctx plugin installed), context loads automatically via hooks.

    With VS Code Copilot Chat, install the ctx extension and use @ctx /status, @ctx /agent, and other slash commands directly in chat. Run ctx setup copilot --write to generate .github/copilot-instructions.md for automatic context loading.

    For other tools, paste the output of:

    ctx agent --budget 8000\n
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5b-set-up-for-your-ai-tool","level":3,"title":"5B. Set Up for Your AI Tool","text":"

    If you use an MCP-compatible tool, generate the integration config with ctx setup:

    KiroCursorCline
    ctx setup kiro --write\n# Creates .kiro/settings/mcp.json and syncs steering files\n
    ctx setup cursor --write\n# Creates .cursor/mcp.json and syncs steering files\n
    ctx setup cline --write\n# Creates .vscode/mcp.json and syncs steering files\n

    This registers the ctx MCP server and syncs any steering files into the tool's native format. Re-run after adding or changing steering files.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#6-verify-it-works","level":3,"title":"6. Verify It Works","text":"

    Ask your AI: \"Do you remember?\"

    It should cite specific context: current tasks, recent decisions, or previous session topics.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#7-set-up-companion-tools-highly-recommended","level":3,"title":"7. Set Up Companion Tools (Highly Recommended)","text":"

    ctx works on its own, but two companion MCP servers unlock significantly better agent behavior. The investment is small and the benefits compound over sessions:

    • Gemini Search grounded web search with citations. Skills like /ctx-code-review and /ctx-explain use it for up-to-date documentation lookups instead of relying on training data.
    • GitNexus: code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Skills like /ctx-refactor and /ctx-code-review use it for impact analysis and dependency awareness.

    # Index your project for GitNexus (run once, then after major changes)\nnpx gitnexus analyze\n

    Both are optional MCP servers: if they are not connected, skills degrade gracefully to built-in capabilities. See Companion Tools for setup details and verification.

    Next Up:

    • Your First Session →: a step-by-step walkthrough from ctx init to verified recall
    • Common Workflows →: day-to-day commands for tracking context, checking health, and browsing history
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/hub/","level":1,"title":"Hub","text":"","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#sharing-is-caring","level":2,"title":"Sharing Is Caring","text":"

    ctx projects are normally independent: each project has its own .context/ directory, its own decisions, its own learnings, its own journal. That's the right default, since most work is project-local, and mixing context across projects tends to dilute more than it helps.

    But sometimes a decision or a learning should cross project boundaries. A convention you codified in one project deserves to be visible in another. A gotcha you discovered debugging service A is the same gotcha waiting for you in service B. The ctx Hub is the feature that makes those specific entries travel, without replicating everything else.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#what-the-hub-actually-is","level":2,"title":"What the Hub Actually Is","text":"

    In one paragraph: the ctx Hub is a fan-out channel for four specific kinds of structured entries: decision, learning, convention, and task. You publish an entry with ctx add --share in one project, and it appears in .context/hub/ for every other project subscribed to that type. When you run ctx agent --include-hub, those shared entries become part of your next agent context packet.

    That is the entire feature. The Hub does not:

    • Share your session journal (.context/journal/). That stays local to each project.
    • Share your scratchpad (.context/pad). Encrypted notes never leave the machine that created them.
    • Share your TASKS.md, DECISIONS.md, LEARNINGS.md, or CONVENTIONS.md wholesale. Only entries you explicitly --share cross the boundary.
    • Provide user identity or attribution. The Hub identifies projects, not people.

    If you want \"my agent in project B sees everything my agent did in project A,\" that's not the Hub. Local session density stays local.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#who-its-for","level":2,"title":"Who It's For","text":"

    Two shapes, same mechanics, different trust models.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"

    One developer, many projects. You want a learning from project A to show up when you open project B a week later. You want a convention you codified in your dotfiles project to be visible everywhere else on your workstation. Run a Hub on localhost, register each project, done.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#small-trusted-team","level":3,"title":"Small Trusted Team","text":"

    A few teammates on a LAN or a hub.ctx-like self-hosted server. You want team conventions to propagate without a wiki. You want lessons from one on-call engineer's 3 AM incident to reach everyone else's agent on the next session. Same mechanics as the personal case, plus TLS in front and a short security runbook.

    The Hub is not a multi-tenant public service. It assumes everyone holding a client token is friendly. Don't stand up hub.example.com for untrusted participants.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#going-further","level":2,"title":"Going Further","text":"
    • First-time setup: Hub: Getting Started, a five-minute walkthrough on localhost.
    • Mental model and user stories: Hub Overview, what flows, what doesn't, and when not to use it.
    • Team / LAN deployment: Multi-machine setup.
    • Redundancy: HA cluster.
    • Operating a Hub: Hub Operations and Hub Failure Modes.
    • Security posture: Hub Security Model.
    • Command reference: ctx serve, ctx connect, ctx hub.
    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/is-ctx-right/","level":1,"title":"Is It Right for Me?","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#good-fit","level":2,"title":"Good Fit","text":"

    ctx shines when context matters more than code.

    If any of these sound like your project, it's worth trying:

    • Multi-session AI work: You use AI across many sessions on the same codebase, and re-explaining is slowing you down.
    • Architectural decisions that matter: Your project has non-obvious choices (database, auth strategy, API design) that the AI keeps second-guessing.
    • \"Why\" matters as much as \"what\": you need the AI to understand rationale, not just current code
    • Team handoffs: Multiple people (or multiple AI tools) work on the same project and need shared context.
    • AI-assisted development across tools: Uou switch between Claude Code, Cursor, Copilot, or other tools and want context to follow the project, not the tool.
    • Long-lived projects: Anything you'll work on for weeks or months, where accumulated knowledge has compounding value.
    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#may-not-be-the-right-fit","level":2,"title":"May Not Be the Right Fit","text":"

    ctx adds overhead that isn't worth it for every project. Be honest about when to skip it:

    • One-off scripts: If the project is a single file you'll finish today, there's nothing to remember.
    • RAG-only workflows: If retrieval from an external knowledge base already gives the agent everything it needs for each session, adding ctx may be unnecessary. RAG retrieves information; ctx defines the project's working memory: They are complementary.
    • No AI involvement: ctx is designed for human-AI workflows; without an AI consumer, the files are just documentation.
    • Enterprise-managed context platforms: If your organization provides centralized context services, ctx may duplicate that layer.

    For a deeper technical comparison with RAG, prompt management tools, and agent frameworks, see ctx and Similar Tools.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#project-size-guide","level":2,"title":"Project Size Guide","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#solo-developer-single-repo","level":3,"title":"Solo Developer, Single Repo","text":"

    This is ctx's sweet spot.

    You get the most value here: one person, one project, decisions, and learnings accumulating over time. Setup takes 5 minutes and the .context/ directory directory stays small, and every session gets faster.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#small-team-one-or-two-repos","level":3,"title":"Small Team, One or Two Repos","text":"

    Works well.

    Context files commit to git, so the whole team shares the same decisions and conventions. Each person's AI starts with the team's decisions already loaded. Merge conflicts on .context/ files are rare and easy to resolve (they are just Markdown).

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#multiple-repos-or-larger-teams","level":3,"title":"Multiple Repos or Larger Teams","text":"

    ctx operates per repository.

    Each repo has its own .context/ directory with its own decisions, tasks, and learnings. This matches the way code, ownership, and history already work in git.

    There is no built-in cross-repo context layer.

    For organizations that need centralized, organization-wide knowledge, ctx complements a platform solution by providing durable, project-local working memory for AI sessions.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#5-minute-trial","level":2,"title":"5-Minute Trial","text":"

    Zero commitment. Try it, and delete .context/ if it's not for you.

    Using Claude Code?

    Install the ctx plugin from the Marketplace for Claude-native hooks, skills, and automatic context loading:

    1. Type /plugin and press Enter
    2. Select Marketplaces → Add Marketplace
    3. Enter ActiveMemory/ctx
    4. Back in /plugin, select Install and choose ctx

    You'll still need the ctx binary for the CLI: See Getting Started for install options.

    # 1. Initialize\ncd your-project\nctx init\n\n# 2. Activate the project (bind CTX_DIR for this shell).\n#    Required: ctx does not walk the filesystem to find .context/.\neval \"$(ctx activate)\"\n\n# 3. Add one real decision from your project\nctx decision add \"Your actual architectural choice\" \\\n  --context \"What prompted this decision\" \\\n  --rationale \"Why you chose this approach\" \\\n  --consequence \"What changes as a result\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# 4. Check what the AI will see\nctx status\n\n# 5. Start an AI session and ask: \"Do you remember?\"\n

    If the AI cites your decision back to you, it's working.

    Want to remove it later? One command:

    rm -rf .context/\n

    No dependencies to uninstall. No configuration to revert. Just files.

    Ready to try it out?

    • Join the Community→: Open Source is better together.
    • Getting Started →: Full installation and setup.
    • ctx and Similar Tools →: Detailed comparison with other approaches.
    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/joining-a-project/","level":1,"title":"Joining a Project","text":"

    You've joined a team or inherited a project, and there's a .context/ directory in the repo. Good news: someone already set up persistent context. This page gets you oriented fast.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#what-to-read-first","level":2,"title":"What to Read First","text":"

    The files in .context/ have a deliberate priority order. Read them top-down:

    1. CONSTITUTION.md: Hard rules. Read this before you touch anything. These are inviolable constraints the team has agreed on.
    2. TASKS.md: Current and planned work. Shows what's in progress, what's pending, and what's blocked.
    3. CONVENTIONS.md: How the team writes code. Naming patterns, file organization, preferred idioms.
    4. ARCHITECTURE.md: System overview. Components, boundaries, data flow.
    5. DECISIONS.md: Why things are the way they are. Saves you from re-proposing something the team already evaluated and rejected.
    6. LEARNINGS.md: Gotchas, tips, and hard-won lessons. The stuff that doesn't fit anywhere else but will save you hours.

    See Context Files for detailed documentation of each file's structure and purpose.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#activate-the-project","level":2,"title":"Activate the Project","text":"

    Tell ctx which .context/ directory to read from:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the commands in the rest of this guide fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. See Activating a Context Directory for more options (multiple .context/ directories, scripts, CI).

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#checking-context-health","level":2,"title":"Checking Context Health","text":"

    Before you start working, check whether the context is current:

    ctx status\n

    This shows file counts, token estimates, and recent activity. If files haven't been touched in weeks, the context may be stale.

    ctx drift\n

    This compares context files against recent code changes and flags potential drift: decisions that no longer match the codebase, conventions that have shifted, or tasks that look outdated.

    If things are stale, mention it to the team. Don't silently fix it yourself on day one.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#starting-your-first-session","level":2,"title":"Starting Your First Session","text":"

    Generate a context packet to prime your AI:

    ctx agent --budget 8000\n

    This outputs a token-budgeted summary of the project context, ordered by priority. With Claude Code and the ctx plugin, context loads automatically via hooks. You can also use the /ctx-remember skill to get a structured readback of what the AI knows.

    The readback is your verification step: if the AI can cite specific tasks and decisions, the context is working.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#adding-context","level":2,"title":"Adding Context","text":"

    As you work, you'll discover things worth recording. Use the CLI:

    # Record a decision you made or learned about\nctx decision add \"Use connection pooling for DB access\" \\\n  --rationale \"Reduces connection overhead under load\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Capture a gotcha you hit\nctx learning add \"Redis timeout defaults to 5s\" \\\n  --context \"Hit timeouts during bulk operations\" \\\n  --application \"Set explicit timeout for batch jobs\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add a convention you noticed the team follows\nctx convention add \"All API handlers return structured errors\"\n

    You can also just tell the AI: \"Record this as a learning\" or \"Add this decision to context.\" With the ctx plugin, context-update commands handle the file writes.

    See the Knowledge Capture recipe for the full workflow.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#session-etiquette","level":2,"title":"Session Etiquette","text":"

    A few norms for working in a ctx-managed project:

    • Respect existing conventions. If CONVENTIONS.md says \"use filepath.Join,\" use filepath.Join. If you disagree, propose a change, don't silently diverge.
    • Don't restructure context files without asking. The file layout and section structure are shared state. Reorganizing them affects every team member and every AI session.
    • Mark tasks done when complete. Check the box ([x]) in place. Don't move tasks between sections or delete them.
    • Add context as you go. Decisions, learnings, and conventions you discover are valuable to the next person (or the next session).
    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"

    Ignoring CONSTITUTION.md. The constitution exists for a reason. If a task conflicts with a constitution rule, the task is wrong. Raise it with the team instead of working around the constraint.

    Deleting tasks. Never delete a task from TASKS.md. Mark it [x] (done) or [-] (skipped with a reason). The history matters for session replay and audit.

    Bypassing hooks. If the project uses ctx hooks (pre-commit nudges, context autoloading), don't disable them. They exist to keep context fresh. If a hook is noisy or broken, fix it or file a task.

    Over-contributing on day one. Read first, then contribute. Adding a dozen learnings before you understand the project's norms creates noise, not signal.

    Related:

    • Getting Started: installation and setup from scratch
    • Context Files: detailed file reference
    • Knowledge Capture: recording decisions, learnings, and conventions
    • Session Lifecycle: how a typical AI session flows with ctx
    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/keeping-ai-honest/","level":1,"title":"Keeping AI Honest","text":"","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-problem","level":2,"title":"The Problem","text":"

    AI agents confabulate. They invent history that never happened, claim familiarity with decisions that were never made, and sometimes declare a task complete when it is not. This is not malice - it is the default behavior of a system optimizing for plausible-sounding responses.

    When your AI says \"we decided to use Redis for caching last week,\" can you verify that? When it says \"the auth module is complete,\" can you confirm it? Without grounded, persistent context, the answer is no. You are trusting vibes.

    ctx replaces vibes with verifiable artifacts.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#grounded-memory","level":2,"title":"Grounded Memory","text":"

    Every entry in ctx context files has a timestamp and structured fields. When the AI cites a decision, you can check it.

    ## [2026-01-28-143022] Use Event Sourcing for Audit Trail\n\n**Status**: Accepted\n\n**Context**: Compliance requires full mutation history.\n\n**Decision**: Event sourcing for the audit subsystem only.\n\n**Rationale**: Append-only log meets compliance requirements\nwithout imposing event sourcing on the entire domain model.\n

    The timestamp 2026-01-28-143022 is not decoration. It is a verifiable anchor. If the AI references this decision, you can open DECISIONS.md, find the entry, and confirm it says what the AI claims. If the entry does not exist, the AI is hallucinating - and you know immediately.

    This is grounded memory: claims that trace back to artifacts you control and can audit.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#constitutionmd-hard-guardrails","level":2,"title":"CONSTITUTION.md: Hard Guardrails","text":"

    CONSTITUTION.md defines rules the AI must treat as inviolable. These are not suggestions or best practices - they are constraints that override task requirements.

    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these,\nthe task is wrong.\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] All public API changes require a decision record\n* [ ] Never delete context files without explicit user approval\n

    The AI reads these at session start, before anything else. A well- integrated agent will refuse a task that conflicts with a constitutional rule, citing the specific rule it would violate.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-agent-playbooks-anti-hallucination-rules","level":2,"title":"The Agent Playbook's Anti-Hallucination Rules","text":"

    The AGENT_PLAYBOOK.md file includes a section called \"How to Avoid Hallucinating Memory\" with five explicit rules:

    1. Never assume. If it is not in the context files, you do not know it.
    2. Never invent history. Do not claim \"we discussed\" something without a file reference.
    3. Verify before referencing. Search files before citing them.
    4. When uncertain, say so. \"I don't see a decision on this\" is always better than a fabricated one.
    5. Trust files over intuition. If the files say PostgreSQL but your training data suggests MySQL, the files win.

    These rules create a behavioral contract. The AI is not left to guess how confident it should be - it has explicit instructions to ground every claim in the context directory.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#drift-detection","level":2,"title":"Drift Detection","text":"

    Context files can go stale. You rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist. Stale context is almost as dangerous as no context: the AI treats outdated information as current truth.

    ctx drift detects this divergence:

    ctx drift\n

    It scans context files for references to files, paths, and symbols that no longer exist in the codebase. Stale references get flagged so you can update or remove them before they mislead the next session.

    Regular drift checks - weekly, or after major refactors - keep your context files honest the same way tests keep your code honest.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-verification-loop","level":2,"title":"The Verification Loop","text":"

    The /ctx-commit skill includes a built-in verification step: before staging, it maps claims to evidence and runs self-audit questions to surface gaps. This catches inconsistencies at the point where they matter most: right before code is committed.

    This closes the loop. You write context. The AI reads context. The verification step confirms that context still matches reality. When it does not, you fix it - and the next session starts from truth, not from drift.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#trust-through-structure","level":2,"title":"Trust through Structure","text":"

    The common thread across all of these mechanisms is structure over prose. Timestamps make claims verifiable. Constitutional rules make boundaries explicit. Drift detection makes staleness visible. The playbook makes behavioral expectations concrete.

    You do not need to trust the AI. You need to trust the system -- and verify when it matters.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#further-reading","level":2,"title":"Further Reading","text":"
    • Detecting and Fixing Drift: the full workflow for keeping context files accurate
    • Invariants: the properties that must hold for any valid ctx implementation
    • Agent Security: threat model and mitigations for AI agents operating with persistent context
    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/opencode/","level":1,"title":"ctx for OpenCode","text":"","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#the-problem","level":2,"title":"The Problem","text":"

    Every OpenCode session starts from zero. You re-explain your architecture, the AI repeats mistakes it made yesterday, and decisions get rediscovered instead of remembered.

    Without ctx:

    > \"Add the validation middleware we discussed\"\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n

    With ctx:

    > \"Add the validation middleware we discussed\"\n\nYes — from the Jan 15 session. You decided on Zod schemas at the\nroute level (DECISIONS.md #12), and the pattern is in\nCONVENTIONS.md. I'll follow the existing middleware in\nsrc/middleware/auth.ts as a reference.\n

    That's the whole pitch: your AI remembers.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#setup-one-command","level":2,"title":"Setup (One Command)","text":"

    Install the ctx binary first (installation docs), then run from your project root:

    ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n

    This does three things:

    1. ctx setup opencode --write — generates the project-local OpenCode plugin, skills, and AGENTS.md, then merges the ctx MCP server into OpenCode's global config (~/.config/opencode/opencode.json or $OPENCODE_HOME/opencode.json). This writes outside the project root because non-interactive shells (like MCP subprocesses) cannot discover project-local config — the same reason the Copilot CLI integration writes to ~/.copilot/mcp-config.json.
    2. ctx init — creates the .context/ directory with template files
    3. eval \"$(ctx activate)\" — binds CTX_DIR for your shell
    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose .opencode/plugins/ctx.ts Lifecycle plugin (hooks into ctx system commands) ~/.config/opencode/opencode.json Global MCP server registration (or $OPENCODE_HOME/opencode.json) AGENTS.md Agent instructions (OpenCode reads this natively) .opencode/skills/ctx-*/SKILL.md Slash command skills

    The plugin is a single file with no runtime dependencies — no bun install or npm install needed. OpenCode loads it automatically on launch.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"

    The plugin wires OpenCode lifecycle events to ctx. You don't need to do anything — it just works.

    Event What fires What it does New session session.created Warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use Agent idle session.idle Runs persistence and task-completion checks (silent — output is buffered, not surfaced to the TUI) After git commit tool.execute.after Runs ctx system post-commit to capture context state After file edit tool.execute.after Runs ctx system check-task-completion to detect silent task completions Every shell call shell.env Injects CTX_DIR so all ctx commands in the agent's shell resolve to the right project Context compaction experimental.session.compacting Pushes ctx system bootstrap output into the compaction context so the agent retains breadcrumbs to re-read context files post-compaction

    The compaction hook matters most. When OpenCode compresses your context window to free up tokens, the plugin makes sure the compressed summary includes a pointer back to your .context/ directory and its file inventory — so the agent can re-read tasks, decisions, and learnings on demand, even though the original messages are gone.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#how-compaction-works","level":3,"title":"How Compaction Works","text":"

    When your conversation exceeds the context window, OpenCode runs a compaction pass (you can trigger one manually with /compact). The compaction agent summarizes older messages and drops the originals. Without ctx, all accumulated knowledge disappears. With ctx, the plugin intercepts the experimental.session.compacting event and appends ctx system bootstrap output (context directory path and file inventory) into the compaction context. The result: the compressed summary retains the breadcrumbs the agent needs to re-read tasks, decisions, learnings, and conventions on demand, even though the original messages that loaded them are gone.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-is-not-included","level":3,"title":"What Is Not Included","text":"

    Note: dangerous-command blocking is Claude Code-specific and is not part of the OpenCode integration. OpenCode's execution model (explicit user approval for every shell command) makes a pre-execution blocklist unnecessary.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#slash-commands","level":2,"title":"Slash Commands","text":"

    Four skills are available as slash commands:

    Command When to use /ctx-agent Load full context packet. Use at session start or when context feels stale. /ctx-remember \"Do you remember?\" — reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. /ctx-status Context summary at a glance: file count, token estimate, recent activity. /ctx-wrap-up End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to .context/ files.

    You don't need to use these often. The plugin handles most context loading automatically. These are for when you want explicit control.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#mcp-tools","level":2,"title":"MCP Tools","text":"

    The ctx MCP server exposes tools directly to the agent. These let the AI read and write your context files without shell commands:

    Tool Purpose ctx_add Add a task, decision, learning, or convention ctx_complete Mark a task done by number or text match ctx_search Full-text search across all .context/ files ctx_next Suggest the next pending task by priority ctx_drift Detect stale context: dead paths, missing files ctx_compact Archive completed tasks, clean empty sections ctx_remind List pending session-scoped reminders ctx_status Context health: file count, token estimate ctx_steering_get Retrieve steering files applicable to the current prompt ctx_journal_source Query recent AI session history ctx_session_event Signal session start/end lifecycle events ctx_watch_update Apply structured updates to .context/ files ctx_check_task_completion After a write, detect silently completed tasks

    You don't invoke these yourself. The agent uses them as needed.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"

    If you re-run ctx setup opencode --write (e.g., after updating ctx), the plugin and skills are rewritten in place. Restart OpenCode to pick up the refreshed plugin — OpenCode only loads plugins at launch, not mid-session.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix opencode mcp list shows ctx ✗ failed MCP error -32000: Connection closed CTX_DIR not resolving in the MCP subprocess Re-run ctx setup opencode --write to regenerate the sh-wrapper that sets CTX_DIR Plugin installed but no hooks fire Flat-file vs. subdirectory discovery mismatch (OpenCode requires .opencode/plugins/<name>.ts, not a subfolder) Verify the plugin is at .opencode/plugins/ctx.ts. Check with opencode --print-logs --log-level DEBUG ctx agent markdown leaking into the TUI BunShell command missing .nothrow().quiet() Update to the latest plugin: ctx setup opencode --write and restart","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#verify-it-works","level":2,"title":"Verify It Works","text":"

    Start a new OpenCode session and ask:

    Do you remember?\n

    The AI should cite specific context: current tasks, recent decisions, or previous session topics. If it says \"I don't have memory\" or \"Let me check,\" something went wrong — check that the plugin installed correctly and .context/ has files in it.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#whats-next","level":2,"title":"What's Next","text":"
    • Your First Session — step-by-step walkthrough from ctx init to verified recall
    • Common Workflows — day-to-day commands for tracking context, checking health, and browsing history
    • Context Files — what lives in .context/ and how each file is used
    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/prompting-guide/","level":1,"title":"Prompting Guide","text":"

    New to ctx?

    This guide references context files like TASKS.md, DECISIONS.md, and LEARNINGS.md:

    These are plain Markdown files that ctx maintains in your project's .context/ directory.

    If terms like \"context packet\" or \"session ceremony\" are unfamiliar,

    • start with the ctx Manifesto for the why,
    • About for the big picture,
    • then Getting Started to set up your first project.
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#literature-matters","level":2,"title":"Literature Matters","text":"

    This guide is about crafting effective prompts for working with AI assistants in ctx-enabled projects, but the guidelines given here apply to other AI systems, too.

    The right prompt triggers the right behavior.

    This guide documents prompts that reliably produce good results.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#tldr","level":2,"title":"TL;DR","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" What's next /ctx-next Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect /ctx-reflect Wrap up /ctx-wrap-up Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\" One more thing \"What's the single smartest addition?\" Set tone \"Push back if my assumptions are wrong.\" Constrain scope \"Only change files in X. Nothing else.\" Course correct \"Stop. That's not what I meant.\" Check health \"Run ctx drift\" Commit /ctx-commit","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#do-you-remember","level":3,"title":"\"do you remember?\"","text":"

    Triggers the AI to silently read TASKS.md, DECISIONS.md, LEARNINGS.md, and check recent history via ctx journal before responding with a structured readback:

    1. Last session: most recent session topic and date
    2. Active work: pending or in-progress tasks
    3. Recent context: 1-2 recent decisions or learnings
    4. Next step: offer to continue or ask what to focus on

    Use this at the start of every important session.

    Do you remember what we were working on?\n

    This question implies prior context exists. The AI checks files rather than admitting ignorance. The expected response cites specific context (session names, task counts, decisions), not vague summaries.

    If the AI instead narrates its discovery process (\"Let me check if there are files...\"), it has not loaded CLAUDE.md or AGENT_PLAYBOOK.md properly.

    For a detailed case study on making agents actually follow this protocol (including the failure modes, the timing problem, and the hook design that solved it) see The Dog Ate My Homework.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the Current State?\"","text":"

    Prompts reading of TASKS.md, recent sessions, and status overview.

    Use this when resuming work after a break.

    Variants:

    • \"Where did we leave off?\"
    • \"What's in progress?\"
    • \"Show me the open tasks.\"
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why Doesn't X Work?\"","text":"

    This triggers root cause analysis rather than surface-level fixes.

    Use this when something fails unexpectedly.

    Framing as \"why\" encourages investigation before action. The AI will trace through code, check configurations, and identify the actual cause.

    Real Example

    \"Why can't I run /ctx-reflect?\" led to discovering missing permissions in settings.local.json bootstrapping.

    This was a fix that benefited all users of ctx.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is This Consistent with Our Decisions?\"","text":"

    This prompts checking DECISIONS.md before implementing.

    Use this before making architectural choices.

    Variants:

    • \"Check if we've decided on this before\"
    • \"Does this align with our conventions?\"
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-would-break-if-we","level":3,"title":"\"What Would Break If We...\"","text":"

    This triggers defensive thinking and impact analysis.

    Use this before making significant changes.

    What would break if we change the Settings struct?\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before You Start, Read X\"","text":"

    This ensures specific context is loaded before work begins.

    Use this when you know the relevant context exists in a specific file.

    Before you start, check ctx journal source for the auth discussion session\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-control","level":3,"title":"Scope Control","text":"

    Constrain the AI to prevent sprawl. These are some of the most useful prompts in day-to-day work.

    Only change files in internal/cli/add/. Nothing else.\n
    No new files. Modify the existing implementation.\n
    Keep the public API unchanged. Internal refactor only.\n

    Use these when the AI tends to \"helpfully\" modify adjacent code, add documentation you didn't ask for, or create new abstractions.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#course-correction","level":3,"title":"Course Correction","text":"

    Steer the AI when it goes off-track: Don't wait for it to finish a wrong approach.

    Stop! That's not what I meant. Let me clarify.\n
    Let's step back. Explain what you're about to do before changing anything.\n
    Undo that last change and try a different approach.\n

    These work because they interrupt momentum.

    Without explicit course correction, the AI tends to commit harder to a wrong path rather than reconsidering.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#failure-modes","level":3,"title":"Failure Modes","text":"

    When the AI misbehaves, match the symptom to the recovery prompt:

    Symptom Recovery prompt Hand-waves (\"should work now\") \"Show evidence: file/line refs, command output, or test name.\" Creates unnecessary files \"No new files. Modify the existing implementation.\" Expands scope unprompted \"Stop after the smallest working change. Ask before expanding scope.\" Narrates instead of acting \"Skip the explanation. Make the change and show the diff.\" Repeats a failed approach \"That didn't work last time. Try a different approach.\" Claims completion without proof \"Run the test. Show me the output.\"

    These are recovery handles, not rules to paste into CLAUDE.md.

    Use them in the moment when you see the behavior.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-did-we-learn","level":3,"title":"\"What Did We Learn?\"","text":"

    This prompts reflection on the session and often triggers adding learnings to LEARNINGS.md.

    Use this after completing a task or debugging session.

    This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add This as a Learning/decision\"","text":"

    This is an explicit persistence request.

    Use this when you have discovered something worth remembering.

    Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save Context Before We End\"","text":"

    This triggers context persistence before the session closes.

    Use it at the end of the session or before switching topics.

    Variants:

    • \"Let's persist what we did\"
    • \"Update the context files\"
    • /ctx-wrap-up:the recommended end-of-session ceremony (see Session Ceremonies)
    • /ctx-reflect: mid-session reflection checkpoint
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the Codebase for X\"","text":"

    This triggers thorough codebase search rather than guessing.

    Use this when you need to understand how something works.

    This works because \"Explore\" signals that investigation is needed, not immediate action.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How Does X Work in This Codebase?\"","text":"

    This prompts reading actual code rather than explaining general concepts.

    Use this to understand the existing implementation.

    How does session saving work in this codebase?\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find All Places Where X\"","text":"

    This triggers a comprehensive search across the codebase.

    Use this before refactoring or understanding the impact.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What Should We Document from This?\"","text":"

    This prompts identifying learnings, decisions, and conventions worth persisting.

    Use this after complex discussions or implementations.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is This the Right Approach?\"","text":"

    This invites the AI to challenge the current direction.

    Use this when you want a sanity check.

    This works because it allows AI to disagree.

    AIs often default to agreeing; this prompt signals you want an honest assessment.

    Stronger variant: \"Push back if my assumptions are wrong.\" This sets the tone for the entire session: The AI will flag questionable choices proactively instead of waiting to be asked.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-am-i-missing","level":3,"title":"\"What Am I Missing?\"","text":"

    This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.

    Use this before finalizing a design or implementation.

    Forward-looking variant: \"What's the single smartest addition you could make to this at this point?\" Use this after you think you're done: It surfaces improvements you wouldn't have thought to ask for. The constraint to one thing prevents feature sprawl.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#cli-commands-as-prompts","level":2,"title":"CLI Commands as Prompts","text":"

    Asking the AI to run ctx commands is itself a prompt. These load context or trigger specific behaviors:

    Command What it does \"Run ctx status\" Shows context summary, file presence, staleness \"Run ctx agent\" Loads token-budgeted context packet \"Run ctx drift\" Detects dead paths, stale files, missing context","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ctx-skills","level":3,"title":"ctx Skills","text":"

    The SKILS.md Standard

    Skills are formalized prompts stored as SKILL.md files.

    The /slash-command syntax below is Claude Code specific.

    Other agents can use the same skill files, but invocation may differ.

    Use ctx skills by name:

    Skill When to use /ctx-status Quick context summary /ctx-agent Load full context packet /ctx-remember Recall project context and structured readback /ctx-wrap-up End-of-session context persistence /ctx-history Browse session history for past discussions /ctx-reflect Structured reflection checkpoint /ctx-next Suggest what to work on next /ctx-commit Commit with context persistence /ctx-drift Detect and fix context drift /ctx-implement Execute a plan step-by-step with verification /ctx-loop Generate autonomous loop script /ctx-pad Manage encrypted scratchpad /ctx-archive Archive completed tasks /check-links Audit docs for dead links

    Ceremony vs. Workflow Skills

    Most skills work conversationally: \"what should we work on?\" triggers /ctx-next, \"save that as a learning\" triggers /ctx-learning-add. Natural language is the recommended approach.

    Two skills are the exception: /ctx-remember and /ctx-wrap-up are ceremony skills for session boundaries: Invoke them as explicit slash commands: conversational triggers risk partial execution. See Session Ceremonies.

    Skills combine a prompt, tool permissions, and domain knowledge into a single invocation.

    Skills beyond Claude Code

    The /slash-command syntax above is Claude Code native, but the underlying SKILL.md files are a standard markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates.

    See Integrations for setup details.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"

    Based on our ctx development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:

    Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\" \"Implement everything\" No phasing, sprawl risk Break into tasks, implement one at a time \"You should know this\" Assumes context is loaded \"Before you start, read X\"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reliability-checklist","level":2,"title":"Reliability Checklist","text":"

    Before sending a non-trivial prompt, check these four elements. This is the guide's DNA in one screenful.

    1. Goal in one sentence: What does \"done\" look like?
    2. Files to read: What existing code or context should the AI review before acting?
    3. Verification command: How will you prove it worked? (test name, CLI command, expected output)
    4. Scope boundary: What should the AI not touch?

    A prompt that covers all four is almost always good enough.

    A prompt missing #3 is how you get \"should work now\" without evidence.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#safety-invariants","level":2,"title":"Safety Invariants","text":"

    These Are Invariants: Not Suggestions

    A prompting guide earns its trust by being honest about risk.

    These four rules mentioned below don't change with model versions, agent frameworks, or project size.

    Build them into your workflow once and stop thinking about them.

    Tool-using agents can read files, run commands, and modify your codebase. That power makes them useful. It also creates a trust boundary you should be aware of.

    These invariants apply regardless of which agent or model you use.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#treat-the-repository-text-as-untrusted-input","level":3,"title":"Treat the Repository Text as \"Untrusted Input\"","text":"

    Issue descriptions, PR comments, commit messages, documentation, and even code comments can contain text that looks like instructions. An agent that reads a GitHub issue and then runs a command found inside it is executing untrusted input.

    The rule: Before running any command the agent found in repo text (issues, docs, comments), restate the command explicitly and confirm it does what you expect. Don't let the agent copy-paste from untrusted sources into a shell.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ask-before-destructive-operations","level":3,"title":"Ask Before Destructive Operations","text":"

    git push --force, rm -rf, DROP TABLE, docker system prune: these are irreversible or hard to reverse. A good agent should pause before running them, but don't rely on that.

    The rule: For any operation that deletes data, overwrites history, or affects shared infrastructure, require explicit confirmation. If the agent runs something destructive without asking, that's a course-correction moment: \"Stop. Never run destructive commands without asking first.\"

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-the-blast-radius","level":3,"title":"Scope the Blast Radius","text":"

    An agent told to \"fix the tests\" might modify test fixtures, change assertions, or delete tests that inconveniently fail. An agent told to \"deploy\" might push to production. Broad mandates create broad risk.

    The rule: Constrain scope before starting work. The Reliability Checklist's scope boundary (#4) is your primary safety lever. When in doubt, err on the side of a tighter boundary.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#secrets-never-belong-in-context","level":3,"title":"Secrets Never Belong in Context","text":"

    LEARNINGS.md, DECISIONS.md, and session transcripts are plain-text files that may be committed to version control.

    Don't persist API keys, passwords, tokens, or credentials in context files.

    The rule: If the agent encounters a secret during work, it should use it transiently (environment variable, an alias to the secret instead of the actual secret, etc.) and never write it to a context file.

    Any Secret Seen IS Exposed

    If you see a secret in a context file, remove it immediately and rotate the credential.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-plan-implement","level":2,"title":"Explore → Plan → Implement","text":"

    For non-trivial work, name the phase you want:

    Explore src/auth and summarize the current flow.\nThen propose a plan. After I approve, implement with tests.\n

    This prevents the AI from jumping straight to code.

    The three phases map to different modes of thinking:

    • Explore: read, search, understand: no changes
    • Plan: propose approach, trade-offs, scope: no changes
    • Implement: write code, run tests, verify: changes

    Small fixes skip straight to implement. Complex or uncertain work benefits from all three.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#prompts-by-task-type","level":2,"title":"Prompts by Task Type","text":"

    Different tasks need different prompt structures. The pattern: symptom + location + verification.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#bugfix","level":3,"title":"Bugfix","text":"
    Users report search returns empty results for queries with hyphens.\nReproduce in src/search/. Write a failing test for \"foo-bar\",\nfix the root cause, run: go test ./internal/search/...\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#refactor","level":3,"title":"Refactor","text":"
    Inspect src/auth/ and list duplication hotspots.\nPropose a refactor plan scoped to one module.\nAfter approval, remove duplication without changing behavior.\nAdd a test if coverage is missing. Run: make audit\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#research","level":3,"title":"Research","text":"
    Explore the request flow around src/api/.\nSummarize likely bottlenecks with evidence.\nPropose 2-3 hypotheses. Do not implement yet.\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#docs","level":3,"title":"Docs","text":"
    Update docs/cli-reference.md to reflect the new --format flag.\nConfirm the flag exists in the code and the example works.\n

    Notice each prompt includes what to verify and how. Without that, you get a \"should work now\" instead of evidence.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"

    Tasks in TASKS.md are indirect prompts to the AI. How you write them shapes how the AI approaches the work.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-motivation-not-just-the-goal","level":3,"title":"State the Motivation, Not Just the Goal","text":"

    Tell the AI why you are building something, not just what.

    Bad: \"Build a calendar view.\"

    Good: \"Build a calendar view. The motivation is that all notes and tasks we build later should be viewable here.\"

    The second version lets the AI anticipate downstream requirements:

    It will design the calendar's data model to be compatible with future features: Without you having to spell out every integration point. Motivation turns a one-off task into a directional task.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"

    Bad task (implementation-focused):

    - [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?

    Good task (deliverable-focused):

    - [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx journal source` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    Now the AI knows the subtasks serve a specific user-facing deliverable.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"

    For complex tasks, add explicit \"done when\" criteria:

    - [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n

    This prevents premature \"task complete\" when only the implementation details are done, but the feature doesn't actually work.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"

    Completing all subtasks does not mean the parent task is complete.

    The parent task describes what the user gets.

    Subtasks describe how to build it.

    Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-do-these-approaches-work","level":2,"title":"Why Do These Approaches Work?","text":"

    The patterns in this guide aren't invented here: They are practitioner translations of well-established, peer-reviewed research, most of which predate the current AI (hype) wave.

    The underlying ideas come from decades of work in machine learning, cognitive science, and numerical optimization. For a concrete case study showing how these principles play out when an agent decides whether to follow instructions (attention competition, optimization toward least-resistance paths, and observable compliance as a design goal) see The Dog Ate My Homework.

    Phased work (\"Explore → Plan → Implement\") applies chain-of-thought reasoning: Decomposing a problem into sequential steps before acting. Forcing intermediate reasoning steps measurably improves output quality in language models, just as it does in human problem-solving. Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (2022).

    Root-cause prompts (\"Why doesn't X work?\") use step-back abstraction: Retreating to a higher-level question before diving into specifics. This mirrors how experienced engineers debug: they ask \"what should happen?\" before asking \"what went wrong?\" Zheng et al., Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models (2023).

    Exploring alternatives (\"Propose 2-3 approaches\") leverages self-consistency: Generating multiple independent reasoning paths and selecting the most coherent result. The idea traces back to ensemble methods in ML: A committee of diverse solutions outperforms any single one. Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models (2022).

    Impact analysis (\"What would break if we...\") is a form of tree-structured exploration: Branching into multiple consequence paths before committing. This is the same principle behind game-tree search (minimax, MCTS) that has powered decision-making systems since the 1950s. Yao et al., Tree of Thoughts: Deliberate Problem Solving with Large Language Models (2023).

    Motivation prompting (\"Build X because Y\") works through goal conditioning: Providing the objective function alongside the task. In optimization terms, you are giving the gradient direction, not just the loss. The model can make locally coherent decisions that serve the global objective because it knows what \"better\" means.

    Scope constraints (\"Only change files in X\") apply constrained optimization: Bounding the search space to prevent divergence. This is the same principle behind regularization in ML: Without boundaries, powerful optimizers find solutions that technically satisfy the objective but are practically useless.

    CLI commands as prompts (\"Run ctx status\") interleave reasoning with acting: The model thinks, acts on external tools, observes results, then thinks again. Grounding reasoning in real tool output reduces hallucination because the model can't ignore evidence it just retrieved. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022).

    Task decomposition (\"Prompts by Task Type\") applies least-to-most prompting: Breaking a complex problem into subproblems and solving them sequentially, each building on the last. This is the research version of \"plan, then implement one slice.\" Zhou et al., Least-to-Most Prompting Enables Complex Reasoning in Large Language Models (2022).

    Explicit planning (\"Explore → Plan → Implement\") is directly supported by plan-and-solve prompting, which addresses missing-step failures in zero-shot reasoning by extracting a plan before executing. The phased structure prevents the model from jumping to code before understanding the problem. Wang et al., Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (2023).

    Session reflection (\"What did we learn?\", /ctx-reflect) is a form of verbal reinforcement learning: Improving future performance by persisting linguistic feedback as memory rather than updating weights. This is exactly what LEARNINGS.md and DECISIONS.md provide: a durable feedback signal across sessions. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning (2023).

    These aren't prompting \"hacks\" that you will find in the \"1000 AI Prompts for the Curious\" listicles: They are applications of foundational principles:

    • Decomposition,
    • Abstraction,
    • Ensemble Reasoning,
    • Search,
    • and Constrained Optimization.

    They work because language models are, at their core, optimization systems navigating probabilistic landscapes.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#further-reading","level":2,"title":"Further Reading","text":"
    • The Attention Budget: Why your AI forgets what you just told it, and how token budgets shape context strategy
    • The Dog Ate My Homework: A case study in making agents follow instructions: attention timing, delegation decay, and observable compliance as a design goal
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#contributing","level":2,"title":"Contributing","text":"

    Found a prompt that works well? Open an issue or PR with:

    1. The prompt text;
    2. What behavior it triggers;
    3. When to use it;
    4. Why it works (optional but helpful).

    Dive Deeper:

    • Recipes: targeted how-to guides for specific tasks
    • CLI Reference: all commands and flags
    • Integrations: setup for Claude Code, Cursor, Aider
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/repeated-mistakes/","level":1,"title":"My AI Keeps Making the Same Mistakes","text":"","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-problem","level":2,"title":"The Problem","text":"

    You found a bug last Tuesday. You debugged it, understood the root cause, and moved on. Today, a new session hits the exact same bug. The AI rediscovers it from scratch, burning twenty minutes on something you already solved.

    Worse: you spent an hour last week evaluating two database migration strategies, picked one, documented why in a comment somewhere, and now the AI is cheerfully suggesting the approach you rejected. Again.

    This is not a model problem. It is a memory problem. Without persistent context, every session starts with amnesia.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#how-ctx-stops-the-loop","level":2,"title":"How ctx Stops the Loop","text":"

    ctx gives your AI three files that directly prevent repeated mistakes, each targeting a different failure mode.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#decisionsmd-stop-relitigating-settled-choices","level":3,"title":"DECISIONS.md: Stop Relitigating Settled Choices","text":"

    When you make an architectural decision, record it with rationale and rejected alternatives. The AI reads this at session start and treats it as settled.

    ## [2026-02-12] Use JWT for Authentication\n\n**Status**: Accepted\n\n**Context**: Need stateless auth for the API layer.\n\n**Decision**: JWT with short-lived access tokens and refresh rotation.\n\n**Rationale**: Stateless, scales horizontally, team has prior experience.\n\n**Alternatives Considered**:\n- Session-based auth: Rejected. Requires sticky sessions or shared store.\n- API keys only: Rejected. No user identity, no expiry rotation.\n

    Next session, when the AI considers auth, it reads this entry and builds on the decision instead of re-debating it. If someone asks \"why not sessions?\", the rationale is already there.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#learningsmd-capture-gotchas-once","level":3,"title":"LEARNINGS.md: Capture Gotchas Once","text":"

    Learnings are the bugs, quirks, and non-obvious behaviors that cost you time the first time around. Write them down so they cost you zero time the second time.

    ## Build\n\n### CGO Required for SQLite on Alpine\n\n**Discovered**: 2026-01-20\n\n**Context**: Docker build failed silently with \"no such table\" at runtime.\n\n**Lesson**: The go-sqlite3 driver requires CGO_ENABLED=1 and gcc\ninstalled in the build stage. Alpine needs apk add build-base.\n\n**Application**: Always use the golang:alpine image with build-base\nfor SQLite builds. Never set CGO_ENABLED=0.\n

    Without this entry, the next session that touches the Dockerfile will hit the same wall. With it, the AI knows before it starts.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#constitutionmd-draw-hard-lines","level":3,"title":"CONSTITUTION.md: Draw Hard Lines","text":"

    Some mistakes are not about forgetting - they are about boundaries the AI should never cross. CONSTITUTION.md sets inviolable rules.

    * [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never disable security linters without a documented exception\n* [ ] All database migrations must be reversible\n

    The AI reads these as absolute constraints. It does not weigh them against convenience. It refuses tasks that would violate them.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-accumulation-effect","level":2,"title":"The Accumulation Effect","text":"

    Each of these files grows over time. Session one captures two decisions. Session five adds a tricky learning about timezone handling. Session twelve records a convention about error message formatting.

    By session twenty, your AI has a knowledge base that no single person carries in their head. New team members - human or AI - inherit it instantly.

    The key insight: you are not just coding. You are building a knowledge layer that makes every future session faster.

    ctx files version with your code in git. They survive branch switches, team changes, and model upgrades. The context outlives any single session.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#getting-started","level":2,"title":"Getting Started","text":"

    Capture your first decision or learning right now:

    ctx decision add \"Use PostgreSQL\" \\\n  --context \"Need a relational database for the project\" \\\n  --rationale \"Team expertise, JSONB support, mature ecosystem\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\nctx learning add \"Vitest mock hoisting\" \\\n  --context \"Tests failing intermittently\" \\\n  --lesson \"vi.mock() must be at file top level\" \\\n  --application \"Use vi.doMock() for dynamic mocks\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#further-reading","level":2,"title":"Further Reading","text":"
    • Knowledge Capture: the full workflow for persisting decisions, learnings, and conventions
    • Context Files Reference: structure and format for every file in .context/
    • About ctx: the bigger picture - why persistent context changes how you work with AI
    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/steering/","level":1,"title":"Steering Files","text":"","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#steering-files","level":2,"title":"Steering Files","text":"

    ctx projects talk to AI assistants through several layers (context files, decisions, conventions, the agent context packet) but none of those can tell the assistant how to behave when a specific kind of prompt arrives. That's what steering files are for.

    A steering file is a small markdown document with YAML frontmatter that says: \"when the user asks about X, prepend these rules to the prompt.\" ctx manages those files in .context/steering/, decides which ones match each prompt, and syncs them out to each AI tool's native config (Claude Code, Cursor, Kiro, Cline) so the rules actually land in the prompt pipeline.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#not-the-same-as-decisions-or-conventions","level":2,"title":"Not the Same as Decisions or Conventions","text":"

    The three look similar on disk but serve different purposes:

    Kind Purpose Decisions (DECISIONS.md) What was chosen and why Conventions (CONVENTIONS.md) How the codebase is written Steering (.context/steering/*.md) How the AI should behave on matching prompts

    If you find yourself writing \"the AI should always do X when asked about Y,\" that belongs in steering, not decisions.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#your-first-steering-files","level":2,"title":"Your First Steering Files","text":"

    ctx init scaffolds four foundation steering files in .context/steering/ so you start with something to edit rather than an empty directory:

    File What to fill in product.md What the project is, who it's for, what's out of scope tech.md Languages, frameworks, runtime, hard constraints structure.md Directory layout, where new files go, naming rules workflow.md Branch strategy, commit conventions, pre-commit checks

    Each file starts with an inline HTML comment explaining the three inclusion modes, priority semantics, and tool scoping. The comment is invisible in rendered markdown but visible when you open the file to edit it; it's self-documenting scaffolding, not forever guidance. Delete the comment once you've customized the file.

    Default settings for foundation files:

    • inclusion: always: fires on every AI tool call
    • priority: 10: injected near the top of the prompt
    • tools: []: applies to every configured AI tool

    You should open each of these files and replace the placeholder content with your project's actual rules. Re-running ctx init is safe: existing files are left alone, so your edits survive. Use ctx init --no-steering-init to opt out of the scaffold entirely.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#inclusion-modes","level":2,"title":"Inclusion Modes","text":"

    Each steering file declares an inclusion mode in its frontmatter:

    Mode When the file is included always Every prompt, unconditionally auto When the prompt keywords match the file's description manual Only when the user explicitly names the file

    Which mode to pick depends on the AI tool you use, because the two tool families consume steering very differently.

    Claude Code and Codex: prefer inclusion: always for rules that must fire reliably. These tools have two delivery channels:

    1. The plugin's PreToolUse hook runs ctx agent with an empty prompt, so only always files match and get injected automatically on every tool call.
    2. The ctx_steering_get MCP tool, registered automatically when the ctx plugin is installed. Claude can call this tool mid-task to fetch auto or manual files matching a specific prompt. Verify with claude mcp list; look for ctx: ✓ Connected.

    Use always for invariants and anything that must fire every session. Use auto for situational rules where \"Claude fetches this when the prompt is relevant\" is the right behavior; those still land, just on Claude's judgment. Use manual for reference libraries you'll name explicitly.

    Cursor, Cline, Kiro: auto is the natural default. These tools read .cursor/rules/, .clinerules/, or .kiro/steering/ natively and resolve the description match on their own, so auto files fire when the prompt matches. manual files load on explicit invocation. always still works but consumes context budget on every turn.

    Mixed setups: if a rule must fire on Claude Code, pick always, even if it's overkill for your Cursor setup. The context budget cost is small; the alternative (silently not firing) is worse.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-families-of-ai-tools-two-delivery-paths","level":2,"title":"Two Families of AI Tools, Two Delivery Paths","text":"

    Not every AI tool consumes steering the same way. ctx handles two tool families differently, and it's worth knowing which family your editor is in before you wonder why a rule isn't firing.

    Native-rules tools (Cursor, Cline, Kiro) have a built-in rules primitive. They read a specific directory (.cursor/rules/, .clinerules/, .kiro/steering/) and apply the rules they find there. ctx handles these via ctx steering sync, which exports your files into the tool-native format. Run sync whenever you edit a steering file.

    Hook + MCP tools (Claude Code, Codex) have no native rules primitive, so ctx steering sync is a no-op for them. Instead, ctx delivers steering through two non-sync channels:

    1. Automatic injection via a PreToolUse hook. The ctx setup claude-code plugin wires a hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads your steering files, filters them by the active prompt, and includes matching bodies in the context packet it prints. Claude Code feeds that output back into its context. Every tool call, automatically.
    2. On-demand via the ctx_steering_get MCP tool. The ctx MCP server exposes a tool Claude can call mid-task to fetch matching steering files for a specific prompt. Claude decides when to call it; it's not automatic.

    Both channels activate when you run ctx setup claude-code --write. After that, steering just works for Claude Code.

    Practical takeaway:

    • Using Cursor/Cline/Kiro only? Run ctx steering sync after edits.
    • Using Claude Code or Codex only? Never run sync; the hook+MCP pipeline handles it.
    • Using both? Run sync for the native-rules tools; the hook+MCP pipeline covers Claude Code automatically.
    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-shapes-of-automation-rules-and-scripts","level":2,"title":"Two Shapes of Automation: Rules and Scripts","text":"

    Steering is one of two hook-like layers ctx provides for customizing AI behavior. They're complementary:

    • Steering: persistent rules that get prepended to prompts. Declarative, text-only, scored by match.
    • Triggers: executable shell scripts that fire at lifecycle events. Imperative, runs arbitrary code, gated by exit codes.

    Pick steering when you want \"always remind the AI of X.\" Pick triggers when you want \"do Y when event Z happens.\" They can coexist; many projects use both.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Writing Steering Files: a six-step walkthrough: scaffold, write the rule, preview matches, list, get-rules-in-front-of-the-AI (two paths depending on tool family), verify.
    • ctx steering reference: full command, flag, and frontmatter reference; includes the per-tool delivery-mechanism table and a dedicated section on how Claude Code and Codex consume steering.
    • ctx setup: configure which AI tools receive steering. For Cursor/Cline/Kiro this is about sync targets; for Claude Code/Codex it installs the plugin that wires the PreToolUse hook and MCP server.
    • Lifecycle Triggers: the imperative companion to steering files.
    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/triggers/","level":1,"title":"Lifecycle Triggers","text":"","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#lifecycle-triggers","level":2,"title":"Lifecycle Triggers","text":"

    Some things can't be expressed as a rule you want the AI to follow. Sometimes you want something to happen: block a dangerous tool call, inject today's standup notes into the next session, log every file save to a journal. That's what triggers are for.

    A trigger is an executable shell script that ctx runs at a specific lifecycle event: the start of a session, before a tool call, when a file is saved, and so on. Triggers read a JSON payload from stdin, do whatever they need, and write a JSON response on stdout. They can allow, block, or inject context into the pipeline depending on the event type.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#trigger-types","level":2,"title":"Trigger Types","text":"Type Fires when Use case session-start A new AI session begins Inject rotating context, standup notes session-end An AI session ends Persist summaries, send notifications pre-tool-use Before a tool call executes Block, gate, or audit post-tool-use After a tool call completes Log, react, post-process file-save A file is saved Lint on save, update indices context-add A new entry is added to .context/ Cross-link, notify, enrich","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-are-arbitrary-code-treat-them-like-pre-commit-hooks","level":2,"title":"Triggers Are Arbitrary Code: Treat Them like Pre-Commit Hooks","text":"

    Only Enable Scripts You've Read and Understand

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    ctx trigger add intentionally creates new scripts disabled (no executable bit). You must ctx trigger enable <name> after reviewing the contents. That's not a suggestion; it's the security model.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#three-hook-like-layers-in-ctx","level":2,"title":"Three Hook-like Layers in ctx","text":"

    Triggers are one of three distinct hook-like concepts in ctx. The names are similar but the owners and use cases are not:

    Layer Owned by Where they live When to use ctx trigger You .context/hooks/<type>/*.sh Project-specific automation, any AI tool ctx system hooks ctx itself built-in, wired into tool configs Built-in nudges (you don't author these) Claude Code hooks Claude Code .claude/settings.local.json Claude-Code-only tool-specific integration

    This page is about the first category. The other two run automatically and are invisible to you.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-vs-steering-same-problem-different-shape","level":2,"title":"Triggers vs Steering: Same Problem, Different Shape","text":"

    Triggers are the imperative counterpart to steering files. Steering expresses persistent rules the AI reads before each prompt; triggers express side effects that run on lifecycle events. They're complementary, not competing:

    • Want the AI to remember something? → Steering.
    • Want a script to run when something happens? → Trigger.

    Most projects use both.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Authoring Lifecycle Triggers: walkthrough with security guidance: scaffold, test, enable, iterate.
    • ctx trigger reference: command reference, trigger type table, input/output contract.
    • Steering files: the declarative counterpart to triggers.
    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/vscode/","level":1,"title":"ctx for VS Code","text":"","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#the-problem","level":2,"title":"The Problem","text":"

    Every Copilot Chat session in VS Code starts from zero. You re-explain what you were doing, the AI repeats yesterday's mistakes, and decisions you spent an hour reasoning through last week get rediscovered instead of remembered.

    Without ctx:

    @workspace add the validation middleware we discussed\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n

    With ctx:

    @ctx Do you remember?\n\nLast session (2026-05-09): you decided on Zod schemas at the route level\n(DECISIONS.md #12). Pattern lives in CONVENTIONS.md. Open task: wire\nthe auth middleware into the new /admin routes (TASKS.md, in-progress).\nThe reference implementation is src/middleware/auth.ts.\n

    That's the whole pitch: your AI remembers, right inside the IDE you already work in.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#setup","level":2,"title":"Setup","text":"

    Install the extension and the ctx binary, then ctx init your project:

    1. Install the extension from the VS Code Marketplace (publisher: activememory, display name: ctx — Persistent Context for AI). Or build from source (see editors/vscode/README.md).
    2. Install the ctx CLI if you haven't already (installation docs). If you skip this step, the extension will auto-download the right binary for your platform on first use (see Auto-Bootstrap below).
    3. From your project root, run:
    ctx init && eval \"$(ctx activate)\"\n
    1. Open Copilot Chat in VS Code and type @ctx /init to verify the extension can reach the CLI.
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose .context/ Project-local context directory (created by ctx init) .github/copilot-instructions.md Repository instructions Copilot reads natively; regenerated automatically whenever .context/ files change

    The extension itself lives in VS Code's extension storage. No project files are added beyond .context/ and the Copilot instructions.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#how-you-use-it","level":2,"title":"How You Use It","text":"

    Type @ctx in the Copilot Chat view to invoke the chat participant. Then either:

    • Use a slash command: @ctx /status, @ctx /wrapup, etc. There are 45 commands; the most common ones live in the Slash Commands table below.
    • Use natural language: @ctx what should I work on? routes to /next; @ctx time to wrap up routes to /wrapup. See Natural Language.

    The extension shows context-aware follow-up suggestions after each command. For example, after /init you'll see buttons for \"Show status\" or \"Generate copilot integration.\"

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"

    The extension registers several VS Code event handlers that mirror Claude Code's hook system. These run in the background; no user action needed.

    Trigger What fires File save Task-completion check on non-.context/ files Git commit Notification prompting to add a Decision, Learning, run /verify, or Skip .context/ file change Refreshes pending reminders and regenerates .github/copilot-instructions.md Dependency file change When go.mod, package.json, etc. change, prompts to refresh the dependency map (/map) Every 5 minutes Updates the reminder status-bar item and writes a heartbeat timestamp Extension activate Fires ctx system session-event --type start Extension deactivate Fires ctx system session-event --type end","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#status-bar","level":3,"title":"Status Bar","text":"

    A $(bell) ctx indicator appears in the status bar when you have pending reminders. It refreshes every 5 minutes and hides itself when nothing is due.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#slash-commands","level":2,"title":"Slash Commands","text":"

    The extension surfaces 45 commands across six categories. The most commonly used:

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#core-context","level":3,"title":"Core Context","text":"Command When to use /init Initialize a .context/ directory with template files /status Token estimate, file count, what's recent /agent Print AI-ready context packet /drift Detect stale paths, missing files, dead references /recall Browse and search prior AI session history /add Add a task, decision, learning, or convention","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#session-lifecycle","level":3,"title":"Session Lifecycle","text":"Command When to use /wrapup End-of-session ceremony: status, drift, journal audit /remember Structured readback (trigger: \"Do you remember?\") from tasks, decisions, learnings, recent journal /reflect Surface items worth persisting as decisions or learnings /pause / /resume Save and restore session state for later","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#discovery-planning","level":3,"title":"Discovery & Planning","text":"Command When to use /brainstorm Browse and develop ideas from ideas/ /spec List or scaffold feature specs from templates /verify Run verification (doctor + drift) /map Show dependency map (go.mod, package.json)

    Full list (with maintenance, audit, metadata, and system commands) is in editors/vscode/README.md.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#natural-language","level":2,"title":"Natural Language","text":"

    Plain English after @ctx is routed to the right command:

    • \"What should I work on next?\" → /next
    • \"Time to wrap up\" → /wrapup
    • \"Show me the status\" → /status
    • \"Add a decision\" → /add
    • \"Check for drift\" → /drift

    If the phrase doesn't match a known pattern, the extension surfaces a short menu of likely matches.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#auto-bootstrap","level":2,"title":"Auto-Bootstrap","text":"

    If the ctx CLI isn't on PATH (or at a path configured via ctx.executablePath), the extension auto-downloads the right binary:

    1. Detects OS and architecture (darwin / linux / windows, amd64 / arm64).
    2. Fetches the latest release from GitHub Releases.
    3. Downloads and verifies the matching binary.
    4. Caches it in VS Code's global storage directory.

    Subsequent sessions reuse the cached binary. To pin a specific version, set ctx.executablePath in your VS Code settings.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#prerequisites","level":2,"title":"Prerequisites","text":"
    • VS Code 1.93+
    • GitHub Copilot Chat extension
    • ctx CLI on PATH, or let the extension auto-download it
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#configuration","level":2,"title":"Configuration","text":"Setting Default Description ctx.executablePath ctx Path to the ctx CLI binary. Set this if ctx isn't on PATH and you don't want auto-download.","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"

    The extension updates through the VS Code Marketplace like any other extension; install new versions via the Extensions view. Updates to the ctx CLI are independent: bump it via your package manager, or let the auto-bootstrap fetch the latest release.

    Unlike the OpenCode integration, there is no ctx setup step for VS Code. The extension carries its own runtime; ctx's role is only to provide the CLI it shells out to.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix @ctx participant doesn't appear in Copilot Chat Copilot Chat not installed or not signed in Install GitHub Copilot Chat and ensure you're signed in to a Copilot-eligible account @ctx /status says ctx not found CLI not on PATH and auto-download disabled Either add ctx to PATH (brew install activememory/tap/ctx or download from Releases), or unset ctx.executablePath to let the extension auto-download Status-bar reminder never updates Heartbeat suppressed or .context/ doesn't exist Run ctx init from your project root; reload VS Code if the indicator still doesn't appear within 5 minutes Commands run but nothing is captured to .context/ Workspace folder missing or .context/ outside the open folder Make sure your project root (the one with .context/) is the workspace root, not a subdirectory of it","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#verify-it-works","level":2,"title":"Verify It Works","text":"

    Open Copilot Chat and ask:

    @ctx Do you remember?\n

    You should see a structured readback citing specific tasks, decisions, and recent session topics. If you instead see \"I don't have memory\" or \"Let me check,\" something went wrong: confirm the CLI is reachable (@ctx /system doctor) and .context/ has files in it.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#whats-next","level":2,"title":"What's Next","text":"
    • Your First Session: step-by-step walkthrough from ctx init to verified recall.
    • Common Workflows: day-to-day commands for tracking context, checking health, and browsing history.
    • Context Files: what lives in .context/ and how each file is used.
    • Setup across AI Tools: wiring ctx for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf alongside VS Code.
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"operations/","level":1,"title":"Operations","text":"

    Guides for installing, upgrading, integrating, and running ctx. Split into three groups by audience.

    ","path":["Operations"],"tags":[]},{"location":"operations/#day-to-day","level":2,"title":"Day-to-Day","text":"

    Everyday operation guides for anyone running ctx in a project or adopting it in a team.

    ","path":["Operations"],"tags":[]},{"location":"operations/#integration","level":3,"title":"Integration","text":"

    Adopt ctx in an existing project: initialize context files, migrate from other tools, and onboard team members.

    ","path":["Operations"],"tags":[]},{"location":"operations/#upgrade","level":3,"title":"Upgrade","text":"

    Upgrade between versions with step-by-step migration notes and breaking-change guidance.

    ","path":["Operations"],"tags":[]},{"location":"operations/#ai-tools","level":3,"title":"AI Tools","text":"

    Configure ctx with Claude Code, Cursor, Aider, Copilot, Windsurf, and other AI coding tools.

    ","path":["Operations"],"tags":[]},{"location":"operations/#autonomous-loops","level":3,"title":"Autonomous Loops","text":"

    Run an unattended AI agent that works through tasks overnight, with ctx providing persistent memory between iterations.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub","level":2,"title":"Hub","text":"

    Operator guides for running a ctx Hub, the gRPC server that fans out structured entries across projects. If you're a client connecting to a Hub someone else runs, see ctx connect and the Hub recipes instead.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub-operations","level":3,"title":"Hub Operations","text":"

    Data directory layout, daemon management, systemd unit, backup and restore, log rotation, monitoring, and upgrades.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub-failure-modes","level":3,"title":"Hub Failure Modes","text":"

    What can go wrong in network, storage, cluster, auth, and clock layers, and what you should do about each one. Includes the short-list table oncall engineers will want bookmarked.

    ","path":["Operations"],"tags":[]},{"location":"operations/#maintainers","level":2,"title":"Maintainers","text":"

    Runbooks for people shipping ctx itself.

    ","path":["Operations"],"tags":[]},{"location":"operations/#cutting-a-release","level":3,"title":"Cutting a Release","text":"

    Step-by-step runbook for maintainers: bump version, generate release notes, run the release script, and verify the result.

    ","path":["Operations"],"tags":[]},{"location":"operations/#runbooks","level":2,"title":"Runbooks","text":"

    Step-by-step procedures you run with your agent. Each runbook includes a prompt to paste into a Claude Code session and guidance on triaging the results.

    Runbook Purpose When to run Release checklist Full pre-release sequence Before every release Plugin release Plugin-specific release steps Plugin changes ship Breaking migration Guide users across breaking changes Releases with renames Hub deployment Set up a ctx Hub end-to-end First-time hub setup New contributor Onboarding: clone to first session New contributors Codebase audit AST audits, magic strings, dead code, doc alignment Before release, quarterly Docs semantic audit Narrative gaps, weak pages, structural problems Before release, after adding pages Sanitize permissions Clean .claude/settings.local.json of over-broad grants After heavy permission granting Architecture exploration Systematic architecture docs across repos New codebase onboarding, reviews

    Recommended cadence:

    • Before every release: release checklist (which includes codebase audit + docs semantic audit)
    • Monthly: sanitize permissions
    • Quarterly: full sweep of all audit runbooks
    ","path":["Operations"],"tags":[]},{"location":"operations/autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"

    Iterate until done.

    An autonomous loop is an iterative AI development workflow where an agent works on tasks until completion, without constant human intervention.

    ctx provides the memory that makes this possible:

    • ctx provides the memory: persistent context that survives across iterations
    • The loop provides the automation: continuous execution until done

    Together, they enable fully autonomous AI development where the agent remembers everything across iterations.

    Origin

    This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique.

    We use generic terminology here so the concepts remain clear regardless of trends.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"
    graph TD\n    A[Start Loop] --> B[Load .context/loop.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B
    1. Loop reads .context/loop.md and invokes AI
    2. AI loads context from .context/
    3. AI picks one task and completes it
    4. AI updates context files (mark task done, add learnings)
    5. AI commits changes
    6. Loop checks for completion signals
    7. Repeat until converged or blocked
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#quick-start-shell-while-loop-recommended","level":2,"title":"Quick Start: Shell While Loop (Recommended)","text":"

    The best way to run an autonomous loop is a plain shell script that invokes your AI tool in a fresh process on each iteration. This is \"pure ralph\":

    The only state that carries between iterations is what lives in .context/ and the git history. No context window bleed, no accumulated tokens, no hidden state.

    Create a loop.sh:

    #!/bin/bash\n# loop.sh: an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-.context/loop.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n

    Make it executable and run:

    chmod +x loop.sh\n./loop.sh\n

    You can also generate this script with ctx loop (see CLI Reference).

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-do-we-use-a-shell-loop","level":3,"title":"Why Do We Use a Shell Loop?","text":"

    Each iteration starts a fresh AI process with zero context window history. The agent knows only what it reads from .context/ files: Exactly the information you chose to persist.

    This is the core loop principle: memory is explicit, not accidental.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#alternative-claude-codes-built-in-loop","level":2,"title":"Alternative: Claude Code's Built-in Loop","text":"

    Claude Code has built-in loop support:

    # Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n

    This is convenient for quick iterations, but be aware of important caveats:

    This Loop Is Not Pure

    Claude Code's /loop runs all iterations within the same session. This means:

    • State leaks between iterations: The context window accumulates output from every previous iteration. The agent \"remembers\" things it saw earlier (even if they were never persisted to .context/).
    • Token budget degrades: Each iteration adds to the context window, leaving less room for actual work in later iterations.
    • Not ergonomic for long runs: Users report that the built-in loop is less predictable for 10+ iteration runs compared to a shell loop.

    For short explorations (2-5 iterations) or interactive use, /loop works fine. For overnight unattended runs or anything where iteration independence matters, use the shell while loop instead.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#the-contextloopmd-file","level":2,"title":"The .context/loop.md File","text":"

    The prompt file instructs the AI on how to work autonomously. Here's a template:

    # Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n\n1. `.context/CONSTITUTION.md`: NEVER violate these rules\n2. `.context/TASKS.md`: Find work to do\n3. `.context/CONVENTIONS.md`: Follow these patterns\n4. `.context/DECISIONS.md`: Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n\n- Mark task complete in `TASKS.md`\n- Add any learnings to `LEARNINGS.md`\n- Add any decisions to `DECISIONS.md`\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED`: All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n- (no signal): More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"

    The loop watches for these signals in AI output:

    Signal Meaning When to Use SYSTEM_CONVERGED All tasks complete No pending tasks in TASKS.md SYSTEM_BLOCKED Cannot proceed Needs clarification, access, or decision BOOTSTRAP_COMPLETE Initial setup done Project scaffolding finished","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"

    converged state

    I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n

    blocked state

    I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-ctx-and-loops-work-well-together","level":2,"title":"Why ctx and Loops Work Well Together","text":"Without ctx With ctx Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in DECISIONS.md Learnings are lost Learnings accumulate in LEARNINGS.md Tasks can be forgotten Tasks tracked in TASKS.md","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"

    During the loop, the AI should update context files:

    Mark task complete:

    ctx task complete \"implement user auth\"\n

    Or emit an update command (parsed by ctx watch):

    <context-update type=\"complete\">user auth</context-update>\n

    Add learning:

    ctx learning add \"Rate limiting requires Redis connection\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    Or via update command:

    <context-update type=\"learning\"\n  context=\"Implementing rate limiter\"\n  lesson=\"Rate limiting requires Redis connection\"\n  application=\"Ensure Redis is provisioned before enabling rate limits\"\n>Rate Limiting Redis Dependency</context-update>\n

    Record decision:

    ctx decision add \"Use JWT tokens for API authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"

    Run ctx watch alongside the loop to automatically process context updates:

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n

    The watch command processes context updates from the loop output in real time.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#project-setup","level":2,"title":"Project Setup","text":"

    Initialize a project for autonomous loop operation, then activate it so the loop's ctx commands know which .context/ to use:

    ctx init\neval \"$(ctx activate)\"\n

    For unattended overnight runs, put the binding directly at the top of your loop script (export CTX_DIR=/abs/path/.context) so it survives without depending on a live shell. See Activating a Context Directory.

    The loop prompt template is deployed to .context/loop.md during initialization. It instructs the agent to:

    • Work autonomously without asking clarifying questions;
    • Follow one-task-per-iteration discipline;
    • Use SYSTEM_CONVERGED / SYSTEM_BLOCKED signals;
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-project-structure","level":2,"title":"Example Project Structure","text":"
    my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample TASKS.md for Autonomous Loops","text":"
    # Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n

    The loop will work through these systematically, marking each complete.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"

    Cause: AI not emitting completion signals

    Fix: Ensure .context/loop.md explicitly instructs signaling:

    End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"

    Cause: AI not updating context files

    Fix: Add explicit instructions to .context/loop.md:

    After completing a task, you MUST:\n1. Run: ctx task complete \"<task>\"\n2. Add learnings: ctx learning add \"...\" --session-id abc12345 --branch main --commit 68fbc00a\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"

    Cause: Task not marked complete before next iteration

    Fix: Ensure commit happens after context update:

    Order of operations:\n1. Complete coding work\n2. Update context files (*`ctx task complete`, `ctx add`*)\n3. Commit **ALL** changes including `.context/`\n4. Then signal status\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"

    Cause: Constitution not read first

    Fix: Make constitution check explicit in .context/loop.md:

    BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#further-reading","level":2,"title":"Further Reading","text":"
    • Building ctx Using ctx: The dogfooding story: how autonomous loops built the tool that powers them
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#resources","level":2,"title":"Resources","text":"
    • Geoffrey Huntley's Ralph Wiggum Technique: The original inspiration
    • Context CLI: Command reference
    • Integrations: Tool-specific setup
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/hub-failure-modes/","level":1,"title":"Hub Failure Modes","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#ctx-hub-failure-modes","level":1,"title":"ctx Hub: Failure Modes","text":"

    What can go wrong, what the system does about it, and what you should do. Complementary to ctx Hub Operations.

    Design Posture

    The hub is best-effort knowledge sharing, not a durable ledger. Local .context/ files are the source of truth for each project; the hub is a fan-out channel. This framing informs every failure-mode decision below.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#network","level":2,"title":"Network","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#client-loses-connection-mid-stream","level":3,"title":"Client Loses Connection Mid-Stream","text":"

    What happens: ctx connection listen detects the EOF, waits with exponential backoff, and reconnects. On reconnect it passes its last-seen sequence; the hub replays everything newer.

    What you should do: nothing. If reconnects are looping, check firewall state on the hub and ctx hub status output.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-majority-side-reachable","level":3,"title":"Partition: Majority Side Reachable","text":"

    What happens: clients routed to the majority side continue to publish and listen. The minority nodes step down to followers that cannot accept writes (Raft quorum lost).

    What you should do: let it heal. When the partition closes, followers catch up via sequence-based sync automatically.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-split-brain-no-quorum","level":3,"title":"Partition: Split Brain (No Quorum)","text":"

    What happens: no node holds a majority, so no leader is elected. All nodes become read-only. ctx connection publish and ctx add --share fail with a \"no leader\" error; local writes still succeed.

    What you should do: fix the network. If the partition is permanent (e.g., a data center is gone), bootstrap a new cluster from the survivors with ctx hub peer remove for the dead nodes.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#hub-unreachable-during-ctx-add-share","level":3,"title":"Hub Unreachable during ctx add --share","text":"

    What happens: the local write succeeds; the share step prints a warning and exits non-zero on the share leg only. --share is best-effort; it never blocks local context updates.

    What you should do: run ctx connection publish later to backfill, or rely on another --share for the same entry ID. The hub deduplicates by entry ID.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#storage","level":2,"title":"Storage","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#disk-full-on-the-leader","level":3,"title":"Disk Full on the Leader","text":"

    What happens: entries.jsonl append fails. The hub rejects writes with an error and stays up for read traffic. Clients retry; followers keep their in-sync status using whatever the leader already wrote.

    What you should do: free disk or grow the volume, then nothing else; the hub resumes accepting writes on the next append attempt.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#corrupt-entriesjsonl","level":3,"title":"Corrupt entries.jsonl","text":"

    What happens: if the last line is a partial JSON write from a crash, the hub truncates it on startup and logs a warning. If any earlier line is malformed, the hub refuses to start.

    What you should do: inspect with jq -c . <data-dir>/entries.jsonl > /dev/null to find the bad line. Move the bad region to a .quarantine file, then start. Nothing is ever silently dropped.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#metajson-entriesjsonl-sequence-mismatch","level":3,"title":"meta.json / entries.jsonl Sequence Mismatch","text":"

    What happens: the hub refuses to start. This usually means someone copied one file without the other.

    What you should do: restore both files from the same backup, or accept the higher sequence by regenerating meta.json from entries.jsonl (manual for now; file a bug).

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#cluster","level":2,"title":"Cluster","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-clean-shutdown","level":3,"title":"Leader Crash, Clean Shutdown","text":"

    What happens: ctx hub stop triggers stepdown first, so a new leader is elected before the old one exits. In-flight writes drain. Clients reconnect to the new leader transparently.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-hard-fail-kill-9-power-loss","level":3,"title":"Leader Crash, Hard Fail (Kill -9, Power Loss)","text":"

    What happens: Raft detects the missing heartbeat and elects a new leader within a few seconds. Writes the old leader accepted but had not yet replicated can be lost. See the Raft-lite warning in the cluster recipe.

    What you should do: if you need stronger durability, run ctx connection listen on a dedicated \"collector\" project that persists entries locally as a write-ahead backup.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#split-brain-after-rejoin","level":3,"title":"Split-Brain After Rejoin","text":"

    What happens: Raft reconciles: the minority side's uncommitted writes are discarded, and the majority's log is authoritative.

    What you should do: nothing automatic. If you know the minority had important writes, grep for them in <data-dir>/entries.jsonl.rejected (written by the reconciliation pass) and replay them with ctx connection publish.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#auth-and-tokens","level":2,"title":"Auth and Tokens","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#lost-admin-token","level":3,"title":"Lost Admin Token","text":"

    What happens: you cannot register new projects.

    What you should do: retrieve it from <data-dir>/admin.token. If that file is also gone, stop the hub and regenerate. Note that all existing client tokens keep working; only new registrations need the admin token.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-admin-token","level":3,"title":"Compromised Admin Token","text":"

    What happens: anyone with the token can register new projects and publish. They cannot read existing entries without a client token for a project that subscribes.

    What you should do: rotate the admin token (regenerate <data-dir>/admin.token and restart), revoke suspicious client registrations via clients.json, and audit entries.jsonl for unexpected origins.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-client-token","level":3,"title":"Compromised Client Token","text":"

    What happens: the attacker can publish as that project and read anything that project is subscribed to. Because Origin is self-asserted on publish, the attacker can also publish entries tagged with any other project's name, so attribution in entries.jsonl cannot be trusted after a token compromise.

    What you should do: remove the client's entry from clients.json, restart the hub, and re-register the legitimate project with a fresh token. Audit entries.jsonl for entries published after the compromise timestamp and quarantine any that look suspicious; remember that Origin on those entries proves nothing.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-hub-host","level":3,"title":"Compromised Hub Host","text":"

    What happens: <data-dir>/clients.json stores client tokens verbatim (not hashed). Anyone with read access to that file has every client token in hand and can impersonate any registered project until each one is rotated.

    What you should do: treat it as a total hub compromise. Stop the hub, wipe <data-dir> (keep a forensic copy first), regenerate the admin token, and have every client re-register. See Security model for the mitigations that reduce the blast radius while the hashing follow-up is pending.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#clock-skew","level":2,"title":"Clock Skew","text":"

    Hub entries carry a timestamp assigned by the publishing client. The hub does not rewrite timestamps. Clients with significant clock skew will publish entries that look out of order in the shared feed.

    What you should do: run NTP on all client machines. If you see entries dated in the future or far past, the publisher's clock is the culprit.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#the-short-list","level":2,"title":"The Short List","text":"Symptom First thing to check Client can't reach hub Firewall, then ctx hub status \"No leader\" errors Cluster quorum; run ctx hub status on each peer Hub won't start after crash Last line of entries.jsonl Entries missing after restore Check clients.json sequence vs local .sync-state.json Duplicate entries in shared feed Client replayed after restore, safe (dedup by ID) Followers lagging Disk or network on the follower, not the leader","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub Operations
    • ctx Hub security model
    • HA cluster recipe
    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub/","level":1,"title":"Hub Operations","text":"","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#ctx-hub-operations","level":1,"title":"ctx Hub: Operations","text":"

    Running the ctx ctx Hub in production. This page is for operators: people running a hub for themselves or a team, not people writing to a hub someone else is running.

    If you have not read it yet, start with the ctx Hub overview. It explains what the hub is, the two user stories it supports (personal cross-project brain vs small trusted team), and what it does not do. A client-side tour is in Getting Started.

    Operator Cheat Sheet

    • The hub fans out four entry types only: decision, learning, convention, task. Journals, scratchpad, and other local state are out of scope.
    • Identity is per-project, not per-user. Attribution is limited to Origin, which is self-asserted by the publishing client.
    • The data model is an append-only JSONL log plus two small JSON sidecar files. Nothing is rewritten in place.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#data-directory-layout","level":2,"title":"Data Directory Layout","text":"

    The hub stores everything under a single data directory (default ~/.ctx/hub-data/, override with --data-dir).

    <data-dir>/\n  admin.token        # Initial admin token (chmod 600)\n  clients.json       # Registered client tokens and project names\n  meta.json          # Sequence counter, version, cluster metadata\n  entries.jsonl      # Append-only log (single source of truth)\n  hub.pid            # Daemon PID file (daemon mode only)\n  raft/              # Raft state (cluster mode only)\n    log.db\n    stable.db\n    snapshots/\n

    Invariants:

    • entries.jsonl is append-only. Every line is a valid JSON object. Corrupt lines are fatal at startup: fix or truncate before restart.
    • meta.json is authoritative for the next sequence number. On restart, the hub reads the last valid line of entries.jsonl and refuses to start if the sequences disagree.
    • clients.json holds hashed client tokens; losing it invalidates all client registrations.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#starting-and-stopping","level":2,"title":"Starting and Stopping","text":"ForegroundDaemon
    ctx hub start                    # Ctrl-C to stop\nctx hub start --port 8080        # Custom port\nctx hub start --data-dir /srv/ctx-hub\n
    ctx hub start --daemon           # Fork to background\nctx hub stop                      # Graceful shutdown\n

    --stop sends SIGTERM to the PID in hub.pid, waits for in-flight RPCs to drain, then exits. If the daemon is wedged, remove hub.pid and send SIGKILL manually. entries.jsonl is crash-safe, so you will not lose accepted writes.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#systemd-unit","level":2,"title":"Systemd Unit","text":"

    For production single-node deployments, run the hub as a systemd service instead of --daemon:

    # /etc/systemd/system/ctx-hub.service\n[Unit]\nDescription=ctx `ctx` Hub\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=ctx\nGroup=ctx\nExecStart=/usr/local/bin/ctx hub start --port 9900 \\\n    --data-dir /var/lib/ctx-hub\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/ctx-hub\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\n
    sudo systemctl enable --now ctx-hub\nsudo journalctl -u ctx-hub -f\n
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#backup-and-restore","level":2,"title":"Backup and Restore","text":"

    Because entries.jsonl is append-only, backups are trivial:

    # Hot backup, safe while the hub is running.\ncp <data-dir>/entries.jsonl backups/entries-$(date +%F).jsonl\ncp <data-dir>/meta.json      backups/meta-$(date +%F).json\ncp <data-dir>/clients.json   backups/clients-$(date +%F).json\n

    For a consistent snapshot across all three files, stop the hub, copy, then start again, or use a filesystem-level snapshot (LVM, ZFS, Btrfs).

    Restore:

    ctx hub stop                           # Stop the hub\ncp backups/entries-2026-04-10.jsonl <data-dir>/entries.jsonl\ncp backups/meta-2026-04-10.json      <data-dir>/meta.json\ncp backups/clients-2026-04-10.json   <data-dir>/clients.json\nctx hub start --daemon\n

    Clients that pushed sequences above the restored watermark will re-publish on the next listen reconnect, because the hub now reports a lower sequence than what clients have on disk. This is safe; the store deduplicates by entry ID.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#log-rotation","level":2,"title":"Log Rotation","text":"

    entries.jsonl grows unbounded. For long-lived hubs, rotate it offline:

    ctx hub stop\nmv <data-dir>/entries.jsonl <data-dir>/entries-$(date +%F).jsonl.old\n# Replay the last N days into a fresh entries.jsonl if you want a\n# trimmed active log, or leave the old file in place as history.\nctx hub start --daemon\n

    Do not truncate entries.jsonl while the hub is running. The hub holds an open file handle; an in-place truncation confuses the sequence counter and loses writes.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#monitoring","level":2,"title":"Monitoring","text":"

    Liveness probe:

    ctx hub status --exit-code\n

    Exit code 0 means the node is healthy (leader or in-sync follower); non-zero means degraded. Wire this into your monitoring of choice.

    For cluster deployments, watch for:

    • Role flaps: the leader changing more than once per hour suggests network instability or disk contention.
    • Replication lag: ctx hub status shows per-peer sequence offsets. Sustained lag > 100 sequences on a follower is worth investigating.
    • entries.jsonl growth rate: sudden spikes often indicate a misbehaving ctx connection listen reconnect loop.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#upgrading","level":2,"title":"Upgrading","text":"

    The JSONL format is versioned in meta.json. ctx refuses to start against a newer store version than it understands; older store versions are upgraded in place at first start after an upgrade.

    Always back up <data-dir>/ before upgrading.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub failure modes
    • ctx Hub security model
    • ctx serve reference
    • ctx hub reference
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/integrations/","level":1,"title":"AI Tools","text":"","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#ai-tools","level":2,"title":"AI Tools","text":"

    Context works with any AI tool that can read files. This guide covers setup for popular AI coding assistants.

    Activate the Project Before Running ctx Commands

    After ctx init, run:

    eval \"$(ctx activate)\"\n

    This tells ctx which .context/ directory the rest of the commands on this page should use. If you skip it, you'll see Error: no context directory specified. The ctx setup <tool> commands work without activation, but most others (ctx agent, ctx add, ctx status, ctx watch) need it. See Activating a Context Directory.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"

    Claude Code has the deepest integration via the ctx plugin.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup","level":3,"title":"Setup","text":"

    First, install ctx and initialize your project, then activate it for the current shell:

    ctx init\neval \"$(ctx activate)\"\n

    Then, install the ctx plugin in Claude Code:

    # From the ctx repository\nclaude /plugin install ./internal/assets/claude\n\n# Or from the marketplace\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n

    Ensure the Plugin Is Enabled

    Installing a plugin registers it, but local installs may not auto-enable it globally. Verify ~/.claude/settings.json contains:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Without this, the plugin's hooks and skills won't appear in other projects. Running ctx init auto-enables the plugin; use --no-plugin-enable to skip this step.

    This gives you:

    Component Purpose .context/ All context files CLAUDE.md Bootstrap instructions Plugin hooks Lifecycle automation Plugin skills Agent Skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works","level":3,"title":"How It Works","text":"
    graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]
    1. Session start: Claude reads CLAUDE.md, which tells it to check .context/
    2. First tool use: PreToolUse hook runs ctx agent and emits the context packet (subsequent invocations within the cooldown window are silent)
    3. Next session: Claude reads context files and continues with context
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#plugin-hooks","level":3,"title":"Plugin Hooks","text":"

    The ctx plugin provides lifecycle hooks implemented as Go subcommands (ctx system *):

    Hook Event Purpose ctx system context-load-gate PreToolUse (.*) Auto-inject context on first tool use ctx system block-non-path-ctx PreToolUse (Bash) Block ./ctx or go run: force $PATH install ctx system qa-reminder PreToolUse (Bash) Remind agent to lint/test before committing ctx system specs-nudge PreToolUse (EnterPlanMode) Nudge agent to use project specs when planning ctx system check-context-size UserPromptSubmit Nudge context assessment as sessions grow ctx system check-ceremonies UserPromptSubmit Nudge /ctx-remember and /ctx-wrap-up adoption ctx system check-persistence UserPromptSubmit Remind to persist learnings/decisions ctx system check-journal UserPromptSubmit Remind to export/enrich journal entries ctx system check-reminders UserPromptSubmit Relay pending reminders at session start ctx system check-version UserPromptSubmit Warn when binary/plugin versions diverge ctx system check-resources UserPromptSubmit Warn when memory/swap/disk/load hit DANGER level ctx system check-knowledge UserPromptSubmit Nudge when knowledge files grow large ctx system check-map-staleness UserPromptSubmit Nudge when ARCHITECTURE.md is stale ctx system heartbeat UserPromptSubmit Session-alive signal with prompt count metadata ctx system post-commit PostToolUse (Bash) Nudge context capture and QA after git commits

    A catch-all PreToolUse hook also runs ctx agent on every tool use (with cooldown) to autoload context.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#hook-configuration","level":3,"title":"Hook Configuration","text":"

    The plugin's hooks.json wires everything automatically: no manual configuration in settings.local.json needed:

    {\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system context-load-gate\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system block-non-path-ctx\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system qa-reminder\" }\n        ]\n      },\n      {\n        \"matcher\": \"EnterPlanMode\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system specs-nudge\" }\n        ]\n      },\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx agent --budget 4000 2>/dev/null || true\" }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system post-commit\" }\n        ]\n      }\n    ],\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system check-context-size\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-ceremonies\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-persistence\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-journal\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-reminders\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-version\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-resources\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-knowledge\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-map-staleness\" },\n          { \"type\": \"command\", \"command\": \"ctx system heartbeat\" }\n        ]\n      }\n    ]\n  }\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#customizing-token-budget-and-cooldown","level":3,"title":"Customizing Token Budget and Cooldown","text":"

    Edit the PreToolUse command to change the token budget or cooldown:

    \"command\": \"ctx agent --budget 8000 --session $PPID >/dev/null || true\"\n\"command\": \"ctx agent --budget 4000 --cooldown 5m --session $PPID >/dev/null || true\"\n

    The --session $PPID flag isolates the cooldown per session: $PPID resolves to the Claude Code process PID, so concurrent sessions don't interfere. The default cooldown is 10 minutes; use --cooldown 0 to disable it.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"
    1. Start a new Claude Code session;
    2. Ask: \"Do you remember?\"
    3. Claude should cite specific context:
      • Current tasks from .context/TASKS.md;
      • Recent decisions or learnings;
      • Recent session history from ctx journal.
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#local-plugin-development","level":3,"title":"Local Plugin Development","text":"

    When developing ctx locally (adding skills, hooks, or changing plugin behavior), Claude Code caches the plugin by version. You must bump the version in both files and update the marketplace for changes to take effect:

    1. Bump version in both:
    2. internal/assets/claude/.claude-plugin/plugin.json (plugin manifest), .claude-plugin/marketplace.json (marketplace listing*);

    3. Update the marketplace in Claude Code:

    4. Open the Plugins UI (/plugins or Esc menu),
    5. Go to Marketplaces tab,
    6. Select the activememory-ctx Marketplace,
    7. Choose Update marketplace;

    8. Start a new Claude Code session: skill changes aren't reflected in existing sessions.

    Both Version Files Must Match

    If you only bump plugin.json but not marketplace.json (or vice versa), Claude Code may not detect the update. Always bump both together.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check ctx is in PATH: which ctx Hook errors Verify plugin is installed: claude /plugin list New skill not visible Bump version in both plugin.json files, update marketplace","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"

    If hooks aren't working, manually load context:

    # Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#agent-skills","level":3,"title":"Agent Skills","text":"

    The ctx plugin ships Agent Skills following the agentskills.io specification.

    These are invoked in Claude Code with /skill-name.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-lifecycle-skills","level":4,"title":"Session Lifecycle Skills","text":"Skill Description /ctx-remember Recall project context at session start (ceremony) /ctx-wrap-up End-of-session context persistence (ceremony) /ctx-status Show context summary (tasks, decisions, learnings) /ctx-agent Get AI-optimized context packet /ctx-next Suggest 1-3 concrete next actions from context /ctx-commit Commit with integrated context capture /ctx-reflect Review session and suggest what to persist /ctx-remind Manage session-scoped reminders /ctx-pause Pause context hooks for this session /ctx-resume Resume context hooks after a pause","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-persistence-skills","level":4,"title":"Context Persistence Skills","text":"Skill Description /ctx-task-add Add a task to TASKS.md /ctx-learning-add Add a learning to LEARNINGS.md /ctx-decision-add Add a decision with context/rationale/consequence /ctx-convention-add Add a coding convention to CONVENTIONS.md /ctx-archive Archive completed tasks","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#scratchpad-skills","level":4,"title":"Scratchpad Skills","text":"Skill Description /ctx-pad Manage encrypted scratchpad entries","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-history-skills","level":4,"title":"Session History Skills","text":"Skill Description /ctx-history Browse AI session history /ctx-journal-enrich Enrich a journal entry with frontmatter/tags /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#blogging-skills","level":4,"title":"Blogging Skills","text":"

    Blogging Is a Better Way of Creating Release Notes

    The blogging workflow can also double as generating release notes:

    AI reads your git commit history and creates a \"narrative\", which is essentially what a release note is for.

    Skill Description /ctx-blog Generate blog post from recent activity /ctx-blog-changelog Generate blog post from commit range with theme","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#auditing-health-skills","level":4,"title":"Auditing & Health Skills","text":"Skill Description /ctx-doctor Troubleshoot ctx behavior with structural health checks /ctx-drift Detect and fix context drift (structural + semantic) /ctx-consolidate Merge redundant learnings or decisions into denser entries /ctx-alignment-audit Audit doc claims against playbook instructions /ctx-prompt-audit Analyze session logs for vague prompts /check-links Audit docs for dead internal and external links","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#planning-execution-skills","level":4,"title":"Planning & Execution Skills","text":"Skill Description /ctx-loop Generate a Ralph Loop iteration script /ctx-implement Execute a plan step-by-step with checks /ctx-plan-import Import Claude Code plan files into project specs /ctx-worktree Manage git worktrees for parallel agents /ctx-architecture Build and maintain architecture maps","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage-examples","level":4,"title":"Usage Examples","text":"
    /ctx-status\n/ctx-learning-add \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n

    Skills support partial matching where applicable (e.g., session slugs).

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"

    Cursor can use context files through its system prompt or by reading files directly.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_1","level":3,"title":"Setup","text":"
    # Generate Cursor configuration\nctx setup cursor\n\n# Initialize context\nctx init --minimal\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration","level":3,"title":"Configuration","text":"

    Add to Cursor settings (.cursor/settings.json):

    // split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage","level":3,"title":"Usage","text":"
    1. Open your project in Cursor
    2. Context files are available in the file tree
    3. Reference them in prompts: \"Check .context/DECISIONS.md for our approach to...\"
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"

    For more control, paste context directly:

    # Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n

    Paste into Cursor's chat.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#aider","level":2,"title":"Aider","text":"

    Aider works well with context files through its --read flag.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_2","level":3,"title":"Setup","text":"
    # Generate Aider configuration\nctx setup aider\n\n# Initialize context\nctx init\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_1","level":3,"title":"Configuration","text":"

    Create .aider.conf.yml:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_1","level":3,"title":"Usage","text":"
    # Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"

    Run ctx watch alongside Aider to capture context updates:

    # Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"

    GitHub Copilot integrates with ctx at three levels: an automated instructions file, a VS Code Chat extension, and manual patterns.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_3","level":3,"title":"Setup","text":"
    # Initialize context\nctx init\n\n# Generate .github/copilot-instructions.md\nctx setup copilot --write\n

    The --write flag creates .github/copilot-instructions.md, which Copilot reads automatically at the start of every session. This file contains your project's constitution rules, current tasks, conventions, and architecture: giving Copilot persistent context without manual copy-paste.

    Re-run ctx setup copilot --write after updating your .context/ files to regenerate the instructions.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#vs-code-chat-extension-ctx","level":3,"title":"VS Code Chat Extension (@ctx)","text":"

    The ctx VS Code extension adds a @ctx chat participant to GitHub Copilot Chat, giving you direct access to 45 context commands from within the editor, plus automatic hooks on file save / git commit / .context/ changes / dependency-file edits, and a reminder status-bar indicator.

    Full guide: ctx for VS Code

    The home-page guide covers daily workflows, the full command list, natural-language routing, auto-bootstrap of the ctx CLI, troubleshooting, and \"Verify It Works.\" This subsection is the install-and-pointers overview; the dedicated page is the authoritative reference.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#installation","level":4,"title":"Installation","text":"

    The extension ships to the VS Code Marketplace under publisher activememory (display name: ctx — Persistent Context for AI). Install via the Extensions view or code --install-extension.

    To build from source instead (requires Node.js 20+):

    cd editors/vscode\nnpm ci\nnpm run build\nnpx @vscode/vsce package\ncode --install-extension ctx-context-<version>.vsix\n

    Reload VS Code. Type @ctx in Copilot Chat to verify.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created","level":4,"title":"What Gets Created","text":"File Purpose .context/ Project-local context directory (created by ctx init, not by the extension) .github/copilot-instructions.md Repository instructions Copilot reads natively; regenerated automatically when .context/ files change

    The extension itself lives in VS Code's extension storage; no project files beyond .context/ and the Copilot instructions are added.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_1","level":4,"title":"How It Works","text":"
    • Chat participant: @ctx is registered with VS Code's Chat API; 45 slash commands route to dedicated handlers that shell out to the ctx CLI.
    • Automatic hooks: file save → task-completion check; git commit → decision/learning prompt; .context/ change → regenerate Copilot instructions; dependency-file change → /map prompt.
    • Status-bar reminder: a $(bell) ctx indicator surfaces pending session reminders, refreshing every 5 minutes.
    • Natural language: plain English after @ctx is routed to the nearest matching command.
    • Auto-bootstrap: if the ctx CLI isn't on PATH, the extension downloads the correct platform binary from GitHub Releases and caches it.
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_2","level":4,"title":"Configuration","text":"Setting Default Description ctx.executablePath ctx Path to the ctx binary. Set this if ctx is not in your PATH.","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-persistence","level":3,"title":"Session Persistence","text":"

    ctx init creates a .context/sessions/ directory for storing session data from non-Claude tools. The Markdown session parser scans this directory during ctx journal, enabling session history for Copilot and other tools.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-patterns","level":3,"title":"Manual Patterns","text":"

    These patterns work without the extension, using Copilot's built-in file awareness:

    Pattern 1: Keep context files open

    Open .context/CONVENTIONS.md in a split pane. Copilot will reference it.

    Pattern 2: Reference in comments

    // See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n

    Pattern 3: Paste context into Copilot Chat

    ctx agent --budget 2000\n

    Paste output into Copilot Chat for context-aware responses.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#opencode","level":2,"title":"OpenCode","text":"

    OpenCode is a terminal-first AI coding agent. ctx integrates via a thin lifecycle plugin, MCP server, and AGENTS.md instructions.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_4","level":3,"title":"Setup","text":"
    # Generate OpenCode plugin, global MCP config, skills, and AGENTS.md\nctx setup opencode --write\n\n# Initialize context\nctx init\neval \"$(ctx activate)\"\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created_1","level":3,"title":"What Gets Created","text":"File Purpose .opencode/plugins/ctx.ts Lifecycle plugin (hooks to ctx system) ~/.config/opencode/opencode.json Global MCP server registration (or $OPENCODE_HOME/opencode.json) AGENTS.md Agent instructions (read natively) .opencode/skills/ctx-*/SKILL.md ctx skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_2","level":3,"title":"How It Works","text":"

    The plugin wires OpenCode lifecycle events to ctx system:

    • session.created — warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use
    • tool.execute.after (shell, on git commit) — runs ctx system post-commit
    • tool.execute.after (edit/write) — runs ctx system check-task-completion
    • session.idle — runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI)
    • shell.env — injects CTX_DIR into the agent's shell so ctx commands resolve to the right project
    • experimental.session.compacting — pushes ctx system bootstrap output into the compaction context so the agent keeps breadcrumbs back to .context/

    The plugin is a single file with no runtime dependencies — no bun install needed. OpenCode loads it automatically on launch.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-updates","level":3,"title":"Context Updates","text":"
    # Get AI-optimized context packet\nctx agent\n\n# Check context health\nctx status\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"

    Windsurf supports custom instructions and file-based context.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_5","level":3,"title":"Setup","text":"
    # Generate Windsurf configuration\nctx setup windsurf\n\n# Initialize context\nctx init\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_3","level":3,"title":"Configuration","text":"

    Add to Windsurf settings:

    // Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_2","level":3,"title":"Usage","text":"

    Context files appear in the file tree. Reference them when chatting:

    • \"What's in our task list?\" → AI reads .context/TASKS.md
    • \"What convention do we use for naming?\" → AI reads .context/CONVENTIONS.md
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#generic-integration","level":2,"title":"Generic Integration","text":"

    For any AI tool that can read files, use these patterns:

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"
    # Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"
    You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#automated-updates","level":3,"title":"Automated Updates","text":"

    If your AI tool outputs to a log, use ctx watch:

    # Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    The AI can emit updates like:

    <context-update type=\"complete\">implement caching</context-update>\n<context-update type=\"learning\"\n  context=\"Implementing caching layer\"\n  lesson=\"Important thing learned today\"\n  application=\"Apply this insight going forward\"\n>Caching Insight</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"

    The ctx watch command parses update commands from AI output. Use this format:

    <context-update type=\"TYPE\" [attributes]>Content</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes task TASKS.md None decision DECISIONS.md context, rationale, consequence learning LEARNINGS.md context, lesson, application convention CONVENTIONS.md None complete TASKS.md None","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (Tasks, Conventions, Complete)","text":"
    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (Learnings, Decisions)","text":"

    Learnings and decisions support structured attributes for better documentation:

    Learning with full structure:

    <context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Parse JSON stdin with the host language (Go, Python, etc.): no jq needed\"\n>Hook Input Format</context-update>\n

    Decision with full structure:

    <context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequence=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n

    Learnings require: context, lesson, application attributes. Decisions require: context, rationale, consequence attributes. Updates missing required attributes are rejected with an error.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#further-reading","level":2,"title":"Further Reading","text":"
    • Skills That Fight the Platform: Common pitfalls in skill design that work against the host tool
    • The Anatomy of a Skill That Works: What makes a skill reliable: the E/A/R framework and quality gates
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/migration/","level":1,"title":"Integration","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#adopting-ctx-in-existing-projects","level":2,"title":"Adopting ctx in Existing Projects","text":"

    Claude Code User?

    You probably want the plugin instead of this page.

    Install ctx from the marketplace: (/plugin → search \"ctx\" → Install) and you're done: hooks, skills, and updates are handled for you.

    See Getting Started for the full walkthrough.

    This guide covers adopting ctx in existing projects regardless of which tools your team uses.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#quick-paths","level":2,"title":"Quick Paths","text":"You have... Command What happens Nothing (greenfield) ctx init Creates .context/, CLAUDE.md, permissions Existing CLAUDE.md ctx init --merge Backs up your file, inserts ctx block after the H1 Existing CLAUDE.md + ctx markers ctx init --reset Replaces the ctx block, leaves your content intact .cursorrules / .aider.conf.yml ctx init ctx ignores those files: they coexist cleanly Team repo, first adopter ctx init --merge && git add .context/ CLAUDE.md Initialize and commit for the team","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-claudemd","level":2,"title":"Existing CLAUDE.md","text":"

    This is the most common scenario:

    You have a CLAUDE.md with project-specific instructions and don't want to lose them.

    You Own CLAUDE.md

    After initialization, CLAUDE.md is yours: edit it freely.

    Add project instructions, remove sections you don't need, reorganize as you see fit.

    The only part ctx manages is the block between the <!-- ctx:context --> and <!-- ctx:end --> markers; everything outside those markers is yours to change at any time.

    If you remove the markers, nothing breaks: ctx simply treats the file as having no ctx content and will offer to merge again on the next ctx init.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-ctx-init-does","level":3,"title":"What ctx init Does","text":"

    When ctx init detects an existing CLAUDE.md, it checks for ctx markers (<!-- ctx:context --> ... <!-- ctx:end -->):

    State Default behavior With --merge With --force No CLAUDE.md Creates from template Creates from template Creates from template Exists, no ctx markers Prompts to merge Auto-merges (no prompt) Auto-merges (no prompt) Exists, has ctx markers Skips (already set up) Skips Replaces the ctx block only","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-merge-flag","level":3,"title":"The --merge Flag","text":"

    --merge auto-merges without prompting. The merge process:

    1. Backs up your existing CLAUDE.md to CLAUDE.md.<timestamp>.bak;
    2. Finds the H1 heading (e.g., # My Project) in your file;
    3. Inserts the ctx block immediately after it;
    4. Preserves everything else untouched.

    Your content before and after the ctx block remains exactly as it was.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#before-after-example","level":3,"title":"Before / After Example","text":"

    Before: your existing CLAUDE.md:

    # My Project\n\n## Build Commands\n\n-`npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n

    After ctx init --merge:

    # My Project\n\n<!-- ctx:context -->\n<!-- DO NOT REMOVE: This marker indicates ctx-managed content -->\n\n## IMPORTANT: You Have Persistent Memory\n\nThis project uses Context (`ctx`) for context persistence across sessions.\n...\n\n<!-- ctx:end -->\n\n## Build Commands\n\n- `npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n

    Your build commands and code style sections are untouched. The ctx block sits between markers and can be updated independently.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-force-flag","level":3,"title":"The --force Flag","text":"

    If your CLAUDE.md already has ctx markers (from a previous ctx init), the default behavior is to skip it. Use --force to replace the ctx block with the latest template: This is useful after upgrading ctx:

    ctx init --reset\n

    This only replaces content between <!-- ctx:context --> and <!-- ctx:end -->. Your own content outside the markers is preserved. A timestamped backup is created before any changes.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#undoing-a-merge","level":3,"title":"Undoing a Merge","text":"

    Every merge creates a backup:

    $ ls CLAUDE.md*.bak\nCLAUDE.md.1738000000.bak\n

    To restore:

    cp CLAUDE.md.1738000000.bak CLAUDE.md\n

    Or if you are using git, simply:

    git checkout CLAUDE.md\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-cursorrules-aider-copilot","level":2,"title":"Existing .cursorrules / Aider / Copilot","text":"

    ctx doesn't touch tool-specific config files. It creates its own files (.context/, CLAUDE.md) and coexists with whatever you already have.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-does-ctx-create","level":3,"title":"What Does ctx Create?","text":"ctx creates ctx does NOT touch .context/ directory .cursorrules CLAUDE.md (or merges into) .aider.conf.yml .claude/settings.local.json (seeded by ctx init; the plugin manages hooks and skills) .github/copilot-instructions.md .windsurfrules Any other tool-specific config

    Claude Code hooks and skills are provided by the ctx plugin, installed from the Claude Code marketplace (/plugin → search \"ctx\" → Install).

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#running-ctx-alongside-other-tools","level":3,"title":"Running ctx Alongside Other Tools","text":"

    The .context/ directory is the source of truth. Tool-specific configs point to it:

    • Cursor: Reference .context/ files in your system prompt (see Cursor setup)
    • Aider: Add .context/ files to the read: list in .aider.conf.yml (see Aider setup)
    • Copilot: Keep .context/ files open or reference them in comments (see Copilot setup)

    You can generate a tool-specific configuration with:

    ctx setup cursor    # Generate Cursor config snippet\nctx setup aider     # Generate .aider.conf.yml\nctx setup copilot   # Generate Copilot tips\nctx setup windsurf  # Generate Windsurf config\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#migrating-content-into-context","level":3,"title":"Migrating Content into .context/","text":"

    If you have project knowledge scattered across .cursorrules or custom prompt files, consider migrating it:

    1. Rules / invariants → .context/CONSTITUTION.md
    2. Code patterns → .context/CONVENTIONS.md
    3. Architecture notes → .context/ARCHITECTURE.md
    4. Known issues / tips → .context/LEARNINGS.md

    You don't need to delete the originals: ctx and tool-specific files can coexist. But centralizing in .context/ means every tool gets the same context.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#team-adoption","level":2,"title":"Team Adoption","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#context-is-designed-to-be-committed","level":3,"title":".context/ Is Designed to Be Committed","text":"

    The context files (tasks, decisions, learnings, conventions, architecture) are meant to live in version control. However, some subdirectories are personal or sensitive and should not be committed.

    ctx init automatically adds these .gitignore entries:

    # Journals contain full session transcripts: personal, potentially large\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Runtime state and logs (ephemeral, machine-specific):\n.context/state/\n.context/logs/\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n

    With those in place, committing is straightforward:

    # One person initializes\nctx init --merge\n\n# Commit context files (journals and keys are already gitignored)\ngit add .context/ CLAUDE.md\ngit commit -m \"Add ctx context management\"\ngit push\n

    Teammates pull and immediately have context. No per-developer setup needed.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-about-claude","level":3,"title":"What about .claude/?","text":"

    The .claude/ directory contains permissions that ctx init seeds. Hooks and skills are provided by the ctx plugin (not per-project files).

    File Commit? Why .claude/settings.local.json No Machine-specific, accumulates session permissions .claude/settings.golden.json Yes Curated permission snapshot (via ctx permission snapshot)","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#merge-conflicts-in-context-files","level":3,"title":"Merge Conflicts in Context Files","text":"

    Context files are plain Markdown. Resolve conflicts the same way you would for any other documentation file:

    # After a conflicting pull\ngit diff .context/TASKS.md    # See both sides\n# Edit to keep both sets of tasks, then:\ngit add .context/TASKS.md\ngit commit\n

    Common conflict scenarios:

    • TASKS.md: Two people added tasks: Keep both.
    • DECISIONS.md: Same decision recorded differently: Unify the entry.
    • LEARNINGS.md: Parallel discoveries: Keep both, remove duplicates.
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#gradual-adoption","level":3,"title":"Gradual Adoption","text":"

    You don't need the whole team to switch at once:

    1. One person runs ctx init --merge and commits;
    2. CLAUDE.md instructions work immediately for Claude Code users;
    3. Other tool users can adopt at their own pace using ctx setup <tool>;
    4. Context files benefit everyone who reads them, even without tool integration.
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verifying-it-worked","level":2,"title":"Verifying It Worked","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#activate-the-project","level":3,"title":"Activate the Project","text":"

    Tell ctx which .context/ directory to use for the rest of the verification steps:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the status check below fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#check-status","level":3,"title":"Check Status","text":"
    ctx status\n

    You should see your context files listed with token counts and no warnings.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#test-memory","level":3,"title":"Test Memory","text":"

    Start a new AI session and ask: \"Do you remember?\"

    The AI should cite specific context:

    • Current tasks from .context/TASKS.md;
    • Recent decisions or learnings;
    • Session history (if you've had prior sessions);

    If it responds with generic \"I don't have memory\", check that ctx is in your PATH (which ctx) and that hooks are configured (see Troubleshooting).

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verify-the-merge","level":3,"title":"Verify the Merge","text":"

    If you used --merge, check that your original content is intact:

    # Your original content should still be there\ncat CLAUDE.md\n\n# The ctx block should be between markers\ngrep -c \"ctx:context\" CLAUDE.md  # Should print 1\ngrep -c \"ctx:end\" CLAUDE.md      # Should print 1\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#further-reading","level":2,"title":"Further Reading","text":"
    • Getting Started: Full setup walkthrough
    • Context Files: What each .context/ file does
    • Integrations: Per-tool setup (Claude Code, Cursor, Aider, Copilot)
    • CLI Reference: All ctx commands and flags
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/release/","level":1,"title":"Cutting a Release","text":"

    Full Release Checklist

    This page covers the mechanics of cutting a release (bump, tag, push). For the complete pre-release ceremony (audits, tests, verification, and post-release steps), see the Release Checklist runbook.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#prerequisites","level":2,"title":"Prerequisites","text":"

    Before you can cut a release you need:

    • Push access to origin (GitHub)
    • GPG signing configured (make gpg-test)
    • Go installed (version in go.mod)
    • Zensical installed (make site-setup)
    • A clean working tree (git status shows nothing to commit)
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#step-by-step","level":2,"title":"Step-by-Step","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#1-update-the-version-file","level":3,"title":"1. Update the VERSION File","text":"
    echo \"0.9.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.9.0\"\n

    The VERSION file uses bare semver (0.9.0), no v prefix. The release script adds the v prefix for git tags.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#2-generate-release-notes","level":3,"title":"2. Generate Release Notes","text":"

    In Claude Code:

    /_ctx-release-notes\n

    This analyzes commits since the last tag and writes dist/RELEASE_NOTES.md. The release script refuses to proceed without this file.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#3-verify-docs-and-commit-any-remaining-changes","level":3,"title":"3. Verify Docs and Commit Any Remaining Changes","text":"
    /ctx-link-check    # audit docs for dead links\nmake audit          # full check: fmt, vet, lint, style, test\ngit status          # must be clean\n
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#4-run-the-release","level":3,"title":"4. Run the Release","text":"
    make release\n

    Or, if you are in a Claude Code session:

    /_ctx-release\n

    The release script does everything in order:

    Step What happens 1 Reads VERSION, verifies release notes exist 2 Verifies working tree is clean 3 Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) 4 Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) 5 Adds new row to versions.md 6 Rebuilds the documentation site (make site) 7 Commits all version and docs updates 8 Runs make test and make smoke 9 Builds binaries for all 6 platforms via hack/build-all.sh 10 Creates a signed git tag (v0.9.0) 11 Pushes the tag to origin 12 Updates and pushes the latest tag","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#5-github-ci-takes-over","level":3,"title":"5. GitHub CI Takes Over","text":"

    Pushing a v* tag triggers .github/workflows/release.yml:

    1. Checks out the tagged commit
    2. Runs the full test suite
    3. Builds binaries for all platforms
    4. Creates a GitHub Release with auto-generated notes
    5. Uploads binaries and SHA256 checksums
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#6-verify","level":3,"title":"6. Verify","text":"
    • GitHub Releases shows the new version
    • All 6 binaries are attached (linux/darwin x amd64/arm64, windows x amd64)
    • SHA256 files are attached
    • Release notes look correct
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#what-gets-updated-automatically","level":2,"title":"What Gets Updated Automatically","text":"

    The release script updates 8 files so you do not have to:

    File What changes internal/assets/claude/.claude-plugin/plugin.json Plugin version .claude-plugin/marketplace.json Marketplace version (2 fields) editors/vscode/package.json VS Code extension version editors/vscode/package-lock.json VS Code lock version (2 fields) docs/index.md Download URLs docs/home/getting-started.md Download URLs docs/operations/integrations.md VSIX filename version docs/reference/versions.md New version row + latest pointer

    The Go binary version is injected at build time via -ldflags from the VERSION file. No source file needs editing.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#build-targets-reference","level":2,"title":"Build Targets Reference","text":"Target What it does make release Full release (script + tag + push) make build Build binary for current platform make build-all Build all 6 platform binaries make test Unit tests make smoke Integration smoke tests make audit Full check (fmt + vet + lint + drift + docs + test) make site Rebuild documentation site","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#release-notes-not-found","level":3,"title":"\"Release Notes Not Found\"","text":"
    ERROR: dist/RELEASE_NOTES.md not found.\n

    Run /_ctx-release-notes in Claude Code first, or write dist/RELEASE_NOTES.md manually.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#working-tree-is-not-clean","level":3,"title":"\"Working Tree Is Not Clean\"","text":"
    ERROR: Working tree is not clean.\n

    Commit or stash all changes before running make release.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#tag-already-exists","level":3,"title":"\"Tag Already Exists\"","text":"
    ERROR: Tag v0.9.0 already exists.\n

    You cannot release the same version twice. Either bump VERSION to a new version, or delete the old tag if the release was incomplete:

    git tag -d v0.9.0\ngit push origin :refs/tags/v0.9.0\n
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#ci-build-fails-after-tag-push","level":3,"title":"CI Build Fails After Tag Push","text":"

    The tag is already published. Fix the issue, bump to a patch version (e.g. 0.9.1), and release again. Do not force-push tags that others may have already fetched.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/upgrading/","level":1,"title":"Upgrade","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade","level":2,"title":"Upgrade","text":"

    New versions of ctx may ship updated permissions, CLAUDE.md directives, or plugin hooks and skills.

    Claude Code User?

    The marketplace can update skills, hooks, and prompts independently: /plugin → select ctx → Update now (or enable auto-update).

    The ctx binary is separate: rebuild from source or download a new release when one is available, then run ctx init --reset --merge. Knowledge files are preserved automatically.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#tldr","level":2,"title":"TL:DR","text":"
    # Plugin users (Claude Code)\n# /plugin → select ctx → Update now\n# Then update the binary and reinitialize:\nctx init --reset --merge\n\n# From-source / manual users\n# install new ctx binary, then:\nctx init --reset --merge\n# /plugin → select ctx → Update now   (if using Claude Code)\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-changes-between-versions","level":2,"title":"What Changes between Versions","text":"

    ctx init generates two categories of files:

    Category Examples Changes between versions? Infrastructure .claude/settings.local.json (permissions), ctx-managed sections in CLAUDE.md, ctx plugin (hooks + skills) Yes Knowledge .context/TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md, ARCHITECTURE.md, GLOSSARY.md, CONSTITUTION.md, AGENT_PLAYBOOK.md No: this is your data

    Infrastructure is regenerated by ctx init and plugin updates. Knowledge files are yours and should never be overwritten.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade-steps","level":2,"title":"Upgrade Steps","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#1-install-the-new-version","level":3,"title":"1. Install the New Version","text":"

    Build from source or download the binary:

    cd /path/to/ctx-source\ngit pull\nmake build\nsudo make install\nctx --version   # verify\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#2-reinitialize","level":3,"title":"2. Reinitialize","text":"
    ctx init --reset --merge\n
    • --force regenerates infrastructure files (permissions, ctx-managed sections in CLAUDE.md).
    • --merge preserves your content outside ctx markers.

    Knowledge files (.context/TASKS.md, DECISIONS.md, etc.) are preserved automatically: ctx init only overwrites infrastructure, never your data.

    Encryption key: The encryption key lives at ~/.ctx/.ctx.key (outside the project). Reinit does not affect it. If you have a legacy key at .context/.ctx.key or ~/.local/ctx/keys/, copy it manually (see Syncing Scratchpad Notes).

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#3-update-the-ctx-plugin","level":3,"title":"3. Update the ctx Plugin","text":"

    If you use Claude Code, update the plugin to get new hooks and skills:

    1. Open /plugin in Claude Code.
    2. Select ctx.
    3. Click Update now.

    Or enable auto-update so the plugin stays current without manual steps.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#4-review-custom-settings","level":3,"title":"4. Review Custom Settings","text":"

    If you added custom permissions to .claude/settings.local.json beyond what ctx init provides, diff and merge:

    diff .claude.bak/settings.local.json .claude/settings.local.json\n

    Manually add back any custom entries that the new init dropped.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#5-verify","level":3,"title":"5. Verify","text":"

    Activate the project first, otherwise ctx status and ctx drift will fail with Error: no context directory specified:

    eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#6-clean-up","level":3,"title":"6. Clean Up","text":"

    If you made manual backups, remove them once satisfied:

    rm -rf .context.bak .claude.bak CLAUDE.md.bak\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-if-i-skip-the-upgrade","level":2,"title":"What If I Skip the Upgrade?","text":"

    The old binary still works with your existing .context/ files. But you may miss:

    • New plugin hooks that enforce better practices or catch mistakes;
    • Updated skill prompts that produce better results;
    • New .gitignore entries for directories added in newer versions;
    • Bug fixes in the CLI itself.

    The plugin and the binary can be updated independently. You can update the plugin (for new hooks/skills) even if you stay on an older binary, and vice versa.

    Context files are plain Markdown: They never break between versions.

    The surrounding infrastructure is what evolves.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/","level":1,"title":"Architecture Exploration","text":"","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#architecture-exploration","level":1,"title":"Architecture Exploration","text":"

    Systematically build architecture documentation across one or more repositories using ctx skills. Each invocation does one unit of work; a simple loop drives the agent through all phases.

    When to use: When onboarding to a new codebase, performing architecture reviews, or building up .context/ documentation across a workspace of repos.

    Prerequisites: ctx installed, repos cloned under a shared workspace directory (e.g., ~/WORKSPACE/).

    Companion skills:

    • /ctx-architecture: structural baseline and principal analysis
    • /ctx-architecture-enrich: code intelligence enrichment via GitNexus
    • /ctx-architecture-failure-analysis: adversarial failure analysis
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#overview","level":2,"title":"Overview","text":"

    The agent progresses through phases per repo, depth-first:

    Phase Skill What it does bootstrap ctx init + /ctx-architecture Initialize context and build structural baseline principal /ctx-architecture principal Deep analysis: vision, bottlenecks, alternatives enriched /ctx-architecture-enrich Quantify with code intelligence (blast radius, flows) frontier-N /ctx-architecture (re-run) Explore unexplored areas found in convergence report lens-* /ctx-architecture with lens Focused exploration through conceptual lenses

    Exploration stops when convergence >= 0.85, frontier runs plateau, or all lenses are exhausted.

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#setup","level":2,"title":"Setup","text":"

    Create a tracking directory in your workspace root:

    cd ~/WORKSPACE\nmkdir -p .arch-explorer\n

    Create .arch-explorer/manifest.json listing your repos:

    {\n  \"repos\": [\"ctx\", \"portal\", \"infra\"],\n  \"current_repo_index\": 0,\n  \"progress\": {}\n}\n

    Create .arch-explorer/run-log.md (empty, the agent appends to it).

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#prompt","level":2,"title":"Prompt","text":"

    Save this as .arch-explorer/PROMPT.md and invoke with your agent. The prompt is self-contained: the agent reads the manifest, picks the next unit of work, executes it, updates tracking, and stops.

    You are an autonomous architecture exploration agent. Your job is to\nsystematically build and evolve architecture documentation across all\nrepositories in this workspace using `ctx` skills.\n\n## Execution Protocol\n\n### Step 1: Read State\n\nRead `.arch-explorer/manifest.json`. This tells you:\n- Which repos exist and their order\n- What has been done per repo (`progress` object)\n- Which repo to work on next (`current_repo_index`)\n\n### Step 2: Pick the Next Unit of Work\n\n**Strategy: depth-first, sequential.**\n\nFind the current repo (by `current_repo_index`). Determine its next\nphase from the progression below. If all phases are exhausted for this\nrepo (convergence score >= 0.85 or 3+ frontier runs with no new\nfindings), advance `current_repo_index` and pick the next repo.\n\n### Phase Progression (per repo)\n\nEach repo progresses through these phases in order:\n\n| Phase | Skill | Prerequisite |\n|-------|-------|-------------|\n| `bootstrap` | `ctx init` + `/ctx-architecture` | None |\n| `principal` | `/ctx-architecture principal` | bootstrap done |\n| `enriched` | `/ctx-architecture-enrich` | principal done, GitNexus indexed |\n| `frontier-N` | `/ctx-architecture` (re-run) | enriched done |\n\n**`bootstrap` is a single composite unit:** `ctx init` followed by\nstructural analysis. This is the ONLY phase that combines two actions.\nNo other phase may chain actions.\n\n**Frontier runs** are numbered: `frontier-1`, `frontier-2`, etc.\nEach frontier run reads CONVERGENCE-REPORT.md and picks unexplored\nareas. The skill handles this automatically.\n\nAfter the third frontier run OR when convergence >= 0.85, apply\n**conceptual lenses** (one per run):\n\n| Lens | Focus Areas |\n|------|-------------|\n| `security` | Auth flows, input validation, secrets, attack surfaces, trust boundaries |\n| `performance` | Hot paths, caching, concurrency, resource lifecycle, allocation patterns |\n| `stability` | Error handling, retries, graceful degradation, circuit breakers, timeouts |\n| `observability` | Logging, metrics, tracing, alerting, debugging affordances |\n| `data-integrity` | Storage, serialization, migrations, consistency, backup, recovery |\n\nFor lens runs, prepend the lens context as an explicit instruction to\nthe skill invocation:\n\n> \"Focus exploration on security: auth flows, input validation, secrets,\n> attack surfaces, trust boundaries.\"\n\nDo NOT wait for the skill to ask what to explore. Provide the lens\nfocus as input upfront.\n\n### Step 3: Do the Work\n\n1. `cd` into the sub-repo directory (`~/WORKSPACE/<repo-name>`, NOT\n   `~/WORKSPACE` itself).\n2. Verify `CTX_DIR` already points at THIS sub-repo's `.context/`:\n\n    ```bash\n    test \"$CTX_DIR\" = \"$PWD/.context\" || {\n      echo \"STOP: CTX_DIR=$CTX_DIR but this sub-repo needs $PWD/.context.\"\n      echo \"Re-launch the agent with CTX_DIR set to the sub-repo:\"\n      echo \"  cd $PWD && CTX_DIR=\\\"\\$PWD/.context\\\" claude --print 'Follow .arch-explorer/PROMPT.md' --allowedTools '*'\"\n      exit 1\n    }\n    ```\n\n    If it fails, STOP. The agent cannot change `CTX_DIR` for itself:\n    child shells and skill invocations inherit the parent Claude\n    process environment, which only the caller can control. Do not\n    proceed, do not run `ctx` commands, do not skip the check.\n3. If phase is `bootstrap`:\n    - Run `ctx init`, confirm `.context/` exists.\n    - Then run `/ctx-architecture` (structural baseline).\n4. If phase is `principal` or `frontier-*`:\n    - Run `/ctx-architecture` (add `principal` argument for principal phase).\n    - The skill will read existing artifacts and build on them.\n5. If phase is `enriched`:\n    - Verify GitNexus is connected: call `mcp__gitnexus__list_repos`.\n    - Success = non-empty list returned with no error.\n    - If GitNexus unavailable, log as `enriched-skipped` and advance\n      to `frontier-1`.\n    - Run `/ctx-architecture-enrich`.\n6. If phase is a lens run (`lens-security`, etc.):\n    - Run `/ctx-architecture` with lens focus prepended as instruction\n      (see lens table above for exact wording).\n\n### Step 4: Extract Results\n\nAfter the skill completes, gather:\n\n- **Convergence score**: from `map-tracking.json`, computed as:\n  average of all module `confidence` values (0.0-1.0). If\n  `map-tracking.json` is missing or has no confidence values,\n  record `null` and log a warning.\n- **Frontier count**: from CONVERGENCE-REPORT.md, count the number\n  of listed unexplored areas. If CONVERGENCE-REPORT.md is missing,\n  record `frontier_count: null` and log a warning. Treat missing\n  as \"exploration should continue\" (do not stall).\n- **Key findings**: 2-3 bullet points of what was discovered or\n  changed in this run (new modules mapped, danger zones found, etc.)\n- **New artifacts**: list any new files created in `.context/`\n\n### Step 5: Update Tracking\n\nUpdate `.arch-explorer/manifest.json`:\n\n```json\n{\n  \"progress\": {\n    \"ctx\": {\n      \"phases_completed\": [\"bootstrap\", \"principal\"],\n      \"current_phase\": \"enriched\",\n      \"lenses_explored\": [],\n      \"last_run\": \"2026-04-07T14:00:00Z\",\n      \"convergence_score\": 0.72,\n      \"frontier_count\": 3,\n      \"total_runs\": 2,\n      \"findings_summary\": \"14 modules mapped, 3 danger zones, 2 extension points\"\n    }\n  }\n}\n```\n\nAppend to `.arch-explorer/run-log.md`:\n\n```markdown\n## 2026-04-07T14:00:00Z / ctx / principal\n\n**Phase:** principal\n**Convergence:** 0.45 -> 0.72\n**Frontiers remaining:** 3\n**Key findings:**\n- Identified CLI dispatch as primary bottleneck (fan-out to 12 subsystems)\n- Security: context files readable by any process (no access control)\n- Strategic recommendation: extract context engine into library package\n\n**Artifacts updated:** ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md, map-tracking.json\n```\n\n### Step 6: Report and Stop\n\nPrint this exact format as the FINAL output of the invocation:\n\n```\n[arch-explorer] DONE\n  repo: ctx\n  phase: principal\n  convergence: 0.72\n  frontiers: 3\n  runs_on_repo: 3\n  next: ctx / enriched\n```\n\nThe `[arch-explorer] DONE` line is the terminal marker. After printing\nit, produce no further output. Execution is complete.\n\n## Rules\n\n1. **One unit per invocation.** The only composite unit is `bootstrap`\n   (init + structural). All other phases are exactly one skill run.\n2. **Additive only.** Never delete or overwrite existing artifacts.\n   The skills already handle incremental updates.\n3. **No duplicated work.** Read manifest before acting. If a phase is\n   already recorded as completed, skip it.\n4. **Log everything.** Every run gets a run-log entry, even failures\n   and skips.\n5. **Fail gracefully.** If a skill fails (missing GitNexus, broken repo,\n   etc.), log the failure with reason and advance to the next phase or\n   repo. Don't retry in the same invocation.\n6. **Respect `ctx` conventions.** Each repo gets its own `.context/`\n   directory. Never write architecture artifacts outside `.context/`.\n\n## Stopping Logic\n\nA repo is considered \"explored\" when ANY of these is true:\n- Convergence score >= 0.85 (from map-tracking.json)\n- 3+ frontier runs produced no new findings (frontier_count unchanged\n  across consecutive runs)\n- All 5 lenses have been applied\n- Convergence score is `null` after 3 attempts (artifacts aren't being\n  generated properly; log warning and move on)\n\nWhen a repo is explored, advance `current_repo_index` in the manifest.\n\n## When All Repos Are Done\n\nWhen every repo has reached its stopping condition, print:\n\n```\n[arch-explorer] ALL DONE\n  - ctx: 0.92 convergence, 8 runs, 5 lenses\n  - portal: 0.87 convergence, 6 runs, 3 lenses\n  ...\n```\n
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#invocation","level":2,"title":"Invocation","text":"

    The caller MUST set CTX_DIR to the sub-repo the agent will work on. The agent verifies this at Step 3.2 and stops if it does not match. The wrapper reads the manifest to pick the current sub-repo, then launches claude with CTX_DIR pinned to that sub-repo's .context/.

    Single run (safest for quota):

    cd ~/WORKSPACE\nREPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\nCTX_DIR=\"$PWD/$REPO/.context\" \\\n  claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n

    Batch of N runs:

    cd ~/WORKSPACE\nfor i in $(seq 1 5); do\n  REPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\n  CTX_DIR=\"$PWD/$REPO/.context\" \\\n    claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n  echo \"--- Run $i complete (repo: $REPO) ---\"\ndone\n

    Resume after interruption:

    Just run the wrapper again. The manifest tracks state; the agent picks up where it left off. CTX_DIR is recomputed from the manifest on each invocation, so the right sub-repo is always bound.

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#tips","level":2,"title":"Tips","text":"
    • Start small: list 1-2 repos in the manifest first. Add more once you're confident in the output quality.
    • GitNexus is optional: the enrichment phase is skipped gracefully if GitNexus isn't connected. You still get structural and principal analysis.
    • Review between batches: check the run-log and generated artifacts between batch runs. The agent is additive-only, but early course correction saves wasted runs.
    • Lens runs are the payoff: the first three phases build the map; lens runs find the interesting things (security gaps, performance cliffs, stability risks).
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#history","level":2,"title":"History","text":"
    • 2026-04-07: Original prompt created as hack/agents/architecture-explorer.md.
    • 2026-04-16: Moved to docs as a runbook for discoverability.
    • 2026-04-20: Added CTX_DIR verification at Step 3.2 and per-invocation CTX_DIR binding in the wrapper, so the agent writes artifacts to the sub-repo's .context/ instead of the inherited workspace one.
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/backup-strategy/","level":1,"title":"Backup Strategy","text":"

    ctx backup was removed. File-level backup is not ctx's responsibility; your OS or a dedicated backup tool handles it better and without locking you into a specific mount strategy.

    This runbook explains what to back up, how ctx hub reduces the surface, and what options exist for the rest.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-to-back-up","level":2,"title":"What To Back Up","text":"

    Per project:

    • .context/: all context files, journal, state, scratchpad.
    • .claude/: Claude Code settings, hooks, skills specific to the project. Skip this entry when it lives in git; the repo is the backup.

    Per user:

    • ~/.ctx/: global config, the encryption key (~/.ctx/.ctx.key), hub data directory (if running a local hub).
    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#how-hub-reduces-backup-needs","level":2,"title":"How Hub Reduces Backup Needs","text":"

    ctx hub replicates the knowledge surface across machines:

    • DECISIONS.md
    • LEARNINGS.md
    • CONVENTIONS.md
    • CONSTITUTION.md
    • ARCHITECTURE.md
    • Task items promoted to hub

    If you run ctx hub (as a server or by subscribing to someone else's), the data that matters most survives losing any single machine.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-hub-does-not-replicate","level":2,"title":"What Hub Does Not Replicate","text":"

    Hub is not a file-level backup. The following still live only on the machine that produced them:

    • Journal entries (.context/journal/*.md)
    • Runtime state (.context/state/*)
    • Session event log (.context/events.jsonl)
    • Scratchpad (.context/.pad)
    • Encrypted notify/webhook config (.context/.notify.enc)
    • The encryption key itself (~/.ctx/.ctx.key)

    If you need those to survive a disk failure, use a file-level backup.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#example-strategies","level":2,"title":"Example Strategies","text":"","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#1-cron-rsync-to-nas-or-external-drive","level":3,"title":"1. cron + rsync to NAS or External Drive","text":"
    # Daily at 03:00, mirror ~/WORKSPACE and ~/.ctx to NAS\n0 3 * * * rsync -a --delete \\\n    --exclude='node_modules' \\\n    --exclude='dist' \\\n    --exclude='.context/state' \\\n    ~/WORKSPACE/ /mnt/nas/backup/workspace/\n0 3 * * * rsync -a --delete ~/.ctx/ /mnt/nas/backup/ctx-global/\n

    Adjust excludes for the trash you don't want to back up. The .context/state/ dir is ephemeral per-session; skip it.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#2-cron-cp-to-a-cloud-synced-directory","level":3,"title":"2. cron + cp to a Cloud-Synced Directory","text":"

    iCloud Drive, Dropbox, or any directory watched by a sync client:

    0 3 * * * cp -a ~/WORKSPACE/some-project/.context \\\n    ~/CloudDrive/ctx-backups/some-project/$(date +\\%Y-\\%m-\\%d)\n

    Daily snapshots, cloud provider handles the replication.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#3-time-machine-macos","level":3,"title":"3. Time Machine (macOS)","text":"

    If you already run Time Machine, ensure ~/WORKSPACE and ~/.ctx are not in its exclusion list. Time Machine handles versioning; you get point-in-time recovery for free.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#4-borg-or-restic-for-versioned-backups","level":3,"title":"4. Borg or restic for Versioned Backups","text":"

    For deduplicated, versioned, encrypted backups:

    # Borg init (once)\nborg init --encryption=repokey /mnt/nas/borg-ctx\n\n# Daily backup\nborg create /mnt/nas/borg-ctx::'ctx-{now}' \\\n    ~/WORKSPACE ~/.ctx \\\n    --exclude '*/node_modules' \\\n    --exclude '*/.context/state'\n

    Use restic if you prefer S3-compatible targets.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#when-you-still-need-file-level-backup-even-with-hub","level":2,"title":"When You Still Need File-Level Backup Even With Hub","text":"
    • Journal: session histories are local-only until exported.
    • Scratchpad: private notes, encrypted locally.
    • Encryption key: losing ~/.ctx/.ctx.key means losing access to every encrypted file in every project.
    • Non-hub projects: projects that never called ctx hub register have zero cross-machine persistence.

    For these, pick one strategy above and forget about it.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#why-ctx-no-longer-ships-a-backup-command","level":2,"title":"Why ctx No Longer Ships a Backup Command","text":"

    Backup is inherently environment-specific: SMB, NFS, S3, rsync, Time Machine, Borg, restic. Every user has a different story. The previous ctx backup picked SMB via GVFS, which was Linux-only and narrow. Chasing mount strategies would never generalize.

    Hub is the right answer for the data ctx owns (knowledge). For everything else, your OS or a dedicated backup tool is the right layer.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/breaking-migration/","level":1,"title":"Breaking Migration","text":"","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#breaking-migration-guide","level":1,"title":"Breaking Migration Guide","text":"

    Template for upgrading across breaking CLI renames or behavior changes. Use this as a starting point when writing migration notes for a specific release, or hand it to your agent as context for generating release-specific guidance.

    When to use: When a release includes breaking changes (command renames, removed flags, changed defaults) that require user action.

    Companion: Upgrade guide covers the general upgrade flow. This runbook covers the breaking-change specifics.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-1-identify-what-changed","level":2,"title":"Step 1: Identify What Changed","text":"

    Ask your agent to diff the CLI surface between the old and new version:

    Compare the CLI command surface between the previous release tag\nand HEAD. For each change, categorize as: renamed, removed,\nnew, or changed-behavior. Include old and new command signatures.\n

    Or use the /_ctx-command-audit skill after the rename.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-2-regenerate-infrastructure","level":2,"title":"Step 2: Regenerate Infrastructure","text":"
    # Install the new binary\nmake build && sudo make install\n\n# Regenerate CLAUDE.md and permissions\nctx init --reset --merge\n

    --merge preserves your knowledge files (TASKS.md, DECISIONS.md, etc.) while regenerating infrastructure (permissions, CLAUDE.md managed sections).

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-3-update-the-plugin","level":2,"title":"Step 3: Update the Plugin","text":"
    /plugin -> select ctx -> Update now\n

    Or, if using a local clone:

    make plugin-reload\n# restart Claude Code\n
    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-4-update-personal-scripts","level":2,"title":"Step 4: Update Personal Scripts","text":"

    Search your scripts and aliases for old command names:

    # Example: find references to old command names\ngrep -r \"ctx old-command\" ~/scripts/ ~/.zshrc ~/.bashrc\n

    Replace with the new names per the changelog.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-5-update-hook-configs","level":2,"title":"Step 5: Update Hook Configs","text":"

    If you have custom hooks in .claude/settings.local.json that reference ctx commands, update them:

    jq '.hooks' .claude/settings.local.json | grep \"ctx \"\n
    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-6-verify","level":2,"title":"Step 6: Verify","text":"

    Activate the project first, otherwise ctx status and ctx drift will fail with Error: no context directory specified:

    eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\nmake test           # if you're a contributor\n

    See Activating a Context Directory.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#writing-release-specific-migration-notes","level":2,"title":"Writing Release-Specific Migration Notes","text":"

    When preparing a release with breaking changes, create a section in the release notes using this template:

    ## Breaking Changes\n\n### `old-command` renamed to `new-command`\n\n**What changed**: `ctx old-command` is now `ctx new-command`.\nThe old name is removed (no deprecation alias).\n\n**Action required**:\n1. Run `ctx init --reset --merge` to update CLAUDE.md\n2. Update any scripts referencing `ctx old-command`\n3. Update hook configs if applicable\n\n**Why**: [brief rationale for the rename]\n

    Repeat for each breaking change. Users should be able to follow the notes mechanically without needing to understand the codebase.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/codebase-audit/","level":1,"title":"Codebase Audit","text":"","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#codebase-audit","level":1,"title":"Codebase Audit","text":"

    A structured audit of the codebase: dead code, magic strings, documentation drift, security surface, and roadmap opportunities.

    When to run: Before a release, after a long YOLO sprint, quarterly, or when planning the next phase of work.

    Time: ~15-30 minutes with a team of agents.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#how-to-use-this-runbook","level":2,"title":"How to Use This Runbook","text":"

    Start a Claude Code session with a clean git state (git stash or commit first). Paste or adapt the prompt below. The agent does the analysis; you triage the findings.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#prompt","level":2,"title":"Prompt","text":"
    I want you to create an agent team to audit this codebase. Save each report as\na separate markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable: every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (session mining)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (godoc + inline)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check package-level comments match\npackage names. Output: drift items ranked by severity with exact file:line refs.\n\n### 3. Maintainability\nLook for: functions >80 lines that have logical split points; switch blocks\nwith >5 cases that could be table-driven or extracted; inline comments that\nsay \"step 1\", \"step 2\" or similar (sign the block wants to be a function);\nfiles with >400 lines; packages with flat structure that could benefit from\nsub-packages; functions that seem misplaced in their file. Do NOT flag\nthings that are fine as-is just because they could theoretically be different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app: focus on CLI-relevant attack surface, not web OWASP:\nfile path traversal (does user input flow into file paths unsanitized?),\ncommand injection (does user input flow into exec calls?), symlink following\n(does the tool follow symlinks when writing to .context/?), permission\nhandling (are file permissions set correctly?), sensitive data in outputs\n(do any commands leak secrets or session content?). Output: findings with\nseverity ratings and exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git log,\nrecent session discussions, and DECISIONS.md for story arcs worth writing\nabout. Suggest 3-5 blog post themes with: title, angle, target audience,\nkey commits/sessions to reference, and a 2-sentence pitch. Prioritize\nthemes that build a coherent narrative across posts.\n\n### 6. Roadmap & Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses:\nwhat are the highest-value improvements? Consider: user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with effort/impact estimates (not time estimates).\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and any user docs. Suggest improvements\nstructured as use-case pages: the problem, how ctx solves it, typical\nworkflow, gotchas. Identify gaps where a user would get stuck without\nreading source code. Output: list of documentation gaps and suggested\npage outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each: team composition (roles, agent types),\ntask distribution strategy, coordination approach, and which types of work\nit suits. Ground suggestions in actual project patterns, not generic advice.\n
    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#tips","level":2,"title":"Tips","text":"
    • Clean state matters: the prompt says \"no code changes\" but accidents happen. Start from a clean git state so you can git checkout . if needed.

    • Adjust scope: drop analyses you don't need. Analyses 1-4 are the most actionable. Analyses 5-8 are planning/creative and can be skipped if you just want a technical audit.

    • Reports feed TASKS.md: after the audit, read each report and create tasks in the appropriate Phase section. The reports are input, not output.

    • ideas/ is gitignored: reports saved there won't be committed. Move specific findings to TASKS.md, DECISIONS.md, or LEARNINGS.md to persist them.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#history","level":2,"title":"History","text":"
    • 2026-02-08: Original prompt created after a codebase audit sprint.
    • 2026-02-17: Improved with read-only agents, report structure template, CLI-scoped security review, and maintainability thresholds.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/","level":1,"title":"Docs Semantic Audit","text":"","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#documentation-semantic-audit","level":1,"title":"Documentation Semantic Audit","text":"

    Find structural problems that linters and link checkers cannot: weak pages that should be merged, heavy pages that should be split, missing cross-links, and narrative arcs that don't land.

    When to run: Before a release, after adding several new pages, when the site feels sprawling, or when you suspect narrative gaps.

    Time: ~20-40 minutes with an agent session.

    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#why-this-is-a-runbook","level":2,"title":"Why This Is a Runbook","text":"

    These judgments are inherently subjective and context-dependent. A page is \"weak\" relative to its neighbors; a narrative arc only matters if the docs intend to tell a story. Deterministic tools (broken-link checkers, word counters) can't do this. An LLM reading the full doc set can.

    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#prompt","level":2,"title":"Prompt","text":"

    Paste or adapt the following into a Claude Code session. The agent needs read access to docs/ and the site nav structure.

    Read every file under docs/ (including docs/blog/ and docs/recipes/).\nFor each file, note: title, word count, outbound links, inbound links\n(how many other pages link to it), and a one-line summary of its purpose.\n\nThen produce a report with these sections:\n\n## 1. Weak Dangling Pages\n\nPages that are thin, isolated, or redundant. Signs:\n- Under ~300 words with no unique content (just restates what another page says)\n- Zero or one inbound links (orphaned in the nav)\n- Content that would be stronger merged into an adjacent page\n- \"Try it in 5 minutes\" sections that assume installation already happened\n- Pages whose title doesn't work as a nav entry (too long, too vague)\n\nFor each: identify the page, explain why it's weak, and recommend\nmerge target or deletion.\n\n## 2. Overly Heavy Pages\n\nPages doing too much. Signs:\n- Over ~1500 words with multiple distinct topics\n- More than 4 H2 sections that could stand alone\n- Reader has to scroll past irrelevant content to find what they need\n- Mixed audience (beginner setup + advanced config on same page)\n\nFor each: identify the page, list the distinct topics, and suggest\nsplit points.\n\n## 3. Missing Cross-Links\n\nPlaces where a reader would naturally want to jump to related content\nbut no link exists. Look for:\n- Concepts mentioned but not linked (e.g., \"scratchpad\" without linking\n  to the scratchpad page)\n- Blog posts that describe features without linking to the reference docs\n- Recipes that reference workflows without linking to the relevant\n  getting-started section\n- Pages that end without a \"Next Up\" or \"See Also\" pointer\n\nFor each: source page, anchor text, suggested link target.\n\n## 4. Narrative Gaps\n\nThe docs should tell a coherent story: problem -> install -> first session\n-> daily workflow -> advanced patterns -> contributing. Look for:\n- Gaps in the progression (e.g., no bridge from \"first session\" to\n  \"daily habits\")\n- Blog posts that introduce concepts the reference docs don't cover\n- Recipes that assume knowledge no other page teaches\n- Features documented in CLI reference but missing from workflows/recipes\n\nFor each: describe the gap and suggest what page or section would fill it.\n\n## 5. Blog Cross-Linking Opportunities\n\nBlog posts are often written in isolation. Look for:\n- Posts that cover the same theme but don't reference each other\n- Posts that describe the evolution of a feature (natural \"part 1 / part 2\")\n- Posts that would benefit from a \"Related posts\" footer\n- Thematic clusters that could be linked from a recipe or reference page\n\nFor each: list the posts, the shared theme, and the suggested links.\n\n## Output Format\n\nFor every finding, include:\n- File path (docs/whatever.md)\n- Severity: high (actively confusing), medium (missed opportunity),\n  low (nice to have)\n- Concrete recommendation (merge into X, split at H2 Y, add link to Z)\n\nEnd with a prioritized action list: what to fix first.\n
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#after-the-audit","level":2,"title":"After the Audit","text":"
    1. Triage findings: not everything needs fixing. Focus on high severity.
    2. Merge weak pages first: fewer pages is almost always better.
    3. Add cross-links: cheapest improvement, highest reader impact.
    4. File split decisions in DECISIONS.md: page splits are architectural.
    5. Regenerate the site and spot-check nav after structural changes.
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#history","level":2,"title":"History","text":"
    • 2026-02-17: Created after merging docs/re-explaining.md into docs/about.md, which surfaced the pattern of weak standalone pages that dilute rather than add.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/hub-deployment/","level":1,"title":"Hub Deployment","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#hub-deployment","level":1,"title":"Hub Deployment","text":"

    Linear runbook for setting up a ctx Hub for yourself or a team. Consolidates pieces currently scattered across hub recipes and operations docs.

    When to use: First-time hub setup, or when onboarding a new team onto an existing hub.

    Prerequisites: ctx binary installed, network connectivity between hub and clients.

    Companion docs:

    • Hub overview: what the hub is and is not
    • Hub operations: data directory, systemd, backup, monitoring
    • Hub failure modes: what can go wrong
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"Quick Start (foreground)Production (systemd)
    ctx hub start\n

    See Hub Operations: Systemd Unit for the full unit file.

    sudo systemctl enable --now ctx-hub\n

    The hub creates admin.token on first start. Save this token; it is the only way to register clients.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-2-generate-the-admin-token","level":2,"title":"Step 2: Generate the Admin Token","text":"

    On first start, the hub writes admin.token to the data directory (default ~/.ctx/hub-data/):

    cat ~/.ctx/hub-data/admin.token\n

    This token has full admin privileges. Keep it secret.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-3-register-clients","level":2,"title":"Step 3: Register Clients","text":"

    For each client (person or machine) that will connect:

    # On the hub machine\nctx hub register --name \"volkan-laptop\" --admin-token <admin-token>\n

    This returns a client token. Distribute it securely to the client.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-4-connect-clients","level":2,"title":"Step 4: Connect Clients","text":"

    On each client machine, register the project with the hub. The ctx hub * commands above run on the hub server itself and don't need a project. The ctx connection * commands below are different: they live inside a project (the encrypted hub config is stored at .context/.connect.enc), so you have to tell ctx which project first.

    # In the project directory on the client machine:\neval \"$(ctx activate)\"\nctx connection register <hub-address> --token <client-token>\n

    Verify the connection:

    ctx connection status\n

    If the client doesn't have a project yet, run ctx init first, then eval \"$(ctx activate)\". See Activating a Context Directory.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-5-verify-sync","level":2,"title":"Step 5: Verify Sync","text":"

    Push a test entry from one client and verify it arrives. Make sure each client already ran eval \"$(ctx activate)\" from Step 4: otherwise ctx add and ctx status fail with Error: no context directory specified.

    # Client A (in its project directory, after activating):\nctx learning add \"Hub sync test\" --context \"Verifying hub setup\"\n\n# Client B (in its project directory, after activating):\nctx status   # should show the new learning\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-6-configure-backup","level":2,"title":"Step 6: Configure Backup","text":"

    Set up regular backups of the hub data directory. See Hub Operations: Backup and Restore.

    Minimum:

    # Add to cron\n0 */6 * * * cp ~/.ctx/hub-data/entries.jsonl ~/backups/entries-$(date +\\%F).jsonl\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-7-configure-tls-when-available","level":2,"title":"Step 7: Configure TLS (When Available)","text":"

    Coming Soon

    TLS support is planned (H-01/H-02). Until then, run the hub on a trusted network or behind a reverse proxy with TLS termination.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#team-onboarding-checklist","level":2,"title":"Team Onboarding Checklist","text":"

    When adding a new team member to an existing hub:

    • Generate a client token (ctx hub register --name \"<name>\")
    • Share the token and hub address securely
    • Have them run ctx connect <hub-address> --token <token>
    • Verify with ctx connection status
    • Point them to the Hub Getting Started recipe
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#connection-refused","level":3,"title":"\"Connection Refused\"","text":"

    The hub isn't running or the port is wrong. Check:

    ctx hub status          # on the hub machine\nss -tlnp | grep 9900   # default port\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#authentication-failed","level":3,"title":"\"Authentication Failed\"","text":"

    The client token is wrong or was never registered. Re-register:

    ctx hub register --name \"<name>\" --admin-token <admin-token>\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#entries-not-syncing","level":3,"title":"Entries Not Syncing","text":"

    Check that the client is listening:

    ctx connection status\n

    If connected but not syncing, check the hub logs for sequence mismatch errors. See Hub Failure Modes for details.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/new-contributor/","level":1,"title":"New Contributor","text":"","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#new-contributor-onboarding","level":1,"title":"New Contributor Onboarding","text":"

    Step-by-step onboarding sequence for new contributors. Consolidates setup instructions currently scattered across the README, contributing guide, and setup docs.

    When to use: First-time contributor setup, or when verifying your development environment after a major upgrade.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-1-clone-the-repository","level":2,"title":"Step 1: Clone the Repository","text":"
    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n

    Or fork first on GitHub, then clone your fork.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-2-initialize-context","level":2,"title":"Step 2: Initialize Context","text":"
    ctx init\neval \"$(ctx activate)\"\n

    ctx init creates the .context/ directory with knowledge files and the .claude/ directory with agent configuration. eval \"$(ctx activate)\" tells ctx to use that directory for the rest of this runbook. If you skip the second line, the later steps fail with Error: no context directory specified.

    If ctx is not yet installed, proceed to Step 3 first, then come back.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-3-build-and-install","level":2,"title":"Step 3: Build and Install","text":"
    make build\nsudo make install\n

    Verify:

    ctx --version\n
    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-4-install-the-plugin-claude-code-users","level":2,"title":"Step 4: Install the Plugin (Claude Code Users)","text":"

    If you use Claude Code, install the plugin from your local clone so skills and hooks reflect your working tree:

    1. Launch claude
    2. Type /plugin and press Enter
    3. Select Marketplaces -> Add Marketplace
    4. Enter the absolute path to your clone (e.g., ~/WORKSPACE/ctx)
    5. Back in /plugin, select Install and choose ctx

    Verify:

    claude /plugin list   # should show ctx\n

    See Contributing: Install the Plugin for details on cache clearing.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-5-switch-to-dev-profile","level":2,"title":"Step 5: Switch to Dev Profile","text":"
    ctx config switch dev\n

    This enables verbose logging and notify events (useful during development).

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-6-verify-hooks","level":2,"title":"Step 6: Verify Hooks","text":"

    Start a Claude Code session and check that hooks fire:

    claude\n

    You should see ctx session hooks (ceremonies reminder, context loading) on session start. If not, check that the plugin is installed correctly (Step 4).

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-7-run-your-first-session","level":2,"title":"Step 7: Run Your First Session","text":"

    In Claude Code:

    /ctx-status\n

    This should show context file health, active tasks, and recent decisions. If it works, your setup is complete.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-8-verify-context-persistence","level":2,"title":"Step 8: Verify Context Persistence","text":"

    End the session and start a new one:

    /ctx-remember\n

    The agent should recall what happened in the previous session. This confirms that context persistence is working end-to-end.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-9-run-tests","level":2,"title":"Step 9: Run Tests","text":"
    make test     # unit tests\nmake audit    # full check: fmt + vet + lint + drift + docs + test\n

    All tests should pass with a clean clone.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#quick-reference","level":2,"title":"Quick Reference","text":"Task Command Build make build Install sudo make install Test make test Full audit make audit Rebuild docs site make site Serve docs locally make site-serve Clear plugin cache make plugin-reload Switch config profile ctx config switch dev","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#next-steps","level":2,"title":"Next Steps","text":"
    • Read the contributing guide for project layout, code style, and PR process
    • Check TASKS.md for open work items
    • Ask /ctx-next for suggested work
    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/plugin-release/","level":1,"title":"Plugin Release","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#plugin-release","level":1,"title":"Plugin Release","text":"

    Plugin-specific release procedure. The general release checklist covers the full ctx release; this runbook covers the plugin-specific steps that are not part of that flow.

    When to use: When releasing plugin changes (new skills, hook updates, permission changes) independently of a ctx binary release, or as a sub-procedure within the full release.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#what-ships-in-the-plugin","level":2,"title":"What Ships in the Plugin","text":"

    The plugin lives at internal/assets/claude/ and includes:

    Component Path What it does Skills internal/assets/claude/skills/ User-facing /ctx-* slash commands Hooks internal/assets/claude/hooks/ Pre/post tool-use hooks Plugin manifest internal/assets/claude/.claude-plugin/plugin.json Declares skills, hooks, version Marketplace .claude-plugin/marketplace.json Points Claude Code to the plugin","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-1-update-hooksjson-if-hooks-changed","level":2,"title":"Step 1: Update hooks.json (If Hooks Changed)","text":"

    If you added, removed, or modified hooks:

    # Verify hook definitions match implementations\nmake audit\n

    Check that plugin.json lists all hooks correctly. Missing hooks silently fail to fire.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-2-bump-version","level":2,"title":"Step 2: Bump Version","text":"

    Update the version in three places:

    • internal/assets/claude/.claude-plugin/plugin.json
    • .claude-plugin/marketplace.json (two fields)
    • editors/vscode/package.json + package-lock.json (if VS Code extension is affected)

    The Release Script Does This

    If you're running make release, the script bumps these automatically from VERSION. Only bump manually if you're releasing the plugin independently.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-3-test-against-a-fresh-install","level":2,"title":"Step 3: Test Against a Fresh Install","text":"
    # Clear cached plugin\nmake plugin-reload\n\n# Restart Claude Code, then:\nclaude /plugin list    # verify version\n

    Test the critical paths:

    • /ctx-status works
    • Session hooks fire (ceremonies, context loading)
    • At least one user-facing skill works end-to-end
    • Pre-tool-use hooks block when they should
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-4-test-against-a-clean-project","level":2,"title":"Step 4: Test Against a Clean Project","text":"

    Create a temporary project to verify the plugin works outside the ctx repo:

    mkdir /tmp/test-ctx-plugin && cd /tmp/test-ctx-plugin\ngit init\nctx init\nclaude   # start a session, verify hooks fire\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-5-verify-skill-count","level":2,"title":"Step 5: Verify Skill Count","text":"

    The plugin manifest declares all user-invocable skills. Verify the count matches:

    # Count skills in plugin.json\njq '.skills | length' internal/assets/claude/.claude-plugin/plugin.json\n\n# Count skill directories\nls -d internal/assets/claude/skills/ctx-*/ | wc -l\n

    These numbers should match (some skills are not user-invocable and won't appear in both counts).

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-6-commit-and-tag","level":2,"title":"Step 6: Commit and Tag","text":"

    If releasing independently of a binary release:

    git add internal/assets/claude/ .claude-plugin/\ngit commit -m \"chore: release plugin v0.X.Y\"\ngit tag plugin-v0.X.Y\ngit push origin main --tags\n

    If part of a full release, the release checklist handles this.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#skills-dont-appear-after-update","level":3,"title":"Skills Don't Appear After Update","text":"

    Claude Code caches plugin files aggressively:

    make plugin-reload    # clears cache\n# restart Claude Code\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#hooks-dont-fire","level":3,"title":"Hooks Don't Fire","text":"

    Check that the hook is registered in plugin.json and that the command it calls exists:

    jq '.hooks' internal/assets/claude/.claude-plugin/plugin.json\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#version-mismatch","level":3,"title":"Version Mismatch","text":"

    If claude /plugin list shows an old version after updating:

    make plugin-reload\n# restart Claude Code\nclaude /plugin list   # should show new version\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/release-checklist/","level":1,"title":"Release Checklist","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release-checklist","level":1,"title":"Release Checklist","text":"

    The canonical pre-release sequence. This runbook ties together the audits, tests, and release steps that are otherwise scattered across docs and the operator's head.

    When to run: Before every release. No exceptions.

    Companion: The /_ctx-release skill automates the tag-and-push portion; this checklist covers everything before and after that automation.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#pre-release","level":2,"title":"Pre-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#1-run-the-codebase-audit","level":3,"title":"1. Run the Codebase Audit","text":"

    Use the codebase audit runbook prompt with your agent. Focus on analyses 1-4 (extractable patterns, documentation drift, maintainability, security). Triage findings into TASKS.md; anything blocking ships before the release.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#2-run-the-docs-semantic-audit","level":3,"title":"2. Run the Docs Semantic Audit","text":"

    Use the docs semantic audit runbook prompt. Fix high-severity findings (weak pages, broken narrative arcs). Medium-severity items can be deferred.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#3-sanitize-permissions","level":3,"title":"3. Sanitize Permissions","text":"

    Follow the sanitize permissions runbook. Clean up .claude/settings.local.json before it gets committed as part of the release.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#4-run-the-full-test-suite","level":3,"title":"4. Run the Full Test Suite","text":"
    make audit    # fmt + vet + lint + drift + docs + test\nmake smoke    # integration smoke tests\n

    All tests must pass. No exceptions.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#5-check-context-health","level":3,"title":"5. Check Context Health","text":"

    Activate the project so the next commands know which .context/ to read:

    eval \"$(ctx activate)\"\nctx drift          # broken references, stale patterns\nctx status         # context file health\n/ctx-link-check    # dead links in docs\n

    Fix anything flagged. If you see Error: no context directory specified, you skipped the eval line above. See Activating a Context Directory.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#6-review-tasksmd","level":3,"title":"6. Review TASKS.md","text":"

    Scan for incomplete tasks tagged as release-blocking. Either finish them or explicitly defer with a reason in the task note.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release","level":2,"title":"Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#7-bump-version","level":3,"title":"7. Bump Version","text":"
    echo \"0.X.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.X.0\"\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#8-generate-release-notes","level":3,"title":"8. Generate Release Notes","text":"

    In Claude Code:

    /_ctx-release-notes\n

    Review dist/RELEASE_NOTES.md. Ensure it captures all user-visible changes.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#9-cut-the-release","level":3,"title":"9. Cut the Release","text":"
    make release\n

    Or in Claude Code: /_ctx-release. See Cutting a Release for the full step-by-step.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#post-release","level":2,"title":"Post-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#10-verify-the-github-release","level":3,"title":"10. Verify the GitHub Release","text":"
    • GitHub Releases shows the new version
    • All 6 binaries are attached
    • SHA256 checksums are attached
    • Release notes render correctly
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#11-update-the-plugin-marketplace","level":3,"title":"11. Update the Plugin Marketplace","text":"

    If the plugin version changed, verify the marketplace entry:

    claude /plugin list   # shows updated version\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#12-announce","level":3,"title":"12. Announce","text":"

    Post in the project's communication channels. Reference the release notes.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#13-clean-up","level":3,"title":"13. Clean Up","text":"
    rm dist/RELEASE_NOTES.md   # consumed by the release script\ngit stash pop              # if you stashed earlier\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/","level":1,"title":"Sanitize Permissions","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#sanitize-permissions","level":1,"title":"Sanitize Permissions","text":"

    Manual procedure for cleaning up .claude/settings.local.json. The agent may analyze and recommend, but you make every edit.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#why-manual-not-automated","level":2,"title":"Why Manual, Not Automated","text":"

    settings.local.json controls what the agent can do without asking. An agent that can edit its own permission file is a self-escalation vector, especially if the skill is auto-accepted. Keep this manual.

    When to run: After busy sessions where you clicked \"Allow\" many times, weekly hygiene (pair with ctx drift), or before committing .claude/settings.local.json.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-1-snapshot","level":2,"title":"Step 1: Snapshot","text":"
    cp .claude/settings.local.json /tmp/settings-backup-$(date +%Y%m%d).json\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-2-extract-the-allow-list","level":2,"title":"Step 2: Extract the Allow List","text":"
    jq '.permissions.allow[]' .claude/settings.local.json | sort\n

    Eyeball it. You're looking for four categories:

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-3-identify-problems","level":2,"title":"Step 3: Identify Problems","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#a-garbage-nonsense","level":3,"title":"A. Garbage / Nonsense","text":"

    Entries that are clearly broken or meaningless:

    Bash(done)\nBash(__NEW_LINE_aa838494a90279c4__ echo \"\")\n

    Action: Delete.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#b-one-off-commands-session-debris","level":3,"title":"B. One-Off Commands (Session Debris)","text":"

    Entries with hardcoded paths, literal arguments, or exact commands that were accepted during a specific debugging session:

    Bash(git -C /home/jose/WORKSPACE/ctx log --oneline --all -20)\nBash(/home/jose/WORKSPACE/ctx/ctx decision add \"Use PostgreSQL\" --context ...)\n

    Signs of a one-off:

    • Full absolute paths to specific files
    • Literal string arguments (not wildcards)
    • Very specific flag combinations
    • Commands that look like they came from a single task

    Action: Delete unless you want to promote to a wildcard pattern.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#c-subsumed-entries-redundant","level":3,"title":"C. Subsumed Entries (Redundant)","text":"

    A narrow entry that's already covered by a broader one:

    # Narrow (redundant):\nBash(ctx journal source)\nBash(git -C /home/jose/WORKSPACE/ctx log --oneline -5)\n\n# Broad (already covers the above):\nBash(ctx journal source:*)\nBash(git -C:*)\n

    To find these, look for entries where removing the specific args would match an existing wildcard entry.

    Action: Delete the narrow entry.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#d-duplicate-intent-different-spelling","level":3,"title":"D. Duplicate Intent, Different Spelling","text":"

    Same command with env vars in different order, or slight variations:

    Bash(CGO_ENABLED=0 CTX_SKIP_PATH_CHECK=1 go test:*)\nBash(CTX_SKIP_PATH_CHECK=1 CGO_ENABLED=0 go test:*)\n

    Action: Keep one, delete the other.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-4-check-for-security-concerns","level":2,"title":"Step 4: Check for Security Concerns","text":"

    While you're in here, also flag:

    Pattern Risk Bash(git push:*) Bypasses block-git-push.sh hook Bash(rm -rf:*) Recursive delete, no confirmation Bash(sudo:*) Privilege escalation Bash(echo:*), Bash(cat:*) Can compose into writes to sensitive files Bash(curl:*), Bash(wget:*) Arbitrary network access Any write to .claude/ paths Agent self-modification

    See the /ctx-permission-sanitize skill for the full threat matrix.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-5-edit","level":2,"title":"Step 5: Edit","text":"

    Edit .claude/settings.local.json directly in your editor. Remove flagged entries. Keep the JSON valid.

    # Validate JSON after editing\njq . .claude/settings.local.json > /dev/null && echo \"valid\" || echo \"BROKEN\"\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-6-verify","level":2,"title":"Step 6: Verify","text":"
    # Compare before/after\ndiff /tmp/settings-backup-$(date +%Y%m%d).json .claude/settings.local.json\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-7-optionally-commit","level":2,"title":"Step 7: Optionally Commit","text":"
    git add .claude/settings.local.json\ngit commit -m \"chore: sanitize agent permissions\"\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#asking-the-agent-for-help","level":2,"title":"Asking the Agent for Help","text":"

    You can safely ask the agent to analyze the file:

    \"Look at my settings.local.json and tell me which permissions look like one-offs or are redundant.\"

    The agent can read and report. You do the edits.

    Do not add these to your allow list:

    • Skill(ctx-permission-sanitize)
    • Edit(.claude/settings.local.json)
    • Any Bash(...) pattern that writes to .claude/
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#history","level":2,"title":"History","text":"
    • 2026-02-15: Created as manual-only procedure after deciding against a self-modifying skill.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"recipes/","level":1,"title":"Recipes","text":"

    Workflow recipes combining ctx commands and skills to solve specific problems.

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#getting-started","level":2,"title":"Getting Started","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#guide-your-agent","level":3,"title":"Guide Your Agent","text":"

    How commands, skills, and conversational patterns work together. Train your agent to be proactive through ask, guide, reinforce.

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#setup-across-ai-tools","level":3,"title":"Setup across AI Tools","text":"

    Initialize ctx and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf. Includes shell completion, watch mode for non-native tools, and verification.

    Uses: ctx init, ctx setup, ctx agent, ctx completion, ctx watch

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#multilingual-session-parsing","level":3,"title":"Multilingual Session Parsing","text":"

    Parse session journal entries written in other languages. Configure recognized session-header prefixes so the journal pipeline works for Turkish, Japanese, and any other locale.

    Uses: ctx journal source, ctx journal import, session_prefixes in .ctxrc

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#keeping-context-in-a-separate-repo","level":3,"title":"Keeping Context in a Separate Repo","text":"

    Store context files outside the project tree: in a private repo, shared directory, or anywhere else. Useful for open source projects with private context or multi-repo setups.

    Uses: ctx init, CTX_DIR, .ctxrc, /ctx-status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#sessions","level":2,"title":"Sessions","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#the-complete-session","level":3,"title":"The Complete Session","text":"

    Walk through a full ctx session from start to finish:

    • Loading context,
    • Picking what to work on,
    • Committing with context,
    • Capturing, reflecting, and saving a snapshot.

    Uses: ctx status, ctx agent, /ctx-remember, /ctx-next, /ctx-commit, /ctx-reflect

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#session-ceremonies","level":3,"title":"Session Ceremonies","text":"

    The two bookend rituals for every session: /ctx-remember at the start to load and confirm context, /ctx-wrap-up at the end to review the session and persist learnings, decisions, and tasks.

    Uses: /ctx-remember, /ctx-wrap-up, /ctx-commit, ctx agent, ctx add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#browsing-and-enriching-past-sessions","level":3,"title":"Browsing and Enriching Past Sessions","text":"

    Export your AI session history to a browsable journal site. Enrich entries with metadata and search across months of work.

    Uses: ctx journal source/import, ctx journal site, ctx journal obsidian, ctx serve, /ctx-history, /ctx-journal-enrich, /ctx-journal-enrich-all

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#session-reminders","level":3,"title":"Session Reminders","text":"

    Leave a message for your next session. Reminders surface automatically at session start and repeat until dismissed. Date-gate reminders to surface only after a specific date.

    Uses: ctx remind, ctx remind list, ctx remind dismiss, ctx system check-reminders

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#reviewing-session-changes","level":3,"title":"Reviewing Session Changes","text":"

    See what moved since your last session: context file edits, code commits, directories touched. Auto-detects session boundaries from state markers.

    Uses: ctx change, ctx agent, ctx status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#pausing-context-hooks","level":3,"title":"Pausing Context Hooks","text":"

    Silence all nudge hooks for a quick task that doesn't need ceremony overhead. Session-scoped: Other sessions are unaffected. Security hooks still fire.

    Uses: ctx hook pause, ctx hook resume, /ctx-pause, /ctx-resume

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-and-tasks","level":2,"title":"Knowledge and Tasks","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#persisting-decisions-learnings-and-conventions","level":3,"title":"Persisting Decisions, Learnings, and Conventions","text":"

    Record architectural decisions with rationale, capture gotchas and lessons learned, and codify conventions so they survive across sessions and team members.

    Uses: ctx decision add, ctx learning add, ctx convention add, ctx decision reindex, ctx learning reindex, /ctx-decision-add, /ctx-learning-add, /ctx-convention-add, /ctx-reflect

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#tracking-work-across-sessions","level":3,"title":"Tracking Work across Sessions","text":"

    Add, prioritize, complete, snapshot, and archive tasks. Keep TASKS.md focused as your project evolves across dozens of sessions.

    Uses: ctx task add, ctx task complete, ctx task archive, ctx task snapshot, /ctx-task-add, /ctx-archive, /ctx-next

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#using-the-scratchpad","level":3,"title":"Using the Scratchpad","text":"

    Use the encrypted scratchpad for quick notes, working memory, and sensitive values during AI sessions. Natural language in, encrypted storage out.

    Uses: ctx pad, /ctx-pad, ctx pad show, ctx pad edit

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#syncing-scratchpad-notes-across-machines","level":3,"title":"Syncing Scratchpad Notes across Machines","text":"

    Distribute your scratchpad encryption key, push and pull encrypted notes via git, and resolve merge conflicts when two machines edit simultaneously.

    Uses: ctx init, ctx pad, ctx pad resolve, scp

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#bridging-claude-code-auto-memory","level":3,"title":"Bridging Claude Code Auto Memory","text":"

    Mirror Claude Code's auto memory (MEMORY.md) into .context/ for version control, portability, and drift detection. Import entries into structured context files with heuristic classification.

    Uses: ctx memory sync, ctx memory status, ctx memory diff, ctx memory import, ctx memory publish, ctx system check-memory-drift

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hooks-and-notifications","level":2,"title":"Hooks and Notifications","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-output-patterns","level":3,"title":"Hook Output Patterns","text":"

    Choose the right output pattern for your Claude Code hooks: VERBATIM relay for user-facing reminders, hard gates for invariants, agent directives for nudges, and five more patterns across the spectrum.

    Uses: ctx plugin hooks, settings.local.json

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#customizing-hook-messages","level":3,"title":"Customizing Hook Messages","text":"

    Customize what hooks say without changing what they do. Override the QA gate for Python (pytest instead of make lint), silence noisy ceremony nudges, or tailor post-commit instructions for your stack.

    Uses: ctx hook message list, ctx hook message show, ctx hook message edit, ctx hook message reset

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-sequence-diagrams","level":3,"title":"Hook Sequence Diagrams","text":"

    Mermaid sequence diagrams for every system hook: entry conditions, state reads, output, throttling, and exit points. Includes throttling summary table and state file reference.

    Uses: All ctx system hooks

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#auditing-system-hooks","level":3,"title":"Auditing System Hooks","text":"

    The 12 system hooks that run invisibly during every session: what each one does, why it exists, and how to verify they're actually firing. Covers webhook-based audit trails, log inspection, and detecting silent hook failures.

    Uses: ctx system, ctx hook notify, .context/logs/, .ctxrc notify.events

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"

    Get push notifications when loops complete, hooks fire, or agents hit milestones. Webhook URL is encrypted: never stored in plaintext. Works with IFTTT, Slack, Discord, ntfy.sh, or any HTTP endpoint.

    Uses: ctx hook notify setup, ctx hook notify test, ctx hook notify --event, .ctxrc notify.events

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"

    Switch between dev and base runtime configurations without editing .ctxrc by hand. Verbose logging and webhooks for debugging, clean defaults for normal sessions.

    Uses: ctx config switch, ctx config status, /ctx-config

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#maintenance","level":2,"title":"Maintenance","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#detecting-and-fixing-drift","level":3,"title":"Detecting and Fixing Drift","text":"

    Keep context files accurate by detecting structural drift (stale paths, missing files, stale file ages) and task staleness.

    Uses: ctx drift, ctx sync, ctx compact, ctx status, /ctx-drift, /ctx-status, /ctx-prompt-audit

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#state-directory-maintenance","level":3,"title":"State Directory Maintenance","text":"

    Clean up session tombstones from .context/state/. Prune old per-session files, identify stale global markers, and keep the state directory lean.

    Uses: ctx prune

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#troubleshooting","level":3,"title":"Troubleshooting","text":"

    Diagnose hook failures, noisy nudges, stale context, and configuration issues. Start with ctx doctor for a structural health check, then use /ctx-doctor for agent-driven analysis of event patterns.

    Uses: ctx doctor, ctx hook event, /ctx-doctor

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#claude-code-permission-hygiene","level":3,"title":"Claude Code Permission Hygiene","text":"

    Keep .claude/settings.local.json clean: recommended safe defaults, what to never pre-approve, and a maintenance workflow for cleaning up session debris.

    Uses: ctx init, /ctx-drift, /ctx-permission-sanitize, ctx permission snapshot, ctx permission restore

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#permission-snapshots","level":3,"title":"Permission Snapshots","text":"

    Capture a known-good permission baseline as a golden image, then restore at session start to automatically drop session-accumulated permissions.

    Uses: ctx permission snapshot, ctx permission restore, /ctx-permission-sanitize

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#turning-activity-into-content","level":3,"title":"Turning Activity into Content","text":"

    Generate blog posts from project activity, write changelog posts from commit ranges, and publish a browsable journal site from your session history.

    The output is generic Markdown, but the skills are tuned for the ctx-style blog artifacts you see on this website.

    Uses: ctx journal site, ctx journal obsidian, ctx serve, ctx journal import, /ctx-blog, /ctx-blog-changelog, /ctx-journal-enrich

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#importing-claude-code-plans","level":3,"title":"Importing Claude Code Plans","text":"

    Import Claude Code plan files (~/.claude/plans/*.md) into specs/ as permanent project specs. Filter by date, select interactively, and optionally create tasks referencing each imported spec.

    Uses: /ctx-plan-import, /ctx-task-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#design-before-coding","level":3,"title":"Design Before Coding","text":"

    Front-load design with a four-skill chain: brainstorm the approach, spec the design, task the work, implement step-by-step. Each step produces an artifact that feeds the next.

    Uses: /ctx-brainstorm, /ctx-spec, /ctx-task-add, /ctx-implement, /ctx-decision-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#scrutinizing-a-plan","level":3,"title":"Scrutinizing a Plan","text":"

    Once a plan exists, run an adversarial interview to surface what's weak, missing, or unexamined before you commit. Walks the plan depth-first: assumptions, failure modes, alternatives, sequencing, reversibility. The complement to brainstorm: brainstorm produces plans, this attacks them.

    Uses: /ctx-plan, /ctx-spec, /ctx-decision-add, /ctx-learning-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#agents-and-automation","level":2,"title":"Agents and Automation","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#building-project-skills","level":3,"title":"Building Project Skills","text":"

    Encode repeating workflows into reusable skills the agent loads automatically. Covers the full cycle: identify a pattern, create the skill, test with realistic prompts, and iterate until it triggers correctly.

    Uses: /ctx-skill-create, ctx init

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#running-an-unattended-ai-agent","level":3,"title":"Running an Unattended AI Agent","text":"

    Set up a loop where an AI agent works through tasks overnight without you at the keyboard, using ctx for persistent memory between iterations.

    This recipe shows how ctx supports long-running agent loops without losing context or intent.

    Uses: ctx init, ctx loop, ctx watch, ctx load, /ctx-loop, /ctx-implement

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#when-to-use-a-team-of-agents","level":3,"title":"When to Use a Team of Agents","text":"

    Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.

    This recipe covers the file overlap test, when teams make things worse, and what ctx provides at each level.

    Uses: /ctx-worktree, /ctx-next, ctx status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#parallel-agent-development-with-git-worktrees","level":3,"title":"Parallel Agent Development with Git Worktrees","text":"

    Split a large backlog across 3-4 agents using git worktrees, each on its own branch and working directory. Group tasks by file overlap, work in parallel, merge back.

    Uses: /ctx-worktree, /ctx-next, git worktree, git merge

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#architecture-deep-dive","level":3,"title":"Architecture Deep Dive","text":"

    Three-pass pipeline for understanding a codebase: map what exists, enrich with code intelligence, then hunt for where it will silently fail. Produces architecture docs, quantified dependency data, and ranked failure hypotheses.

    Uses: /ctx-architecture, /ctx-architecture-enrich, /ctx-architecture-failure-analysis

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#writing-steering-files","level":3,"title":"Writing Steering Files","text":"

    Tell your AI assistant how to behave with rule-based prompt injection that fires automatically when prompts match a description. Walks through scaffolding a steering file, previewing matches, and syncing to each AI tool's native format.

    Uses: ctx steering add, ctx steering preview, ctx steering list, ctx steering sync

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#authoring-lifecycle-triggers","level":3,"title":"Authoring Lifecycle Triggers","text":"

    Run executable shell scripts at session-start, pre-tool-use, file-save, and other lifecycle events. Script-based automation (complementary to steering's rule-based prompts), with a security-first workflow: scaffold disabled, test with mock input, enable only after review.

    Uses: ctx trigger add, ctx trigger test, ctx trigger enable, ctx trigger disable, ctx trigger list

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hub","level":2,"title":"Hub","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hub-overview","level":3,"title":"Hub Overview","text":"

    Mental model and three user stories for the ctx Hub. What flows, what doesn't, and when not to use it. Read this before any of the other Hub recipes.

    Uses: ctx hub, ctx connection, ctx add --share

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-getting-started","level":3,"title":"ctx Hub: Getting Started","text":"

    Stand up a single-node hub on localhost, register two projects, publish a decision from one, and watch it appear in the other. End-to-end in under five minutes.

    Uses: ctx hub start, ctx connection register, ctx connection subscribe, ctx connection sync, ctx connection listen, ctx add --share, ctx agent --include-hub

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"

    Story 1 day-to-day workflow: one developer, many projects, one hub on localhost. Records a learning in project A, watches it show up automatically in project B. Walks through a realistic day of using the hub as passive infrastructure (no manual sync, no git push, no ceremony).

    Uses: ctx add --share, ctx connection subscribe, ctx agent --include-hub

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#team-knowledge-bus","level":3,"title":"Team Knowledge Bus","text":"

    Story 2 day-to-day workflow: a small trusted team sharing decisions, learnings, and conventions via a hub on an internal server. Covers the team publishing culture, what belongs on the hub vs. local, token management, and the social rules that make a shared knowledge stream stay signal-rich.

    Uses: ctx add --share, ctx connection status, ctx connection subscribe, ctx hub status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-multi-machine","level":3,"title":"ctx Hub: Multi-Machine","text":"

    Run the hub on a LAN host as a daemon and connect from project directories on other workstations. Firewall guidance, TLS via a reverse proxy, and safe daemon restart semantics.

    Uses: ctx hub start --daemon, ctx hub stop, ctx connection register, ctx connection status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-ha-cluster","level":3,"title":"ctx Hub: HA Cluster","text":"

    Raft-based leader election across three or more nodes for redundancy. Covers bootstrap, runtime peer management, graceful stepdown, and the Raft-lite durability caveat.

    Uses: ctx hub start --peers, ctx hub status, ctx hub peer add/remove, ctx hub stepdown

    ","path":["Recipes"],"tags":[]},{"location":"recipes/activating-context/","level":1,"title":"Activating a Context Directory","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#the-problem","level":2,"title":"The Problem","text":"

    You ran a ctx command and got:

    Error: no context directory specified for this project\n

    This means ctx doesn't know which .context/ directory to operate on. It will not guess, and it will not walk up from your current working directory looking for one; that behavior was removed deliberately, because silent inference was the source of several bugs (stray agent-created directories, cross-project bleed-through, webhook-route misrouting, sub-agent fragmentation). Every ctx command requires you to declare the target directory explicitly.

    This page shows you the three ways to do that and when to use each.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#tldr","level":2,"title":"TL;DR","text":"

    If the project has already been initialized and you just need to bind it for your shell:

    eval \"$(ctx activate)\"\n

    That's 95% of the time. Add it to .zshrc / .bashrc per project with direnv, or run it once per terminal.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#when-you-see-the-error","level":2,"title":"When You See the Error","text":"

    The exact error message depends on how many .context/ directories are visible from the current directory:

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#zero-candidates","level":3,"title":"Zero Candidates","text":"
    Error: no context directory specified for this project\n

    Either you haven't initialized this project yet (run ctx init) or you're in a directory that doesn't belong to a ctx-tracked project. If you know the project lives elsewhere, use one of the declaration methods below with its absolute path.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-candidate","level":3,"title":"One Candidate","text":"
    Error: no context directory specified; a likely candidate is at\n    /Users/you/repos/myproject/.context\n

    ctx found a single .context/ on the way up from here but won't bind to it automatically. Run eval \"$(ctx activate)\" and ctx will emit the export for the candidate. Or set CTX_DIR by hand.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#multiple-candidates","level":3,"title":"Multiple Candidates","text":"
    Error: no context directory specified; multiple candidates visible:\n  /Users/you/repos/myproject/.context\n  /Users/you/repos/myproject/packages/web/.context\n

    You're inside nested projects. Pick the one you mean:

    ctx activate /Users/you/repos/myproject/.context\n# …copy and paste the `export` line it prints, or wrap in eval:\neval \"$(ctx activate /Users/you/repos/myproject/.context)\"\n
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#three-ways-to-declare","level":2,"title":"Three Ways to Declare","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#1-ctx-activate-recommended-for-shells","level":3,"title":"1. ctx activate (Recommended for Shells)","text":"

    ctx activate emits a shell-native export CTX_DIR=... line to stdout. Wrap it in eval and the binding takes effect for the current shell:

    # Walk up from current dir and bind the single visible candidate:\neval \"$(ctx activate)\"\n\n# Bind a specific path explicitly:\neval \"$(ctx activate /abs/path/to/.context)\"\n\n# Clear the binding:\neval \"$(ctx deactivate)\"\n

    ctx activate validates paths strictly: the target must exist, be a directory, and contain at least one canonical context file (CONSTITUTION.md or TASKS.md). It refuses to emit for multiple upward candidates; pick one explicitly in that case.

    Under the hood, the emitted line is just:

    export CTX_DIR='/abs/path/to/.context'\n

    So you can copy it into your .zshrc / .bashrc if you want the binding permanent for a given shell setup. Better: use direnv with a per-project .envrc.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#2-ctx_dir-env-var","level":3,"title":"2. CTX_DIR Env Var","text":"

    If you already know the path, export it directly:

    export CTX_DIR=/abs/path/to/.context\nctx status\n

    CTX_DIR is the same variable ctx activate writes; activate is just a convenience that figures out the path for you.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#3-inline-one-shot","level":3,"title":"3. Inline One-Shot","text":"

    For one-shot commands (CI jobs, scripts, debugging a specific project without changing your shell state), prefix the binding inline:

    CTX_DIR=/abs/path/to/.context ctx status\n

    This binds CTX_DIR for that invocation only.

    CTX_DIR must be an absolute path with .context as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (export CTX_DIR=$(pwd)) before stray writes can leak to the project root.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-ci-and-scripts","level":2,"title":"For CI and Scripts","text":"

    Do not rely on shell activation in automated flows. Set CTX_DIR explicitly at the top of the script:

    #!/usr/bin/env bash\nset -euo pipefail\n\nexport CTX_DIR=\"$GITHUB_WORKSPACE/.context\"\nctx status\nctx drift\n
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-claude-code-users","level":2,"title":"For Claude Code Users","text":"

    The ctx plugin's hooks are generated with CTX_DIR=\"$CLAUDE_PROJECT_DIR/.context\" prefixed to each command, so hook-driven ctx invocations resolve correctly without any per-session setup. You only need to activate manually when running ctx yourself in a terminal.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-project-one-context","level":2,"title":"One Project, One .context/","text":"

    The context directory is not a free-floating bag of files. It is pinned to a project by contract: filepath.Dir(ContextDir()) is the project root. That parent directory is what ctx sync, ctx drift, and the memory-drift hook scan for code, secret files, and MEMORY.md respectively.

    The practical consequences:

    • Don't share one .context/ across multiple projects. It holds per-project journals, per-session state, and per-project secrets. Pointing two codebases at the same directory corrupts all three.
    • If you want to share knowledge (CONSTITUTION, CONVENTIONS, ARCHITECTURE) across projects, use ctx hub. It cherry-picks entries at the right granularity and keeps the per-project bits where they belong.
    • The CTX_DIR you activate is implicitly a project-root declaration. Setting CTX_DIR=/weird/place/.context means you're telling ctx the project root is /weird/place/. That's your call to make; ctx does not police it.
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#recommended-layout","level":3,"title":"Recommended Layout","text":"
    ~/WORKSPACE/my-to-do-list\n  ├── .git\n  ├── .context          ← owned by this project; do not share\n  ├── ideas\n  │   └── ...\n  ├── Makefile\n  ├── Makefile.ctx\n  └── specs\n      └── ...\n

    .context/ sits at the project root, next to .git. ctx activate binds to it; every ctx subsystem reads the project from its parent.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#why-not-walk-up-automatically","level":2,"title":"Why Not Walk Up Automatically?","text":"

    Nested projects, submodules, rogue agent-created .context/ directories, and sub-agent sessions all produced silent misrouting under the old walk-up model. See the explicit-context-dir spec and the analysis doc for the full reasoning.

    The short version: ctx decided to stop guessing and require the caller to declare. Every other decision flows from there.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/architecture-deep-dive/","level":1,"title":"Architecture Deep Dive","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-problem","level":2,"title":"The Problem","text":"

    Understanding a codebase at the surface level is easy. Understanding where it will break under real-world conditions takes three passes: mapping what exists, quantifying how it connects, and hunting for where it silently fails. Most teams stop at the first pass.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tldr","level":2,"title":"TL;DR","text":"
    # Pass 1: Map the system\n/ctx-architecture\n\n# Pass 2: Enrich with code intelligence\n/ctx-architecture-enrich\n\n# Pass 3: Hunt for failure modes\n/ctx-architecture-failure-analysis\n

    Each pass builds on the previous one. Run them in order. The output accumulates in .context/; each pass reads the prior artifacts and extends them.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-architecture Skill Map modules, dependencies, data flow, patterns /ctx-architecture-enrich Skill Verify blast radius and flows with code intel /ctx-architecture-failure-analysis Skill Generate falsifiable incident hypotheses ctx drift CLI Detect stale paths and broken references ctx status CLI Quick structural overview","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-1-map-what-exists","level":3,"title":"Pass 1: Map What Exists","text":"
    /ctx-architecture\n

    Produces:

    • ARCHITECTURE.md: succinct project map (< 4000 tokens), loaded at every session start
    • DETAILED_DESIGN*.md: deep per-module reference with exported API, data flow, danger zones, extension points
    • CHEAT-SHEETS.md: lifecycle flow diagrams
    • map-tracking.json: coverage state with confidence scores

    This pass forces deep code reading. No shortcuts, no code intelligence tools; the agent reads every module it analyzes. That forced reading is what makes the subsequent passes useful.

    When to run: First time on a codebase, or after significant structural changes (new packages, moved files, changed dependencies).

    Principal mode: Add principal to get strategic analysis (ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md from P4):

    /ctx-architecture principal\n
    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-2-enrich-with-code-intelligence","level":3,"title":"Pass 2: Enrich with Code Intelligence","text":"
    /ctx-architecture-enrich\n

    Takes the Pass 1 artifacts as baseline and layers on verified, graph-backed data from GitNexus:

    • Blast radius numbers for key functions
    • Execution flow traces through hot paths
    • Domain clustering validation
    • Registration site discovery

    This pass does not replace reading; it quantifies what reading found. If Pass 1 says \"module X depends on module Y,\" Pass 2 says \"module X has 47 callers in module Y, and changing function Z would affect 12 downstream consumers.\"

    When to run: After Pass 1, when you need quantified confidence for refactoring decisions or risk assessment.

    Requires: GitNexus MCP server connected.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-3-hunt-for-failure-modes","level":3,"title":"Pass 3: Hunt for Failure Modes","text":"
    /ctx-architecture-failure-analysis\n

    The adversarial pass. Reads all prior artifacts, then systematically hunts for correctness bugs across 9 failure categories:

    1. Concurrency (races, deadlocks, goroutine leaks)
    2. Ordering assumptions (init, registration, shutdown)
    3. Cache staleness (TTL-less, read-your-writes, cross-process)
    4. Fan-out amplification (N+1, retry storms)
    5. Ownership and lifecycle (orphans, double-close)
    6. Error handling (silent swallowing, partial failure)
    7. Scaling cliffs (quadratic, unbounded, global locks)
    8. Idempotency failures (duplicate processing, retry mutations)
    9. State machine drift (illegal states, unvalidated transitions)

    Every finding must meet an evidence standard: code path, trigger, failure path, silence reason, and code evidence. A mandatory challenge phase attempts to disprove each finding before it is accepted. Findings carry a confidence level (High/Medium/Low) and explicit risk score.

    Produces DANGER-ZONES.md, a ranked inventory of findings split into Critical and Elevated tiers.

    When to run: Before releases, after major refactors, when investigating incident categories, or when onboarding.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#what-you-get","level":2,"title":"What You Get","text":"

    After all three passes, .context/ contains:

    File From Purpose ARCHITECTURE.md Pass 1 System map (session-start context) DETAILED_DESIGN*.md Pass 1 Module-level deep reference CHEAT-SHEETS.md Pass 1 Lifecycle flow diagrams map-tracking.json Pass 1 Coverage and confidence data CONVERGENCE-REPORT.md Pass 1 What's covered, what's not DANGER-ZONES.md Pass 3 Ranked failure hypotheses

    Pass 2 enriches Pass 1 artifacts in-place rather than creating new files.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tips","level":2,"title":"Tips","text":"
    • Run Pass 1 with focus areas if the codebase is large. The skill asks what to go deep on, so name the modules you're about to change.
    • You don't need all three passes every time. Pass 1 is the foundation. Pass 2 and 3 are for when you need quantified confidence or adversarial rigor.
    • Re-run Pass 1 incrementally. It tracks coverage in map-tracking.json and only re-analyzes stale modules.
    • Pass 3 is most valuable before releases. The ranked DANGER-ZONES.md is a pre-release checklist.
    • The trilogy maps to a question progression: How does it work? How well does it connect? Where will it break?
    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#see-also","level":2,"title":"See Also","text":"

    See also: Detecting and Fixing Context Drift to keep architecture artifacts fresh between deep-dive sessions.

    See also: Detecting and Fixing Context Drift for structural checks that complement architecture analysis.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/autonomous-loops/","level":1,"title":"Running an Unattended AI Agent","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-problem","level":2,"title":"The Problem","text":"

    You have a project with a clear list of tasks, and you want an AI agent to work through them autonomously: overnight, unattended, without you sitting at the keyboard.

    Each iteration needs to remember what the previous one did, mark tasks as completed, and know when to stop.

    Without persistent memory, every iteration starts fresh and the loop collapses. With ctx, each iteration can pick up where the last one left off, but only if the agent persists its context as part of the work.

    Unattended operation works because the agent treats context persistence as a first-class deliverable, not an afterthought.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                                    # 1. init context\neval \"$(ctx activate)\"                      # 2. bind CTX_DIR for this shell\n# Edit TASKS.md with phased work items\nctx loop --tool claude --max-iterations 10  # 3. generate loop.sh\n./loop.sh 2>&1 | tee /tmp/loop.log &        # 4. run the loop\nctx watch --log /tmp/loop.log               # 5. process context updates\n# Next morning:\nctx status && ctx load                      # 6. review the results\n

    Activate, or Set CTX_DIR Inline for Unattended Runs

    eval \"$(ctx activate)\" is fine for an interactive terminal. For an overnight unattended loop, put the binding at the top of loop.sh instead (export CTX_DIR=/abs/path/.context) so the loop doesn't depend on a live shell. If you skip both, ctx loop, ctx watch, ctx status, and ctx load fail with Error: no context directory specified. See Activating a Context Directory.

    Read on for permissions, isolation, and completion signals.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init Command Initialize project context and prompt templates ctx loop Command Generate the loop shell script ctx watch Command Monitor AI output and persist context updates ctx load Command Display assembled context (for debugging) /ctx-loop Skill Generate loop script from inside Claude Code /ctx-implement Skill Execute a plan step-by-step with verification","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-1-initialize-for-unattended-operation","level":3,"title":"Step 1: Initialize for Unattended Operation","text":"

    Start by creating a .context/ directory configured so the agent can work without human input.

    ctx init\n

    This creates .context/ with the template files (including a loop prompt at .context/loop.md), and seeds Claude Code permissions in .claude/settings.local.json. Install the ctx plugin for hooks and skills.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-2-populate-tasksmd-with-phased-work","level":3,"title":"Step 2: Populate TASKS.md with Phased Work","text":"

    Open .context/TASKS.md and organize your work into phases. The agent works through these systematically, top to bottom, using priority tags to break ties.

    # Tasks\n\n## Phase 1: Foundation\n\n- [ ] Set up project structure and build system `#priority:high`\n- [ ] Configure testing framework `#priority:high`\n- [ ] Create CI pipeline `#priority:medium`\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Hardening\n\n- [ ] Add rate limiting to API endpoints `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n- [ ] Write integration tests `#priority:medium`\n

    Phased organization matters because it gives the agent natural boundaries. Phase 1 tasks should be completable without Phase 2 code existing yet.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-3-configure-the-loop-prompt","level":3,"title":"Step 3: Configure the Loop Prompt","text":"

    The loop prompt at .context/loop.md instructs the agent to operate autonomously:

    1. Read .context/CONSTITUTION.md first (hard rules, never violated)
    2. Load context from .context/ files
    3. Pick one task per iteration
    4. Complete the task and update context files
    5. Commit changes (including .context/)
    6. Signal status with a completion signal

    You can customize .context/loop.md for your project. The critical parts are the one-task-per-iteration discipline, proactive context persistence, and completion signals at the end:

    ## Signal Status\n\nEnd your response with exactly ONE of:\n\n* `SYSTEM_CONVERGED`: All tasks in `TASKS.md` are complete (*this is the\n  signal the loop script detects by default*)\n* `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n* (*no signal*): More work remains, continue to the next iteration\n\nNote: the loop script only checks for `SYSTEM_CONVERGED` by default.\n`SYSTEM_BLOCKED` is a convention for the human reviewing the log.\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-4-configure-permissions","level":3,"title":"Step 4: Configure Permissions","text":"

    An unattended agent needs permission to use tools without prompting. By default, Claude Code asks for confirmation on file writes, bash commands, and other operations, which stops the loop and waits for a human who is not there.

    There are two approaches.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-a-explicit-allowlist-recommended","level":4,"title":"Option A: Explicit Allowlist (Recommended)","text":"

    Grant only the permissions the agent needs. In .claude/settings.local.json:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Bash(ctx:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n

    Adjust the Bash patterns for your project's toolchain. The agent can run make, go, git, and ctx commands but cannot run arbitrary shell commands.

    This is recommended even in sandboxed environments because it limits blast radius.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-b-skip-all-permission-checks","level":4,"title":"Option B: Skip All Permission Checks","text":"

    Claude Code supports a --dangerously-skip-permissions flag that disables all permission prompts:

    claude --dangerously-skip-permissions -p \"$(cat .context/loop.md)\"\n

    This Flag Means What It Says

    With --dangerously-skip-permissions, the agent can execute any shell command, write to any file, and make network requests without confirmation.

    Only use this on a sandboxed machine: ideally a virtual machine with no access to host credentials, no SSH keys, and no access to production systems.

    If you would not give an untrusted intern sudo on this machine, do not use this flag.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#enforce-isolation-at-the-os-level","level":4,"title":"Enforce Isolation at the OS Level","text":"

    The only controls an agent cannot override are the ones enforced by the operating system, the container runtime, or the hypervisor.

    Do Not Skip This Section

    This is not optional hardening:

    An unattended agent with unrestricted OS access is an unattended shell with unrestricted OS access.

    The allowlist above is a strong first layer, but do not rely on a single runtime boundary.

    For unattended runs, enforce isolation at the infrastructure level:

    Layer What to enforce User account Run the agent as a dedicated unprivileged user with no sudo access and no membership in privileged groups (docker, wheel, adm). Filesystem Restrict the project directory via POSIX permissions or ACLs. The agent should have no access to other users' files or system directories. Container Run inside a Docker/Podman sandbox. Mount only the project directory. Drop capabilities (--cap-drop=ALL). Disable network if not needed (--network=none). Never mount the Docker socket and do not run privileged containers. Prefer rootless containers. Virtual machine Prefer a dedicated VM with no shared folders, no host passthrough, and no keys to other machines. Network If the agent does not need the internet, disable outbound access entirely. If it does, restrict to specific domains via firewall rules. Resource limits Apply CPU, memory, and disk limits (cgroups/container limits). A runaway loop should not fill disk or consume all RAM. Self-modification Make instruction files read-only. CLAUDE.md, .claude/settings.local.json, and .context/CONSTITUTION.md should not be writable by the agent user. If using project-local hooks, protect those too.

    A minimal Docker setup for overnight runs:

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh 2>&1 | tee /tmp/loop.log\n

    Defense in Depth

    Use multiple layers together: OS-level isolation (the boundary the agent cannot cross), a permission allowlist (what Claude Code will do within that boundary), and CONSTITUTION.md (a soft nudge for the common case).

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-5-generate-the-loop-script","level":3,"title":"Step 5: Generate the Loop Script","text":"

    Use ctx loop to generate a loop.sh tailored to your AI tool:

    # Generate for Claude Code with a 10-iteration cap\nctx loop --tool claude --max-iterations 10\n\n# Generate for Aider\nctx loop --tool aider --max-iterations 10\n\n# Custom prompt file and output filename\nctx loop --tool claude --prompt my-prompt.md --output my-loop.sh\n

    The generated script reads .context/loop.md, runs the tool, checks for completion signals, and loops until done or the cap is reached.

    You can also use the /ctx-loop skill from inside Claude Code.

    A Shell Loop Is the Best Practice

    The shell loop approach spawns a fresh AI process each iteration, so the only state that carries between iterations is what lives in .context/ and git.

    Claude Code's built-in /loop runs iterations within the same session, which can allow context window state to leak between iterations. This can be convenient for short runs, but it is less reliable for unattended loops.

    See Shell Loop vs Built-in Loop for details.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-6-run-with-watch-mode","level":3,"title":"Step 6: Run with Watch Mode","text":"

    Open two terminals. In the first, run the loop. In the second, run ctx watch to process context updates from the AI output.

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n

    The watch command parses XML context-update commands from the AI output and applies them:

    <context-update type=\"complete\">user registration</context-update>\n<context-update type=\"learning\"\n  context=\"Setting up user registration\"\n  lesson=\"Email verification needs SMTP configured\"\n  application=\"Add SMTP setup to deployment checklist\"\n>SMTP Requirement</context-update>\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-7-completion-signals-end-the-loop","level":3,"title":"Step 7: Completion Signals End the Loop","text":"

    The generated script checks for one completion signal per run. By default this is SYSTEM_CONVERGED. You can change it with the --completion flag:

    ctx loop --tool claude --completion BOOTSTRAP_COMPLETE --max-iterations 5\n

    The following signals are conventions used in .context/loop.md:

    Signal Convention How the script handles it SYSTEM_CONVERGED All tasks in TASKS.md are done Detected by default (--completion default value) SYSTEM_BLOCKED Agent cannot proceed Only detected if you set --completion to this BOOTSTRAP_COMPLETE Initial scaffolding done Only detected if you set --completion to this

    The script uses grep -q on the agent's output, so any string works as a signal. If you need to detect multiple signals in one run, edit the generated loop.sh to add additional grep checks.

    When you return in the morning, check the log and the context files:

    tail -100 /tmp/loop.log\nctx status\nctx load\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-8-use-ctx-implement-for-plan-execution","level":3,"title":"Step 8: Use /ctx-implement for Plan Execution","text":"

    Within each iteration, the agent can use /ctx-implement to execute multi-step plans with verification between steps. This is useful for complex tasks that touch multiple files.

    The skill breaks a plan into atomic, verifiable steps:

    Step 1/6: Create user model .................. OK\nStep 2/6: Add database migration ............. OK\nStep 3/6: Implement registration handler ..... OK\nStep 4/6: Write unit tests ................... OK\nStep 5/6: Run test suite ..................... FAIL\n  -> Fixed: missing test dependency\n  -> Re-verify ............................... OK\nStep 6/6: Update TASKS.md .................... OK\n

    Each step is verified (build, test, syntax check) before moving to the next.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A typical overnight run:

    ctx init\n# Edit TASKS.md and .context/loop.md\n\nctx loop --tool claude --max-iterations 20\n\n./loop.sh 2>&1 | tee /tmp/loop.log &\nctx watch --log /tmp/loop.log\n\n# Next morning:\nctx status\nctx load\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#why-autonomous-loops-work-proactive-context-persistence","level":2,"title":"Why Autonomous Loops Work: Proactive Context Persistence","text":"

    The autonomous loop pattern works because the agent persists context as part of the job.

    Without proactive persistence, the loop degrades into disconnected iterations that repeat work, contradict decisions, and lose track of progress. The agent cannot rely on a human to prompt it. It must treat context updates as part of every task, not as a separate step.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-agent-playbook-self-check-drives-autonomy","level":3,"title":"The Agent Playbook Self-Check Drives Autonomy","text":"

    The Agent Playbook defines a self-check prompt the agent applies continuously:

    \"If this session ended right now, would the next session know what happened?\"\n

    In interactive sessions, a human might remind the agent to save context. When the agent runs unattended, nobody is there to remind it.

    The self-check becomes the trigger: after meaningful work, the agent evaluates whether the context files reflect reality and updates them immediately if they do not.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#what-the-agent-does-proactively-between-iterations","level":3,"title":"What the Agent Does Proactively between Iterations","text":"

    At milestones within an iteration, the agent persists without waiting for instructions:

    Milestone What the agent does proactively Completes a task Marks it [x] in TASKS.md Discovers a gotcha Adds it to LEARNINGS.md Makes a design choice Records it in DECISIONS.md with rationale Identifies follow-up work Creates new tasks in TASKS.md with #added timestamp Hits an unexpected failure Documents the root cause before moving on","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#example-what-proactive-persistence-looks-like","level":3,"title":"Example: What Proactive Persistence Looks Like","text":"

    Here is what the agent does during a single iteration beyond writing code:

    Iteration 4:\n  1. Read TASKS.md -> pick \"Add email verification\" (#priority:high)\n  2. Add #started:2026-01-25-030012 to the task\n  3. Implement the feature (code, tests, docs if needed)\n  4. Tests pass -> mark task [x]\n  5. Add learning: \"SMTP config must be set before verification handler registers. Order matters in init().\"\n  6. Add decision: \"Use token-based verification links (not codes) because links work better in automated tests.\"\n  7. Create follow-up task: \"Add rate limiting to verification endpoint\" #added:...\n  8. Commit all changes including `.context/`\n  9. No signal emitted -> loop continues to iteration 5\n

    Steps 2, 4, 5, 6, and 7 are proactive context persistence:

    The agent was not asked to do any of them.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#context-persistence-at-milestones","level":3,"title":"Context Persistence at Milestones","text":"

    For long autonomous runs, the agent persists context at natural boundaries, often at phase transitions or after completing a cluster of related tasks. It updates TASKS.md, DECISIONS.md, and LEARNINGS.md as it goes.

    If the loop crashes at 4 AM, the context files tell you exactly where to resume. You can also use ctx journal source to review the session transcripts.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-persistence-contract","level":3,"title":"The Persistence Contract","text":"

    The autonomous loop has an implicit contract:

    1. Every iteration reads context: TASKS.md, DECISIONS.md, LEARNINGS.md
    2. Every iteration writes context: task updates, new learnings, decisions
    3. Every commit includes .context/ so the next iteration sees changes
    4. Context stays current: if the loop stopped right now, nothing important is lost

    Break any part of this contract and the loop degrades.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tips","level":2,"title":"Tips","text":"

    Markdown Is Not Enforcement

    Your real guardrails are permissions and isolation, not Markdown. CONSTITUTION.md can nudge the agent, but it is probabilistic.

    The permission allowlist and OS isolation are deterministic:

    For unattended runs, trust the sandbox and the allowlist, not the prose.

    • Start with a small iteration cap. Use --max-iterations 5 on your first run.
    • Keep tasks atomic. Each task should be completable in a single iteration.
    • Check signal discipline. If the loop runs forever, the agent is not emitting SYSTEM_CONVERGED or SYSTEM_BLOCKED. Make the signal requirement explicit in .context/loop.md.
    • Commit after context updates. Finish code, update .context/, commit including .context/, then signal.
    • Set up webhook notifications to get notified when the loop completes, hits max iterations, or when hooks fire nudges. The generated loop script includes ctx hook notify calls automatically.
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#next-up","level":2,"title":"Next Up","text":"

    When to Use a Team of Agents →: Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#see-also","level":2,"title":"See Also","text":"
    • Autonomous Loops: loop pattern, prompt templates, troubleshooting
    • CLI Reference: ctx loop: flags and options
    • CLI Reference: ctx watch: watch mode details
    • CLI Reference: ctx init: init flags
    • The Complete Session: interactive workflow
    • Tracking Work Across Sessions: structuring TASKS.md
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/building-skills/","level":1,"title":"Building Project Skills","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-problem","level":2,"title":"The Problem","text":"

    You have workflows your agent needs to repeat across sessions: a deploy checklist, a review protocol, a release process. Each time, you re-explain the steps. The agent gets it mostly right but forgets edge cases you corrected last time.

    Skills solve this by encoding domain knowledge into a reusable document the agent loads automatically when triggered. A skill is not code - it is a structured prompt that captures what took you sessions to learn.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-skill-create\n

    The skill-creator walks you through: identify a repeating workflow, draft a skill, test with realistic prompts, iterate until it triggers correctly and produces good output.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-skill-create Skill Interactive skill creation and improvement workflow ctx init Command Deploys template skills to .claude/skills/ on first setup","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-1-identify-a-repeating-pattern","level":3,"title":"Step 1: Identify a Repeating Pattern","text":"

    Good skill candidates:

    • Checklists you repeat: deploy steps, release prep, code review
    • Decisions the agent gets wrong: if you keep correcting the same behavior, encode the correction
    • Multi-step workflows: anything with a sequence of commands and conditional branches
    • Domain knowledge: project-specific terminology, architecture constraints, or conventions the agent cannot infer from code alone

    Not good candidates: one-off instructions, things the platform already handles (file editing, git operations), or tasks too narrow to reuse.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-2-create-the-skill","level":3,"title":"Step 2: Create the Skill","text":"

    Invoke the skill-creator:

    You: \"I want a skill for our deploy process\"\n\nAgent: [Asks about the workflow: what steps, what tools,\n        what edge cases, what the output should look like]\n

    Or capture a workflow you just did:

    You: \"Turn what we just did into a skill\"\n\nAgent: [Extracts the steps from conversation history,\n        confirms understanding, drafts the skill]\n

    The skill-creator produces a SKILL.md file in .claude/skills/your-skill/.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-3-test-with-realistic-prompts","level":3,"title":"Step 3: Test with Realistic Prompts","text":"

    The skill-creator proposes 2-3 test prompts - the kind of thing a real user would say. It runs each one and shows the result alongside a baseline (same prompt without the skill) so you can compare.

    Agent: \"Here are test prompts I'd try:\n        1. 'Deploy to staging'\n        2. 'Ship the hotfix'\n        3. 'Run the release checklist'\n        Want to adjust these?\"\n
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-4-iterate-on-the-description","level":3,"title":"Step 4: Iterate on the Description","text":"

    The description field in frontmatter determines when a skill triggers. Claude tends to undertrigger - descriptions need to be specific and slightly \"pushy\":

    # Weak - too vague, will undertrigger\ndescription: \"Use for deployments\"\n\n# Strong - covers situations and synonyms\ndescription: >-\n  Use when deploying to staging or production, running the release\n  checklist, or when the user says 'ship it', 'deploy this', or\n  'push to prod'. Also use after merging to main when a deploy\n  is expected.\n

    The skill-creator helps you tune this iteratively.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-5-deploy-as-template-optional","level":3,"title":"Step 5: Deploy as Template (Optional)","text":"

    If the skill should be available to all projects (not just this one), place it in internal/assets/claude/skills/ so ctx init deploys it to new projects automatically.

    Most project-specific skills stay in .claude/skills/ and travel with the repo.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#skill-anatomy","level":2,"title":"Skill Anatomy","text":"
    my-skill/\n  SKILL.md         # Required: frontmatter + instructions (<500 lines)\n  scripts/         # Optional: deterministic code the skill can execute\n  references/      # Optional: detail loaded on demand (not always)\n  assets/          # Optional: output templates, not loaded into context\n

    Key sections in SKILL.md:

    Section Purpose Required? Frontmatter Name, description (trigger) Yes When to Use Positive triggers Yes When NOT to Use Prevents false activations Yes Process Steps and commands Yes Examples Good/bad output pairs Recommended Quality Checklist Verify before reporting completion For complex skills","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tips","level":2,"title":"Tips","text":"
    • Description is everything. A great skill with a vague description never fires. Spend time on trigger coverage - synonyms, concrete situations, edge cases.
    • Stay under 500 lines. If your skill is growing past this, move detail into references/ files and point to them from SKILL.md.
    • Do not duplicate the platform. If the agent already knows how to do something (edit files, run git commands), do not restate it. Tag paragraphs as Expert/Activation/Redundant and delete Redundant ones.
    • Explain why, not just what. \"Sort by date because users want recent results first\" beats \"ALWAYS sort by date.\" The agent generalizes from reasoning better than from rigid rules.
    • Test negative triggers. Make sure the skill does not fire on unrelated prompts. A skill that activates too broadly becomes noise.
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#next-up","level":2,"title":"Next Up","text":"

    Parallel Agent Development with Git Worktrees ->: Split work across multiple agents using git worktrees.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: full listing of all bundled and project-local skills
    • Guide Your Agent: how commands, skills, and conversational patterns work together
    • Design Before Coding: the four-skill chain for front-loading design work
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/claude-code-permissions/","level":1,"title":"Claude Code Permission Hygiene","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code's .claude/settings.local.json controls what the agent can do without asking. Over time, this file accumulates one-off permissions from individual sessions: Exact commands with hardcoded paths, duplicate entries, and stale skill references.

    A noisy \"allowlist\" makes it harder to spot dangerous permissions and increases the surface area for unintended behavior.

    Since settings.local.json is .gitignored, it drifts independently of your codebase. There is no PR review, no CI check: just whatever you clicked \"Allow\" on.

    This recipe shows what a well-maintained permission file looks like and how to keep it clean.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                            # seeds safe defaults\n/ctx-drift                          # detects missing/stale permissions\n/ctx-permission-sanitize               # audits for dangerous patterns\n

    See Recommended Defaults for the full list.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx init Populates default ctx permissions /ctx-drift Detects missing or stale permission entries /ctx-permission-sanitize Audits for dangerous patterns (security-focused)","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#recommended-defaults","level":2,"title":"Recommended Defaults","text":"

    After running ctx init, your settings.local.json will have the ctx defaults pre-populated. Here is an opinionated safe starting point for a Go project using ctx:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(/tmp/ctx-*:*)\",\n      \"Bash(CGO_ENABLED=0 go build:*)\",\n      \"Bash(CGO_ENABLED=0 go test:*)\",\n      \"Bash(ctx:*)\",\n      \"Bash(git add:*)\",\n      \"Bash(git branch:*)\",\n      \"Bash(git check-ignore:*)\",\n      \"Bash(git checkout:*)\",\n      \"Bash(git commit:*)\",\n      \"Bash(git diff:*)\",\n      \"Bash(git log:*)\",\n      \"Bash(git remote:*)\",\n      \"Bash(git restore:*)\",\n      \"Bash(git show:*)\",\n      \"Bash(git stash:*)\",\n      \"Bash(git status:*)\",\n      \"Bash(git tag:*)\",\n      \"Bash(go build:*)\",\n      \"Bash(go fmt:*)\",\n      \"Bash(go test:*)\",\n      \"Bash(go vet:*)\",\n      \"Bash(golangci-lint run:*)\",\n      \"Bash(grep:*)\",\n      \"Bash(ls:*)\",\n      \"Bash(make:*)\",\n      \"Skill(ctx-convention-add)\",\n      \"Skill(ctx-decision-add)\",\n      \"Skill(ctx-learning-add)\",\n      \"Skill(ctx-task-add)\",\n      \"Skill(ctx-agent)\",\n      \"Skill(ctx-archive)\",\n      \"Skill(ctx-blog)\",\n      \"Skill(ctx-blog-changelog)\",\n      \"Skill(absorb)\",\n      \"Skill(ctx-commit)\",\n      \"Skill(ctx-drift)\",\n      \"Skill(ctx-implement)\",\n      \"Skill(ctx-journal-enrich)\",\n      \"Skill(ctx-journal-enrich-all)\",\n      \"Skill(ctx-loop)\",\n      \"Skill(ctx-next)\",\n      \"Skill(ctx-pad)\",\n      \"Skill(ctx-prompt-audit)\",\n      \"Skill(ctx-history)\",\n      \"Skill(ctx-reflect)\",\n      \"Skill(ctx-remember)\",\n      \"Skill(ctx-status)\",\n      \"Skill(ctx-worktree)\",\n      \"WebSearch\"\n    ],\n    \"deny\": [\n      \"Bash(sudo *)\",\n      \"Bash(git push *)\",\n      \"Bash(git push)\",\n      \"Bash(rm -rf /*)\",\n      \"Bash(rm -rf ~*)\",\n      \"Bash(curl *)\",\n      \"Bash(wget *)\",\n      \"Bash(chmod 777 *)\",\n      \"Read(**/.env)\",\n      \"Read(**/.env.*)\",\n      \"Read(**/*credentials*)\",\n      \"Read(**/*secret*)\",\n      \"Read(**/*.pem)\",\n      \"Read(**/*.key)\",\n      \"Edit(**/.env)\",\n      \"Edit(**/.env.*)\"\n    ]\n  }\n}\n

    This Is a Starting Point, Not a Mandate

    Your project may need more or fewer entries.

    The goal is intentional permissions: Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#design-principles","level":3,"title":"Design Principles","text":"

    Use wildcards for trusted binaries: If you trust the binary (your own project's CLI, make, go), a single wildcard like Bash(ctx:*) beats twenty subcommand entries. It reduces noise and means new subcommands work without re-prompting.

    Keep git commands granular: Unlike ctx or make, git has both safe commands (git log, git status) and destructive ones (git reset --hard, git clean -f). Listing safe commands individually prevents accidentally pre-approving dangerous ones.

    Pre-approve all ctx- skills: Skills shipped with ctx (Skill(ctx-*)) are safe to pre-approve. They are part of your project and you control their content. This prevents the agent from prompting on every skill invocation.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#default-deny-rules","level":3,"title":"Default Deny Rules","text":"

    ctx init automatically populates permissions.deny with rules that block dangerous operations. Deny rules are evaluated before allow rules: A denied pattern always prompts the user, even if it also matches an allow entry.

    The defaults block:

    Pattern Why Bash(sudo *) Cannot enter password; will hang Bash(git push *) Must be explicit user action Bash(rm -rf /*) etc. Recursive delete of system/home directories Bash(curl *) / wget Arbitrary network requests Bash(chmod 777 *) World-writable permissions Read/Edit(**/.env*) Secrets and credentials Read(**/*.pem, *.key) Private keys

    Read/Edit Deny Rules

    Read() and Edit() deny rules have known upstream enforcement issues (claude-code#6631,#24846).

    They are included as defense-in-depth and intent documentation.

    Blocked by default deny rules: no action needed, ctx init handles these:

    Pattern Risk Bash(git push:*) Must be explicit user action Bash(sudo:*) Privilege escalation Bash(rm -rf:*) Recursive delete with no confirmation Bash(curl:*) / Bash(wget:*) Arbitrary network requests

    Requires manual discipline: Never add these to allow:

    Pattern Risk Bash(git reset:*) Can discard uncommitted work Bash(git clean:*) Deletes untracked files Skill(ctx-permission-sanitize) Edits this file: self-modification vector Skill(release) Runs the release pipeline: high impact","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#hooks-regex-safety-net","level":2,"title":"Hooks: Regex Safety Net","text":"

    Deny rules handle prefix-based blocking natively. Hooks complement them by catching patterns that require regex matching: Things deny rules can't express.

    The ctx plugin ships these blocking hooks:

    Hook What it blocks ctx system block-non-path-ctx Running ctx from wrong path

    Project-local hooks (not part of the plugin) catch regex edge cases:

    Hook What it blocks block-dangerous-commands.sh Mid-command sudo/git push (after &&), copies to bin dirs, absolute-path ctx

    Pre-Approved + Hook-Blocked = Silent Block

    If you pre-approve a command that a hook blocks, the user never sees the confirmation dialog. The agent gets a block response and must handle it, which is confusing.

    It's better not to pre-approve commands that hooks are designed to intercept.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-maintenance-workflow","level":2,"title":"The Maintenance Workflow","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#after-busy-sessions","level":3,"title":"After Busy Sessions","text":"

    Permissions accumulate fastest during debugging and exploration sessions. After a session where you clicked \"Allow\" many times:

    1. Open .claude/settings.local.json in your editor;
    2. Look for entries at the bottom of the allowlist (new entries append there);
    3. Delete anything that looks session-specific:
      • Exact commands with hardcoded paths,
      • Commands with literal string arguments,
      • Entries that duplicate an existing wildcard.

    See the Sanitize Permissions runbook for a step-by-step procedure.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#periodically","level":3,"title":"Periodically","text":"

    Run /ctx-drift to catch permission drift:

    • Missing Bash(ctx:*) wildcard;
    • Missing Skill(ctx-*) entries for installed skills;
    • Stale Skill(ctx-*) entries for removed skills;
    • Granular Bash(ctx <subcommand>:*) entries that should be consolidated.

    Run /ctx-permission-sanitize to catch security issues:

    • Hook bypass patterns
    • Destructive commands
    • Overly broad permissions
    • Injection vectors
    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#when-adding-new-skills","level":3,"title":"When Adding New Skills","text":"

    If you create a custom ctx-* skill, add its Skill() entry to the allowlist manually.

    ctx init only populates the default permissions: It won't pick up custom skills.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#golden-image-snapshots","level":3,"title":"Golden Image Snapshots","text":"

    If manual cleanup is too tedious, use a golden image to automate it:

    Snapshot a curated permission set, then restore at session start to automatically drop session-accumulated permissions. See the Permission Snapshots recipe for the full workflow.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#adapting-for-other-languages","level":2,"title":"Adapting for Other Languages","text":"

    The recommended defaults above are Go-specific. For other stacks, swap the build/test tooling:

    Node.js / TypeScript:

    \"Bash(npm run:*)\",\n\"Bash(npm test:*)\",\n\"Bash(npx:*)\",\n\"Bash(node:*)\"\n

    Python:

    \"Bash(pytest:*)\",\n\"Bash(python:*)\",\n\"Bash(pip show:*)\",\n\"Bash(ruff:*)\"\n

    Rust:

    \"Bash(cargo build:*)\",\n\"Bash(cargo test:*)\",\n\"Bash(cargo clippy:*)\",\n\"Bash(cargo fmt:*)\"\n

    The ctx, git, and skill entries remain the same across all stacks.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#next-up","level":2,"title":"Next Up","text":"

    Permission Snapshots →: Save and restore permission baselines for reproducible setups.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#see-also","level":2,"title":"See Also","text":"
    • Setting Up ctx Across AI Tools: full setup recipe including settings.local.json creation
    • Context Health: keeping .context/ files accurate
    • Sanitize Permissions runbook: manual cleanup procedure
    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/configuration-profiles/","level":1,"title":"Configuration Profiles","text":"","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#configuration-profiles","level":1,"title":"Configuration Profiles","text":"

    Switch between dev and base runtime configurations without editing .ctxrc by hand. Useful when you want verbose logging and webhook notifications during development, then clean defaults for normal sessions.

    Uses: ctx config switch, ctx config status, /ctx-config

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#how-it-works","level":2,"title":"How It Works","text":"

    The ctx repo ships two source profiles committed to git:

    File Profile Description .ctxrc.base base All defaults, notifications off .ctxrc.dev dev Verbose logging, webhook notifications on

    The working copy (.ctxrc) is gitignored. Switching profiles copies the source file over .ctxrc, so your runtime configuration is always a clean snapshot of one of the two sources.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#switching-profiles","level":2,"title":"Switching Profiles","text":"
    # Switch to dev (verbose logging, notifications)\nctx config switch dev\n\n# Switch to base (defaults)\nctx config switch base\n\n# Toggle to the opposite profile\nctx config switch\n\n# \"prod\" is an alias for \"base\"\nctx config switch prod\n

    The detection heuristic checks for an uncommented notify: line in .ctxrc: present means dev, absent means base.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#checking-the-active-profile","level":2,"title":"Checking the Active Profile","text":"
    ctx config status\n

    Output examples:

    active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n
    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#typical-workflow","level":2,"title":"Typical Workflow","text":"
    1. Start of a debugging session: switch to dev for verbose logging and webhook notifications so you can trace hook activity and get push alerts.
    ctx config switch dev\n
    1. Work through the issue: hooks log verbosely, webhooks fire on key events (commits, ceremony nudges, drift warnings).

    2. Done debugging: switch back to base to silence the noise.

    ctx config switch base\n
    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#customizing-profiles","level":2,"title":"Customizing Profiles","text":"

    Edit the source files directly:

    • .ctxrc.dev: add any .ctxrc keys you want active during development (e.g., log_level: debug, notify.events, notify.webhook_url).
    • .ctxrc.base: keep this minimal. It represents your \"production\" defaults.

    After editing a source file, re-run ctx config switch <profile> to apply the changes to the working copy.

    Commit Your Profiles

    Both .ctxrc.base and .ctxrc.dev should be committed to git so team members share the same profile definitions. The working copy .ctxrc stays gitignored.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#using-the-skill","level":2,"title":"Using the Skill","text":"

    In a Claude Code session, say any of:

    • \"switch to dev mode\"
    • \"switch to base\"
    • \"what profile am I on?\"
    • \"toggle verbose logging\"

    The /ctx-config skill handles the rest.

    See also: ctx config reference, Configuration

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/context-health/","level":1,"title":"Detecting and Fixing Drift","text":"","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-problem","level":2,"title":"The Problem","text":"

    ctx files drift: you rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist, TASKS.md is 80 percent completed checkboxes, and CONVENTIONS.md describes patterns you stopped using two months ago.

    Stale context is worse than no context:

    An AI tool that trusts outdated references will hallucinate confidently.

    This recipe shows how to detect drift, fix it, and keep your .context/ directory lean and accurate.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tldr","level":2,"title":"TL;DR","text":"
    ctx drift                      # detect problems\nctx drift --fix                # auto-fix the easy ones\nctx sync --dry-run && ctx sync # reconcile after refactors\nctx compact --archive          # archive old completed tasks\nctx fmt                        # normalize line widths\nctx status                     # verify\n

    Or just ask your agent: \"Is our context clean?\"

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, every command above fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx drift Command Detect stale paths, missing files, violations ctx drift --fix Command Auto-fix simple issues ctx sync Command Reconcile context with codebase structure ctx compact Command Archive completed tasks, clean up empty sections ctx fmt Command Normalize context files to 80-char line width ctx status Command Quick health overview /ctx-drift Skill Structural plus semantic drift detection /ctx-architecture Skill Refresh ARCHITECTURE.md from actual codebase /ctx-status Skill In-session context summary /ctx-prompt-audit Skill Audit prompt quality and token efficiency","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-workflow","level":2,"title":"The Workflow","text":"

    The best way to maintain context health is conversational: Ask your agent, guide it, and let it detect problems, explain them, and fix them with your approval. CLI commands exist for CI pipelines, scripting, and fine-grained control.

    For day-to-day maintenance, talk to your agent.

    Your Questions Reinforce the Pattern

    Asking \"is our context clean?\" does two things:

    • It triggers a drift check right now
    • It reinforces the habit

    This is reinforcement, not enforcement.

    Do not wait for the agent to be proactive on its own:

    Guide your agent, especially in early sessions.

    Over time, you will ask less and the agent will start offering more.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-1-ask-your-agent","level":3,"title":"Step 1: Ask Your Agent","text":"

    The simplest way to check context health:

    Is our context clean?\nAnything stale?\nHow healthy are our context files?\n

    Or invoke the skill directly:

    /ctx-drift\n

    The agent performs two layers of analysis:

    Layer 1, structural checks (via ctx drift): Dead paths, missing files, completed task counts, constitution violations. Fast and programmatic.

    Layer 2, semantic analysis (agent-driven): Does CONVENTIONS.md describe patterns the code no longer follows? Does DECISIONS.md contain entries whose rationale no longer applies? Are there learnings about bugs that are now fixed? This is where the agent adds value the CLI cannot: It reads both context files and source code and compares them.

    The agent reports both layers together, explains each finding in plain language, and offers to fix what it can.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-2-maintenance-at-session-start","level":3,"title":"Step 2: Maintenance at Session Start","text":"

    You do not need to ask explicitly.

    Using Claude Code

    ctx ships with Claude Code hooks that remind the agent at the right time to take initiative.

    Checking context health at the session start, offering to persist learnings before you quit, and flagging drift when it matters. The agent stays proactive without you having to prompt it:

    Agent: Good morning. I've loaded the context files. A few things\n       before we start:\n\n       - ARCHITECTURE.md references `pkg/auth/` which is now empty\n       - DECISIONS.md hasn't been updated in 40 days\n       - There are 18 completed tasks ready for archival\n\n       Want me to run a quick maintenance pass, or should we jump\n       straight into today's work?\n

    ☝️️ this is what persistent, initiative-driven sessions feel like when context is treated as a system instead of a prompt.

    If the agent does not offer this on its own, a gentle nudge is enough:

    Anything stale before we start?\nHow's the context looking?\n

    This turns maintenance from a scheduled chore into a conversation that happens when it matters.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-3-real-time-detection-during-work","level":3,"title":"Step 3: Real-Time Detection during Work","text":"

    Agents can notice drift while working: When a mismatch is directly in the path of their current task. If an agent reads ARCHITECTURE.md to find where to add a handler and internal/handlers/ doesn't exist, it will notice because the stale reference blocks its work:

    Agent: ARCHITECTURE.md references `internal/handlers/` but that directory\n       doesn't exist. I'll look at the actual source tree to find where\n       handlers live now.\n

    This happens reliably when the drift intersects the task. What is less reliable is the agent generalizing from one mismatch to \"there might be more stale references; let me run drift detection\" That leap requires the agent to know /ctx-drift exists and to decide the current task should pause for maintenance.

    If you want that behavior, reinforce it:

    Good catch. Yes, run /ctx-drift and clean up any other stale references.\n

    Over time, agents that have seen this pattern will start offering proactively. But do not expect it from a cold start.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-4-archival-and-cleanup","level":3,"title":"Step 4: Archival and Cleanup","text":"

    ctx drift detects when TASKS.md has more than 10 completed items and flags it as a staleness warning. Running ctx drift --fix archives completed tasks automatically.

    You can also run /ctx-archive to compact on demand.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#knowledge-health-flow","level":3,"title":"Knowledge Health Flow","text":"

    Over time, LEARNINGS.md and DECISIONS.md accumulate entries that overlap or partially repeat each other. The check-persistence hook detects when entry counts exceed a configurable threshold and surfaces a nudge:

    \"LEARNINGS.md has 25+ entries. Consider running /ctx-consolidate to merge overlapping items.\"

    The consolidation workflow:

    1. Review: /ctx-consolidate groups entries by keyword similarity and presents candidate merges for your approval.
    2. Merge: Approved groups are combined into single entries that preserve the key information from each original.
    3. Archive: Originals move to .context/archive/, not deleted -- the full history is preserved in git and the archive directory.
    4. Verify: Run ctx drift after consolidation to confirm no cross-references were broken by the merge.

    This replaces ad-hoc cleanup with a repeatable, nudge-driven cycle: detect accumulation, review candidates, merge with approval, archive originals.

    See also: Knowledge Capture for the recording workflow that feeds into this maintenance cycle.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-doctor-the-superset-check","level":2,"title":"ctx doctor: The Superset Check","text":"

    ctx doctor combines drift detection with hook auditing, configuration checks, event logging status, and token size reporting in a single command. If you want one command that covers structural health, hooks, and state:

    ctx doctor          # everything in one pass\nctx doctor --json   # machine-readable for scripting\n

    Use /ctx-doctor Too

    For agent-driven diagnosis that adds semantic analysis on top of the structural checks, use /ctx-doctor.

    See the Troubleshooting recipe for the full workflow.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#cli-reference","level":2,"title":"CLI Reference","text":"

    The conversational approach above uses CLI commands under the hood. When you need direct control, use the commands directly.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift","level":3,"title":"ctx drift","text":"

    Scan context files for structural problems:

    ctx drift\n

    Sample output:

    Drift Report\n============\n\nWarnings (3):\n  ARCHITECTURE.md:14  path \"internal/api/router.go\" does not exist\n  ARCHITECTURE.md:28  path \"pkg/auth/\" directory is empty\n  CONVENTIONS.md:9    path \"internal/handlers/\" not found\n\nViolations (1):\n  TASKS.md            31 completed tasks (recommend archival)\n\nStaleness:\n  DECISIONS.md        last modified 45 days ago\n  LEARNINGS.md        last modified 32 days ago\n\nExit code: 1 (warnings found)\n
    Level Meaning Action Warning Stale path references, missing files Fix or remove Violation Constitution rule heuristic failures, heavy clutter Fix soon Staleness Files not updated recently Review content

    Exit codes: 0 equals clean, 1 equals warnings, 3 equals violations.

    For CI integration:

    ctx drift --json | jq '.warnings | length'\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift-fix","level":3,"title":"ctx drift --fix","text":"

    Auto-fix mechanical issues:

    ctx drift --fix\n

    This handles removing dead path references, updating unambiguous renames, clearing empty sections. Issues requiring judgment are flagged but left for you.

    Run ctx drift again afterward to confirm what remains.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-sync","level":3,"title":"ctx sync","text":"

    After a refactor, reconcile context with the actual codebase structure:

    ctx sync --dry-run   # preview first\nctx sync             # apply\n

    ctx sync scans for structural changes, compares with ARCHITECTURE.md, checks for new dependencies worth documenting, and identifies context referring to code that no longer exists.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate completed tasks and clean up empty sections:

    ctx compact            # move completed tasks to Completed section,\n                       # remove empty sections\nctx compact --archive  # also archive old tasks to .context/archive/\n
    • Tasks: moves completed items (with all subtasks done) into the Completed section of TASKS.md
    • All files: removes empty sections left behind
    • With --archive: writes tasks older than 7 days to .context/archive/tasks-YYYY-MM-DD.md

    Without --archive, nothing is deleted: Tasks are reorganized in place.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-fmt","level":3,"title":"ctx fmt","text":"

    Normalize context file line widths:

    ctx fmt              # wrap long lines to 80 chars\nctx fmt --check      # CI: exit 1 if files need formatting\n

    Long task descriptions, decision rationale, and learning entries accumulate as single-line entries. ctx fmt wraps them at word boundaries with 2-space continuation indent for list items. Headings, tables, and comments are preserved.

    Idempotent: safe to run repeatedly.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-status","level":3,"title":"ctx status","text":"

    Quick health overview:

    ctx status --verbose\n

    Shows file counts, token estimates, modification times, and drift warnings in a single glance.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-prompt-audit","level":3,"title":"/ctx-prompt-audit","text":"

    Checks whether your context files are readable, compact, and token-efficient for the model.

    /ctx-prompt-audit\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Conversational approach (recommended):

    Is our context clean?  -> agent runs structural plus semantic checks\nFix what you can       -> agent auto-fixes and proposes edits\nArchive the done tasks -> agent runs ctx compact --archive\nHow's token usage?     -> agent checks ctx status\n

    CLI approach (for CI, scripts, or direct control):

    ctx drift                      # 1. Detect problems\nctx drift --fix                # 2. Auto-fix the easy ones\nctx sync --dry-run && ctx sync # 3. Reconcile after refactors\nctx compact --archive          # 4. Archive old completed tasks\nctx fmt                        # 5. Normalize line widths\nctx status                     # 6. Verify\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tips","level":2,"title":"Tips","text":"

    Agents cross-reference context files with source code during normal work. When drift intersects their current task, they will notice: a renamed package, a deleted directory, a path that doesn't resolve. But they rarely generalize from one mismatch to a full audit on their own. Reinforce the pattern: when an agent mentions a stale reference, ask it to run /ctx-drift. Over time, it starts offering.

    When an agent says \"this reference looks stale,\" it is usually right.

    Semantic drift is more damaging than structural drift: ctx drift catches dead paths. But CONVENTIONS.md describing a pattern your code stopped following three weeks ago is worse. When you ask \"is our context clean?\", the agent can do both checks.

    Use ctx status as a quick check: It shows file counts, token estimates, and drift warnings in a single glance. Good for a fast \"is everything ok?\" before diving into work.

    Drift detection in CI: add ctx drift --json to your CI pipeline and fail on exit code 3 (violations). This catches constitution-level problems before they reach upstream.

    Do not over-compact: Completed tasks have historical value. The --archive flag preserves them in .context/archive/ so you can search past work without cluttering active context.

    Sync is cautious by default: Use --dry-run after large refactors, then apply.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#next-up","level":2,"title":"Next Up","text":"

    Claude Code Permission Hygiene →: Recommended permission defaults and maintenance workflow for Claude Code.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#see-also","level":2,"title":"See Also","text":"
    • Troubleshooting: full diagnostic workflow using ctx doctor, event logs, and /ctx-doctor
    • Tracking Work Across Sessions: task lifecycle and archival
    • Persisting Decisions, Learnings, and Conventions: keeping knowledge files current
    • The Complete Session: where maintenance fits in the daily workflow
    • CLI Reference: full flag documentation for all commands
    • Context Files: structure and purpose of each .context/ file
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/customizing-hook-messages/","level":1,"title":"Customizing Hook Messages","text":"","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-problem","level":2,"title":"The Problem","text":"

    ctx hooks speak ctx's language, not your project's. The QA gate says \"lint the ENTIRE project\" and \"make build,\" but your Python project uses pytest and ruff. The post-commit nudge suggests running lints, but your project uses npm test. You could remove the hook entirely, but then you lose the logic (counting, state tracking, adaptive frequency) just to change the words.

    How do you customize what hooks say without removing what they do?

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tldr","level":2,"title":"TL;DR","text":"
    ctx hook message list                     # see all hooks and their messages\nctx hook message show qa-reminder gate    # view the current template\nctx hook message edit qa-reminder gate    # copy default to .context/ for editing\nctx hook message reset qa-reminder gate   # revert to embedded default\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root: hook message overrides live in your .context/ directory, so ctx needs to know which one. If you skip the eval, ctx hook message ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose ctx hook message list CLI command Show all hook messages with category and override status ctx hook message show CLI command Print the effective message template ctx hook message edit CLI command Copy embedded default to .context/ for editing ctx hook message reset CLI command Delete user override, revert to default","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#how-it-works","level":2,"title":"How It Works","text":"

    Hook messages use a 3-tier fallback:

    1. User override: .context/hooks/messages/{hook}/{variant}.txt
    2. Embedded default: compiled into the ctx binary
    3. Hardcoded fallback: belt-and-suspenders safety net

    The hook logic (when to fire, counting, state tracking, cooldowns) is unchanged. Only the content (what text gets emitted) comes from the template. You customize what the hook says without touching how it decides to speak.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#finding-the-original-templates","level":3,"title":"Finding the Original Templates","text":"

    The default templates live in the ctx source tree at:

    internal/assets/hooks/messages/{hook}/{variant}.txt\n

    You can also browse them on GitHub: internal/assets/hooks/messages/

    Or use ctx hook message show to print any template without digging through source code:

    ctx hook message show qa-reminder gate        # QA gate instructions\nctx hook message show check-persistence nudge  # persistence nudge\nctx hook message show post-commit nudge        # post-commit reminder\n

    The show output includes the template source and available variables -- everything you need to write a replacement.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables","level":3,"title":"Template Variables","text":"

    Some messages use Go text/template variables for dynamic content:

    No context files updated in {{.PromptsSinceNudge}}+ prompts.\nHave you discovered learnings, made decisions,\nestablished conventions, or completed tasks\nworth persisting?\n

    The show and edit commands list available variables for each message. When writing a replacement, keep the same {{.VariableName}} placeholders to preserve dynamic content. Variables that you omit render as <no value>: no error, but the output may look odd.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#intentional-silence","level":3,"title":"Intentional Silence","text":"

    An empty template file (0 bytes or whitespace-only) means \"don't emit a message\". The hook still runs its logic but produces no output. This lets you silence specific messages without removing the hook from hooks.json.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-python-project-qa-gate","level":2,"title":"Example: Python Project QA Gate","text":"

    The default QA gate says \"lint the ENTIRE project\" and references make lint. For a Python project, you want pytest and ruff:

    # See the current default\nctx hook message show qa-reminder gate\n\n# Copy it to .context/ for editing\nctx hook message edit qa-reminder gate\n\n# Edit the override\n

    Replace the content in .context/hooks/messages/qa-reminder/gate.txt:

    HARD GATE! DO NOT COMMIT without completing ALL of these steps first:\n(1) Run the full test suite: pytest -x\n(2) Run the linter: ruff check .\n(3) Verify a clean working tree\nRun tests and linter BEFORE every git commit, no exceptions.\n

    The hook still fires on every Edit call. The logic is identical. Only the instructions changed.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-silencing-ceremony-nudges","level":2,"title":"Example: Silencing Ceremony Nudges","text":"

    The ceremony check nudges you to use /ctx-remember and /ctx-wrap-up. If your team has a different workflow and finds these noisy:

    ctx hook message edit check-ceremonies both\nctx hook message edit check-ceremonies remember\nctx hook message edit check-ceremonies wrapup\n

    Then empty each file:

    echo -n \"\" > .context/hooks/messages/check-ceremonies/both.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/remember.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/wrapup.txt\n

    The hooks still track ceremony usage internally, but they no longer emit any visible output.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-javascript-project-post-commit","level":2,"title":"Example: JavaScript Project Post-Commit","text":"

    The default post-commit nudge mentions generic \"lints and tests.\" For a JavaScript project:

    ctx hook message edit post-commit nudge\n

    Replace with:

    Commit succeeded. 1. Offer context capture to the user: Decision (design\nchoice?), Learning (gotcha?), or Neither. 2. Ask the user: \"Want me to\nrun npm test and eslint before you push?\" Do NOT push. The user pushes\nmanually.\n
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-two-categories","level":2,"title":"The Two Categories","text":"

    Not all messages are equal. The list command shows each message's category:

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#customizable-17-messages","level":3,"title":"Customizable (17 Messages)","text":"

    Messages that are opinions: project-specific wording that benefits from customization. These are the primary targets for override.

    Hook Variant Description check-freshness stale Technology constant freshness warning check-ceremonies both Both ceremonies missing check-ceremonies remember Start-of-session ceremony check-ceremonies wrapup End-of-session ceremony check-context-size checkpoint Context capacity warning check-context-size oversize Injection oversize nudge check-context-size window Context window usage warning (>80%) check-journal both Unimported sessions + unenriched entries check-journal unenriched Unenriched journal entries check-journal unimported Unimported sessions check-knowledge warning Knowledge file growth check-map-staleness stale Architecture map staleness check-persistence nudge Context persistence nudge post-commit nudge Post-commit context capture qa-reminder gate Pre-commit QA gate","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#ctx-specific-10-messages","level":3,"title":"ctx-Specific (10 Messages)","text":"

    Messages specific to ctx's own development workflow. You can customize them, but edit will warn you first.

    Hook Variant Description block-dangerous-commands cp-to-bin Block copy to bin dirs block-dangerous-commands install-to-local-bin Block copy to ~/.local/bin block-dangerous-commands mid-git-push Block git push block-dangerous-commands mid-sudo Block sudo block-non-path-ctx absolute-path Block absolute path invocation block-non-path-ctx dot-slash Block ./ctx invocation block-non-path-ctx go-run Block go run invocation check-reminders reminders Pending reminders relay check-resources alert Resource pressure alert check-version key-rotation Key rotation nudge check-version mismatch Version mismatch","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables-reference","level":2,"title":"Template Variables Reference","text":"Hook Variant Variables check-freshness stale {{.StaleFiles}} check-context-size checkpoint (none) check-context-size oversize {{.TokenCount}} check-context-size window {{.TokenCount}}, {{.Percentage}} check-ceremonies both, remember, wrapup (none) check-journal both {{.UnimportedCount}}, {{.UnenrichedCount}} check-journal unenriched {{.UnenrichedCount}} check-journal unimported {{.UnimportedCount}} check-knowledge warning {{.FileWarnings}} check-map-staleness stale {{.LastRefreshDate}}, {{.ModuleCount}} check-persistence nudge {{.PromptsSinceNudge}} check-reminders reminders {{.ReminderList}} check-resources alert {{.AlertMessages}} check-version key-rotation {{.KeyAgeDays}} check-version mismatch {{.BinaryVersion}}, {{.PluginVersion}} post-commit nudge (none) qa-reminder gate (none) block-dangerous-commands all variants (none) block-non-path-ctx all variants (none)

    Templates that reference undefined variables render <no value>: no error, graceful degradation.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tips","level":2,"title":"Tips","text":"
    • Override files are version-controlled: they live in .context/ alongside your other context files. Team members get the same customized messages.
    • Start with show: always check the current default before editing. The embedded template is the baseline your override replaces.
    • Use reset to undo: if a customization causes confusion, reset reverts to the embedded default instantly.
    • Empty file = silence: you don't need to delete the hook. An empty override file silences the message while preserving the hook's logic.
    • JSON output for scripting: ctx hook message list --json returns structured data for automation.
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#see-also","level":2,"title":"See Also","text":"
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Auditing System Hooks: verifying hooks are running and auditing their output
    • Configuration: project-level settings via .ctxrc
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/design-before-coding/","level":1,"title":"Design Before Coding","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-problem","level":2,"title":"The Problem","text":"

    You start coding a feature. Halfway through, you realize the approach doesn't handle a key edge case. You refactor. Then you discover the CLI interface doesn't fit the existing patterns. More refactoring.

    The design work happened during implementation, mixed in with debugging and trial-and-error. The result works, but the spec was never written down, the trade-offs were never recorded, and the next session has no idea why things are shaped this way.

    How do you front-load design so the implementation is straightforward?

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-brainstorm          # explore the design space\n/ctx-spec                # write the spec document\n/ctx-task-add            # break it into tasks\n/ctx-implement           # execute step-by-step\n

    Four skills, used in sequence. Each produces an artifact that feeds the next.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-brainstorm Skill Structured design dialogue: explore approaches /ctx-spec Skill Scaffold and fill out a spec from the template /ctx-task-add Skill Add implementation tasks to TASKS.md /ctx-implement Skill Execute a plan step-by-step with verification /ctx-decision-add Skill Record design choices made during brainstorming","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-1-brainstorm-the-design","level":3,"title":"Step 1: Brainstorm the Design","text":"

    Start with /ctx-brainstorm when you have a rough idea but haven't committed to an approach. The skill follows a gated process:

    1. Understand context: what exists today, what's the gap
    2. Clarify the idea: one question at a time, not a wall of questions
    3. Surface non-functional requirements: performance, security, compatibility
    4. Lock understanding: confirm before exploring solutions
    5. Explore 2-3 approaches: with trade-offs for each
    6. Stress-test: poke holes in the chosen approach
    7. Present the design: detailed enough to implement from
    You: \"I want to add RSS feed generation to the blog.\"\n\nAgent: \"Before we design, let me understand: is this for the journal\n  site, the docs site, or both?\"\n\nYou: \"Journal site only.\"\n\nAgent: [asks about update frequency, content scope, existing infra...]\n       [presents 3 approaches with trade-offs]\n\nYou: \"Option 2 - generate at build time.\"\n\nAgent: \"Let me stress-test that. What happens when...\"\n       [probes edge cases]\n       \"Design looks solid. Want me to record the approach as a\n       decision and write a spec?\"\n

    The brainstorm produces a validated design in conversation. It doesn't produce a file - that's what the spec is for.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-2-write-the-spec","level":3,"title":"Step 2: Write the Spec","text":"

    Once the design is clear, use /ctx-spec to produce the written artifact. The skill reads the project's spec template (specs/tpl/spec-template.md) and walks through each section:

    You: /ctx-spec (rss feed generation)\n\nAgent: \"Creating specs/rss-feed.md from template.\"\n\n       \"Problem: What user-visible problem does this solve? Why now?\"\n\nYou: \"Users can't subscribe to blog updates. They have to check\n      the site manually.\"\n\nAgent: [writes the Problem section, moves to Approach]\n\n       \"Approach: High-level, how does this work?\"\n\nYou: \"Generate feed.xml during ctx journal site. Atom format.\"\n\nAgent: [writes Approach, moves to Happy Path, then Edge Cases...]\n

    The skill spends extra time on Edge Cases and Error Handling: These are where specs earn their value. The skill pushes for at least three edge cases and won't accept \"none\" without a challenge.

    Sections that don't apply can be skipped. The result is a complete spec at specs/{feature-name}.md.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-3-break-into-tasks","level":3,"title":"Step 3: Break into Tasks","text":"

    After the spec is written, the skill offers to create tasks:

    Agent: \"Spec written to specs/rss-feed.md. Want me to break this\n  into tasks in TASKS.md?\"\n\nYou: \"Yes.\"\n\nAgent: [creates a Phase section referencing the spec, adds tasks]\n       \"Added Phase 0.8: RSS/Atom Feed Generation with 4 tasks:\n         - [ ] Implement feed.xml generation in ctx journal site\n         - [ ] Add --feed flag and .ctxrc feed config\n         - [ ] Write tests for feed generation\n         - [ ] Add recipe and update docs\"\n

    Each task is independently completable and references the spec via Spec: specs/rss-feed.md in the Phase header.

    You can also invoke /ctx-task-add directly at any point to add individual tasks.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-4-implement-step-by-step","level":3,"title":"Step 4: Implement Step-by-Step","text":"

    Pick up tasks with /ctx-implement. The skill loads the spec, breaks work into atomic steps, and checkpoints after every 3-5 steps with a build and test verification:

    You: /ctx-implement (specs/rss-feed.md)\n\nAgent: \"Loaded spec. Breaking into steps:\n         1. Add FeedEntry type to journal package\n         2. Implement feed.xml template\n         3. Wire into site generation pipeline\n         [builds and tests after step 3]\n         4. Add --feed flag to ctx journal site\n         5. Add .ctxrc feed configuration\n         [builds and tests after step 5]\n         ...\"\n

    If a build or test fails, the agent stops, diagnoses, and fixes before continuing.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#when-to-skip-steps","level":2,"title":"When to Skip Steps","text":"

    Not every feature needs all four steps. Use your judgment:

    Situation Start at Vague idea, multiple valid approaches Step 1: Brainstorm Clear approach, need to document it Step 2: Spec Spec already exists, need to plan work Step 3: Tasks Tasks exist, ready to code Step 4: Implement

    A brainstorm without a spec is fine for small decisions. A spec without a brainstorm is fine when the design is obvious. The full chain is for features complex enough to warrant front-loaded design.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need skill names. Natural language works:

    You say What happens \"Let's think through this feature\" /ctx-brainstorm \"Spec this out\" /ctx-spec \"Write a design doc for...\" /ctx-spec \"Break this into tasks\" /ctx-task-add \"Implement the spec\" /ctx-implement \"Let's design before we build\" Starts at brainstorm","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tips","level":2,"title":"Tips","text":"
    • Brainstorm first when uncertain. If you can articulate the approach in two sentences, skip to spec. If you can't, brainstorm.
    • Specs prevent scope creep. The Non-Goals section is as important as the approach. Writing down what you won't do keeps implementation focused.
    • Edge cases are the point. A spec that only describes the happy path isn't a spec - it's a wish. The /ctx-spec skill pushes for at least 3 edge cases because that's where designs break.
    • Record decisions during brainstorming. When you choose between approaches, the agent offers to persist the trade-off via /ctx-decision-add. Accept - future sessions need to know why, not just what.
    • Specs are living documents. Update them when implementation reveals new constraints. A spec that diverges from reality is worse than no spec.
    • The spec template is customizable. Edit specs/tpl/spec-template.md to match your project's needs. The /ctx-spec skill reads whatever template it finds there.
    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: /ctx-brainstorm: structured design dialogue
    • Skills Reference: /ctx-spec: spec scaffolding from template
    • Skills Reference: /ctx-implement: step-by-step execution with verification
    • Tracking Work Across Sessions: task lifecycle and archival
    • Importing Claude Code Plans: turning ephemeral plans into permanent specs
    • Persisting Decisions, Learnings, and Conventions: capturing design trade-offs
    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/external-context/","level":1,"title":"Keeping Context in a Separate Repo","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-problem","level":2,"title":"The Problem","text":"

    ctx files contain project-specific decisions, learnings, conventions, and tasks. By default, they live in .context/ inside the project tree, and that works well when the context can be public.

    But sometimes you need the context outside the project:

    • Open-source projects with private context: Your architectural notes, internal task lists, and scratchpad entries shouldn't ship with the public repo.
    • Compliance or IP concerns: Context files reference sensitive design rationale that belongs in a separate access-controlled repository.
    • Personal preference: You want to keep notes separate from code.

    ctx supports this by letting you point CTX_DIR anywhere. This recipe shows how to set that up and how to tell your AI assistant where to find the context.

    One .context/ per project

    The parent of the context directory is the project root by contract. ctx sync, ctx drift, and the memory-drift hook all read the codebase at filepath.Dir(ContextDir()). Pointing two projects at the same directory corrupts their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use ctx hub, not a shared .context/.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tldr","level":2,"title":"TL;DR","text":"

    Create the external context directory, initialize it, and bind it:

    mkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\ncd ~/repos/myproject\n\n# Bind CTX_DIR to the external location, then init creates files there.\nexport CTX_DIR=~/repos/myproject-context/.context\nctx init\n

    All ctx commands now use the external directory. If you share the setup across shells, add the export CTX_DIR=... line to your shell rc, or source a per-project .envrc with direnv.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#what-works-what-quietly-degrades","level":2,"title":"What Works, What Quietly Degrades","text":"

    The single-source-anchor contract states that filepath.Dir(CTX_DIR) is the project root. When the context lives outside the project tree, ctx still resolves correctly for every operation that reads or writes inside .context/. But any operation that scans the codebase scans the wrong tree, and does so silently:

    Operation Behavior with external .context/ ctx status, agent, add ✅ Works. Operates on files inside CTX_DIR. Journal, scratchpad, hub ✅ Works. Same reason. ctx sync ⚠️ Scans the context repo, not the code repo. ctx drift ⚠️ Same. Reports nothing useful. Memory-drift hook (MEMORY.md) ⚠️ Looks for MEMORY.md next to the external .context/, not the code.

    Nothing errors. The code-aware operations just find an empty or unrelated tree where the project root should be.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#workaround-symlink-the-context-into-the-code-tree","level":3,"title":"Workaround: symlink the .context/ into the code tree","text":"

    If you want both the privacy of an external git repo and working ctx sync / drift / memory-drift, symlink the external .context/ into the code repo and point CTX_DIR at the symlink:

    # External repo holds the real files\nmkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\n\n# Symlink it into the code repo\nln -s ~/repos/myproject-context/.context ~/repos/myproject/.context\n\n# Bind CTX_DIR to the symlink path; ctx init will follow it\nexport CTX_DIR=~/repos/myproject/.context\nctx init\n

    Now filepath.Dir(CTX_DIR) is the code repo, so code-aware operations scan the right tree. The actual files still live in the external repo and commit there. Add .context to the code repo's .gitignore (or .git/info/exclude) so the symlink itself isn't tracked by the code repo.

    The basename guard is permissive about symlinks: it checks the declared name, not the resolved target, so a .context symlink pointing anywhere is accepted as long as the declared basename is .context.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init CLI command Initialize context directory ctx activate CLI command Emit export CTX_DIR=... for the shell CTX_DIR Env variable Declare context directory per-session .ctxrc Config file Per-project configuration /ctx-status Skill Verify context is loading correctly","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-1-create-the-private-context-repo","level":3,"title":"Step 1: Create the Private Context Repo","text":"

    Create a separate repository for your context files. This can live anywhere: a private GitHub repo, a shared drive, a sibling directory:

    # Create the context repo\nmkdir -p ~/repos/myproject-context\ncd ~/repos/myproject-context\ngit init\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-2-initialize-ctx-pointing-at-it","level":3,"title":"Step 2: Initialize ctx Pointing at It","text":"

    From your project root, declare CTX_DIR pointing to the external location, then initialize:

    cd ~/repos/myproject\nCTX_DIR=~/repos/myproject-context/.context ctx init\n

    This creates the canonical .context/ file set inside ~/repos/myproject-context/ instead of ~/repos/myproject/.context/.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-3-make-it-stick","level":3,"title":"Step 3: Make It Stick","text":"

    Declaring CTX_DIR on every command is tedious. Pick one of these methods to make the configuration permanent. The context directory itself must be declared via CTX_DIR; .ctxrc does not carry the path.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-a-ctx_dir-environment-variable-recommended","level":4,"title":"Option A: CTX_DIR Environment Variable (Recommended)","text":"
    # Direct path. Works for ctx status / agent / add but degrades\n# code-aware operations. See \"What Works, What Quietly Degrades\".\nexport CTX_DIR=~/repos/myproject-context/.context\n\n# Or, with the symlink approach above, point at the symlink path\n# inside the code repo so code-aware operations stay healthy.\nexport CTX_DIR=~/repos/myproject/.context\n

    Put either form in your shell profile (~/.bashrc, ~/.zshrc) or a direnv .envrc.

    For a single session, run eval \"$(ctx activate)\" from any directory inside the project where exactly one .context/ candidate is visible (the symlink counts). activate does not accept a path argument; bind a specific path by exporting CTX_DIR directly instead.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-b-ctxrc-for-other-settings","level":4,"title":"Option B: .ctxrc for Other Settings","text":"

    Put any settings (token budget, priority order, freshness files) in a .ctxrc at the project root (dirname(CTX_DIR)), which here is the parent of the external .context/:

    # ~/repos/myproject-context/.ctxrc\ntoken_budget: 16000\n

    .ctxrc is always read from the parent of CTX_DIR, so this file is picked up whenever CTX_DIR points at ~/repos/myproject-context/.context.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#resolution","level":4,"title":"Resolution","text":"

    ctx reads the context directory from a single channel: the CTX_DIR environment variable. When CTX_DIR is unset, ctx errors with a \"no context directory specified\" hint pointing at ctx activate and this recipe. When set, the value must be an absolute path with .context as its basename; relative paths and other names are rejected on first use.

    See Activating a Context Directory for the full recipe.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-4-agent-auto-discovery-via-bootstrap","level":3,"title":"Step 4: Agent Auto-Discovery via Bootstrap","text":"

    When context lives outside the project tree, your AI assistant needs to know where to find it. The ctx system bootstrap command resolves the configured context directory and communicates it to the agent automatically:

    $ ctx system bootstrap\nctx system bootstrap\n====================\n\ncontext_dir: /home/user/repos/myproject-context/.context\n\nFiles:\n  CONSTITUTION.md, TASKS.md, DECISIONS.md, ...\n

    The CLAUDE.md template generated by ctx init already instructs the agent to run ctx system bootstrap at session start. Because CTX_DIR is inherited by child processes, your agent picks up the external path automatically.

    Here is the relevant section from CLAUDE.md for reference:

    <!-- CLAUDE.md -->\n1. **Run `ctx system bootstrap`**: CRITICAL, not optional.\n   This tells you where the context directory is. If it returns any\n   error, relay the error output to the user verbatim, point them at\n   https://ctx.ist/recipes/activating-context/ for setup, and STOP.\n   Do not try to recover; the user decides.\n

    Moreover, every nudge (context checkpoint, persistence reminder, etc.) also includes a Context: /home/user/repos/myproject-context/.context footer, so the agent remains anchored to the correct directory even in long sessions.

    Export CTX_DIR in your shell profile so every hook process inherits it:

    export CTX_DIR=~/repos/myproject-context/.context\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-5-share-with-teammates","level":3,"title":"Step 5: Share with Teammates","text":"

    Teammates clone both repos and export CTX_DIR:

    # Clone the project\ngit clone git@github.com:org/myproject.git\ncd myproject\n\n# Clone the private context repo\ngit clone git@github.com:org/myproject-context.git ~/repos/myproject-context\nexport CTX_DIR=~/repos/myproject-context/.context\n

    If teammates use different paths, each developer sets their own CTX_DIR.

    For encryption key distribution across the team, see the Syncing Scratchpad Notes recipe.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-6-day-to-day-sync","level":3,"title":"Step 6: Day-to-Day Sync","text":"

    The external context repo has its own git history. Treat it like any other repo: commit and push after sessions:

    cd ~/repos/myproject-context\n\n# After a session\ngit add -A\ngit commit -m \"Session: refactored auth module, added rate-limit learning\"\ngit push\n

    Your AI assistant can do this too. When ending a session:

    You: \"Save what we learned and push the context repo.\"\n\nAgent: [runs ctx learning add, then commits and pushes the context repo]\n

    You can also set up a post-session habit: project code gets committed to the project repo, context gets committed to the context repo.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember the flags; simply ask your assistant:

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#set-up-your-system-using-natural-language","level":3,"title":"Set Up Your System Using Natural Language","text":"
    You: \"Set up ctx to use ~/repos/myproject-context as the context directory.\"\n\nAgent: \"I'll set CTX_DIR to that path, run ctx init to materialize\n       it, and show you the export line to add to your shell\n       profile. Want me to seed the core context files too?\"\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#configure-separate-repo-for-context-folder-using-natural-language","level":3,"title":"Configure Separate Repo for .context Folder Using Natural Language","text":"
    You: \"My context is in a separate repo. Can you load it?\"\n\nAgent: [reads CTX_DIR, loads context from the external dir]\n       \"Loaded. You have 3 pending tasks, last session was about the auth\n       refactor.\"\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tips","level":2,"title":"Tips","text":"
    • Start simple. If you don't need external context yet, don't set it up. The default .context/ in-tree is the easiest path. Move to an external repo when you have a concrete reason.
    • One context repo per project. Sharing a single context directory across multiple projects corrupts journals, state, and secrets. Use ctx hub for cross-project knowledge sharing.
    • Export CTX_DIR in your shell profile so hooks and tools inherit the path without per-command flags.
    • Commit both repos at session boundaries. Context without code history (or code without context history) loses half the value.
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#next-up","level":2,"title":"Next Up","text":"

    The Complete Session →: Walk through a full ctx session from start to finish.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#see-also","level":2,"title":"See Also","text":"
    • Setting Up ctx Across AI Tools: initial setup recipe
    • Syncing Scratchpad Notes Across Machines: distribute encryption keys when context is shared
    • CLI Reference: full command list and global options
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/guide-your-agent/","level":1,"title":"Guide Your Agent","text":"

    Commands vs. Skills

    Commands (ctx status, ctx task add) run in your terminal.

    Skills (/ctx-reflect, /ctx-next) run inside your AI coding assistant.

    Recipes combine both.

    Think of commands as structure and skills as behavior.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#proactive-behavior","level":2,"title":"Proactive Behavior","text":"

    These recipes show explicit commands and skills, but agents trained on the ctx playbook are proactive: They offer to save learnings after debugging, record decisions after trade-offs, create follow-up tasks after completing work, and suggest what to work on next.

    Your questions train the agent. Asking \"what have we learned?\" or \"is our context clean?\" does two things:

    • It triggers the workflow right now,
    • and it reinforces the pattern.

    The more you guide, the more the agent habituates the behavior and begins offering on its own.

    Each recipe includes a Conversational Approach section showing these natural-language patterns.

    Tip

    Don't wait passively for proactive behavior: especially in early sessions.

    Ask, guide, reinforce. Over time, you ask less and the agent offers more.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#next-up","level":2,"title":"Next Up","text":"

    Setup Across AI Tools →: Initialize ctx and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle from start to finish
    • Prompting Guide: general tips for working effectively with AI coding assistants
    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/hook-output-patterns/","level":1,"title":"Hook Output Patterns","text":"","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code hooks can output text, JSON, or nothing at all. But the format of that output determines who sees it and who acts on it.

    Choose the wrong pattern, and your carefully crafted warning gets silently absorbed by the agent, or your agent-directed nudge gets dumped on the user as noise.

    This recipe catalogs the known hook output patterns and explains when to use each one.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#tldr","level":2,"title":"TL;DR","text":"

    Eight patterns from full control to full invisibility:

    • hard gate (exit 2),
    • VERBATIM relay (agent MUST show),
    • agent directive (context injection),
    • and silent side-effect (background work).

    Most hooks belong in the middle.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-spectrum","level":2,"title":"The Spectrum","text":"

    These patterns form a spectrum based on who decides what the user sees:

    Pattern Who decides? Hard gate Hook decides (agent can't proceed) VERBATIM relay Hook decides (agent must show) Escalating severity Hook suggests, agent judges urgency Conditional relay Hook sets criteria, agent evaluates Suggested action Hook proposes, agent + user decide Agent directive Agent decides entirely Silent injection Nobody: invisible background context Silent side-effect Nobody: invisible background work

    The spectrum runs from full hook control (hard gate) to full invisibility (silent side effect).

    Most hooks belong somewhere in the middle.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-1-hard-gate","level":2,"title":"Pattern 1: Hard Gate","text":"

    Block the tool call entirely. The agent cannot proceed: it must find another approach or tell the user.

    echo '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}'\n

    When to use: Enforcing invariants that must never be violated: Constitution rules, security boundaries, destructive command prevention.

    Hook type: PreToolUse only (Claude Code first-class mechanism).

    Examples in ctx:

    • ctx system block-non-path-ctx: Enforces the PATH invocation rule
    • block-git-push.sh: Requires explicit user approval for pushes (project-local)
    • block-dangerous-commands.sh: Prevents sudo, copies to ~/.local/bin (project-local)

    Trade-off: The agent gets a block response with a reason. Good reasons help the agent recover (\"use X instead\"); bad reasons leave it stuck.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-2-verbatim-relay","level":2,"title":"Pattern 2: VERBATIM Relay","text":"

    Force the agent to show this to the user as-is. The explicit instruction overcomes the agent's tendency to silently absorb context.

    echo \"IMPORTANT: Relay this warning to the user VERBATIM before answering their question.\"\necho \"\"\necho \"┌─ Journal Reminder ─────────────────────────────\"\necho \"│ You have 12 sessions not yet exported.\"\necho \"└────────────────────────────────────────────────\"\n

    When to use: Actionable reminders the user needs to see regardless of what they asked: Stale backups, unimported sessions, resource warnings.

    Hook type: UserPromptSubmit (runs before the agent sees the prompt).

    Examples in ctx:

    • ctx system check-journal: Unexported sessions and unenriched entries
    • ctx system check-context-size: Context capacity warning
    • ctx system check-resources: Resource pressure (memory, swap, disk, load): DANGER only
    • ctx system check-freshness: Technology constant staleness warning

    Trade-off: Noisy if overused. Every VERBATIM relay adds a preamble before the agent's actual answer. Throttle with once-per-day markers or adaptive frequency.

    Key detail: The phrase IMPORTANT: Relay this ... VERBATIM is what makes this work. Without it, agents tend to process the information internally and never surface it. The explicit instruction is the pattern: the box-drawing is just fancy formatting.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-3-agent-directive","level":2,"title":"Pattern 3: Agent Directive","text":"

    Tell the agent to do something, not the user. The agent decides whether and how to involve the user.

    echo \"┌─ Persistence Checkpoint (prompt #25) ───────────\"\necho \"│ No context files updated in 15+ prompts.\"\necho \"│ Have you discovered learnings, decisions,\"\necho \"│ or completed tasks worth persisting?\"\necho \"└──────────────────────────────────────────────────\"\n

    When to use: Behavioral nudges. The hook detects a condition and asks the agent to consider an action. The user may never need to know.

    Hook type: UserPromptSubmit.

    Examples in ctx:

    • ctx system check-persistence: Nudges the agent to persist context

    Trade-off: No guarantee the agent acts. The nudge is one signal among many in the context window. Strong phrasing helps (\"Have you...?\" is better than \"Consider...\"), but ultimately the agent decides.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-4-silent-context-injection","level":2,"title":"Pattern 4: Silent Context Injection","text":"

    Load context with no visible output. The agent gets enriched without either party noticing.

    ctx agent --budget 4000 >/dev/null || true\n

    When to use: Background context loading that should be invisible. The agent benefits from the information, but neither it, nor the user needs to know it happened.

    Hook type: PreToolUse with .* matcher (runs on every tool call).

    Examples in ctx:

    • The ctx agent PreToolUse hook: injects project context silently

    Trade-off: Adds latency to every tool call. Keep the injected content small and fast to generate.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-5-silent-side-effect","level":2,"title":"Pattern 5: Silent Side-Effect","text":"

    Do work, produce no output: Housekeeping that needs no acknowledgment.

    find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n

    When to use: Cleanup, log rotation, temp file management. Anything where the action is the point and nobody needs to know it happened.

    Hook type: Any hook where output is irrelevant.

    Examples in ctx:

    • Log rotation, marker file cleanup, state directory maintenance

    Trade-off: None, if the action is truly invisible. If it can fail in a way that matters, consider logging.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-6-conditional-relay","level":3,"title":"Pattern 6: Conditional Relay","text":"

    Tell the agent to relay only if a condition holds in context.

    echo \"If the user's question involves modifying .context/ files,\"\necho \"relay this warning VERBATIM:\"\necho \"\"\necho \"┌─ Context Integrity ─────────────────────────────\"\necho \"│ CONSTITUTION.md has not been verified in 7 days.\"\necho \"└────────────────────────────────────────────────\"\necho \"\"\necho \"Otherwise, proceed normally.\"\n

    When to use: Warnings that only matter in certain contexts. Avoids noise when the user is doing unrelated work.

    Trade-off: Depends on the agent's judgment about when the condition holds. More fragile than VERBATIM relay, but less noisy.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-7-suggested-action","level":3,"title":"Pattern 7: Suggested Action","text":"

    Give the agent a specific command to propose to the user.

    echo \"┌─ Stale Dependencies ──────────────────────────\"\necho \"│ go.sum is 30+ days newer than go.mod.\"\necho \"│ Suggested: run \\`go mod tidy\\`\"\necho \"│ Ask the user before proceeding.\"\necho \"└───────────────────────────────────────────────\"\n

    When to use: The hook detects a fixable condition and knows the fix. Goes beyond a nudge: Gives the agent a concrete next step. The agent still asks for permission but knows exactly what to propose.

    Trade-off: The suggestion might be wrong or outdated. The \"ask the user before proceeding\" part is critical.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-8-escalating-severity","level":3,"title":"Pattern 8: Escalating Severity","text":"

    Different urgency tiers with different relay expectations.

    # INFO: agent processes silently, mentions if relevant\necho \"INFO: Last test run was 3 days ago.\"\n\n# WARN: agent should mention to user at next natural pause\necho \"WARN: 12 uncommitted changes across 3 branches.\"\n\n# CRITICAL: agent must relay immediately, before any other work\necho \"CRITICAL: Relay VERBATIM before answering. Disk usage at 95%.\"\n

    When to use: When you have multiple hooks producing output and need to avoid overwhelming the user. INFO gets absorbed, WARN gets mentioned, CRITICAL interrupts.

    Examples in ctx:

    • ctx system check-resources: Uses two tiers (WARNING/DANGER) internally but only fires the VERBATIM relay at DANGER level: WARNING is silent. See ctx system for the user-facing command that shows both tiers.

    Trade-off: Requires agent training or convention to recognize the tiers. Without a shared protocol, the prefixes are just text.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#choosing-a-pattern","level":2,"title":"Choosing a Pattern","text":"
    Is the agent about to do something forbidden?\n  └─ Yes → Hard gate\n\nDoes the user need to see this regardless of what they asked?\n  └─ Yes → VERBATIM relay\n  └─ Sometimes → Conditional relay\n\nShould the agent consider an action?\n  └─ Yes, with a specific fix → Suggested action\n  └─ Yes, open-ended → Agent directive\n\nIs this background context the agent should have?\n  └─ Yes → Silent injection\n\nIs this housekeeping?\n  └─ Yes → Silent side-effect\n
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#design-tips","level":2,"title":"Design Tips","text":"

    Throttle aggressively: VERBATIM relays that fire every prompt will be ignored or resented. Use once-per-day markers (touch $REMINDED), adaptive frequency (every Nth prompt), or staleness checks (only fire if condition persists).

    Include actionable commands: \"You have 12 unimported sessions\" is less useful than \"You have 12 unimported sessions. Run: ctx journal import --all.\" Give the user (or agent) the exact next step.

    Use box-drawing for visual structure: The ┌─ ─┐ │ └─ ─┘ pattern makes hook output visually distinct from agent prose. It also signals \"this is machine-generated, not agent opinion.\"

    Test the silence path: Most hook runs should produce no output (the condition isn't met). Make sure the common case is fast and silent.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"

    Lessons from 19 days of hook debugging in ctx. Every one of these was encountered, debugged, and fixed in production.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#silent-misfire-wrong-key-name","level":3,"title":"Silent Misfire: Wrong Key Name","text":"
    { \"PreToolUseHooks\": [ ... ] }\n

    The key is PreToolUse, not PreToolUseHooks. Claude Code validates silently: A misspelled key means the hook is ignored with no error. Always test with a debug echo first to confirm the hook fires before adding real logic.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#json-escaping-breaks-shell-commands","level":3,"title":"JSON Escaping Breaks Shell Commands","text":"

    Go's json.Marshal escapes >, <, and & as Unicode sequences (\\u003e) by default. This breaks shell commands in generated config:

    \"command\": \"ctx agent 2\\u003e/dev/null\"\n

    Fix: use json.Encoder with SetEscapeHTML(false) when generating hook configuration.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#stdin-not-environment-variables","level":3,"title":"stdin, Not Environment Variables","text":"

    Hook input arrives as JSON via stdin, not environment variables:

    # Wrong:\nCOMMAND=\"$CLAUDE_TOOL_INPUT\"\n\n# Right:\nHOOK_INPUT=$(cat)\nCOMMAND=$(echo \"$HOOK_INPUT\" | jq -r '.tool_input.command // empty')\n
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#regex-overfitting","level":3,"title":"Regex Overfitting","text":"

    A regex meant to catch ctx as a binary will also match ctx as a directory component:

    # Too broad: blocks: git -C /home/jose/WORKSPACE/ctx status\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# Narrow to binary only:\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n

    Test hook regexes against paths that contain the target string as a substring, not just as the final component.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#repetition-fatigue","level":3,"title":"Repetition Fatigue","text":"

    Injecting context on every tool call sounds safe. In practice, after seeing the same context injection fifteen times, the agent treats it as background noise: Conventions stated in the injected context get violated because salience has been destroyed by repetition.

    Fix: cooldowns. ctx agent --session $PPID --cooldown 10m injects at most once per ten minutes per session using a tombstone file in /tmp/. This is not an optimization; it is a correction for a design flaw. Every injection consumes attention budget: 50 tool calls at 4,000 tokens each means 200,000 tokens of repeated context, most of it wasted.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#hardcoded-paths","level":3,"title":"Hardcoded Paths","text":"

    A username rename (parallels to jose) broke every hook at once. Use $CLAUDE_PROJECT_DIR instead of absolute paths:

    \"command\": \"\\\"$CLAUDE_PROJECT_DIR\\\"/.claude/hooks/block-git-push.sh\"\n

    If the platform provides a runtime variable for paths, always use it.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#next-up","level":2,"title":"Next Up","text":"

    Webhook Notifications →: Get push notifications when loops complete, hooks fire, or agents hit milestones.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#see-also","level":2,"title":"See Also","text":"
    • Customizing Hook Messages: override what hooks say without changing what they do
    • Claude Code Permission Hygiene: how permissions and hooks work together
    • Defense in Depth: why hooks matter for agent security
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/","level":1,"title":"Hook Sequence Diagrams","text":"","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#hook-lifecycle","level":2,"title":"Hook Lifecycle","text":"

    This page documents the ctx system hooks: the built-in ctx system * subcommands that Claude Code invokes via .claude/hooks.json at lifecycle events. These are owned by ctx itself, not authored by users.

    Not to Be Confused with ctx trigger

    ctx has three distinct hook-like layers:

    • ctx system hooks (this page): built-in, owned by ctx, wired into Claude Code via internal/assets/claude/hooks/hooks.json.
    • ctx trigger: user-authored shell scripts in .context/hooks/<type>/*.sh. See ctx trigger reference and the trigger authoring recipe.
    • Claude Code hooks configured directly in .claude/settings.local.json, tool-specific, not portable across AI tools.

    This page is only about the first category.

    Every ctx system hook is a Go binary invoked by Claude Code at one of three lifecycle events: PreToolUse (before a tool runs, can block), PostToolUse (after a tool completes), or UserPromptSubmit (on every user prompt, before any tools run). Hooks receive JSON on stdin and emit JSON or plain text on stdout.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#pretooluse-hooks","level":2,"title":"PreToolUse Hooks","text":"

    These fire before a tool executes. They can block, gate, or inject context.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#context-load-gate","level":3,"title":"Context-Load-Gate","text":"

    Matcher: .* (all tools)

    Injects the full context packet on first tool use of a session. One-shot per session.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as context-load-gate\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Git as git log\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized\n    alt not initialized\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check ctx-loaded-{session} marker\n    alt marker exists\n        Hook-->>CC: (silent exit, already fired)\n    end\n    Hook->>State: Create marker (one-shot guard)\n    Hook->>State: Prune stale session files\n    loop Each file in ReadOrder\n        alt GLOSSARY or TASK\n            Note over Hook: Skip (Task mentioned in footer only)\n        else DECISION or LEARNING\n            Hook->>Ctx: Extract index table only\n        else other files\n            Hook->>Ctx: Read full content\n        end\n        Hook->>Hook: Estimate tokens per file\n    end\n    Hook->>Git: Detect changes since last session\n    Hook->>Hook: Build injection (files + changes + token counts)\n    Hook-->>CC: JSON {additionalContext: injection}\n    Hook->>Hook: Send webhook (metadata only)\n    Hook->>State: Write oversize flag if tokens > threshold
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-non-path-ctx","level":3,"title":"Block-Non-Path-ctx","text":"

    Matcher: Bash

    Blocks ./ctx, go run ./cmd/ctx, or absolute-path ctx invocations. Constitutionally enforced.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-non-path-ctx\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Test regex: relative-path, go-run, absolute-path\n    alt no match\n        Hook-->>CC: (silent exit)\n    end\n    alt absolute-path + test exception\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason + constitution suffix}\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#qa-reminder","level":3,"title":"Qa-Reminder","text":"

    Matcher: Bash

    Gate nudge before any git command. Reminds agent to lint/test.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as qa-reminder\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check command contains \"git\"\n    alt no git command\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, gate, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: QA gate}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#specs-nudge","level":3,"title":"Specs-Nudge","text":"

    Matcher: EnterPlanMode

    Nudges agent to save plans/specs when new implementation detected.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as specs-nudge\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: specs nudge}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#posttooluse-hooks","level":2,"title":"PostToolUse Hooks","text":"

    These fire after a tool completes. They observe, nudge, and track state.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#post-commit","level":3,"title":"Post-Commit","text":"

    Matcher: Bash

    Fires after git commit (not amend). Nudges for context capture and checks version drift.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as post-commit\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"git commit\"?\n    alt not a git commit\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"--amend\"?\n    alt is amend\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: post-commit nudge}\n    Hook->>Hook: Relay(message)\n    Hook->>Hook: CheckVersionDrift()
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-task-completion","level":3,"title":"Check-Task-Completion","text":"

    Matcher: Edit, Write

    Configurable-interval nudge after edits. Per-session counter resets after firing.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-task-completion\n    participant State as .context/state/\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read task nudge interval\n    alt interval <= 0 (disabled)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read per-session counter\n    Hook->>Hook: Increment counter\n    alt counter < interval\n        Hook->>State: Write counter\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Reset counter to 0\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: JSON {additionalContext: task nudge}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#userpromptsubmit-hooks","level":2,"title":"UserPromptSubmit Hooks","text":"

    These fire on every user prompt, before any tools run. They perform health checks, track state, and nudge for housekeeping.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-context-size","level":3,"title":"Check-Context-Size","text":"

    Adaptive context window monitoring. Fires checkpoints, window warnings, and billing alerts based on prompt count and token usage.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-context-size\n    participant State as .context/state/\n    participant Session as Session JSONL\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized\n    Hook->>Hook: Read input, resolve session ID\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: Pause acknowledgment message\n    end\n    Hook->>State: Increment session prompt counter\n    Hook->>Session: Read token info (tokens, model, window)\n\n    rect rgb(255, 240, 240)\n        Note over Hook: Billing check (independent, never suppressed)\n        alt tokens >= billing threshold (one-shot)\n            Hook->>Tpl: LoadMessage(hook, billing, vars)\n            Hook-->>CC: Billing warning nudge box\n            Hook->>Hook: NudgeAndRelay(billing message)\n        end\n    end\n\n    Hook->>State: Check wrap-up marker\n    alt wrapped up recently (< 2h)\n        Hook->>State: Write stats (event: suppressed)\n        Hook-->>CC: (silent exit)\n    end\n\n    rect rgb(240, 248, 255)\n        Note over Hook: Adaptive frequency check\n        alt count > 30 and count % 3 == 0\n            Note over Hook: High frequency trigger\n        else count > 15 and count % 5 == 0\n            Note over Hook: Medium frequency trigger\n        else\n            Hook->>State: Write stats (event: silent)\n            Hook-->>CC: (silent exit)\n        end\n    end\n\n    alt context window >= 80%\n        Hook->>Tpl: LoadMessage(hook, window, vars)\n        Hook-->>CC: Window warning nudge box\n        Hook->>Hook: NudgeAndRelay(window message)\n    else checkpoint trigger\n        Hook->>Tpl: LoadMessage(hook, checkpoint)\n        Hook-->>CC: Checkpoint nudge box\n        Hook->>Hook: NudgeAndRelay(checkpoint message)\n    end\n    Hook->>State: Write session stats
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-ceremonies","level":3,"title":"Check-Ceremonies","text":"

    Daily check for /ctx-remember and /ctx-wrap-up usage in recent journal entries.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-ceremonies\n    participant State as .context/state/\n    participant Journal as Journal files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Read recent files (lookback window)\n    alt no journal files\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Scan for /ctx-remember and /ctx-wrap-up\n    alt both ceremonies present\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Note over Hook: variant: both | remember | wrapup\n    Hook-->>CC: Nudge box (missing ceremonies)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-freshness","level":3,"title":"Check-Freshness","text":"

    Daily check for technology-dependent constants that may need review.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-freshness\n    participant State as .context/state/\n    participant FS as Filesystem\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>FS: Stat tracked files (5 source files)\n    alt all files modified within 6 months\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {StaleFiles})\n    Hook-->>CC: Nudge box (stale file list + review URL)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-journal","level":3,"title":"Check-Journal","text":"

    Daily check for unimported sessions and unenriched journal entries.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-journal\n    participant State as .context/state/\n    participant Journal as Journal dir\n    participant Claude as Claude projects dir\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Check dir exists\n    Hook->>Claude: Check dir exists\n    alt either dir missing\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Get newest entry mtime\n    Hook->>Claude: Count .jsonl files newer than journal\n    Hook->>Journal: Count unenriched entries\n    alt unimported == 0 and unenriched == 0\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, {counts})\n    Note over Hook: variant: both | unimported | unenriched\n    Hook-->>CC: Nudge box (counts)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-knowledge","level":3,"title":"Check-Knowledge","text":"

    Daily check for knowledge file entry/line counts exceeding configured thresholds.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-knowledge\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read thresholds (decisions, learnings, conventions)\n    alt all thresholds disabled (0)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Ctx: Parse DECISIONS.md entry count\n    Hook->>Ctx: Parse LEARNINGS.md entry count\n    Hook->>Ctx: Count CONVENTIONS.md lines\n    Hook->>Hook: Compare against thresholds\n    alt all within limits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, warning, {FileWarnings})\n    Hook-->>CC: Nudge box (file warnings)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-map-staleness","level":3,"title":"Check-Map-Staleness","text":"

    Daily check for architecture map age and relevant code changes.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-map-staleness\n    participant State as .context/state/\n    participant Tracking as map-tracking.json\n    participant Git as git log\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tracking: Read map-tracking.json\n    alt missing, invalid, or opted out\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Parse LastRun date\n    alt map not stale (< N days)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Git: Count commits touching internal/ since LastRun\n    alt no relevant commits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {date, count})\n    Hook-->>CC: Nudge box (last refresh + commit count)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-memory-drift","level":3,"title":"Check-Memory-Drift","text":"

    Per-session check for MEMORY.md changes since last sync.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-memory-drift\n    participant State as .context/state/\n    participant Mem as memory.Discover\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check session tombstone\n    alt already nudged this session\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: DiscoverMemoryPath(projectRoot)\n    alt auto memory not active\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: HasDrift(contextDir, sourcePath)\n    alt no drift\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: Nudge box (drift reminder)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch session tombstone
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-persistence","level":3,"title":"Check-Persistence","text":"

    Tracks context file modification and nudges when edits happen without persisting context. Adaptive threshold based on prompt count.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-persistence\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read persistence state {Count, LastNudge, LastMtime}\n    alt first prompt (no state)\n        Hook->>State: Initialize state {Count:1, LastNudge:0, LastMtime:now}\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Increment Count\n    Hook->>Ctx: Get current context mtime\n    alt context modified since LastMtime\n        Hook->>State: Reset LastNudge = Count, update LastMtime\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: sinceNudge = Count - LastNudge\n    Hook->>Hook: PersistenceNudgeNeeded(Count, sinceNudge)?\n    alt threshold not reached\n        Hook->>State: Write state\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, vars)\n    Hook-->>CC: Nudge box (prompt count, time since last persist)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Update LastNudge = Count, write state
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-reminders","level":3,"title":"Check-Reminders","text":"

    Per-prompt check for due reminders. No throttle.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-reminders\n    participant Store as Reminders store\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Store: ReadReminders()\n    alt load error\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter by due date (After <= today)\n    alt no due reminders\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, reminders, {list})\n    Hook-->>CC: Nudge box (reminder list + dismiss hints)\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-resources","level":3,"title":"Check-Resources","text":"

    Checks system resources (memory, swap, disk, load). Fires on every prompt. No initialization required.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-resources\n    participant Sys as sysinfo\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: HookPreamble (parse input, check pause)\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Sys: Collect snapshot (memory, swap, disk, load)\n    Hook->>Sys: Evaluate thresholds per metric\n    alt max severity < Danger\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter alerts to Danger level only\n    Hook->>Hook: Build alertMessages from danger alerts\n    Hook->>Tpl: LoadMessage(hook, alert, {alertMessages}, fallback)\n    Hook-->>CC: Nudge box (danger alerts)\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-version","level":3,"title":"Check-Version","text":"

    Daily binary-vs-plugin version comparison with piggybacked key rotation check.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-version\n    participant State as .context/state/\n    participant Config as Binary + Plugin version\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read binary version\n    alt dev build\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read plugin version\n    alt plugin version not found or parse error\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Compare major.minor\n    alt versions match\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, mismatch, {versions})\n    Hook-->>CC: Nudge box (version mismatch)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle\n    Hook->>Hook: CheckKeyAge() (piggybacked)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#heartbeat","level":3,"title":"Heartbeat","text":"

    Silent per-prompt pulse. Tracks prompt count, context modification, and token usage. The agent never sees this hook's output.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as heartbeat\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Notify as Webhook + EventLog\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Increment heartbeat counter\n    Hook->>Ctx: Get latest context file mtime\n    Hook->>State: Compare with last recorded mtime\n    Hook->>State: Update mtime record\n    Hook->>State: Read session token info\n    Hook->>Notify: Send heartbeat notification\n    Hook->>Notify: Append to event log\n    Hook->>State: Write heartbeat log entry\n    Note over Hook: No stdout - agent never sees this
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#project-local-hooks","level":2,"title":"Project-Local Hooks","text":"

    These hooks are configured in settings.local.json and are not shipped with ctx. They are specific to individual developer setups.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-dangerous-commands","level":3,"title":"Block-Dangerous-Commands","text":"

    Lifecycle: PreToolUse. Matcher: Bash

    Blocks dangerous shell patterns (sudo, git push, cp to bin). No initialization or pause checks: always active.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-dangerous-commands\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Note over Hook: Cascade: first matching regex wins\n    Hook->>Hook: Test MidSudo regex\n    alt match\n        Hook->>Hook: variant = sudo\n    end\n    Hook->>Hook: Test MidGitPush regex (if no variant)\n    alt match\n        Hook->>Hook: variant = git-push\n    end\n    Hook->>Hook: Test CpMvToBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = cp-to-bin\n    end\n    Hook->>Hook: Test InstallToLocalBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = install-to-bin\n    end\n    alt no variant matched\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason}\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#throttling-summary","level":2,"title":"Throttling Summary","text":"Hook Lifecycle Throttle Type Scope context-load-gate PreToolUse One-shot marker Per session block-non-path-ctx PreToolUse None Every match qa-reminder PreToolUse None Every git command specs-nudge PreToolUse None Every prompt post-commit PostToolUse None Every git commit check-task-completion PostToolUse Configurable interval Per session check-context-size UserPromptSubmit Adaptive counter Per session check-ceremonies UserPromptSubmit Daily marker Once per day check-freshness UserPromptSubmit Daily marker Once per day check-journal UserPromptSubmit Daily marker Once per day check-knowledge UserPromptSubmit Daily marker Once per day check-map-staleness UserPromptSubmit Daily marker Once per day check-memory-drift UserPromptSubmit Session tombstone Once per session check-persistence UserPromptSubmit Adaptive counter Per session check-reminders UserPromptSubmit None Every prompt check-resources UserPromptSubmit None Every prompt check-version UserPromptSubmit Daily marker Once per day heartbeat UserPromptSubmit None Every prompt block-dangerous-commands PreToolUse * None Every match

    * Project-local hook (settings.local.json), not shipped with ctx.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#state-file-reference","level":2,"title":"State File Reference","text":"

    All state files live in .context/state/.

    File Pattern Hook Purpose ctx-loaded-{session} context-load-gate One-shot injection marker ctx-paused-{session} (all) Session pause marker ctx-wrapped-up check-context-size Suppress nudges after wrap-up (2h expiry) freshness-checked check-freshness Daily throttle ceremony-reminded check-ceremonies Daily throttle journal-reminded check-journal Daily throttle knowledge-reminded check-knowledge Daily throttle map-staleness-reminded check-map-staleness Daily throttle version-checked check-version Daily throttle memory-drift-nudged-{session} check-memory-drift Per-session tombstone ctx-context-count-{session} check-context-size Prompt counter stats-{session}.jsonl check-context-size Session stats log persist-{session} check-persistence Counter + mtime state ctx-task-count-{session} check-task-completion Prompt counter heartbeat-count-{session} heartbeat Prompt counter heartbeat-mtime-{session} heartbeat Last context mtime","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hub-cluster/","level":1,"title":"HA Cluster","text":"","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#ctx-hub-high-availability-cluster","level":1,"title":"ctx Hub: High-Availability Cluster","text":"

    Run multiple hub nodes with Raft-based leader election for redundancy. Any follower can take over if the leader dies.

    This recipe assumes you've read the ctx Hub overview and the Multi-machine setup. HA only makes sense in the \"small trusted team\" story; a personal cross-project brain on one workstation does not need three Raft peers.

    Raft-Lite

    ctx uses Raft only for leader election, not for data consensus. Entry replication happens via sequence-based gRPC sync on the append-only JSONL store. This is simpler than full Raft log replication and is possible because the store is append-only and clients are idempotent. The implication: a write accepted by the leader is durable on the leader immediately; followers catch up asynchronously. If the leader crashes between accepting a write and replicating it, that write can be lost. Do not use the hub as a bank ledger.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#topology","level":2,"title":"Topology","text":"

    A minimum HA cluster is three nodes. Two is worse than one: it doubles failure probability without providing quorum.

             +-------------+\n         |  client(s)  |\n         +------+------+\n                |\n    +-----------+-----------+\n    |           |           |\n+---v---+   +---v---+   +---v---+\n| hub A |   | hub B |   | hub C |\n| :9900 |   | :9900 |   | :9900 |\n+-------+   +-------+   +-------+\n    ^           ^           ^\n    +-----------+-----------+\n        Raft (leader election)\n        gRPC (data sync)\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-1-bootstrap-the-first-node","level":2,"title":"Step 1: Bootstrap the First Node","text":"
    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n

    The node starts a Raft election as soon as it sees its peers.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-2-start-the-other-nodes","level":2,"title":"Step 2: Start the Other Nodes","text":"

    On hub-b.lan:

    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-c.lan:9900\n

    On hub-c.lan:

    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-b.lan:9900\n

    After a few seconds, one node wins the election and becomes the leader. The other two are followers.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-3-verify-cluster-state","level":2,"title":"Step 3: Verify Cluster State","text":"

    From any node:

    ctx hub status\n

    Expected output:

    role:       leader\npeers:      hub-a.lan:9900 (leader)\n            hub-b.lan:9900 (follower, in-sync)\n            hub-c.lan:9900 (follower, in-sync)\nentries:    1248\nuptime:     3h42m\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-4-register-clients-with-failover-peers","level":2,"title":"Step 4: Register Clients with Failover Peers","text":"

    The ctx hub * commands above run on the hub nodes themselves and don't need a project. The ctx connection * commands below are different: they live inside a project (the encrypted hub config is stored at .context/.connect.enc), so you have to tell ctx which project first.

    When registering a client, give it the full peer list:

    # In the project directory on the client:\neval \"$(ctx activate)\"\nctx connection register hub-a.lan:9900 \\\n  --token ctx_adm_... \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n

    If the leader becomes unreachable, the client reconnects to the next peer. Followers redirect to the current leader, so writes always land on the right node.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#runtime-membership-changes","level":2,"title":"Runtime Membership Changes","text":"

    Add a new peer without downtime:

    ctx hub peer add hub-d.lan:9900\n

    Remove a decommissioned peer:

    ctx hub peer remove hub-c.lan:9900\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#planned-maintenance","level":2,"title":"Planned Maintenance","text":"

    Before taking a leader offline, hand off leadership:

    ssh hub-a.lan 'ctx hub stepdown'\n

    stepdown triggers a new election among the remaining followers before the leader goes offline. In-flight clients briefly pause, then reconnect to the new leader.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#failure-modes-at-a-glance","level":2,"title":"Failure Modes at a Glance","text":"Event What happens Leader crashes New election; clients reconnect to new leader Follower crashes No write impact; catches up on restart Network partition (majority) Majority side keeps serving; minority read-only Network partition (split) No quorum; all nodes read-only Disk full on leader Writes rejected; read traffic continues

    For the full list, see Hub failure modes.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#see-also","level":2,"title":"See Also","text":"
    • Multi-machine recipe: single-node deployment
    • Hub operations: backup and maintenance
    • Hub security model: TLS, tokens
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-getting-started/","level":1,"title":"Getting Started","text":"","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#ctx-hub-getting-started","level":1,"title":"ctx Hub: Getting Started","text":"

    Stand up a single-node ctx Hub on localhost, register two projects, publish a decision from one, and see it appear in the other, all in under five minutes.

    Read This First

    If you haven't already, skim the ctx Hub overview. It explains the mental model, names the two user stories (personal vs small team), and (importantly) lists what the hub does not do. This recipe assumes you already know you want the feature.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-youll-get-out-of-this-recipe","level":2,"title":"What You'll Get out of This Recipe","text":"

    By the end, you will have:

    1. A local hub process running on port 9900.
    2. Two project directories both registered with the ctx Hub.
    3. A decision published from project alpha that appears automatically in project beta's .context/hub/ and in ctx agent --include-hub output.

    Concretely, the payoff this unlocks: a lesson you record in one project becomes visible to your agent the next time you open another project, without touching local files in the second project or opening another editor window.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-this-recipe-does-not-cover","level":2,"title":"What This Recipe Does Not Cover","text":"
    • Sharing .context/journal/, .context/pad, or any other local state. The hub only fans out decision, learning, convention, and task entries. Everything else stays local.
    • Multi-user attribution. The hub identifies projects, not people.
    • Running over a LAN; see Multi-machine setup.
    • Redundancy; see HA cluster.
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"
    • ctx installed and on PATH
    • Two project directories, each already initialized with ctx init
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"

    In a dedicated terminal:

    ctx hub start\n

    On first run, the hub generates an admin token and prints it to stdout. Copy it; you'll need it for each project registration:

    ctx hub listening on :9900\nadmin token: ctx_adm_7f3a1c2d...\ndata dir: ~/.ctx/hub-data/\n

    The admin token is written to ~/.ctx/hub-data/admin.token so you can recover it later. Treat it like a password.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-2-register-the-first-project","level":2,"title":"Step 2: Register the First Project","text":"

    ctx hub start above runs on the hub server and doesn't need a project. Step 2 is different: the encrypted hub config is stored inside a project at .context/.connect.enc, so you have to tell ctx which project first.

    cd ~/projects/alpha\neval \"$(ctx activate)\"\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\n

    This stores an encrypted connection config in .context/.connect.enc. The admin token is exchanged for a per-project client token; the admin token itself is never persisted in the project.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-3-choose-what-to-receive","level":2,"title":"Step 3: Choose What to Receive","text":"
    ctx connection subscribe decision learning convention\n

    Only the entry types you subscribe to will be delivered by sync and listen.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-4-publish-a-decision","level":2,"title":"Step 4: Publish a Decision","text":"

    Either use ctx add --share to write locally and push to the ctx Hub:

    ctx decision add \"Use UTC timestamps everywhere\" --share \\\n  --context \"We had timezone drift between the API and journal\" \\\n  --rationale \"Single source of truth avoids conversion bugs\" \\\n  --consequence \"The UI does conversion at render time\"\n

    Or publish an existing entry directly:

    ctx connection publish decision \"Use UTC timestamps everywhere\"\n
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-5-register-a-second-project-and-sync","level":2,"title":"Step 5: Register a Second Project and Sync","text":"
    cd ~/projects/beta\neval \"$(ctx activate)\"   # bind CTX_DIR for this project\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\nctx connection subscribe decision learning convention\nctx connection sync\n

    The decision from alpha now appears in ~/projects/beta/.context/hub/decisions.md with an origin tag and timestamp.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-6-watch-entries-arrive-live","level":2,"title":"Step 6: Watch Entries Arrive Live","text":"

    Instead of re-running sync, stream new entries as they land:

    ctx connection listen\n

    Leave this running in a terminal; every --share publish from any registered project will appear in .context/hub/ immediately.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-7-feed-shared-knowledge-into-the-agent","level":2,"title":"Step 7: Feed Shared Knowledge into the Agent","text":"

    Once entries exist in .context/hub/, include them in the agent context packet:

    ctx agent --include-hub\n

    Shared entries are added as a dedicated tier in the budget-aware assembly, scored by recency and type relevance.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#auto-sync-on-session-start","level":2,"title":"Auto-Sync on Session Start","text":"

    After register, the check-hub-sync hook pulls new entries at the start of each session (daily throttled). Most users never need to call ctx connection sync manually.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Multi-machine hub: run the hub on a LAN host and connect from other workstations.
    • HA cluster: Raft-based leader election for high availability.
    • Hub operations: daemon mode, backup, log rotation, JSONL store layout.
    • Hub security model: token lifecycle, encryption at rest, threat model.
    • ctx connect reference and ctx hub start reference.
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-multi-machine/","level":1,"title":"Multi-Machine","text":"","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#ctx-hub-multi-machine","level":1,"title":"ctx Hub: Multi-Machine","text":"

    Run the hub on a LAN host and connect from project directories on other workstations. This recipe is the Story 2 (\"small trusted team\") shape described in the ctx Hub overview; read that first if you haven't, especially the trust-model warnings.

    This recipe assumes you've already walked through Getting Started and understand what flows through the hub (decisions, learnings, conventions, tasks, not journals, scratchpad, or raw context files).

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#topology","level":2,"title":"Topology","text":"
    +------------------+        +------------------+\n| workstation A    |        | workstation B    |\n|  ~/projects/x    |        |  ~/projects/y    |\n|  ctx connection  |        |  ctx connection  |\n+---------+--------+        +---------+--------+\n          |                           |\n          +-----------+   +-----------+\n                      v   v\n              +-------------------+\n              | LAN host \"nexus\"  |\n              | ctx hub start     |\n              | --daemon          |\n              | :9900             |\n              +-------------------+\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-1-start-the-daemon-on-the-lan-host","level":2,"title":"Step 1: Start the Daemon on the LAN Host","text":"

    On the machine that will hold the hub (call it nexus):

    ctx hub start --daemon --port 9900\n

    The daemon writes a PID file to ~/.ctx/hub-data/hub.pid. Stop it later with:

    ctx hub stop\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-2-firewall-and-port","level":2,"title":"Step 2: Firewall and Port","text":"

    Open port 9900/tcp on nexus to the LAN only. Never expose the hub to the public internet without a reverse proxy and TLS in front of it (see Hub security model).

    Typical LAN allowlist rules:

    firewalldufwnftables
    sudo firewall-cmd --zone=internal \\\n  --add-port=9900/tcp --permanent\nsudo firewall-cmd --reload\n
    sudo ufw allow from 192.168.1.0/24 to any port 9900 proto tcp\n
    sudo nft add rule inet filter input ip saddr 192.168.1.0/24 \\\n  tcp dport 9900 accept\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-3-retrieve-the-admin-token","level":2,"title":"Step 3: Retrieve the Admin Token","text":"

    The daemon prints the admin token to stdout on first run. Running as a daemon, that output goes to the log instead:

    cat ~/.ctx/hub-data/admin.token\n

    Copy the token over a trusted channel (SSH, password manager, or an encrypted note). Do not email it or put it in chat.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-4-register-projects-from-each-workstation","level":2,"title":"Step 4: Register Projects from Each Workstation","text":"

    The ctx hub * commands above run on the LAN host (nexus) and don't need a project. Step 4 is different: each workstation registers from inside a project (the encrypted hub config and the fan-out inbox both live under .context/), so you have to tell ctx which project first.

    On workstation A:

    cd ~/projects/x\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n

    On workstation B:

    cd ~/projects/y\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n

    Each registration exchanges the admin token for a per-project client token. Only the client token is persisted in .context/.connect.enc, encrypted with the same AES-256-GCM scheme ctx uses for notification credentials.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-5-verify","level":2,"title":"Step 5: Verify","text":"

    From either workstation:

    ctx connection status\n

    You should see the ctx Hub address, role (leader for single-node), subscription filters, and the sequence number you're synced to.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#tls-recommended","level":2,"title":"TLS (Recommended)","text":"

    For anything beyond a trusted home LAN, terminate TLS in front of the hub. The hub speaks gRPC, so the reverse proxy must speak HTTP/2:

    server {\n    listen 443 ssl http2;\n    server_name nexus.example.com;\n\n    ssl_certificate     /etc/letsencrypt/live/nexus.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/nexus.example.com/privkey.pem;\n\n    location / {\n        grpc_pass grpc://127.0.0.1:9900;\n    }\n}\n

    Point ctx connection register at the public hostname and port 443.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#handling-daemon-restarts","level":2,"title":"Handling Daemon Restarts","text":"

    The hub is append-only JSONL, so restarts are safe. Clients keep their last-seen sequence in .context/hub/.sync-state.json and pick up exactly where they left off on the next sync or listen reconnect.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#see-also","level":2,"title":"See Also","text":"
    • HA cluster recipe: for redundancy
    • Hub operations: backup, rotation
    • Hub failure modes
    • Hub security model
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-overview/","level":1,"title":"Overview","text":"","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#ctx-hub-overview","level":1,"title":"ctx Hub: Overview","text":"

    Start here before the other hub recipes. This page answers what the hub is, who it's for, why you'd run one, and, equally important, what it is not.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#mental-model-in-one-paragraph","level":2,"title":"Mental Model in One Paragraph","text":"

    The hub is a fan-out channel for structured knowledge entries across projects. When you publish a decision, learning, convention, or task with --share, the hub stores it in an append-only log and delivers it to every other project subscribed to that type. The next time your agent loads context in any of those projects, shared entries can be included in the context packet alongside local ones.

    That's the whole feature. It is a project-to-project knowledge bus for a small, curated set of entry types. It is not a shared memory, a shared journal, or a multi-user database.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-flows-through-the-hub","level":2,"title":"What Flows through the Hub","text":"

    Only four entry types:

    Type What it is decision Architectural decisions with rationale learning Gotchas, lessons, surprising behaviors convention Coding patterns and standards task Work items worth sharing across projects

    Each entry is an immutable record with a content blob, the publishing project's name as Origin, a timestamp, and a hub-assigned sequence number. Once published, entries are never rewritten.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-does-not-flow-through-the-hub","level":2,"title":"What Does Not Flow through the Hub","text":"

    This is the part new users get wrong most often:

    • Session journals (~/.claude/ logs, .context/journal/) stay local. The hub does not sync your AI session history.
    • Scratchpad (.context/pad) stays local. Encrypted notes never leave the machine they were written on.
    • Local context files as a whole (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) are not mirrored wholesale. Only entries you explicitly --share, or publish later with ctx connection publish, cross the boundary.
    • Anything under .context/ that isn't one of the four entry types above. Configuration, state, logs, memory, journal metadata: all local.

    If you were expecting \"now my agent in project B can see everything my agent did in project A,\" that's not this feature. Local session density still lives on the local machine.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#two-user-stories","level":2,"title":"Two User Stories","text":"

    The hub makes sense in two different shapes. Pick the one that matches your situation; the mechanics are identical but the trust model and threat surface are very different.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-1-personal-cross-project-brain","level":3,"title":"Story 1: Personal Cross-Project Brain","text":"

    One developer, many projects, one hub, usually on localhost.

    You're working across several projects on the same machine (or a handful of machines you own). You want a lesson learned debugging project A to show up when you open project B a week later, without re-discovering it. You want a convention you codified in one project to be visible as-you-type in another.

    Concrete payoff:

    • ctx learning add --share \"...\" in project A → ctx agent --include-hub in project B shows that learning in the next context packet.
    • A decision recorded in your personal \"dotfiles\" project is instantly visible to every other project on your workstation.
    • Cross-project conventions (e.g., \"use UTC timestamps everywhere\") live in one place and propagate.

    Trust model: high, because you trust every participant since every participant is you. Run the hub on localhost or on your own LAN, use the default single-node setup, don't worry about TLS.

    Start here: Getting Started for the one-time setup, then Personal cross-project brain for the day-to-day workflow.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-2-small-trusted-team","level":3,"title":"Story 2: Small Trusted Team","text":"

    A few teammates, projects they each own, one hub on a LAN host they all trust.

    Your team has a handful of services and you want a shared \"things we've learned the hard way\" stream. Someone on the platform team records a convention about timestamp handling; everyone else's agents see it the next session. An on-call engineer records a learning from a 3 AM incident; the rest of the team inherits the lesson without needing to read the postmortem.

    Concrete payoff:

    • Team conventions propagate without needing a wiki or chat.
    • Lessons from one team member become available to everyone else's agent context packets automatically.
    • Cross-project decisions (shared libraries, deployment patterns, naming rules) live in a single log the whole team reads.

    Trust model: the hub assumes everyone holding a client token is friendly. There is no per-user attribution you can rely on, Origin is self-asserted by the publishing client, and there is no read ACL beyond the subscription filter. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.

    Operational shape: run the hub on a LAN host (or a three-node HA cluster for redundancy), put TLS in front of it for anything beyond a home LAN, distribute client tokens over a trusted channel.

    Start here: Multi-machine setup for the deployment, Team knowledge bus for the day-to-day team workflow, then HA cluster if you need redundancy.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#identity-projects-not-users","level":2,"title":"Identity: Projects, Not Users","text":"

    The hub has no concept of users. Its unit of identity is the project. ctx connection register binds a hub token to a project directory, not to a person. Two developers working on the same project share either:

    • The same .connect.enc, copied between machines over a trusted channel, or
    • Different project names (alpha@laptop-a, alpha@laptop-b), because the hub rejects duplicate registrations of the same project name.

    Either works; neither gives you per-human attribution. If you need \"who wrote this,\" the hub is the wrong tool.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#when-not-to-use-it","level":2,"title":"When Not to Use It","text":"
    • Solo, single-project work. Local .context/ files are enough. The hub adds operational surface for no payoff.
    • Untrusted participants. The hub assumes everyone with a client token is friendly. It is not hardened against hostile insiders or compromised tokens.
    • Compliance-sensitive environments. There is no audit trail that can prove who published what, only which project published what, and Origin is self-asserted.
    • Secrets or PII. Entry content is stored plaintext on the hub and fanned out to every subscribed client. Don't publish anything you wouldn't paste in a team chat.
    • Wholesale journal sharing. See \"what does not flow\" above. If that's what you want, this feature won't provide it. Talk to us in the issue tracker about what would.
    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#how-entries-reach-your-agent","level":2,"title":"How Entries Reach Your Agent","text":"

    Once a project is registered and subscribed, entries arrive by three mechanisms:

    1. ctx connection sync: an on-demand pull, replays everything new since the last sequence you saw.
    2. ctx connection listen: a long-lived gRPC stream that writes new entries to .context/hub/ as they arrive.
    3. check-hub-sync hook: runs at session start, daily throttled, so most users never call sync manually.

    Once entries exist in .context/hub/, ctx agent --include-hub adds a dedicated tier to the budget-aware context packet, scored by recency and type relevance. That's the end of the pipeline.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#where-to-go-next","level":2,"title":"Where to Go Next","text":"If you're… Read Trying it for yourself on one machine Getting Started A solo developer using the hub day-to-day Personal cross-project brain Setting up for a small team on a LAN Multi-machine setup A small team using the hub day-to-day Team knowledge bus Running redundant nodes HA cluster Operating a hub in production Operations Assessing the security posture Security model Debugging a hub in trouble Failure modes Just reading the commands ctx connect, ctx serve, ctx hub","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-personal/","level":1,"title":"Personal Cross-Project Brain","text":"","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#personal-cross-project-brain","level":1,"title":"Personal Cross-Project Brain","text":"

    This recipe shows how one developer uses a ctx Hub across their own projects day-to-day, the \"Story 1\" shape from the Hub overview. You're not setting up infrastructure for a team; you're making a lesson you learned last Tuesday in project A automatically surface when you open project B next Thursday.

    Prerequisites: a working ctx Hub on localhost (see Getting Started for the roughly five-minute setup). This recipe assumes the hub is already running and you've registered at least two projects.

    Activate Each Project First

    Run eval \"$(ctx activate)\" after each cd <project> (or wire it into direnv). The hub server (ctx hub start, etc.) runs on the server and doesn't need this; the commands in this recipe (ctx add --share, ctx agent --include-hub, ctx connection ...) live inside a project and do. If you skip the eval, they'll fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#the-core-loop","level":2,"title":"The Core Loop","text":"

    Every day, the same three verbs matter:

    1. Record: notice a decision, learning, or convention and capture it with ctx add --share.
    2. Subscribe: every project you care about is subscribed to the types you want delivered (set once with ctx connection subscribe).
    3. Load: your agent picks up shared entries on next session start via the auto-sync hook, or explicitly via ctx agent --include-hub.

    That's the whole workflow. The rest of this recipe fills in the concrete moments where each verb matters.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#a-realistic-day","level":2,"title":"A Realistic Day","text":"

    You have three projects on your workstation:

    • ~/projects/api, a Go service you're actively developing
    • ~/projects/cli, a companion CLI that consumes the API
    • ~/projects/dotfiles, your personal conventions and cross-project learnings

    All three are registered with a single hub running on localhost:9900 (started once at boot, or via a systemd user unit; see Hub operations). All three subscribe to decision, learning, and convention.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#0900-start-work-on-api","level":3,"title":"09:00 - Start Work on api","text":"

    You cd ~/projects/api and start a Claude Code session. Behind the scenes, the plugin's PreToolUse hook calls ctx agent --budget 8000 --include-hub before the first tool call. Agent loads:

    • Local .context/ (TASKS, DECISIONS, LEARNINGS, etc.)
    • Foundation steering files (always-inclusion)
    • Everything you've shared from the other two projects

    So the \"use UTC timestamps everywhere\" decision you recorded in dotfiles last week is already in Claude's context for this session, without any manual sync.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1030-you-discover-a-gotcha","level":3,"title":"10:30 - You Discover a Gotcha","text":"

    While debugging, you find that the API's retry loop silently drops the last error when the transport times out. This is the kind of thing you'd normally add to LEARNINGS.md in api/. But it's useful across every Go service you'll ever write, not just this one. So:

    ctx learning add --share \\\n  --context \"Go http.Client retries mask the final error\" \\\n  --lesson  \"Transport timeouts don't surface as errors when the retry loop re-assigns err without wrapping. Check for context.DeadlineExceeded on the request context instead.\" \\\n  --application \"Any retry loop over http.Client.Do that uses a per-attempt timeout\"\n

    The --share flag does two things:

    1. Writes the learning to api/.context/LEARNINGS.md locally (as a normal ctx learning add would).
    2. Publishes the same entry to the ctx Hub, which stores it in the append-only JSONL and fans it out to every subscribed client.

    Within seconds, cli/.context/hub/learnings.md and dotfiles/.context/hub/learnings.md both contain a copy of this learning (the ctx connection listen daemon picks it up from the ctx Hub's Listen stream).

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1200-you-switch-to-cli","level":3,"title":"12:00 - You Switch to cli","text":"

    cd ~/projects/cli, open a new session. The agent packet for cli now includes the learning you just recorded in api, because cli is subscribed to learning and the entry has already been synced into cli/.context/hub/learnings.md.

    You don't have to re-explain the retry-loop gotcha. Claude already sees it.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1400-you-codify-a-convention","level":3,"title":"14:00 - You Codify a Convention","text":"

    You've been writing error messages in api and decided you want a consistent pattern: lowercase start, no trailing period, single-sentence. This is a convention, not a decision; it applies to every Go project you touch. Record it in dotfiles (since that's your \"personal standards\" project), and share it:

    cd ~/projects/dotfiles\nctx convention add --share \\\n  \"Error messages: lowercase start, no trailing period, single sentence (follows Go's stdlib style)\"\n

    The convention lands in dotfiles/CONVENTIONS.md locally and fans out to api and cli via the hub. The next Claude Code session in either project gets the convention injected into the steering-adjacent slot of the agent packet.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1630-end-of-day","level":3,"title":"16:30 - End of Day","text":"

    You didn't run ctx connection sync once. You didn't git push anything between projects. You didn't remember to tell your agent about the retry-loop gotcha in the new project. The hub did all of it for you.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-the-workflow-actually-looks-like","level":2,"title":"What the Workflow Actually Looks Like","text":"

    Stripped of prose, the day's commands were:

    # Morning: nothing. Agent loads --include-hub automatically.\n\n# Mid-morning: record a learning that should cross projects\nctx learning add --share \\\n  --context \"...\" --lesson \"...\" --application \"...\"\n\n# Afternoon: codify a convention in the \"standards\" project\nctx convention add --share \"...\"\n\n# Evening: nothing. Everything's already propagated.\n

    The hub is passive infrastructure. You never talk to it directly; you talk through it by using --share on commands you were already running.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#tips-for-solo-use","level":2,"title":"Tips for Solo Use","text":"

    Pick a \"standards\" project. One of your projects should play the role of \"canonical source for rules you want everywhere.\" Your dotfiles, a personal scratch repo, or a dedicated ctx-standards project all work. Record cross-cutting conventions there and let the hub propagate them to everything else.

    Subscribe to task only if you want cross-project todos. The four subscribable types are decision, learning, convention, task. Tasks are usually project-local; subscribing makes every hub-shared task from every project show up in every other project's agent packet. That's probably not what you want. Skip task in ctx connection subscribe unless you have a specific reason.

    Run the hub as a user-level daemon so you don't have to remember to start it. On Linux with systemd:

    # ~/.config/systemd/user/ctx-hub.service\n[Unit]\nDescription=ctx Hub (personal)\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/ctx hub start\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n
    systemctl --user enable --now ctx-hub.service\n

    Don't overthink subscription filters. For personal use, subscribe every project to all four types at first (or three, if you skip task). Tune later if the context packets get noisy.

    Local storage is fine; no TLS needed. The hub runs on localhost. No one else is on the network. Skip the TLS setup from the Multi-machine recipe; it's relevant when the hub is on a LAN host serving multiple workstations, not when it's a personal daemon.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"

    Not a setup guide. For the one-time hub install and project registration, use Getting Started.

    Not a team guide. If you're sharing across humans, not just across your own projects, read Team knowledge bus instead; the trust model and operational concerns are different.

    Not production operations. For backup, log rotation, failure recovery, and HA, see Hub operations and Hub failure modes.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#see-also","level":2,"title":"See Also","text":"
    • Hub overview: when to use the Hub and when not to.
    • Team knowledge bus: the multi-human companion recipe.
    • ctx connect: the client-side commands used above (subscribe, publish, sync, listen, status).
    • ctx add: the --share flag reference.
    • ctx hub: operator commands for starting, stopping, and inspecting the hub.
    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-team/","level":1,"title":"Team Knowledge Bus","text":"","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#team-knowledge-bus","level":1,"title":"Team Knowledge Bus","text":"

    This recipe shows how a small trusted team uses a ctx Hub as a shared knowledge bus, the \"Story 2\" shape from the Hub overview. You're not building a wiki, you're not replacing your issue tracker, and you're not running a multi-tenant service. You're connecting 3-10 developers who trust each other so that lessons, decisions, and conventions flow between them without ceremony.

    Prerequisites:

    • A running ctx Hub on a LAN host or internal server everyone on the team can reach. See Multi-machine setup for the deployment guide.
    • Each team member has ctx installed and has ctx connection register-ed their working projects with the hub.
    • Each project on each workstation has been activated for the shell with eval \"$(ctx activate)\". The hub server (ctx hub start, etc.) doesn't need this — but the client side (ctx connection ..., ctx add --share) lives in a project and does. If you skip activation, those client commands fail with Error: no context directory specified. See Activating a Context Directory.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#trust-model-read-this-first","level":2,"title":"Trust Model: Read This First","text":"

    The hub assumes everyone holding a client token is friendly. There's no per-user attribution you can rely on, no read ACL beyond subscription filters, and Origin is self-asserted by the publishing client. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.

    If your team is:

    • ✅ 3-10 engineers, all known to each other, all trusted with production access
    • ✅ On a single internal network or behind a VPN
    • ✅ Comfortable with \"the hub assumes friendly participants\"

    …this recipe fits. If your team is:

    • ❌ Larger than ~15, with turnover
    • ❌ Includes contractors, untrusted agents, or compromised-workstation concerns
    • ❌ Needs audit trails that prove who published what
    • ❌ Requires per-team-member isolation

    …you're in \"Story 3\" territory, which the hub does not support today. Use a wiki or a dedicated knowledge platform instead.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#the-teams-three-verbs","level":2,"title":"The Team's Three Verbs","text":"

    Everyone on the team does three things, same as in the personal recipe, but with different social expectations:

    1. Record: when you learn something that would save a teammate time, capture it with ctx add --share.
    2. Subscribe: every engineer's project directories subscribe to the types the team cares about.
    3. Load: agents pick up shared entries automatically via the auto-sync hook and the --include-hub flag in the PreToolUse hook pipeline.

    The operational shape is identical to solo use. What's different is the culture around publishing: when do you --share, and what belongs on the hub vs. in your local .context/.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-goes-on-the-hub-team-rules-of-thumb","level":2,"title":"What Goes on the Hub (Team Rules of Thumb)","text":"

    Share it if it's true for more than one person. The central question: \"would the next teammate who hits this problem save time if they already knew this?\" If yes, --share. If no, record it locally and move on.

    Decisions:

    • ✅ Cross-service decisions (database choice, auth model, deployment pattern, monitoring stack).
    • ✅ Policy decisions that apply to all services (naming, API versioning, error-message format).
    • ❌ Internal implementation decisions inside a single service (\"chose a map over a slice here because lookups dominate\").
    • ❌ One-off tactical calls for a specific PR.

    Learnings:

    • ✅ Gotchas, surprising behavior, flaky infrastructure quirks, anything you'd tell a teammate over coffee with \"watch out for X\".
    • ✅ Lessons from incidents, right after the postmortem is the highest-value time to share.
    • ❌ Internal debugging notes that only make sense with context from your current branch.

    Conventions:

    • ✅ Repo layout, commit message format, pre-commit hooks, review expectations.
    • ✅ Language-level style decisions that apply across services.
    • ❌ Per-service idioms (\"in billing/ we prefer…\").

    Tasks: almost always project-local. Don't subscribe to task unless the team has a specific reason (e.g., a cross-cutting migration you want visible everywhere).

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#a-realistic-week","level":2,"title":"A Realistic Week","text":"

    Monday, 3 AM incident, shared learning

    On-call engineer Alice gets paged: the payment service starts returning 500s after a dependency update. After an hour she finds the culprit: a breaking change in a transitive gRPC dep that only manifests under high concurrency. Postmortem on Tuesday, but right now she records the learning:

    ctx learning add --share \\\n  --context \"Payment service 3 AM incident, 2026-04-03\" \\\n  --lesson  \"grpc-go v1.62+ changes DialContext behavior under high \\\n  concurrency: connections from a single channel can deadlock if the \\\n  server emits GOAWAY mid-stream. Symptom: 500 errors cluster in \\\n  30s bursts, no error in grpc client logs.\" \\\n  --application \"Any service on grpc-go. Pin to v1.61 or patch with \\\n  keepalive: https://github.com/grpc/grpc-go/issues/...\" \n

    By Tuesday morning, every other engineer's agent context packet contains this learning. When Bob starts work on the ledger service (which also uses grpc-go), his Claude Code session already knows about the gotcha without Bob having to read the incident channel.

    Wednesday, cross-service decision

    The team agrees on a new pattern for API versioning: header-based instead of URL-based. Platform lead Carol records the decision:

    ctx decision add --share \\\n  --context \"Need consistent API versioning across all 6 services. \\\n  Current URL-based /v1/ isn't working for gradual rollouts.\" \\\n  --rationale \"Header-based versioning lets us route by header at the \\\n  edge, which makes canary rollouts trivial. URL-based versioning \\\n  forces clients to update their paths.\" \\\n  --consequence \"All new endpoints use X-API-Version header. \\\n  Existing /v1/ endpoints stay. Deprecation schedule in q3.\" \\\n  \"Use header-based API versioning for new endpoints\"\n

    Every engineer's next session knows about this decision automatically. When Dave starts adding endpoints to the inventory service on Thursday, Claude already prompts him for the header pattern instead of defaulting to /v1/.

    Friday, convention drift caught at review

    Dave notices that his PR auto-formatted some error messages to end with periods. He recalls the team convention is \"no trailing period\" but can't remember where it was documented. He runs ctx connection status, sees the hub is healthy, greps his local .context/hub/conventions.md, and finds:

    ## [2026-03-12] Error message format\nLowercase start, no trailing period, single sentence.\n

    He fixes the PR. No lookup on the wiki, no question in chat, no context-switch penalty.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#workflow-tips-for-teams","level":2,"title":"Workflow Tips for Teams","text":"

    Designate a \"champion\" for decisions. The team lead or platform engineer should be the person who explicitly --shares cross-cutting decisions. Other team members share learnings freely but should ask \"should this be a decision?\" in review before --shareing a decision. This keeps the decision stream signal-rich.

    Publish postmortem learnings immediately, not after the meeting. The postmortem itself is a document; the actionable rules that come out of it belong on the hub, and they should land within an hour of the incident. \"Share fast, edit later\" is the rule.

    Delete noisy entries, don't tolerate them. The hub is append-only, but the .context/hub/ mirror on each client is just markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate entries.jsonl (see Hub operations). Noisy shared feeds lose trust fast.

    Don't subscribe every project to every type. For backend engineers, subscribing to decision + learning + convention is usually right. For platform or DevOps projects, adding task makes sense. For a prototype or experiment project, subscribing only to convention might be enough.

    Run a single hub, not one per team. If two teams need to share knowledge, they should share a hub. Splitting hubs by team creates silos, which is often exactly the thing you were trying to solve.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#operational-concerns","level":2,"title":"Operational Concerns","text":"

    The team recipe assumes someone owns the hub host. That person (or a small group) is responsible for:

    • Uptime: the hub is infrastructure; treat it like any other internal service you run. See Hub operations.
    • Backups: entries.jsonl is the source of truth. Snapshot it to the same backup tier as your other internal data.
    • Upgrades: cadence the team agrees on. Major upgrades may require everyone to re-register, so do them at natural breaks.
    • Failures: see Hub failure modes for the standard oncall playbook.

    Optional but recommended: run a 3-node Raft cluster so the hub survives individual node failures. See HA cluster. For teams under 10 people, a single-node hub with daily backups is usually fine.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#token-management","level":2,"title":"Token Management","text":"

    Every team member has a client token stored in their .context/.connect.enc. Rules of thumb:

    • One token per engineer per project. Not one token per team; not one shared token. Each engineer registers each of their working projects separately.
    • Token compromise = revoke immediately. When an engineer leaves, their tokens should be removed from clients.json on the hub. This is a manual operation today; see Hub security for the revocation steps.
    • No checked-in tokens. .context/.connect.enc is encrypted with the local machine key, but don't push it to shared repos; it's per-workstation.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"

    Not a wiki replacement. The hub is for structured entries, not prose. Put your architecture overviews, onboarding docs, and design discussions in a real wiki.

    Not an audit log. Origin on the hub is self-asserted. If compliance requires provenance, the hub is the wrong tool.

    Not a ticket system. Task sharing works, but mature teams already have Jira/Linear/Github Issues. Don't try to replace those with hub tasks; use the hub for lightweight cross-project todos that your existing tracker doesn't capture well.

    Not a production service for end users. This is internal team infrastructure. Do not expose the hub to customers, partners, or the open internet.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#see-also","level":2,"title":"See Also","text":"
    • Hub overview: when to use the hub and when not to.
    • Personal cross-project brain: the single-developer companion recipe.
    • Multi-machine setup: standing up the hub on a LAN host.
    • HA cluster: optional redundancy for larger teams.
    • Hub operations: backup, rotation, monitoring.
    • Hub security: threat model and hardening checklist.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/import-plans/","level":1,"title":"Importing Claude Code Plans","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code plan files (~/.claude/plans/*.md) are ephemeral: They have structured context, approach, and file lists, but they're orphaned after the session ends. The filenames are UUIDs, so you can't tell what's in them without opening each one.

    How do you turn a useful plan into a permanent project spec?

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tldr","level":2,"title":"TL;DR","text":"
    You: /ctx-plan-import\nAgent: [lists plans with dates and titles]\n       1. 2026-02-28  Add authentication middleware\n       2. 2026-02-27  Refactor database connection pool\nYou: \"import 1\"\nAgent: [copies to specs/add-authentication-middleware.md]\n

    Plans are copied (not moved) to specs/, slugified by their H1 heading.

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-plan-import Skill List, filter, and import plan files to specs /ctx-task-add Skill Optionally add a task referencing the spec","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-1-list-available-plans","level":3,"title":"Step 1: List Available Plans","text":"

    Invoke the skill and it lists plans with modification dates and titles:

    You: /ctx-plan-import\n\nAgent: Found 3 plan files:\n         1. 2026-02-28  Add authentication middleware\n         2. 2026-02-27  Refactor database connection pool\n         3. 2026-02-25  Import plans skill\n       Which plans would you like to import?\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-2-filter-optional","level":3,"title":"Step 2: Filter (Optional)","text":"

    You can narrow the list with arguments:

    Argument Effect --today Only plans modified today --since YYYY-MM-DD Only plans modified on or after the date --all Import everything without prompting (none) Interactive selection
    You: /ctx-plan-import --today\nYou: /ctx-plan-import --since 2026-02-27\nYou: /ctx-plan-import --all\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-3-select-and-import","level":3,"title":"Step 3: Select and Import","text":"

    Pick one or more plans by number:

    You: \"import 1 and 3\"\n\nAgent: Imported 2 plan(s):\n         ~/.claude/plans/abc123.md -> specs/add-authentication-middleware.md\n         ~/.claude/plans/ghi789.md -> specs/import-plans-skill.md\n       Want me to add tasks referencing these specs?\n

    The agent reads the H1 heading from each plan and slugifies it for the filename. If a plan has no H1 heading, the original filename (minus extension) is used as the slug.

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-4-add-follow-up-tasks-optional","level":3,"title":"Step 4: Add Follow-Up Tasks (Optional)","text":"

    If you say yes, the agent creates tasks in TASKS.md that reference the imported specs:

    You: \"yes, add tasks\"\n\nAgent: [runs /ctx-task-add for each spec]\n       Added:\n         - [ ] Implement authentication middleware (spec: specs/add-authentication-middleware.md)\n         - [ ] Import plans skill (spec: specs/import-plans-skill.md)\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember the exact skill name:

    You say What happens \"import my plans\" /ctx-plan-import (interactive) \"save today's plans as specs\" /ctx-plan-import --today \"import all plans from this week\" /ctx-plan-import --since ... \"turn that plan into a spec\" /ctx-plan-import (filtered)","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tips","level":2,"title":"Tips","text":"
    • Plans are copied, not moved: The originals stay in ~/.claude/plans/. Claude Code manages that directory; ctx doesn't delete from it.
    • Conflict handling: If specs/{slug}.md already exists, the agent asks whether to overwrite or pick a different name.
    • Specs are project memory: Once imported, specs are tracked in git and available to future sessions. Reference them from TASKS.md phase headers with Spec: specs/slug.md.
    • Pair with /ctx-implement: After importing a plan as a spec, use /ctx-implement to execute it step-by-step with verification.
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: /ctx-plan-import: full skill description
    • The Complete Session: where plan import fits in the session flow
    • Tracking Work Across Sessions: managing tasks that reference imported specs
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/knowledge-capture/","level":1,"title":"Persisting Decisions, Learnings, and Conventions","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-problem","level":2,"title":"The Problem","text":"

    You debug a subtle issue, discover the root cause, and move on.

    Three weeks later, a different session hits the same issue. The knowledge existed briefly in one session's memory but was never written down.

    Architectural decisions suffer the same fate: you weigh trade-offs, pick an approach, and six sessions later the AI suggests the alternative you already rejected.

    How do you make sure important context survives across sessions?

    Prefer Skills to Raw Commands

    Use /ctx-decision-add and /ctx-learning-add instead of raw ctx add commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx add ... / ctx reindex / ctx decision ... / ctx learning ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-reflect               # surface items worth persisting\n/ctx-decision-add \"Title\"  # record with context/rationale/consequence\n/ctx-learning-add \"Title\"  # record with context/lesson/application\n

    Or just tell your agent: \"What have we learned this session?\"

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx decision add Command Record an architectural decision ctx learning add Command Record a gotcha, tip, or lesson ctx convention add Command Record a coding pattern or standard ctx reindex Command Rebuild both quick-reference indices ctx decision reindex Command Rebuild the DECISIONS.md index ctx learning reindex Command Rebuild the LEARNINGS.md index /ctx-decision-add Skill AI-guided decision capture with validation /ctx-learning-add Skill AI-guided learning capture with validation /ctx-convention-add Skill AI-guided convention recording with placement /ctx-reflect Skill Surface items worth persisting at breakpoints","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-workflow","level":2,"title":"The Workflow","text":"

    Decision, Learning, or Convention?

    • If you chose between alternatives, it is a decision.
    • If you discovered something surprising, it is a learning.
    • If you are codifying a repeated pattern, it is a convention.
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-1-understand-what-to-persist","level":3,"title":"Step 1: Understand What to Persist","text":"

    Three context files serve different purposes:

    Decisions (DECISIONS.md) answer \"why is it this way?\" They record trade-offs between alternatives with structured fields: context, rationale, and consequences. Decisions prevent re-debating settled questions.

    Learnings (LEARNINGS.md) answer \"what did we discover the hard way?\" They record gotchas and debugging insights specific to this project with structured fields: context, lesson, and application.

    Learnings prevent repeating past mistakes.

    Conventions (CONVENTIONS.md) answer \"how do we do things here?\" They record patterns and standards. No structured fields required: just a name, a rule, and an example. Conventions keep code consistent across sessions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-2-record-decisions","level":3,"title":"Step 2: Record Decisions","text":"

    Decisions have three structured fields:

    • context,
    • rationale,
    • and consequences.

    The easiest way to record one is to tell your agent:

    You: \"Save that as a decision: we picked file-based cooldown\n      tokens over env vars.\"\n\nAgent: \"Recorded in DECISIONS.md:\n  - Decision: Use file-based cooldown tokens instead of env vars\n  - Context: Hook subprocesses cannot persist env vars to parent shell\n  - Rationale: File tokens survive across processes. Simpler than\n    IPC. Cleanup is automatic via TTL.\n  - Consequence: Tombstone files accumulate in /tmp. Cannot share\n    state across machines.\"\n

    The agent was present for the trade-off discussion. It fills in the structured fields from what it observed, which is hard to reconstruct later as a terminal one-liner.

    When multiple options were considered, the agent includes rejected alternatives in the rationale automatically.

    The /ctx-decision-add skill also guides you through the fields interactively. For quick decisions, it supports a Y-statement:

    \"In the context of [situation], facing [constraint], we decided for [choice] and against [alternatives], to achieve [benefit], accepting that [trade-off].\"

    CLI Command for Scripting and Automation

    When no agent is in the loop (CI pipelines, shell scripts, hooks), use the CLI directly with structured flags:

    ctx decision add \"Use file-based cooldown tokens instead of env vars\" \\\n  --context \"Hook subprocesses cannot persist env vars to parent shell\" \\\n  --rationale \"File tokens survive across processes. Simpler than IPC. Cleanup is automatic via TTL.\" \\\n  --consequence \"Tombstone files accumulate in /tmp. Cannot share state across machines.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-3-record-learnings","level":3,"title":"Step 3: Record Learnings","text":"

    Learnings have three structured fields: context, lesson, and application.

    Again, your agent is the best tool for this. It was there when you discovered the gotcha and can express it more completely than a backslash-escaped terminal command:

    You: \"That subprocess thing was painful. Save it as a learning.\"\n\nAgent: \"Added to LEARNINGS.md:\n  - Learning: Claude Code hooks run in a subprocess\n  - Context: Set env var in PreToolUse hook, but it was not visible\n    in the main session\n  - Lesson: Hook scripts execute in a child process. Env changes do\n    not propagate to parent.\n  - Application: Use tombstone files for hook-to-session\n    communication. Never rely on hook env vars.\"\n

    You said one sentence. The agent produced a structured entry with all three fields filled from the session context.

    That is the point: the agent has the full conversation and can be far more expressive than what you would type at a command prompt.

    The /ctx-learning-add skill applies three quality filters:

    1. Could someone Google this in 5 minutes?
    2. Is it specific to this codebase?
    3. Did it take real effort to discover?

    All three must pass.

    Learnings capture principles and heuristics, not code snippets.

    CLI Command for Scripting and Automation

    When no agent is in the loop:

    ctx learning add \"Claude Code hooks run in a subprocess\" \\\n  --context \"Set env var in PreToolUse hook, but it was not visible in the main session\" \\\n  --lesson \"Hook scripts execute in a child process. Env changes do not propagate to parent.\" \\\n  --application \"Use tombstone files for hook-to-session communication. Never rely on hook env vars.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-4-record-conventions","level":3,"title":"Step 4: Record Conventions","text":"

    Conventions are simpler: just a name, a rule, and optionally a section.

    These are short enough that either approach works:

    You: \"We've been using kebab-case for every CLI flag. Codify that.\"\n\nAgent: \"Added to CONVENTIONS.md under Naming:\n  'Use kebab-case for all CLI flag names.'\"\n

    Or from the terminal:

    ctx convention add \"Use kebab-case for all CLI flag names\" --section \"Naming\"\n

    Conventions work best for rules that come up repeatedly. Codify a pattern the third time you see it, not the first.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-5-reindex-after-manual-edits","level":3,"title":"Step 5: Reindex After Manual Edits","text":"

    DECISIONS.md and LEARNINGS.md maintain a quick-reference index at the top: a compact table of date and title for each entry. The index updates automatically via ctx add, but falls out of sync after hand edits.

    ctx reindex\n

    This single command regenerates both indices. You can also reindex individually with ctx decision reindex or ctx learning reindex.

    Run reindex after any manual edit. The index lets AI tools scan all entries without reading the full file, which matters when token budgets are tight.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-6-use-ctx-reflect-to-surface-what-to-capture","level":3,"title":"Step 6: Use /ctx-reflect to Surface What to Capture","text":"

    Keep It Conversational

    /ctx-reflect is not the only way to trigger reflection.

    Agents trained on the ctx playbook naturally surface persist-worthy items at breakpoints, even without invoking the skill explicitly.

    A conversational prompt like \"anything worth saving?\" or \"let's wrap up\" can trigger the same review.

    The skill provides a structured checklist, but the behavior is available through natural conversation.

    At natural breakpoints (after completing a feature, fixing a bug, or before ending a session) use /ctx-reflect to identify items worth persisting.

    /ctx-reflect\n

    The skill walks through learnings, decisions, tasks, and session notes, skipping categories with nothing to report. The output includes specific commands for each suggested persist:

    This session implemented file-based cooldown for `ctx agent` and\ndiscovered that hook subprocesses cannot set env vars in the parent.\n\nI'd suggest persisting:\n- **Learning**: Hook subprocesses cannot propagate env vars\n  `ctx learning add \"...\" --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Decision**: File-based cooldown tokens over env vars\n  `ctx decision add \"...\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n

    The skill always asks before persisting.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-7-the-conversational-approach","level":3,"title":"Step 7: The Conversational Approach","text":"

    The commands shown in Steps 2 to 4 are precise, but you rarely need to type them yourself. When working with an agent that has loaded the ctx playbook, natural language triggers the same operations.

    You ask, the agent acts:

    You say What the agent does \"What have we learned this session?\" Reviews recent work, identifies learnings, offers to persist \"Save that as a decision\" Captures the trade-off you just discussed with structured fields \"Any gotchas worth remembering?\" Scans recent work for non-obvious lessons \"Record that convention\" Adds the pattern you just agreed on to CONVENTIONS.md \"Let's wrap up\" Runs through the reflect checklist conversationally

    Example exchange:

    You: \"What did we learn from that debugging session?\"\n\nAgent: \"We discovered three things worth persisting:\n  1. Hook subprocesses can't propagate env vars to the parent. We\n     spent 20 minutes on this before switching to tombstone files.\n  2. Tombstone files need a TTL or they accumulate in /tmp.\n  3. The cooldown logic must check file mtime, not just existence.\n  Want me to save these as learnings?\"\n\nYou: \"Yes, all of them.\"\n\nAgent: \"Done. Added all three to LEARNINGS.md with structured fields.\"\n

    The agent can also be proactive without being asked. The playbook encourages agents to persist context at natural milestones.

    You may see:

    • After fixing a tricky bug: \"I noticed this was a non-obvious issue. Want me to save it as a learning?\"
    • After choosing between approaches: \"We just made a trade-off between file-based tokens and IPC. Should I record this as a decision?\"
    • At session milestones: the agent checks what is worth persisting and offers a brief summary.

    When agents run unattended (no human in the loop), they may persist directly:

    • \"I've added the subprocess env var gotcha to LEARNINGS.md so we don't hit it again.\"
    • \"Recorded the decision to use file-based tokens over env vars in DECISIONS.md.\"
    • \"Marked the cooldown task done and added a follow-up for TTL cleanup.\"

    This is behavior by design.

    The playbook's self-check prompt, \"If this session ended right now, would the next session know what happened?\" drives agents to persist early and often rather than waiting for explicit instructions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#command-line-approach-scripting-and-automation","level":3,"title":"Command-Line Approach (Scripting and Automation)","text":"
    # Decision: record the trade-off\nctx decision add \"Use PostgreSQL over SQLite\" \\\n  --context \"Need concurrent multi-user access\" \\\n  --rationale \"SQLite locks on writes; Postgres handles concurrency\" \\\n  --consequence \"Requires a database server; team needs Postgres training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Learning: record the gotcha\nctx learning add \"SQL migrations must be idempotent\" \\\n  --context \"Deploy failed when migration ran twice after rollback\" \\\n  --lesson \"CREATE TABLE without IF NOT EXISTS fails on retry\" \\\n  --application \"Always use IF NOT EXISTS guards in migrations\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Convention: record the pattern\nctx convention add \"API handlers return structured errors\" --section \"API\"\n\n# Reindex after manual edits\nctx reindex\n\n# Reflect at breakpoints (from AI assistant)\n# /ctx-reflect\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#conversational-approach-interactive-sessions","level":3,"title":"Conversational Approach (Interactive Sessions)","text":"
    You: \"We just picked Postgres over SQLite for the concurrency\n      reasons. Save that?\"\n\nAgent: \"Recorded. Added to DECISIONS.md:\n  - Decision: Use PostgreSQL over SQLite\n  - Context: Need concurrent multi-user access\n  - Rationale: SQLite locks on writes; Postgres handles concurrency\n  - Consequence: Requires a database server; team needs Postgres\n    training\"\n\nYou: \"Also that migration thing. The deploy blew up when the\n      migration ran twice.\"\n\nAgent: \"Got it. Added to LEARNINGS.md:\n  - Learning: SQL migrations must be idempotent\n  - Lesson: CREATE TABLE without IF NOT EXISTS fails on retry\n  - Application: Always use IF NOT EXISTS guards in migrations\"\n\nYou: \"Good. Anything else worth saving before we wrap up?\"\n\nAgent: \"We've been using structured error returns in every API\n  handler this session. Want me to codify that as a convention?\"\n\nYou: \"Do it.\"\n\nAgent: \"Done. Added 'API handlers return structured errors' to\n  CONVENTIONS.md under the API section.\"\n

    Both approaches produce the same structured entries in the same context files.

    • The conversational approach is the natural fit for interactive sessions;
    • the CLI commands are better suited for scripts, hooks, and automation pipelines.
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tips","level":2,"title":"Tips","text":"
    • Record decisions at the moment of choice. The alternatives you considered and the reasons you rejected them fade quickly. Capture trade-offs while they are fresh.
    • Learnings should fail the Gemini test. If someone could find it in a 5-minute Gemini search, it does not belong in LEARNINGS.md.
    • Conventions earn their place through repetition. Add a convention the third time you see a pattern, not the first.
    • Use /ctx-reflect at natural breakpoints. The checklist catches items you might otherwise lose.
    • Keep the entries self-contained. Each entry should make sense on its own. A future session may load only one due to token budget constraints.
    • Reindex after every hand edit. It takes less than a second. A stale index causes AI tools to miss entries.
    • Prefer the structured fields. The verbosity forces clarity. A decision without a rationale is just a fact. A learning without an application is just a story.
    • Talk to your agent, do not type commands. In interactive sessions, the conversational approach is the recommended way to capture knowledge. Say \"save that as a learning\" or \"any decisions worth recording?\" and let the agent handle the structured fields. Reserve the CLI commands for scripting, automation, and CI/CD pipelines where there is no agent in the loop.
    • Trust the agent's proactive instincts. Agents trained on the ctx playbook will offer to persist context at milestones. A brief \"want me to save this?\" is cheaper than re-discovering the same lesson three sessions later.
    • Relax provenance per-project if --session-id, --branch, or --commit are impractical (e.g., manual notes outside an AI session). Add to .ctxrc:

      provenance_required:\n  session_id: false   # allow entries without --session-id\n  branch: true        # still require --branch\n  commit: true        # still require --commit\n

      Default is all three required. Only human config relaxes: Agents cannot bypass, and that's by design.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#next-up","level":2,"title":"Next Up","text":"

    Tracking Work Across Sessions →: Add, prioritize, complete, and archive tasks across sessions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#see-also","level":2,"title":"See Also","text":"
    • Tracking Work Across Sessions: managing the tasks that decisions and learnings support
    • The Complete Session: full session lifecycle including reflection and context persistence
    • Detecting and Fixing Drift: keeping knowledge files accurate as the codebase evolves
    • CLI Reference: full documentation for ctx add, ctx decision, ctx learning
    • Context Files: format and conventions for DECISIONS.md, LEARNINGS.md, and CONVENTIONS.md
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/memory-bridge/","level":1,"title":"Bridging Claude Code Auto Memory","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code maintains per-project auto memory at ~/.claude/projects/<slug>/memory/MEMORY.md. This file is:

    • Outside the repo - not version-controlled, not portable
    • Machine-specific - tied to one ~/.claude/ directory
    • Invisible to ctx - context loading and hooks don't read it

    Meanwhile, ctx maintains structured context files (DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) that are git-tracked, portable, and token-budgeted - but Claude Code doesn't automatically write to them.

    The two systems hold complementary knowledge with no bridge between them.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#tldr","level":2,"title":"TL;DR","text":"
    ctx memory sync          # Mirror MEMORY.md into .context/memory/mirror.md\nctx memory status        # Check for drift\nctx memory diff          # See what changed since last sync\n

    The check-memory-drift hook nudges automatically when MEMORY.md changes - you don't need to remember to sync manually.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx memory ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx memory sync CLI command Copy MEMORY.md to mirror, archive previous ctx memory status CLI command Show drift, timestamps, line counts ctx memory diff CLI command Show changes since last sync ctx memory import CLI command Classify and promote entries to .context/ files ctx memory publish CLI command Push curated .context/ content to MEMORY.md ctx memory unpublish CLI command Remove published block from MEMORY.md ctx system check-memory-drift Hook Nudge when MEMORY.md has changed (once/session)","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#how-it-works","level":2,"title":"How It Works","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#discovery","level":3,"title":"Discovery","text":"

    Claude Code encodes project paths as directory names under ~/.claude/projects/. The encoding replaces / with - and prefixes with -:

    /home/jose/WORKSPACE/ctx  →  ~/.claude/projects/-home-jose-WORKSPACE-ctx/\n

    ctx memory uses this encoding to locate MEMORY.md automatically from your project root - no configuration needed.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#mirroring","level":3,"title":"Mirroring","text":"

    When you run ctx memory sync:

    1. The previous mirror is archived to .context/memory/archive/mirror-<timestamp>.md
    2. MEMORY.md is copied to .context/memory/mirror.md
    3. Sync state is updated in .context/state/memory-import.json

    The mirror is git-tracked, so it travels with the project. Archives provide a fallback for projects that don't use git.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#drift-detection","level":3,"title":"Drift Detection","text":"

    The check-memory-drift hook compares MEMORY.md's modification time against the mirror. When drift is detected, the agent sees:

    ┌─ Memory Drift ────────────────────────────────────────────────\n│ MEMORY.md has changed since last sync.\n│ Run: ctx memory sync\n│ Context: .context\n└────────────────────────────────────────────────────────────────\n

    The nudge fires once per session to avoid noise.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#typical-workflow","level":2,"title":"Typical Workflow","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#at-session-start","level":3,"title":"At Session Start","text":"

    If the hook fires a drift nudge, sync before diving into work:

    ctx memory diff     # Review what changed\nctx memory sync     # Mirror the changes\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#periodic-check","level":3,"title":"Periodic Check","text":"
    ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#dry-run","level":3,"title":"Dry Run","text":"

    Preview what sync would do without writing:

    ctx memory sync --dry-run\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#storage-layout","level":2,"title":"Storage Layout","text":"
    .context/\n├── memory/\n│   ├── mirror.md                          # Raw copy of MEMORY.md (often git-tracked)\n│   └── archive/\n│       ├── mirror-2026-03-05-143022.md    # Timestamped pre-sync snapshots\n│       └── mirror-2026-03-04-220015.md\n├── state/\n│   └── memory-import.json                 # Sync tracking state\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#edge-cases","level":2,"title":"Edge Cases","text":"Scenario Behavior Auto memory not active sync exits 1 with message. status reports \"not active\". Hook skips silently. First sync (no mirror) Creates mirror without archiving. MEMORY.md is empty Syncs to empty mirror (valid). Not initialized Init guard rejects (same as all ctx commands).","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#importing-entries","level":2,"title":"Importing Entries","text":"

    Once you've synced, you can classify and promote entries into structured .context/ files:

    ctx memory import --dry-run    # Preview classification\nctx memory import              # Actually promote entries\n

    Each entry is classified by keyword heuristics:

    Keywords Target always use, prefer, never use, standard CONVENTIONS.md decided, chose, trade-off, approach DECISIONS.md gotcha, learned, watch out, bug, caveat LEARNINGS.md todo, need to, follow up TASKS.md Everything else Skipped

    Entries that don't match any pattern are skipped - they stay in the mirror for manual review. Deduplication (hash-based) prevents re-importing the same entry on subsequent runs.

    Review Before Importing

    Use --dry-run first. The heuristic classifier is deliberately simple - it may misclassify ambiguous entries. Review the plan, then import.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-workflow","level":3,"title":"Full Workflow","text":"
    ctx memory sync                # 1. Mirror MEMORY.md\nctx memory import --dry-run    # 2. Preview what would be imported\nctx memory import              # 3. Promote entries to .context/ files\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#publishing-context-to-memorymd","level":2,"title":"Publishing Context to MEMORY.md","text":"

    Push curated .context/ content back into MEMORY.md so Claude Code sees structured project context on session start - without needing hooks.

    ctx memory publish --dry-run    # Preview what would be published\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter line budget\n

    Published content is wrapped in markers:

    <!-- ctx:published -->\n# Project Context (managed by ctx)\n\n## Pending Tasks\n- [ ] Implement feature X\n...\n<!-- ctx:end -->\n

    Rules:

    • ctx owns everything between the markers
    • Claude owns everything outside the markers
    • ctx memory import reads only outside the markers
    • ctx memory publish replaces only inside the markers

    To remove the published block entirely:

    ctx memory unpublish\n

    Publish at Wrap-Up, Not on Commit

    The best time to publish is during session wrap-up, after persisting decisions and learnings. Never auto-publish - give yourself a chance to review what's going into MEMORY.md.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-bidirectional-workflow","level":3,"title":"Full Bidirectional Workflow","text":"
    ctx memory sync                 # 1. Mirror MEMORY.md\nctx memory import --dry-run     # 2. Check what Claude wrote\nctx memory import               # 3. Promote entries to .context/\nctx memory publish --dry-run    # 4. Check what would be published\nctx memory publish              # 5. Push context to MEMORY.md\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/multi-tool-setup/","level":1,"title":"Setup Across AI Tools","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-problem","level":2,"title":"The Problem","text":"

    You have installed ctx and want to set it up with your AI coding assistant so that context persists across sessions. Different tools have different integration depths. For example:

    • Claude Code supports native hooks that load and save context automatically.
    • Cursor injects context via its system prompt.
    • Aider reads context files through its --read flag.

    This recipe walks through the complete setup for each tool, from initialization through verification, so you end up with a working memory layer regardless of which AI tool you use.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tldr","level":2,"title":"TL;DR","text":"
    cd your-project\nctx init                      # creates .context/\neval \"$(ctx activate)\"        # bind CTX_DIR for this shell\nsource <(ctx completion zsh)  # shell completion (or bash/fish)\n\n# ## Claude Code (automatic after plugin install) ##\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n\n# ## OpenCode ##\nctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n\n# ## Cursor / Aider / Copilot / Windsurf ##\nctx setup cursor # or: aider, copilot, windsurf\n\n# ## Companion tools (highly recommended) ##\nnpx gitnexus analyze          # code knowledge graph\n# Add Gemini Search MCP server for grounded web search\n

    Activate the Project Once Per Shell

    Run eval \"$(ctx activate)\" after ctx init. The ctx setup, ctx init, and ctx completion commands work without it, but if you skip the eval, most others (ctx agent, ctx load, ctx watch, ctx journal ...) fail with Error: no context directory specified. See Activating a Context Directory.

    Create a .ctxrc in your project root to configure token budgets, context directory, drift thresholds, and more.

    Then start your AI tool and ask: \"Do you remember?\"

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx init Create .context/ directory, templates, and permissions ctx setup Generate integration configuration for a specific AI tool ctx agent Print a token-budgeted context packet for AI consumption ctx load Output assembled context in read order (for manual pasting) ctx watch Auto-apply context updates from AI output (non-native tools) ctx completion Generate shell autocompletion for bash, zsh, or fish ctx journal import Import sessions to editable journal Markdown","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-1-initialize-ctx","level":3,"title":"Step 1: Initialize ctx","text":"

    Run ctx init in your project root. This creates the .context/ directory with all template files and seeds ctx permissions in settings.local.json.

    cd your-project\nctx init\n

    This produces the following structure:

    .context/\n  CONSTITUTION.md     # Hard rules the AI must never violate\n  TASKS.md            # Current and planned work\n  CONVENTIONS.md      # Code patterns and standards\n  ARCHITECTURE.md     # System overview\n  DECISIONS.md        # Architectural decisions with rationale\n  LEARNINGS.md        # Lessons learned, gotchas, tips\n  GLOSSARY.md         # Domain terms and abbreviations\n  AGENT_PLAYBOOK.md   # How AI tools should use this system\n

    Using a Different .context Directory

    The .context/ directory doesn't have to live inside your project. Point ctx to an external folder by exporting CTX_DIR (the only declaration channel).

    Useful when context must stay private while the code is public, or when you want to commit notes to a separate repo.

    Caveats (the recipe covers both with workarounds):

    • Code-aware operations degrade silently. ctx sync, ctx drift, and the memory-drift hook read the codebase from dirname(CTX_DIR). With an external .context/, that's the context repo, not your code repo. They scan the wrong tree without erroring. The recipe shows a symlink workaround that keeps both healthy.
    • One .context/ per project, always. Sharing one directory across multiple projects corrupts journals, state, and secrets. For cross-project knowledge sharing (CONSTITUTION, CONVENTIONS, ARCHITECTURE, etc.) use ctx hub, not a shared .context/.

    See External Context for the full recipe and Configuration for the resolver details.

    For Claude Code, install the ctx plugin to get hooks and skills:

    claude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n

    If you only need the core files (useful for lightweight setups), use the --minimal flag:

    ctx init --minimal\n

    This creates only TASKS.md, DECISIONS.md, and CONSTITUTION.md.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-2-generate-tool-specific-hooks","level":3,"title":"Step 2: Generate Tool-Specific Hooks","text":"

    If you are using a tool other than Claude Code (which is configured automatically by ctx init), generate its integration configuration:

    # For Cursor\nctx setup cursor\n\n# For Aider\nctx setup aider\n\n# For GitHub Copilot\nctx setup copilot\n\n# For Windsurf\nctx setup windsurf\n

    Each command prints the configuration you need. How you apply it depends on the tool.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#claude-code","level":4,"title":"Claude Code","text":"

    No action needed. Just install ctx from the Marketplace as ActiveMemory/ctx.

    Claude Code Is a First-Class Citizen

    With the ctx plugin installed, Claude Code gets hooks and skills automatically. The PreToolUse hook runs ctx agent --budget 4000 on every tool call (with a 10-minute cooldown so it only fires once per window).

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#opencode","level":4,"title":"OpenCode","text":"

    Run the one-liner from the project root:

    ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n

    This deploys a lifecycle plugin, slash command skills, AGENTS.md, and registers the ctx MCP server globally. See ctx for OpenCode for full details.

    OpenCode Is a First-Class Citizen

    With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, and persists at session end — no manual steps needed.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#vs-code","level":4,"title":"VS Code","text":"

    Install the ctx extension from the VS Code Marketplace (publisher: activememory). Then, from your project root:

    ctx init && eval \"$(ctx activate)\"\n

    Open Copilot Chat and type @ctx /init to verify. The extension auto-downloads the ctx CLI if it isn't on PATH. See ctx for VS Code for full details.

    VS Code Is a First-Class Citizen

    The extension carries its own runtime. No ctx setup step is needed. It registers a @ctx chat participant with 45 slash commands, automatic hooks (file save, git commit, .context/ change, dependency-file edit), and a reminder status-bar indicator. Unlike embedded harnesses, the extension ships through its own pipeline to the VS Code Marketplace.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#cursor","level":4,"title":"Cursor","text":"

    Add the system prompt snippet to .cursor/settings.json:

    {\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and .context/CONVENTIONS.md before responding. Follow rules in .context/CONSTITUTION.md.\"\n}\n

    Context files appear in Cursor's file tree. You can also paste a context packet directly into chat:

    ctx agent --budget 4000 | xclip    # Linux\nctx agent --budget 4000 | pbcopy   # macOS\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#aider","level":4,"title":"Aider","text":"

    Create .aider.conf.yml so context files are loaded on every session:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n

    Then start Aider normally:

    aider\n

    Or specify files on the command line:

    aider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-3-set-up-shell-completion","level":3,"title":"Step 3: Set Up Shell Completion","text":"

    Shell completion lets you tab-complete ctx subcommands and flags, which is especially useful while learning the CLI.

    # Bash (add to ~/.bashrc)\nsource <(ctx completion bash)\n\n# Zsh (add to ~/.zshrc)\nsource <(ctx completion zsh)\n\n# Fish\nctx completion fish > ~/.config/fish/completions/ctx.fish\n

    After sourcing, typing ctx a<TAB> completes to ctx agent, and ctx journal <TAB> shows list, show, and export.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-4-verify-the-setup-works","level":3,"title":"Step 4: Verify the Setup Works","text":"

    Start a fresh session in your AI tool and ask:

    \"Do you remember?\"

    A correctly configured tool responds with specific context: current tasks from TASKS.md, recent decisions, and previous session topics. It should not say \"I don't have memory\" or \"Let me search for files.\"

    This question checks the passive side of memory. A properly set-up agent is also proactive: it treats context maintenance as part of its job:

    • After a debugging session, it offers to save a learning.
    • After a trade-off discussion, it asks whether to record the decision.
    • After completing a task, it suggests follow-up items.

    The \"do you remember?\" check verifies both halves: recall and responsibility.

    For example, after resolving a tricky bug, a proactive agent might say:

    That Redis timeout issue was subtle. Want me to save this as a *learning*\nso we don't hit it again?\n

    If you see behavior like this, the setup is working end to end.

    In Claude Code, you can also invoke the /ctx-status skill:

    /ctx-status\n

    This prints a summary of all context files, token counts, and recent activity, confirming that hooks are loading context.

    If context is not loading, check the basics:

    Symptom Fix ctx: command not found Ensure ctx is in your PATH: which ctx Hook errors Verify plugin is installed: claude /plugin list Context not refreshing Cooldown may be active; wait 10 minutes or set --cooldown 0","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-5-enable-watch-mode-for-non-native-tools","level":3,"title":"Step 5: Enable Watch Mode for Non-Native Tools","text":"

    Tools like Aider, Copilot, and Windsurf do not support native hooks for saving context automatically. For these, run ctx watch alongside your AI tool.

    Pipe the AI tool's output through ctx watch:

    # Terminal 1: Run Aider with output logged\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch the log for context updates\nctx watch --log /tmp/aider.log\n

    Or for any generic tool:

    your-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    When the AI emits structured update commands, ctx watch parses and applies them automatically:

    <context-update type=\"learning\"\n  context=\"Debugging rate limiter\"\n  lesson=\"Redis MULTI/EXEC does not roll back on error\"\n  application=\"Wrap rate-limit checks in Lua scripts instead\"\n>Redis Transaction Behavior</context-update>\n

    To preview changes without modifying files:

    ctx watch --dry-run --log /tmp/ai.log\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-6-import-session-transcripts-optional","level":3,"title":"Step 6: Import Session Transcripts (Optional)","text":"

    If you want to browse past session transcripts, import them to the journal:

    ctx journal import --all\n

    This converts raw session data into editable Markdown files in .context/journal/. You can then enrich them with metadata using /ctx-journal-enrich-all inside your AI assistant.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Here is the condensed setup for all three tools:

    # ## Common (run once per project) ##\ncd your-project\nctx init\nsource <(ctx completion zsh)       # or bash/fish\n\n# ## Claude Code (automatic, just verify) ##\n# Start Claude Code, then ask: \"Do you remember?\"\n\n# ## OpenCode ##\nctx setup opencode --write\n# Start OpenCode, then ask: \"Do you remember?\"\n\n# ## Cursor ##\nctx setup cursor\n# Add the system prompt to .cursor/settings.json\n# Paste context: ctx agent --budget 4000 | pbcopy\n\n# ## Aider ##\nctx setup aider\n# Create .aider.conf.yml with read: paths\n# Run watch mode alongside: ctx watch --log /tmp/aider.log\n\n# ## Verify any Tool ##\n# Ask your AI: \"Do you remember?\"\n# Expect: specific tasks, decisions, recent context\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tips","level":2,"title":"Tips","text":"
    • Start with ctx init (not --minimal) for your first project. The full template set gives the agent more to work with, and you can always delete files later.
    • For Claude Code, the token budget is configured in the plugin's hooks.json. To customize, adjust the --budget flag in the ctx agent hook command.
    • The --session $PPID flag isolates cooldowns per Claude Code process, so parallel sessions do not suppress each other.
    • Commit your .context/ directory to version control. Several ctx features (journals, changelogs, blog generation) rely on git history.
    • For Cursor and Copilot, keep CONVENTIONS.md visible. These tools treat open files as higher-priority context.
    • Run ctx drift periodically to catch stale references before they confuse the agent.
    • The agent playbook instructs the agent to persist context at natural milestones (completed tasks, decisions, gotchas). In practice, this works best when you reinforce the habit: a quick \"anything worth saving?\" after a debugging session goes a long way.
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#companion-tools-highly-recommended","level":2,"title":"Companion Tools (Highly Recommended)","text":"

    ctx skills can leverage external MCP servers for web search and code intelligence. ctx works without them, but they significantly improve agent behavior across sessions. The investment is small and the benefits compound. Skills like /ctx-code-review, /ctx-explain, and /ctx-refactor all become noticeably better with these tools connected.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gemini-search","level":3,"title":"Gemini Search","text":"

    Provides grounded web search with citations. Used by skills and the agent playbook as the preferred search backend (faster and more accurate than built-in web search).

    Setup: Add the Gemini Search MCP server to your Claude Code settings. See the Gemini Search MCP documentation for installation.

    Verification:

    # The agent checks this automatically during /ctx-remember\n# Manual test: ask the agent to search for something\n

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gitnexus","level":3,"title":"GitNexus","text":"

    Provides a code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Used by skills like /ctx-refactor (impact analysis) and /ctx-code-review (dependency awareness).

    Setup: Add the GitNexus MCP server to your Claude Code settings, then index your project:

    npx gitnexus analyze\n

    Verification:

    # The agent checks this automatically during /ctx-remember\n# If the index is stale, it will suggest rehydrating\n

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#suppressing-the-check","level":3,"title":"Suppressing the Check","text":"

    If you don't use companion tools and want to skip the availability check at session start, add to .ctxrc:

    companion_check: false\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#future-direction","level":3,"title":"Future Direction","text":"

    The companion tool integration is evolving toward a pluggable model: bring your own search engine, bring your own code intelligence. The current integration is MCP-based and limited to Gemini Search and GitNexus. If you use a different search or code intelligence tool, skills will degrade gracefully to built-in capabilities.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#next-up","level":2,"title":"Next Up","text":"

    Keeping Context in a Separate Repo →: Store context files outside the project tree for multi-repo or open source setups.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle recipe
    • Multilingual Session Parsing: configure session header prefixes for other languages
    • CLI Reference: all commands and flags
    • Integrations: detailed per-tool integration docs
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multilingual-sessions/","level":1,"title":"Multilingual Session Parsing","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#the-problem","level":2,"title":"The Problem","text":"

    Your team works across languages. Session files written by AI tools might use headers like # Oturum: 2026-01-15 - API Düzeltme (Turkish) or # セッション: 2026-01-15 - テスト (Japanese) instead of # Session: 2026-01-15 - Fix API.

    By default, ctx only recognizes Session: as a session header prefix. Files with other prefixes are silently skipped during journal import and journal generation: They look like regular Markdown, not sessions.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#tldr","level":2,"title":"TL;DR","text":"

    Add recognized prefixes to .ctxrc:

    session_prefixes:\n  - \"Session:\"      # English (include to keep default)\n  - \"Oturum:\"       # Turkish\n  - \"セッション:\"     # Japanese\n

    Restart your session. All configured prefixes are now recognized.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#how-it-works","level":2,"title":"How It Works","text":"

    The Markdown session parser detects session files by looking for an H1 header that starts with a known prefix followed by a date:

    # Session: 2026-01-15 - Fix API Rate Limiting\n# Oturum: 2026-01-15 - API Düzeltme\n# セッション: 2026-01-15 - テスト\n

    The list of recognized prefixes comes from session_prefixes in .ctxrc. When the key is absent or empty, ctx falls back to the built-in default: [\"Session:\"].

    Date-only headers (# 2026-01-15 - Morning Work) are always recognized regardless of prefix configuration.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#configuration","level":2,"title":"Configuration","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#adding-a-language","level":3,"title":"Adding a Language","text":"

    Add the prefix with a trailing colon to your .ctxrc:

    session_prefixes:\n  - \"Session:\"\n  - \"Sesión:\"       # Spanish\n

    Include Session: Explicitly

    When you override session_prefixes, the default is replaced, not extended. If you still want English headers recognized, include \"Session:\" in your list.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#team-setup","level":3,"title":"Team Setup","text":"

    Commit .ctxrc to the repo so all team members share the same prefix list. This ensures ctx journal import and journal generation pick up sessions from all team members regardless of language.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#common-prefixes","level":3,"title":"Common Prefixes","text":"Language Prefix English Session: Turkish Oturum: Spanish Sesión: French Session: German Sitzung: Japanese セッション: Korean 세션: Portuguese Sessão: Chinese 会话:","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#verifying","level":3,"title":"Verifying","text":"

    After configuring, test with ctx journal source. Sessions with the new prefixes should appear in the output.

    Activate the Project First

    Run eval \"$(ctx activate)\" from the project root. If you skip it, ctx journal ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#what-this-does-not-do","level":2,"title":"What This Does NOT Do","text":"
    • Change the interface language: ctx output is always English. This setting only controls which session files ctx can parse.
    • Generate headers: ctx never writes session headers. The prefix list is recognition-only (input, not output).
    • Affect JSONL sessions: Claude Code JSONL transcripts don't use header prefixes. This only applies to Markdown session files in .context/sessions/.
    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#see-also","level":2,"title":"See Also","text":"

    See also: Setup Across AI Tools - complete multi-tool setup including Markdown session configuration.

    See also: CLI Reference - full .ctxrc field reference including session_prefixes.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/parallel-worktrees/","level":1,"title":"Parallel Agent Development with Git Worktrees","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-problem","level":2,"title":"The Problem","text":"

    You have a large backlog (10, 20, 30 open tasks) and many of them are independent: docs work that doesn't touch Go code, a new package that doesn't overlap with existing ones, test coverage for a stable module.

    Running one agent at a time means serial execution. You want 3-4 agents working in parallel, each on its own track, without stepping on each other's files.

    Git worktrees solve this.

    Each worktree is a separate working directory with its own branch, but they share the same .git object database. Combined with ctx's persistent context, each agent session picks up the full project state and works independently.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-worktree                                   # 1. group tasks by file overlap\ngit worktree add ../myproject-docs -b work/docs # 2. create worktrees\ncd ../myproject-docs && claude                  # 3. launch agents (one per track)\n/ctx-worktree teardown docs                     # 4. merge back and clean up\n

    TASKS.md will conflict on merge: Accept all [x] completions from both sides.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-worktree Skill Create, list, and tear down worktrees /ctx-next Skill Pick tasks from the backlog for each track git worktree Command Underlying git worktree management git merge Command Merge completed tracks back to main","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-1-assess-the-backlog","level":3,"title":"Step 1: Assess the Backlog","text":"

    Start in your main checkout. Ask the agent to analyze your tasks and group them by blast radius: which files and directories each task touches.

    /ctx-worktree\nLook at TASKS.md and group the pending tasks into 2-3 independent\ntracks based on which files they'd touch. Show me the grouping\nbefore creating anything.\n

    The agent reads TASKS.md, estimates file overlap, and proposes groups:

    Proposed worktree groups:\n\n  work/docs   # recipe updates, blog post (touches: docs/)\n  work/crypto # scratchpad encryption infra (touches: internal/crypto/)\n  work/tests  # journal test coverage (touches: internal/cli/journal/)\n
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-2-create-the-worktrees","level":3,"title":"Step 2: Create the Worktrees","text":"

    Once you approve the grouping, the agent creates worktrees as sibling directories:

    Create the worktrees for those three groups.\n

    Behind the scenes:

    git worktree add ../myproject-docs -b work/docs\ngit worktree add ../myproject-crypto -b work/crypto\ngit worktree add ../myproject-tests -b work/tests\n

    Each worktree is a full working copy on its own branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-3-launch-agents","level":3,"title":"Step 3: Launch Agents","text":"

    Open a separate terminal (or editor window) for each worktree and start a Claude Code session:

    # Terminal 1\ncd ../myproject-docs\nclaude\n\n# Terminal 2\ncd ../myproject-crypto\nclaude\n\n# Terminal 3\ncd ../myproject-tests\nclaude\n

    Each agent sees the full project, including .context/, and can work independently.

    Do Not Initialize Context in Worktrees

    Do not run ctx init in worktrees: The .context directory is already tracked in git.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-4-work","level":3,"title":"Step 4: Work","text":"

    Each agent works through its assigned tasks. They can read TASKS.md to know what's assigned to their track, use /ctx-next to pick the next item, and commit normally on their work/* branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-5-merge-back","level":3,"title":"Step 5: Merge Back","text":"

    As each track finishes, return to the main checkout and merge:

    /ctx-worktree teardown docs\n

    The agent checks for uncommitted changes, merges work/docs into your current branch, removes the worktree, and deletes the branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-6-handle-tasksmd-conflicts","level":3,"title":"Step 6: Handle TASKS.md Conflicts","text":"

    TASKS.md will almost always conflict when merging: Multiple agents will mark different tasks as [x]. This is expected and easy to resolve:

    Accept all completions from both sides. No task should go from [x] back to [ ]. The merge resolution is always additive.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-7-cleanup","level":3,"title":"Step 7: Cleanup","text":"

    After all tracks are merged, verify everything is clean:

    /ctx-worktree list\n

    Should show only the main working tree. All work/* branches should be gone.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't have to use the skill directly for every step. These natural prompts work:

    • \"I have a big backlog. Can we split it across worktrees?\"
    • \"Which of these tasks can run in parallel without conflicts?\"
    • \"Merge the docs track back in.\"
    • \"Clean up all the worktrees, we're done.\"
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#what-works-differently-in-worktrees","level":2,"title":"What Works Differently in Worktrees","text":"

    The encryption key lives at ~/.ctx/.ctx.key (user-level, outside the project). Because all worktrees on the same machine share this path, ctx pad and ctx hook notify work in worktrees automatically - no special setup needed.

    One thing to watch:

    • Journal enrichment: ctx journal import and ctx journal enrich write files relative to the current working directory. Enrichments created in a worktree stay there and are discarded on teardown. Enrich journals on the main branch after merging: the JSONL session logs are always intact, and you don't lose any data.

    Context Files Will Merge Just Fine

    Tracked context files (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) work normally; git handles them.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tips","level":2,"title":"Tips","text":"
    • 3-4 worktrees max. Beyond that, merge complexity outweighs the parallelism benefit. The skill enforces this limit.
    • Group by package or directory, not by priority. Two high-priority tasks that touch the same files must be in the same track.
    • TASKS.md will conflict on merge. This is normal. Accept all [x] completions: The resolution is always additive.
    • Don't run ctx init in worktrees. The .context/ directory is tracked in git. Running init overwrites shared context files.
    • Name worktrees by concern, not by number. work/docs and work/crypto are more useful than work/track-1 and work/track-2.
    • Commit frequently in each worktree. Smaller commits make merge conflicts easier to resolve.
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#next-up","level":2,"title":"Next Up","text":"

    Back to the beginning: Guide Your Agent →

    Or explore the full recipe list.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#see-also","level":2,"title":"See Also","text":"
    • Running an Unattended AI Agent: for serial autonomous loops instead of parallel tracks
    • Tracking Work Across Sessions: managing the task backlog that feeds into parallelization
    • The Complete Session: the complete session workflow end-to-end, with examples
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/permission-snapshots/","level":1,"title":"Permission Snapshots","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code's .claude/settings.local.json accumulates one-off permissions every time you click \"Allow\". After busy sessions the file is full of session-specific entries that expand the agent's surface area beyond intent.

    Since settings.local.json is .gitignored, there is no PR review or CI check. The file drifts independently on every machine, and there is no built-in way to reset to a known-good state.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-permission-sanitize               # audit for dangerous patterns\nctx permission snapshot            # save golden image\n# ... sessions accumulate cruft ...\nctx permission restore             # reset to golden state\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx permission ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-solution","level":2,"title":"The Solution","text":"

    Save a curated settings.local.json as a golden image, then restore from it to drop session-accumulated permissions. The golden file (.claude/settings.golden.json) is committed to version control and shared with the team.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx permission snapshot Save settings.local.json as golden image ctx permission restore Reset settings.local.json from golden image /ctx-permission-sanitize Audit for dangerous patterns before snapshotting","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#step-by-step","level":2,"title":"Step by Step","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#1-curate-your-permissions","level":3,"title":"1. Curate Your Permissions","text":"

    Start with a clean settings.local.json. Optionally run /ctx-permission-sanitize to remove dangerous patterns first.

    Review the file manually. Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.

    See the Permission Hygiene recipe for recommended defaults.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#2-take-a-snapshot","level":3,"title":"2. Take a Snapshot","text":"
    ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n

    This creates a byte-for-byte copy. No re-encoding, no indent changes.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#3-commit-the-golden-file","level":3,"title":"3. Commit the Golden File","text":"
    git add .claude/settings.golden.json\ngit commit -m \"Add permission golden image\"\n

    The golden file is not gitignored (unlike settings.local.json). This is intentional: it becomes a team-shared baseline.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#4-auto-restore-at-the-session-start","level":3,"title":"4. Auto-Restore at the Session Start","text":"

    Add this instruction to your CLAUDE.md:

    ## On Session Start\n\nRun `ctx permission restore` to reset permissions to the golden image.\n

    The agent will restore the golden image at the start of every session, automatically dropping any permissions accumulated during previous sessions.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#5-update-when-intentional-changes-are-made","level":3,"title":"5. Update When Intentional Changes Are Made","text":"

    When you add a new permanent permission (not a one-off debugging entry):

    # Edit settings.local.json with the new permission\n# Then update the golden image:\nctx permission snapshot\ngit add .claude/settings.golden.json\ngit commit -m \"Update permission golden image: add cargo test\"\n
    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember exact commands. These natural-language prompts work with agents trained on the ctx playbook:

    What you say What happens \"Save my current permissions as baseline\" Agent runs ctx permission snapshot \"Reset permissions to the golden image\" Agent runs ctx permission restore \"Clean up my permissions\" Agent runs /ctx-permission-sanitize then snapshot \"What permissions did I accumulate?\" Agent diffs local vs golden","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#next-up","level":2,"title":"Next Up","text":"

    Turning Activity into Content →: Generate blog posts, changelogs, and journal sites from your project activity.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#see-also","level":2,"title":"See Also","text":"
    • Permission Hygiene: recommended defaults and maintenance workflow
    • CLI Reference: ctx permission: full command documentation
    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/publishing/","level":1,"title":"Turning Activity into Content","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-problem","level":2,"title":"The Problem","text":"

    Your .context/ directory is full of decisions, learnings, and session history.

    Your git log tells the story of a project evolving.

    But none of this is visible to anyone outside your terminal.

    You want to turn this raw activity into:

    • a browsable journal site,
    • blog posts,
    • changelog posts.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tldr","level":2,"title":"TL;DR","text":"
    ctx journal import --all             # 1. import sessions to markdown\n\n/ctx-journal-enrich-all             # 2. add metadata and tags\n\nctx journal site --serve            # 3. build and serve the journal\n\n/ctx-blog about the caching layer   # 4. draft a blog post\n/ctx-blog-changelog v0.1.0 \"v0.2\"   # 5. write a changelog post\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx journal ... fails with Error: no context directory specified. See Activating a Context Directory.

    Read on for details on each stage.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx journal import Command Import session JSONL to editable markdown ctx journal site Command Generate a static site from journal entries ctx journal obsidian Command Generate an Obsidian vault from journal entries ctx serve Command Serve any zensical directory (default: journal) ctx site feed Command Generate Atom feed from finalized blog posts make journal Makefile Shortcut for import + site rebuild /ctx-journal-enrich-all Skill Full pipeline: import if needed, then batch-enrich (recommended) /ctx-journal-enrich Skill Add metadata, summaries, and tags to one entry /ctx-blog Skill Draft a blog post from recent project activity /ctx-blog-changelog Skill Write a themed post from a commit range","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-1-import-sessions-to-markdown","level":3,"title":"Step 1: Import Sessions to Markdown","text":"

    Raw session data lives as JSONL files in Claude Code's internal storage. The first step is converting these into readable, editable markdown.

    # Import all sessions from the current project\nctx journal import --all\n\n# Import from all projects (if you work across multiple repos)\nctx journal import --all --all-projects\n\n# Import a single session by ID or slug\nctx journal import abc123\nctx journal import gleaming-wobbling-sutherland\n

    Imported files land in .context/journal/ as individual Markdown files with session metadata and the full conversation transcript.

    --all is safe by default: Only new sessions are imported. Existing files are skipped. Use --regenerate to re-import existing files (YAML frontmatter is preserved). Use --regenerate --keep-frontmatter=false -y to regenerate everything including frontmatter.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-2-enrich-entries-with-metadata","level":3,"title":"Step 2: Enrich Entries with Metadata","text":"

    Raw entries have timestamps and conversations but lack the structured metadata that makes a journal searchable. Use /ctx-journal-enrich-all to process your entire backlog at once:

    /ctx-journal-enrich-all\n

    The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.

    For large backlogs (20+ entries), it can spawn subagents to process entries in parallel.

    To enrich a single entry instead:

    /ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich 2026-01-24\n

    After enrichment, an entry gains YAML frontmatter:

    ---\ntitle: \"Implement Redis caching for API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n

    This metadata powers better navigation in the journal site:

    • titles replace slugs,
    • summaries appear in the index,
    • and search covers topics and technologies.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-3-generate-the-journal-site","level":3,"title":"Step 3: Generate the Journal Site","text":"

    With entries exported and enriched, generate the static site:

    # Generate site files\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally (opens at http://localhost:8000)\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n

    The site is generated in .context/journal-site/ by default. It uses zensical for static site generation (pipx install zensical).

    Or use the Makefile shortcut that combines export and rebuild:

    make journal\n

    This runs ctx journal import --all followed by ctx journal site --build, then reminds you to enrich before rebuilding. To serve the built site, use make journal-serve or ctx serve (serve-only, no regeneration).

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#alternative-export-to-obsidian-vault","level":3,"title":"Alternative: Export to Obsidian Vault","text":"

    If you use Obsidian for knowledge management, generate a vault instead of (or alongside) the static site:

    ctx journal obsidian\nctx journal obsidian --output ~/vaults/ctx-journal\n

    This produces an Obsidian-ready directory with wikilinks, MOC (Map of Content) pages for topics/files/types, and a \"Related Sessions\" footer on each entry for graph connectivity. Open the output directory in Obsidian as a vault.

    The vault uses the same enriched source entries as the static site. Both outputs can coexist: The static site goes to .context/journal-site/, the vault to .context/journal-obsidian/.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-4-draft-blog-posts-from-activity","level":3,"title":"Step 4: Draft Blog Posts from Activity","text":"

    When your project reaches a milestone worth sharing, use /ctx-blog to draft a post from recent activity. The skill gathers context from multiple sources: git log, DECISIONS.md, LEARNINGS.md, completed tasks, and journal entries.

    /ctx-blog about the caching layer we just built\n/ctx-blog last week's refactoring work\n/ctx-blog lessons learned from the migration\n

    The skill gathers recent commits, decisions, and learnings; identifies a narrative arc; drafts an outline for approval; writes the full post; and saves it to docs/blog/YYYY-MM-DD-slug.md.

    Posts are written in first person with code snippets, commit references, and an honest discussion of what went wrong.

    The Output Is zensical-Flavored Markdown

    The blog skills produce Markdown tuned for a zensical site: topics: frontmatter (zensical's tag field), a docs/blog/ output path, and a banner image reference.

    The content is still standard Markdown and can be adapted to other static site generators, but the defaults assume a zensical project structure.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-5-write-changelog-posts-from-commit-ranges","level":3,"title":"Step 5: Write Changelog Posts from Commit Ranges","text":"

    For release notes or \"what changed\" posts, /ctx-blog-changelog takes a starting commit and a theme, then analyzes everything that changed:

    /ctx-blog-changelog 040ce99 \"building the journal system\"\n/ctx-blog-changelog HEAD~30 \"what's new in v0.2.0\"\n/ctx-blog-changelog v0.1.0 \"the road to v0.2.0\"\n

    The skill diffs the commit range, identifies the most-changed files, and constructs a narrative organized by theme rather than chronology, including a key commits table and before/after comparisons.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-6-generate-the-blog-feed","level":3,"title":"Step 6: Generate the Blog Feed","text":"

    After publishing blog posts, generate the Atom feed so readers and automation can discover new content:

    ctx site feed\n

    This scans docs/blog/ for finalized posts (reviewed_and_finalized: true), extracts title, date, author, topics, and summary, and writes a valid Atom 1.0 feed to site/feed.xml. The feed is also generated automatically as part of make site.

    The feed is available at ctx.ist/feed.xml.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-conversational-approach","level":2,"title":"The Conversational Approach","text":"

    You can also drive your publishing anytime with natural language:

    \"write about what we did this week\"\n\"turn today's session into a blog post\"\n\"make a changelog post covering everything since the last release\"\n\"enrich the last few journal entries\"\n

    The agent has full visibility into your .context/ state (tasks completed, decisions recorded, learnings captured), so its suggestions are grounded in what actually happened.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    The full pipeline from raw transcripts to published content:

    # 1. Import all sessions\nctx journal import --all\n\n# 2. In Claude Code: enrich all entries with metadata\n/ctx-journal-enrich-all\n\n# 3. Build and serve the journal site\nmake journal\nmake journal-serve\n\n# 3b. Or generate an Obsidian vault\nctx journal obsidian\n\n# 4. In Claude Code: draft a blog post\n/ctx-blog about the features we shipped this week\n\n# 5. In Claude Code: write a changelog post\n/ctx-blog-changelog v0.1.0 \"what's new in v0.2.0\"\n

    The journal pipeline is idempotent at every stage. You can rerun ctx journal import --all without losing enrichment. You can rebuild the site as many times as you want.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tips","level":2,"title":"Tips","text":"
    • Import regularly. Run ctx journal import --all after each session to keep your journal current. Only new sessions are imported: Existing files are skipped by default.
    • Use batch enrichment. /ctx-journal-enrich-all filters noise (suggestion sessions, trivial sessions, multipart continuations) so you do not have to decide what is worth enriching.
    • Keep journal files in .gitignore. Session journals can contain sensitive data: file contents, commands, internal discussions, and error messages with stack traces. Add .context/journal/ and .context/journal-site/ to .gitignore.
    • Use /ctx-blog for narrative posts and /ctx-blog-changelog for release posts. One finds a story in recent activity, the other explains a commit range by theme.
    • Edit the drafts. These skills produce drafts, not final posts. Review the narrative, add your perspective, and remove anything that does not serve the reader.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#next-up","level":2,"title":"Next Up","text":"

    Running an Unattended AI Agent →: Set up an AI agent that works through tasks overnight without you at the keyboard.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#see-also","level":2,"title":"See Also","text":"
    • Session Journal: journal system, enrichment schema
    • CLI Reference: ctx journal: import, list, show session history
    • CLI Reference: ctx journal site: static site generation
    • CLI Reference: ctx journal obsidian: Obsidian vault export
    • CLI Reference: ctx serve: serve-only (no regeneration)
    • Browsing and Enriching Past Sessions: journal browsing workflow
    • The Complete Session: capturing context during a session
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/scratchpad-sync/","level":1,"title":"Syncing Scratchpad Notes Across Machines","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-problem","level":2,"title":"The Problem","text":"

    You work from multiple machines: a desktop and a laptop, or a local machine and a remote dev server.

    The scratchpad entries are encrypted. The ciphertext (.context/scratchpad.enc) travels with git, but the encryption key lives outside the project at ~/.ctx/.ctx.key and is never committed. Without the key on each machine, you cannot read or write entries.

    How do you distribute the key and keep the scratchpad in sync?

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                                                  # 1. generates key\neval \"$(ctx activate)\"                                    # 2. bind CTX_DIR\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key        # 3. copy key\nchmod 600 ~/.ctx/.ctx.key                                 # 4. secure it\n# Normal git push/pull syncs the encrypted scratchpad.enc\n# On conflict: ctx pad resolve → rebuild → git add + commit\n

    Activate Each Machine

    Run eval \"$(ctx activate)\" from the project root on every machine that reads or writes the scratchpad: after each ctx init, or after each clone on machine B. If you skip it, ctx pad ... fails with Error: no context directory specified. See Activating a Context Directory.

    Finding Your Key File

    The key is always at ~/.ctx/.ctx.key - one key, one machine.

    Treat the Key like a Password

    The scratchpad key is the only thing protecting your encrypted entries.

    Store a backup in a secure enclave such as a password manager, and treat it with the same care you would give passwords, certificates, or API tokens.

    Anyone with the key can decrypt every scratchpad entry.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init CLI command Initialize context (generates the key automatically) ctx pad add CLI command Add a scratchpad entry ctx pad rm CLI command Remove entries by stable ID (supports ranges) ctx pad edit CLI command Edit a scratchpad entry ctx pad resolve CLI command Show both sides of a merge conflict ctx pad merge CLI command Merge entries from other scratchpad files ctx pad import CLI command Bulk-import lines from a file ctx pad export CLI command Export blob entries to a directory scp Shell Copy the key file between machines git push / git pull Shell Sync the encrypted file via git /ctx-pad Skill Natural language interface to pad commands","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-1-initialize-on-machine-a","level":3,"title":"Step 1: Initialize on Machine A","text":"

    Run ctx init on your first machine. The key is created automatically at ~/.ctx/.ctx.key:

    ctx init\n# ...\n# Created ~/.ctx/.ctx.key (0600)\n# Created .context/scratchpad.enc\n

    The key lives outside the project directory and is never committed. The .enc file is tracked in git.

    Key Folder Change (v0.7.0+)

    If you built ctx from source or upgraded past v0.6.0, the key location changed to ~/.ctx/.ctx.key. Check these legacy folders and copy your key manually:

    # Old locations (pick whichever exists)\nls ~/.local/ctx/keys/        # pre-v0.7.0 user-level\nls .context/.ctx.key         # pre-v0.6.0 project-local\n\n# Copy to the new location\nmkdir -p ~/.ctx && chmod 700 ~/.ctx\ncp <old-key-path> ~/.ctx/.ctx.key\nchmod 600 ~/.ctx/.ctx.key\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-2-copy-the-key-to-machine-b","level":3,"title":"Step 2: Copy the Key to Machine B","text":"

    Use any secure transfer method. The key is always at ~/.ctx/.ctx.key:

    # scp - create the target directory first\nssh user@machine-b \"mkdir -p ~/.ctx && chmod 700 ~/.ctx\"\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key\n\n# Or use a password manager, USB drive, etc.\n

    Set permissions on Machine B:

    chmod 600 ~/.ctx/.ctx.key\n

    Secure the Transfer

    The key is a raw 256-bit AES key. Anyone with the key can decrypt the scratchpad. Use an encrypted channel (SSH, password manager, vault).

    Never paste it in plaintext over email or chat.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-3-normal-pushpull-workflow","level":3,"title":"Step 3: Normal Push/Pull Workflow","text":"

    The encrypted file is committed, so standard git sync works:

    # Machine A: add entries and push\nctx pad add \"staging API key: sk-test-abc123\"\ngit add .context/scratchpad.enc\ngit commit -m \"Update scratchpad\"\ngit push\n\n# Machine B: pull and read\ngit pull\nctx pad\n#   1. staging API key: sk-test-abc123\n

    Both machines have the same key, so both can decrypt the same .enc file.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-4-read-and-write-from-either-machine","level":3,"title":"Step 4: Read and Write from Either Machine","text":"

    Once the key is distributed, all ctx pad commands work identically on both machines. Entries added on Machine A are visible on Machine B after a git pull, and vice versa.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-5-handle-merge-conflicts","level":3,"title":"Step 5: Handle Merge Conflicts","text":"

    If both machines add entries between syncs, pulling will create a merge conflict on .context/scratchpad.enc. Git cannot merge binary (encrypted) content automatically.

    The fastest approach is ctx pad merge: It reads both conflict sides, deduplicates, and writes the union:

    # Extract theirs to a temp file, then merge it in\ngit show :3:.context/scratchpad.enc > /tmp/theirs.enc\ngit checkout --ours .context/scratchpad.enc\nctx pad merge /tmp/theirs.enc\n\n# Done: Commit the resolved scratchpad:\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n

    Alternatively, use ctx pad resolve to inspect both sides manually:

    ctx pad resolve\n# === Ours (this machine) ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n#\n# === Theirs (incoming) ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n

    Then reconstruct the merged scratchpad:

    # Start fresh with all entries from both sides\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\n# Mark the conflict resolved\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#merge-conflict-walkthrough","level":2,"title":"Merge Conflict Walkthrough","text":"

    Here's a full scenario showing how conflicts arise and how to resolve them:

    1. Both machines start in sync (1 entry):

    Machine A: 1. staging API key: sk-test-abc123\nMachine B: 1. staging API key: sk-test-abc123\n

    2. Both add entries independently:

    Machine A adds: \"check DNS after deploy\"\nMachine B adds: \"new endpoint: api.example.com/v2\"\n

    3. Machine A pushes first. Machine B pulls and gets a conflict:

    git pull\n# CONFLICT (content): Merge conflict in .context/scratchpad.enc\n

    4. Machine B runs ctx pad resolve:

    ctx pad resolve\n# === Ours ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n#\n# === Theirs ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n

    5. Rebuild with entries from both sides and commit:

    # Clear and rebuild (or use the skill to guide you)\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\ngit add .context/scratchpad.enc\ngit commit -m \"Merge scratchpad: keep entries from both machines\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#conversational-approach","level":3,"title":"Conversational Approach","text":"

    When working with an AI assistant, you can resolve conflicts naturally:

    You: \"I have a scratchpad merge conflict. Can you resolve it?\"\n\nAgent: \"Let me extract theirs and merge it in.\"\n       [runs git show :3:.context/scratchpad.enc > /tmp/theirs.enc]\n       [runs git checkout --ours .context/scratchpad.enc]\n       [runs ctx pad merge /tmp/theirs.enc]\n       \"Merged 2 new entries (1 duplicate skipped). Want me to\n       commit the resolution?\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tips","level":2,"title":"Tips","text":"
    • Back up the key: If you lose it, you lose access to all encrypted entries. Store a copy in your password manager.
    • One key per project: Each ctx init generates a unique key. Don't reuse keys across projects.
    • Keys work in worktrees: Because the key lives at ~/.ctx/.ctx.key (outside the project), git worktrees on the same machine share the key automatically. No special setup needed.
    • Plaintext fallback for non-sensitive projects: If encryption adds friction and you have nothing sensitive, set scratchpad_encrypt: false in .ctxrc. Merge conflicts become trivial text merges.
    • Never commit the key: The key is stored outside the project at ~/.ctx/.ctx.key and should never be copied into the repository.
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#next-up","level":2,"title":"Next Up","text":"

    Hook Output Patterns →: Choose the right output pattern for your Claude Code hooks.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#see-also","level":2,"title":"See Also","text":"
    • Scratchpad: feature overview, all commands, when to use scratchpad vs context files
    • Persisting Decisions, Learnings, and Conventions: for structured knowledge that outlives the scratchpad
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-with-claude/","level":1,"title":"Using the Scratchpad","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-problem","level":2,"title":"The Problem","text":"

    During a session you accumulate quick notes, reminders, intermediate values, and sometimes sensitive tokens. They don't fit TASKS.md (not work items) or DECISIONS.md (not decisions). They don't have the structured fields that LEARNINGS.md requires.

    Without somewhere to put them, they get lost between sessions.

    How do you capture working memory that persists across sessions without polluting your structured context files?

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tldr","level":2,"title":"TL;DR","text":"
    ctx pad add \"check DNS propagation after deploy\"\nctx pad         # list entries\nctx pad show 1  # print entry (pipe-friendly)\n

    Entries are encrypted at rest and travel with git.

    Use the /ctx-pad skill to manage entries from inside your AI session.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx pad ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx pad CLI command List all scratchpad entries ctx pad show N CLI command Output raw text of entry N (pipe-friendly) ctx pad add CLI command Add a new entry ctx pad edit CLI command Replace, append to, or prepend to an entry ctx pad add --file CLI command Ingest a file as a blob entry ctx pad show N --out CLI command Extract a blob entry to a file ctx pad rm CLI command Remove entries by stable ID (supports ranges) ctx pad normalize CLI command Reassign entry IDs as 1..N ctx pad mv CLI command Reorder entries ctx pad --tag CLI command Filter entries by tag ctx pad tags CLI command List all tags with counts ctx pad import CLI command Bulk-import lines from a file (or stdin) ctx pad export CLI command Export all blob entries to a directory /ctx-pad Skill Natural language interface to all pad commands","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-1-add-a-note-naturally","level":3,"title":"Step 1: Add a Note Naturally","text":"

    You don't need to remember any syntax. Just tell your assistant what to jot down:

    You: \"jot down: check DNS propagation after deploy\"\n\nAgent: \"Added to scratchpad:\n  1. check DNS propagation after deploy\"\n

    Behind the scenes, the agent runs ctx pad add \"check DNS propagation after deploy\".

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-2-view-your-scratchpad","level":3,"title":"Step 2: View Your Scratchpad","text":"
    You: \"show my scratchpad\"\n\nAgent: \"Your scratchpad has 3 entries:\n  1. check DNS propagation after deploy\n  2. staging API endpoint: api.staging.example.com/v2\n  3. retry limit should be 5, not 3\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-3-edit-an-entry","level":3,"title":"Step 3: Edit an Entry","text":"
    You: \"update entry 2 to include the new endpoint\"\n\nAgent: \"Updated entry 2:\n  was: staging API endpoint: api.staging.example.com/v2\n  now: staging API endpoint: api.prod.example.com/v2\"\n

    For partial edits, the agent can use --append or --prepend:

    You: \"append the port number to entry 2\"\n\nAgent: \"Updated entry 2:\n  staging API endpoint: api.prod.example.com/v2 :8443\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-4-compose-entries","level":3,"title":"Step 4: Compose Entries","text":"

    Use ctx pad show N to pipe entry content into other operations:

    You: \"append the content of entry 3 to entry 1\"\n\nAgent: [runs ctx pad edit 1 --append \"$(ctx pad show 3)\"]\n       \"Done. Entry 1 is now:\n        check DNS propagation after deploy retry limit should be 5, not 3\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-5-reorder-by-priority","level":3,"title":"Step 5: Reorder by Priority","text":"
    You: \"move entry 4 to the top\"\n\nAgent: [runs ctx pad mv 4 1]\n       \"Moved entry 4 to position 1. Scratchpad reordered.\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-6-clean-up","level":3,"title":"Step 6: Clean Up","text":"
    You: \"remove entry 2, it's done\"\n\nAgent: [runs ctx pad rm 2]\n       \"Removed entry 2. 3 entries remaining.\n        (IDs are stable; remaining entries keep their IDs.)\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-7-store-a-file-as-a-blob","level":3,"title":"Step 7: Store a File as a Blob","text":"

    The scratchpad can hold small files (up to 64 KB) as encrypted blob entries. The file is base64-encoded and stored alongside a label you provide:

    # Ingest a file: the first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# List shows the label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-8-extract-a-blob","level":3,"title":"Step 8: Extract a Blob","text":"

    Use show --out to write the decoded file back to disk:

    # Write blob entry to a file\nctx pad show 2 --out ./recovered-deploy.yaml\n\n# Or print to stdout (for piping)\nctx pad show 2 | head -5\n

    Blob entries are encrypted identically to text entries: They're just base64-encoded before encryption. The --out flag decodes and writes the raw bytes.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-9-bulk-import-notes","level":3,"title":"Step 9: Bulk Import Notes","text":"

    When you have a file with many notes (one per line), import them in bulk instead of adding one at a time:

    # Import from a file: Each non-empty line becomes an entry\nctx pad import notes.txt\n\n# Or pipe from stdin\ngrep TODO *.go | ctx pad import -\n

    All entries are written in a single encrypt/write cycle, regardless of how many lines the file contains.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-10-export-blobs-to-disk","level":3,"title":"Step 10: Export Blobs to Disk","text":"

    Export all blob entries to a directory as individual files. Each blob's label becomes the filename:

    # Export to a directory (created if needed)\nctx pad export ./ideas\n\n# Preview what would be exported\nctx pad export --dry-run ./ideas\n\n# Force overwrite existing files\nctx pad export --force ./backup\n

    When a file already exists, a unix timestamp is prepended to the filename to avoid collisions. Use --force to overwrite instead.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-11-tag-entries-for-organization","level":3,"title":"Step 11: Tag Entries for Organization","text":"

    Tags let you categorize entries without any structure beyond a #word token in the text. Add them when creating or editing entries:

    You: \"jot down: check DNS propagation #later\"\nYou: \"tag entry 2 as urgent\"\n\nAgent: [runs ctx pad edit 2 --tag urgent]\n       \"Updated entry 2.\"\n

    Filter your scratchpad by tag:

    You: \"show me everything tagged later\"\n\nAgent: [runs ctx pad --tag later]\n       \"  1. check DNS propagation #later\n        3. review PR feedback #later #ci\"\n

    Entry IDs are stable; they don't shift when other entries are deleted, so ctx pad rm 3 always targets the same entry regardless of deletions or active filters. Use ctx pad normalize to reassign IDs as 1..N.

    Exclude a tag with ~:

    ctx pad --tag ~later         # everything NOT tagged #later\nctx pad --tag later --tag ci # entries with BOTH tags (AND logic)\n

    See what tags you're using:

    You: \"what tags do I have?\"\n\nAgent: [runs ctx pad tags]\n       \"ci       1\n        later    2\n        urgent   1\"\n

    Tags work on blob entries too; they're extracted from the label:

    ctx pad add \"deploy config #prod\" --file ./deploy.yaml\nctx pad --tag prod\n#   1. deploy config #prod [BLOB]\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#using-ctx-pad-in-a-session","level":2,"title":"Using /ctx-pad in a Session","text":"

    Invoke the /ctx-pad skill first, then describe what you want in natural language. Without the skill prefix, the agent may route your request to TASKS.md or another context file instead of the scratchpad.

    You: /ctx-pad jot down: check DNS after deploy\nYou: /ctx-pad show my scratchpad\nYou: /ctx-pad delete entry 3\n

    Once the skill is active, it translates intent into commands:

    You say (after /ctx-pad) What the agent does \"jot down: check DNS after deploy\" ctx pad add \"check DNS after deploy\" \"remember this: retry limit is 5\" ctx pad add \"retry limit is 5\" \"show my scratchpad\" / \"what's on my pad\" ctx pad \"show me entry 3\" ctx pad show 3 \"delete the third one\" / \"remove entry 3\" ctx pad rm 3 \"remove entries 3 through 5\" ctx pad rm 3-5 \"renumber my scratchpad\" ctx pad normalize \"change entry 2 to ...\" ctx pad edit 2 \"new text\" \"append ' +important' to entry 3\" ctx pad edit 3 --append \" +important\" \"prepend 'URGENT:' to entry 1\" ctx pad edit 1 --prepend \"URGENT: \" \"prioritize entry 4\" / \"move to the top\" ctx pad mv 4 1 \"import my notes from notes.txt\" ctx pad import notes.txt \"export all blobs to ./ideas\" ctx pad export ./ideas \"show entries tagged later\" ctx pad --tag later \"show everything except later\" ctx pad --tag ~later \"what tags do I have\" ctx pad tags \"tag entry 5 as urgent\" ctx pad edit 5 --tag urgent

    When in Doubt, Use the CLI Directly

    The ctx pad commands work the same whether you run them yourself or let the skill invoke them.

    If the agent misroutes a request, fall back to ctx pad add \"...\" in your terminal.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#when-to-use-scratchpad-vs-context-files","level":2,"title":"When to Use Scratchpad vs Context Files","text":"Situation Use Temporary reminders (\"check X after deploy\") Scratchpad Session-start reminders (\"remind me next session\") ctx remind Working values during debugging (ports, endpoints, counts) Scratchpad Sensitive tokens or API keys (short-term storage) Scratchpad Quick notes that don't fit anywhere else Scratchpad Work items with completion tracking TASKS.md Trade-offs between alternatives with rationale DECISIONS.md Reusable lessons with context/lesson/application LEARNINGS.md Codified patterns and standards CONVENTIONS.md

    Decision Guide

    • If it has structured fields (context, rationale, lesson, application), it belongs in a context file like DECISIONS.md or LEARNINGS.md.
    • If it's a work item you'll mark done, it belongs in TASKS.md.
    • If you want a message relayed VERBATIM at the next session start, it belongs in ctx remind.
    • If it's a quick note, reminder, or working value (especially if it's sensitive or ephemeral) it belongs on the scratchpad.

    Scratchpad Is Not a Junk Drawer

    The scratchpad is for working memory, not long-term storage.

    If a note is still relevant after several sessions, promote it:

    A persistent reminder becomes a task, a recurring value becomes a convention, a hard-won insight becomes a learning.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tips","level":2,"title":"Tips","text":"
    • Entries persist across sessions: The scratchpad is committed (encrypted) to git, so entries survive session boundaries. Pick up where you left off.
    • Entries are numbered and reorderable: Use ctx pad mv to put high-priority items at the top.
    • ctx pad show N enables unix piping: Output raw entry text with no numbering prefix. Compose with --append, --prepend, or other shell tools.
    • Never mention the key file contents to the AI: The agent knows how to use ctx pad commands but should never read or print the encryption key (~/.ctx/.ctx.key) directly.
    • Encryption is transparent: You interact with plaintext; the encryption/decryption happens automatically on every read/write.
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#next-up","level":2,"title":"Next Up","text":"

    Syncing Scratchpad Notes Across Machines →: Distribute encryption keys and scratchpad data across environments.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#see-also","level":2,"title":"See Also","text":"
    • Scratchpad: feature overview, all commands, encryption details, plaintext override
    • Persisting Decisions, Learnings, and Conventions: for structured knowledge that outlives the scratchpad
    • The Complete Session: full session lifecycle showing how the scratchpad fits into the broader workflow
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/","level":1,"title":"Scrutinizing a Plan","text":"

    When you have a plan and want it attacked, not validated, the /ctx-plan skill runs an adversarial interview. It surfaces what's weak, missing, or unexamined before you commit.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#when-to-use-it","level":2,"title":"When to Use It","text":"
    • Before starting a multi-day implementation.
    • After writing a spec but before opening the first PR.
    • When the team aligned suspiciously fast on a complex change.
    • When you've drafted something and the obvious next step is to hand it to a senior reviewer.

    /ctx-plan is not a brainstorming tool. Use /ctx-brainstorm for that. /ctx-plan assumes the plan exists and asks: does it survive contact with reality?

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#stop-conditions","level":2,"title":"Stop Conditions","text":"

    The skill runs depth-first through assumptions, tradeoffs, and failure modes. Stop when one of these is true:

    • Plan converges. The bet is clear, the sequencing is justified, and you can articulate what would falsify it.
    • Plan dissolves. The interview surfaces a load-bearing assumption you can't defend; the plan needs rework, not more scrutiny.
    • You hit information you don't have. Pause, gather it, resume.

    If the agent loops on a single angle (asking variations of the same question), say so. The skill is designed to walk depth-first, not exhaust one branch.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#worked-example","level":2,"title":"Worked Example","text":"
    You: /ctx-plan\n\nI want to migrate our queue from RabbitMQ to NATS. I've drafted a\ntwo-week plan: spike NATS in staging, port one consumer, port the\nrest, decommission RabbitMQ.\n\nAgent: One question at a time. What breaks if your message ordering\n       guarantees aren't equivalent across the two systems?\n\nYou: NATS gives us per-subject ordering by default; we don't actually\n     need cross-subject ordering, so I think we're fine.\n\nAgent: How would you notice if you were wrong? What's the loudest\n       symptom of a stray out-of-order message in production today?\n\nYou: ...probably a duplicate notification. Which we already see\n     occasionally. So we wouldn't notice immediately.\n\nAgent: Then your migration's correctness check needs to be stronger\n       than \"no incidents.\" Before you port consumer two, what\n       evidence would convince you that consumer one's ordering\n       behavior is correct under load?\n\nYou: Right. Add a dedicated assertion test in staging that replays\n     a known-out-of-order sequence and verifies the consumer\n     dedupes correctly. Otherwise we're just hoping.\n\nAgent: Good. Next: what would change your mind about NATS itself?\n       What rejected alternative is closest to coming back?\n

    The interview keeps going until the bet is articulated, the falsifiable conditions are written down, and the rejected alternatives have a recall trigger.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#output","level":2,"title":"Output","text":"

    /ctx-plan produces a clearer plan, not a document. Persist the deltas via:

    • /ctx-spec if the conclusions belong in a feature spec.
    • /ctx-decision-add if a tradeoff resolved into an architectural decision.
    • /ctx-learning-add if you discovered a project-specific gotcha during the interview.

    The skill itself is in internal/assets/claude/skills/ctx-plan/SKILL.md; the working contract lives there, the recipe is the on-ramp.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#see-also","level":2,"title":"See Also","text":"
    • Design Before Coding: the brainstorming counterpart, used before a plan exists.
    • ctx-spec: scaffolds a feature spec from the project template.
    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/session-archaeology/","level":1,"title":"Browsing and Enriching Past Sessions","text":"","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-problem","level":2,"title":"The Problem","text":"

    After weeks of AI-assisted development you have dozens of sessions scattered across JSONL files in ~/.claude/projects/. Finding the session where you debugged the Redis connection pool, or remembering what you decided about the caching strategy three Tuesdays ago, often means grepping raw JSON.

    There is no table of contents, no search, and no summaries.

    This recipe shows how to turn that raw session history into a browsable, searchable, and enriched journal site you can navigate in your browser.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tldr","level":2,"title":"TL;DR","text":"

    Export and Generate

    ctx journal import --all\nctx journal site --serve\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx journal ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    Enrich

    /ctx-journal-enrich-all\n

    Rebuild

    ctx journal site --serve\n

    Read on for what each stage does and why.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx journal source Command List parsed sessions with metadata ctx journal source --show Command Inspect a specific session in detail ctx journal import Command Import sessions to editable journal Markdown ctx journal site Command Generate a static site from journal entries ctx journal obsidian Command Generate an Obsidian vault from journal entries ctx journal schema check Command Validate JSONL files and report schema drift ctx journal schema dump Command Print the embedded JSONL schema definition ctx serve Command Serve any zensical directory (default: journal) /ctx-history Skill Browse sessions inside your AI assistant /ctx-journal-enrich Skill Add frontmatter metadata to a single entry /ctx-journal-enrich-all Skill Full pipeline: import if needed, then batch-enrich","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-workflow","level":2,"title":"The Workflow","text":"

    The session journal follows a four-stage pipeline.

    Each stage is idempotent and safe to re-run:

    By default, each stage skips entries that have already been processed.

    import -> enrich -> rebuild\n
    Stage Tool What it does Skips if Where Import ctx journal import --all Converts session JSONL to Markdown File already exists (safe default) CLI or agent Enrich /ctx-journal-enrich-all Adds frontmatter, summaries, topic tags Frontmatter already present Agent only Rebuild ctx journal site --build Generates browsable static HTML N/A CLI only Obsidian ctx journal obsidian Generates Obsidian vault with wikilinks N/A CLI only

    Where Do You Run Each Stage?

    Import (Steps 1 to 3) works equally well from the terminal or inside your AI assistant via /ctx-history. The CLI is fine here: the agent adds no special intelligence, it just runs the same command.

    Enrich (Step 4) requires the agent: it reads conversation content and produces structured metadata.

    Rebuild and serve (Step 5) is a terminal operation that starts a long-running server.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-1-list-your-sessions","level":3,"title":"Step 1: List Your Sessions","text":"

    Start by seeing what sessions exist for the current project:

    ctx journal source\n

    Sample output:

    Sessions (newest first)\n=======================\n\n  Slug                           Project   Date         Duration  Turns  Tokens\n  gleaming-wobbling-sutherland   ctx       2026-02-07   1h 23m    47     82,341\n  twinkly-stirring-kettle        ctx       2026-02-06   0h 45m    22     38,102\n  bright-dancing-hopper          ctx       2026-02-05   2h 10m    63     124,500\n  quiet-flowing-dijkstra         ctx       2026-02-04   0h 18m    11     15,230\n  ...\n

    Slugs Look Cryptic?

    These auto-generated slugs (gleaming-wobbling-sutherland) are hard to recognize later.

    Use /ctx-journal-enrich to add human-readable titles, topic tags, and summaries to exported journal entries, making them easier to find.

    Filter by project or tool if you work across multiple codebases:

    ctx journal source --project ctx --limit 10\nctx journal source --tool claude-code\nctx journal source --all-projects\n
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-2-inspect-a-specific-session","level":3,"title":"Step 2: Inspect a Specific Session","text":"

    Before exporting everything, inspect a single session to see its metadata and conversation summary:

    ctx journal source --show --latest\n

    Or look up a specific session by its slug, partial ID, or UUID:

    ctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show twinkly\nctx journal source --show abc123\n

    Add --full to see the complete message content instead of the summary view:

    ctx journal source --show --latest --full\n

    This is useful for checking what happened before deciding whether to export and enrich it.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-3-import-sessions-to-the-journal","level":3,"title":"Step 3: Import Sessions to the Journal","text":"

    Import converts raw session data into editable Markdown files in .context/journal/:

    # Import all sessions from the current project\nctx journal import --all\n\n# Import a single session\nctx journal import gleaming-wobbling-sutherland\n\n# Include sessions from all projects\nctx journal import --all --all-projects\n

    --keep-frontmatter=false Discards Enrichments

    --keep-frontmatter=false discards enriched YAML frontmatter during regeneration.

    Back up your journal before using this flag.

    Each imported file contains session metadata (date, time, duration, model, project, git branch), a tool usage summary, and the full conversation transcript.

    Re-importing is safe. Running ctx journal import --all only imports new sessions: Existing files are never touched. Use --dry-run to preview what would be imported without writing anything.

    To re-import existing files (e.g., after a format improvement), use --regenerate: Conversation content is regenerated while preserving any YAML frontmatter you or the enrichment skill has added. You'll be prompted before any files are overwritten.

    --regenerate Replaces the Markdown Body

    --regenerate preserves YAML frontmatter but replaces the entire Markdown body with freshly generated content from the source JSONL.

    If you manually edited the conversation transcript (added notes, redacted sensitive content, restructured sections), those edits will be lost.

    BACK UP YOUR JOURNAL FIRST.

    To protect entries you've hand-edited, you can explicitly lock them:

    ctx journal lock <pattern>\n

    Locked entries are always skipped, regardless of flags.

    If you prefer to add locked: true directly in frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json:

    ctx journal sync\n

    See ctx journal lock --help and ctx journal sync --help for details.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-4-enrich-with-metadata","level":3,"title":"Step 4: Enrich with Metadata","text":"

    Raw imports have timestamps and transcripts but lack the semantic metadata that makes sessions searchable: topics, technology tags, outcome status, and summaries. The /ctx-journal-enrich* skills add this structured frontmatter.

    Locked entries are skipped by enrichment skills, just as they are by import. Lock entries you want to protect before running batch enrichment.

    Batch enrichment (recommended):

    /ctx-journal-enrich-all\n

    The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.

    It shows you a grouped summary before applying changes so you can scan quickly rather than reviewing one by one.

    For large backlogs (20+ entries), the skill can spawn subagents to process entries in parallel.

    Single-entry enrichment:

    /ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-02-06\n

    Each enriched entry gets YAML frontmatter like this:

    ---\ntitle: \"Implement Redis caching middleware\"\ndate: 2026-02-06\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/api/middleware/cache.go\n---\n

    The skill also generates a summary and can extract decisions, learnings, and tasks mentioned during the session.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-5-generate-and-serve-the-site","level":3,"title":"Step 5: Generate and Serve the Site","text":"

    With imported and enriched journal files, generate the static site:

    # Generate site structure only\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate, build, and serve locally\nctx journal site --serve\n

    Then open http://localhost:8000 to browse.

    The site includes a date-sorted index, individual session pages with full conversations, search (press /), dark mode, and enriched titles in the navigation when frontmatter exists.

    You can also serve an already-generated site without regenerating using ctx serve (serve-only, no regeneration).

    The site generator requires zensical (pipx install zensical).

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#where-does-agent-add-value","level":2,"title":"Where Does Agent Add Value?","text":"

    Export, list, and show are mechanical: The agent runs the same CLI commands you would, so you can stay in your terminal for those.

    The agent earns its keep in enrich. It reads conversation content, understands what happened, and produces structured metadata. That is agent work, not CLI work.

    You can also ask your agent to browse sessions conversationally instead of remembering flags:

    What did we work on last week?\nShow me the session about Redis.\nImport everything to the journal.\n

    This is convenient but not required: ctx journal source gives you the same inventory.

    Where the agent genuinely helps is chaining the pipeline:

    You:   What happened last Tuesday?\nAgent: Last Tuesday you worked on two sessions:\n       - bright-dancing-hopper (2h 10m): refactored the middleware\n         pipeline and added Redis caching\n       - quiet-flowing-dijkstra (18m): quick fix for a nil pointer\n         in the config loader\n       Want me to export and enrich them?\nYou:   Yes, do it.\nAgent: Exports both, enriches, then proposes frontmatter.\n

    The value is staying in one context while the agent runs import -> enrich without you manually switching tools.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A typical pipeline from raw sessions to a browsable site:

    # Terminal: import and generate\nctx journal import --all\nctx journal site --serve\n
    # AI assistant: enrich\n/ctx-journal-enrich-all\n
    # Terminal: rebuild with enrichments\nctx journal site --serve\n

    If your project includes Makefile.ctx (deployed by ctx init), use make journal to combine import and rebuild stages. Then enrich inside Claude Code, then make journal again to pick up enrichments.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#session-retention-and-cleanup","level":2,"title":"Session Retention and Cleanup","text":"

    Claude Code does not keep JSONL transcripts forever. Understanding its cleanup behavior helps you avoid losing session history.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#default-behavior","level":3,"title":"Default Behavior","text":"

    Claude Code retains session transcripts for approximately 30 days. After that, JSONL files are automatically deleted during cleanup. Once deleted, ctx journal can no longer see those sessions - the data is gone.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-cleanupperioddays-setting","level":3,"title":"The cleanupPeriodDays Setting","text":"

    Claude Code exposes a cleanupPeriodDays setting in its configuration (~/.claude/settings.json) that controls retention:

    Value Behavior 30 (default) Transcripts older than 30 days are deleted 60, 90, etc. Extends the retention window 0 Disables writing new transcripts entirely - not \"keep forever\"

    Setting cleanupPeriodDays To 0

    Setting this to 0 does not mean \"never delete.\" It disables transcript creation altogether. No new JSONL files are written, which means ctx journal sees nothing new. This is rarely what you want.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#why-journal-import-matters","level":3,"title":"Why Journal Import Matters","text":"

    The journal import pipeline (Steps 1-4 above) is your archival mechanism. Imported Markdown files in .context/journal/ persist independently of Claude Code's cleanup cycle. Even after the source JSONL files are deleted, your journal entries remain.

    Recommendation: import regularly - weekly, or after any session worth revisiting. A quick ctx journal import --all takes seconds and ensures nothing falls through the 30-day window.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#quick-archival-checklist","level":3,"title":"Quick Archival Checklist","text":"
    1. Run ctx journal import --all at least weekly
    2. Enrich high-value sessions with /ctx-journal-enrich before the details fade from your own memory
    3. Lock enriched entries (ctx journal lock <pattern>) to protect them from accidental regeneration
    4. Rebuild the journal site periodically to keep it current
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tips","level":2,"title":"Tips","text":"
    • Start with /ctx-history inside your AI assistant. If you want to quickly check what happened in a recent session without leaving your editor, /ctx-history lets you browse interactively without importing.
    • Large sessions may be split automatically. Sessions with 200+ messages can be split into multiple parts (session-abc123.md, session-abc123-p2.md, session-abc123-p3.md) with navigation links between them. The site generator can handle this.
    • Suggestion sessions can be separated. Claude Code can generate short suggestion sessions for autocomplete. These may appear under a separate section in the site index, so they do not clutter your main session list.
    • Your agent is a good session browser. You do not need to remember slugs, dates, or flags. Ask \"what did we do yesterday?\" or \"find the session about Redis\" and it can map the question to recall commands.

    Journal Files Are Sensitive

    Journal files MUST be .gitignored.

    Session transcripts can contain sensitive data such as file contents, commands, error messages with stack traces, and potentially API keys.

    Add .context/journal/, .context/journal-site/, and .context/journal-obsidian/ to your .gitignore.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#next-up","level":2,"title":"Next Up","text":"

    Persisting Decisions, Learnings, and Conventions →: Record decisions, learnings, and conventions so they survive across sessions.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: where session saving fits in the daily workflow
    • Turning Activity into Content: generating blog posts from session history
    • Session Journal: full documentation of the journal system
    • CLI Reference: ctx journal: all journal subcommands and flags
    • CLI Reference: ctx serve: serve-only (no regeneration)
    • Context Files: the .context/ directory structure
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-ceremonies/","level":1,"title":"Session Ceremonies","text":"","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#the-problem","level":2,"title":"The Problem","text":"

    Sessions have two critical moments: the start and the end.

    • At the start, you need the agent to load context and confirm it knows what is going on.
    • At the end, you need to capture whatever the session produced before the conversation disappears.

    Most ctx skills work conversationally: \"jot down: check DNS after deploy\" is as good as /ctx-pad add \"check DNS after deploy\". But session boundaries are different. They are well-defined moments with specific requirements, and partial execution is costly.

    If the agent only half-loads context at the start, it works from stale assumptions. If it only half-persists at the end, learnings and decisions are lost.

    This Is One of the Few Times Being Explicit Matters

    Session ceremonies are the two bookend skills that mark these boundaries.

    They are the exception to the conversational rule:

    Invoke /ctx-remember and /ctx-wrap-up explicitly as slash commands.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tldr","level":2,"title":"TL;DR","text":"

    Start: /ctx-remember: load context, get a structured readback.

    End: /ctx-wrap-up: review session, propose candidates, persist approved items.

    Use the slash commands, not conversational triggers, for completeness.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#explicit-invocation-matters","level":2,"title":"Explicit Invocation Matters","text":"

    Most ctx skills encourage natural language. These two are different:

    Well-defined moments: Sessions have clear boundaries. A slash command marks the boundary unambiguously.

    Ambiguity risk: \"Do you remember?\" could mean many things. /ctx-remember means exactly one thing: load context and present a structured readback.

    Completeness: Conversational triggers risk partial execution. The agent might load some files but skip the session history, or persist one learning but forget to check for uncommitted changes. The slash command runs the full ceremony.

    Muscle memory: Typing /ctx-remember at session start and /ctx-wrap-up at session end becomes a habit, like opening and closing braces.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-remember Skill Load context and present structured readback /ctx-wrap-up Skill Gather session signal, propose and persist context /ctx-commit Skill Commit with context capture (offered by wrap-up) ctx agent CLI Load token-budgeted context packet ctx journal source CLI List recent sessions ctx add CLI Persist learnings, decisions, conventions, tasks","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-start-ctx-remember","level":2,"title":"Session Start: /ctx-remember","text":"

    Invoke at the beginning of every session:

    /ctx-remember\n

    The skill silently:

    1. Loads the context packet via ctx agent --budget 4000
    2. Reads TASKS.md, DECISIONS.md, LEARNINGS.md
    3. Checks recent sessions via ctx journal source --limit 3

    Then presents a structured readback with four sections:

    • Last session: topic, date, what was accomplished
    • Active work: pending and in-progress tasks
    • Recent context: 1-2 relevant decisions or learnings
    • Next step: suggestion or question about what to focus on

    The readback should feel like recall, not a file system tour. If the agent says \"Let me check if there are files...\" instead of a confident summary, the skill is not working correctly.

    What about 'do you remember?'

    The conversational trigger still works. But /ctx-remember guarantees the full ceremony runs:

    • context packet,
    • file reads,
    • session history,
    • and all four readback sections.

    The conversational version may cut corners.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-end-ctx-wrap-up","level":2,"title":"Session End: /ctx-wrap-up","text":"

    Invoke before ending a session where meaningful work happened:

    /ctx-wrap-up\n

    The skill runs four phases:

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-1-gather-signal","level":3,"title":"Phase 1: Gather Signal","text":"

    Silently checks git diff --stat, recent commits, and scans the conversation for themes: architectural choices, gotchas, patterns established, follow-up work identified.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-2-propose-candidates","level":3,"title":"Phase 2: Propose Candidates","text":"

    Presents a structured list grouped by type:

    ## Session Wrap-Up\n\n### Learnings (2 candidates)\n1. **PyMdownx details extension breaks pre/code rendering**\n   - Context: Journal site showed broken code blocks inside details tags\n   - Lesson: details extension wraps content in <details> HTML, which\n     interferes with <pre><code> rendering\n   - Application: Use fenced code blocks instead of indented code inside\n     admonitions when details extension is active\n\n2. **Hook subprocesses cannot propagate env vars**\n   - Context: Set env var in PreToolUse hook, invisible in main session\n   - Lesson: Hooks execute in child processes; env changes don't propagate\n   - Application: Use tombstone files for hook-to-session communication\n\n### Decisions (1 candidate)\n1. **File-based cooldown tokens over env vars**\n   - Context: Need session-scoped cooldown for ctx agent auto-loading\n   - Rationale: File tokens survive across processes, simpler than IPC\n   - Consequence: Tombstone files accumulate in /tmp; need TTL cleanup\n\nPersist all? Or select which to keep?\n

    Each candidate has complete structured fields, not just a title. Empty categories are omitted.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-3-persist","level":3,"title":"Phase 3: Persist","text":"

    After you approve (all, some, or modified), the skill runs the appropriate ctx add commands and reports results.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#nudge-suppression","level":3,"title":"Nudge Suppression","text":"

    After persisting, the skill marks the session as wrapped up via ctx system mark-wrapped-up. This suppresses context checkpoint nudges for 2 hours so the wrap-up ceremony itself does not trigger noisy reminders.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-4-commit-offer","level":3,"title":"Phase 4: Commit Offer","text":"

    If there are uncommitted changes, offers to run /ctx-commit. Does not auto-commit.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#when-to-skip","level":2,"title":"When to Skip","text":"

    Not every session needs ceremonies.

    Skip /ctx-remember when:

    • You are doing a quick one-off lookup (reading a file, checking a value)
    • Context was already loaded this session via /ctx-agent
    • You are continuing immediately after a previous session and context is still fresh

    Skip /ctx-wrap-up when:

    • Nothing meaningful happened (only read files, answered a question)
    • You already persisted everything manually during the session
    • The session was trivial (typo fix, quick config change)

    A good heuristic: if the session produced something a future session should know about, run /ctx-wrap-up. If not, just close.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#quick-reference","level":2,"title":"Quick Reference","text":"
    # Session start\n/ctx-remember\n\n# ... do work ...\n\n# Session end\n/ctx-wrap-up\n

    That is the complete ceremony. Two commands, bookending your session.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#relationship-to-other-skills","level":2,"title":"Relationship to Other Skills","text":"Skill When Purpose /ctx-remember Session start Load and confirm context /ctx-reflect Mid-session breakpoints Checkpoint at milestones /ctx-wrap-up Session end Full session review and persist /ctx-commit After completing work Commit with context capture

    /ctx-reflect is for mid-session checkpoints. /ctx-wrap-up is for end-of-session: it is more thorough, covers the full session arc, and includes the commit offer. If you already ran /ctx-reflect recently, /ctx-wrap-up avoids proposing the same candidates again.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tips","level":2,"title":"Tips","text":"
    • Make it a habit: The value of ceremonies compounds over sessions. Each /ctx-wrap-up makes the next /ctx-remember richer.
    • Trust the candidates: The agent scans the full conversation. It often catches learnings you forgot about.
    • Edit before approving: If a proposed candidate is close but not quite right, tell the agent what to change. Do not settle for a vague learning when a precise one is possible.
    • Do not force empty ceremonies: If /ctx-wrap-up finds nothing worth persisting, that is fine. A session that only read files and answered questions does not need artificial learnings.
    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#next-up","level":2,"title":"Next Up","text":"

    Browsing and Enriching Past Sessions →: Export session history to a browsable journal and enrich entries with metadata.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: the full session workflow that ceremonies bookend
    • Persisting Decisions, Learnings, and Conventions: deep dive on what gets persisted during wrap-up
    • Detecting and Fixing Drift: keeping context files accurate between ceremonies
    • Pausing Context Hooks: skip ceremonies entirely for quick tasks that don't need them
    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-changes/","level":1,"title":"Reviewing Session Changes","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-changed-while-you-were-away","level":2,"title":"What Changed While You Were Away?","text":"

    Between sessions, teammates commit code, context files get updated, and decisions pile up. ctx change gives you a single-command summary of everything that moved since your last session.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#quick-start","level":2,"title":"Quick Start","text":"
    # Auto-detects your last session and shows what changed\nctx change\n\n# Check what changed in the last 48 hours\nctx change --since 48h\n\n# Check since a specific date\nctx change --since 2026-03-10\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx change fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#how-reference-time-works","level":2,"title":"How Reference Time Works","text":"

    ctx change needs a reference point to compare against. It tries these sources in order:

    1. --since flag: explicit duration (24h, 72h) or date (2026-03-10, RFC3339 timestamp)
    2. Session markers: ctx-loaded-* files in .context/state/; picks the second-most-recent (your previous session start)
    3. Event log: last context-load-gate event from .context/state/events.jsonl
    4. Fallback: 24 hours ago

    The marker-based detection means ctx change usually just works without any flags: it knows when you last loaded context and shows everything after that.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-it-reports","level":2,"title":"What It Reports","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#context-file-changes","level":3,"title":"Context File Changes","text":"

    Any .md file in .context/ modified after the reference time:

    ### Context File Changes\n- `TASKS.md` - modified 2026-03-11 14:30\n- `DECISIONS.md` - modified 2026-03-11 09:15\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#code-changes","level":3,"title":"Code Changes","text":"

    Git activity since the reference time:

    ### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#integrating-into-session-start","level":2,"title":"Integrating into Session Start","text":"

    Pair ctx change with the /ctx-remember ceremony for a complete session-start picture:

    # 1. Load context (this also creates the session marker)\nctx agent --budget 4000\n\n# 2. See what changed since your last session\nctx change\n

    Or script it:

    # .context/hooks/session-start.sh\nctx agent --budget 4000\necho \"---\"\nctx change\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#team-workflows","level":2,"title":"Team Workflows","text":"

    When multiple people share a .context/ directory, ctx change shows who changed what:

    # After pulling from remote\ngit pull\nctx change --since 72h\n

    This surfaces context file changes from teammates that you might otherwise miss in the commit log.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#tips","level":2,"title":"Tips","text":"
    • No changes? If nothing shows up, the reference time might be wrong. Use --since 48h to widen the window.
    • Works without git. Context file changes are detected by filesystem mtime, not git. Code changes require git.
    • Hook integration. The context-load-gate hook writes the session marker that ctx change uses for auto-detection. If you're not using the ctx plugin, markers won't exist and it falls back to the event log or 24h window.
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-lifecycle/","level":1,"title":"The Complete Session","text":"","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-problem","level":2,"title":"The Problem","text":"

    \"What does a full ctx session look like from start to finish?\"

    You have ctx installed and your .context/ directory initialized, but the individual commands and skills feel disconnected.

    How do they fit together into a coherent workflow?

    This recipe walks through a complete session, from opening your editor to persisting context before you close it, so you can see how each piece connects.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tldr","level":2,"title":"TL;DR","text":"
    1. Load: /ctx-remember: load context, get structured readback.
    2. Orient: /ctx-status: check file health and token usage.
    3. Pick: /ctx-next: choose what to work on.
    4. Work: implement, test, iterate.
    5. Commit: /ctx-commit: commit and capture decisions/learnings.
    6. Reflect: /ctx-reflect: identify what to persist (at milestones)
    7. Wrap up: /ctx-wrap-up: end-of-session ceremony.

    Read on for the full walkthrough with examples.

    Before You Start: Activate the Project

    ctx commands (and the skills that call them) require CTX_DIR to be declared for the shell you're working in; ctx does not walk the filesystem to find .context/. Once per shell (or via your shell rc / direnv):

    eval \"$(ctx activate)\"\n

    If you skip this, every skill below will surface an error naming the fix. See Activating a Context Directory for the full recipe.

    What Is a Readback?

    A readback is a structured summary where the agent plays back what it knows:

    • last session,
    • active tasks,
    • recent decisions.

    This way, you can confirm it loaded the right context.

    The term \"readback\" comes from aviation, where pilots repeat instructions back to air traffic control to confirm they heard correctly.

    Same idea in ctx: The agent tells you what it \"thinks\" is going on, and you correct anything that's off before the work begins.

    • Last session: topic, date, what was accomplished
    • Active work: pending and in-progress tasks
    • Recent context: 1-2 decisions or learnings that matter now
    • Next step: suggestion or question about what to focus on
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx status CLI command Quick health check on context files ctx agent CLI command Load token-budgeted context packet ctx journal source CLI command List previous sessions ctx journal source --show CLI command Inspect a specific session in detail /ctx-remember Skill Recall project context with structured readback /ctx-agent Skill Load full context packet inside the assistant /ctx-status Skill Show context summary with commentary /ctx-next Skill Suggest what to work on with rationale /ctx-commit Skill Commit code and prompt for context capture /ctx-reflect Skill Structured reflection checkpoint /ctx-history Skill Browse session history inside your AI assistant","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-workflow","level":2,"title":"The Workflow","text":"

    The session lifecycle has seven steps. You will not always use every step (for example, a quick bugfix might skip reflection, and a research session might skip committing), but the full arc looks like this:

    Load context > Orient > Pick a Task > Work > Commit > Reflect

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-1-load-context","level":3,"title":"Step 1: Load Context","text":"

    Start every session by loading what you know. The fastest way is a single prompt:

    Do you remember what we were working on?\n

    This triggers the /ctx-remember skill. Behind the scenes, the assistant runs ctx agent --budget 4000, reads the files listed in the context packet (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md), checks ctx journal source --limit 3 for recent sessions, and then presents a structured readback.

    The readback should feel like a recall, not a file system tour. If you see \"Let me check if there are files...\" instead of a confident summary, the context system is not loaded properly.

    As an alternative, if you want raw data instead of a readback, run ctx status in your terminal or invoke /ctx-status for a summarized health check showing file counts, token usage, and recent activity.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-2-orient","level":3,"title":"Step 2: Orient","text":"

    After loading context, verify you understand the current state.

    /ctx-status\n

    The status output shows which context files are populated, how many tokens they consume, and which files were recently modified. Look for:

    • Empty core files: TASKS.md or CONVENTIONS.md with no content means the context is sparse
    • High token count (over 30k): the context is bloated and might need ctx compact
    • No recent activity: files may be stale and need updating

    If the status looks healthy and the readback from Step 1 gave you enough context, skip ahead.

    If something seems off (stale tasks, missing decisions...), spend a minute reading the relevant file before proceeding.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"

    With context loaded, choose a task. You can pick one yourself, or ask the assistant to recommend:

    /ctx-next\n

    The skill reads TASKS.md, checks recent sessions to avoid re-suggesting completed work, and presents 1-3 ranked recommendations with rationale.

    It prioritizes in-progress tasks over new starts (finishing is better than starting), respects explicit priority tags, and favors momentum: continuing a thread from a recent session is cheaper than context-switching.

    If you already know what you want to work on, state it directly:

    Let's work on the session enrichment feature.\n
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-4-do-the-work","level":3,"title":"Step 4: Do the Work","text":"

    This is the main body of the session: write code, fix bugs, refactor, research: whatever the task requires.

    During this phase, a few ctx-specific patterns help:

    Check decisions before choosing: when you face a design choice, check if a prior decision covers it.

    Is this consistent with our decisions?\n

    Constrain scope: keep the assistant focused on the task at hand.

    Only change files in internal/cli/session/. Nothing else.\n

    Use /ctx-implement for multistep plans: if the task has multiple steps, this skill executes them one at a time with build/test verification between each step.

    Context monitoring runs automatically: the check-context-size hook monitors context capacity at adaptive intervals. Early in a session it stays silent. After 16+ prompts it starts monitoring, and past 30 prompts it checks frequently. If context capacity is running high, it will suggest saving unsaved work. No manual invocation is needed.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-5-commit-with-context","level":3,"title":"Step 5: Commit with Context","text":"

    When the work is ready, use the context-aware commit instead of raw git commit:

    /ctx-commit\n

    The Agent May Recommend Committing

    You do not always need to invoke /ctx-commit explicitly.

    After a commit, the agent may proactively offer to capture context:

    \"We just made a trade-off there. Want me to record it as a decision?\"

    This is normal: The Agent Playbook encourages persisting at milestones, and a commit is a natural milestone.

    As an alternative, you can ask the assistant \"can we commit this?\" and it will pick up the /ctx-commit skill for you.

    The skill runs a pre-commit build check (for Go projects, go build), reviews the staged changes, drafts a commit message focused on \"why\" rather than \"what\", and then commits.

    After the commit succeeds, it prompts you:

    **Any context to capture?**\n\n- **Decision**: Did you make a design choice or trade-off?\n- **Learning**: Did you hit a gotcha or discover something?\n- **Neither**: No context to capture; we are done.\n

    If you made a decision, the skill records it with ctx decision add. If you learned something, it records it with ctx learning add including context, lesson, and application fields. This is the bridge between committing code and remembering why the code looks the way it does.

    If source code changed in areas that affect documentation, the skill also offers to check for doc drift.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-6-reflect","level":3,"title":"Step 6: Reflect","text":"

    At natural breakpoints (after finishing a feature, resolving a complex bug, or before switching tasks) pause to reflect:

    /ctx-reflect\n

    Agents Reflect at Milestones

    Agents often reflect without explicit invocation.

    After completing a significant piece of work, the agent may naturally surface items worth persisting:

    \"We discovered that $PPID resolves differently inside hooks. Should I save that as a learning?\"

    This is the agent following the Work-Reflect-Persist cycle from the Agent Playbook.

    You do not need to say /ctx-reflect for this to happen; the agent treats milestones as reflection triggers on its own.

    The skill works through a checklist: learnings discovered, decisions made, tasks completed or created, and whether there are items worth persisting. It then presents a summary with specific items to persist, each with the exact command to run:

    I would suggest persisting:\n\n- **Learning**: `$PPID` in PreToolUse hooks resolves to the Claude Code PID\n  `ctx learning add --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Task**: mark \"Add cooldown to ctx agent\" as done\n- **Decision**: tombstone-based cooldown with 10m default\n  `ctx decision add \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n

    The skill asks before persisting anything. You choose what to keep.

    Not every commit needs reflection. A typo fix does not. But when you have been debugging for an hour and finally understand the root cause, that is worth a reflection checkpoint.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-7-persist-before-ending","level":3,"title":"Step 7: Persist Before Ending","text":"

    Before ending the session, run the wrap-up ceremony to capture outstanding learnings, decisions, conventions, and tasks:

    /ctx-wrap-up\n

    Ceremony Skills

    /ctx-remember at session start and /ctx-wrap-up at session end are ceremony skills: Invoke them as explicit slash commands for completeness, not conversationally.

    See Session Ceremonies for details on why these two skills are the exception to the conversational rule.

    The skill gathers signal from git diff, recent commits, and the conversation, then proposes structured candidates for your approval. After you select which to keep, it persists them via ctx add and offers /ctx-commit if uncommitted changes remain.

    Session transcripts are automatically captured by Claude Code and can be browsed later with ctx journal source and ctx journal source --show.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#conversational-session-flow","level":2,"title":"Conversational Session Flow","text":"

    The steps above describe the mechanics. In practice, a session with a ctx-aware agent feels more like a conversation than a command sequence.

    The agent knows the lifecycle and moves through it naturally, often without explicit invocation. Here is a realistic example:

    You: Do you remember what we were working on?

    Agent (loads context silently, presents structured readback):

    Last session (Feb 7, caching-layer): we implemented the in-memory cache with\nTTL expiration and wrote tests for the eviction policy. \n\nYou have 3 pending tasks; the biggest one is adding Redis as a backend.\n\nRecent decision:<br>\nwe chose LRU over LFU because the access pattern is mostly recency-based.\n\nWant to continue with the Redis backend, or tackle something else?\n

    You: Let's work on the Redis caching backend.

    Agent (picks up the task, begins implementation):

    Got it. I see the task in TASKS.md: \"Add Redis backend for cache layer.\"\n\nI will start with the connection pool setup. Only touching\n`internal/cache/redis/` for now.\n

    (The agent works... writes code, runs tests, iterates.)

    Agent (after implementing the connection pool and choosing a retry strategy):

    The connection pool is working. I went with exponential backoff with jitter\nfor retries instead of fixed intervals; it handles thundering herd better\nunder reconnection storms. That is a design choice worth recording. \n\nWant me to save it as a decision?\n

    You: Yes, go ahead.

    Agent:

    Done. Added to DECISIONS.md: \"Exponential backoff with jitter for Redis\nreconnection, to avoid thundering herd on pool recovery.\"\n

    (More work happens... the agent implements the cache read/write paths.)

    You: What else did we learn during this session?

    Agent:

    A few things worth capturing:\n\n* **Learning**: Redis `SET` with `NX` and `PX` flags handles atomic\n  set-if-not-exists with TTL in a single call. No need for a separate\n  `EXPIRE` command. We initially did it in two calls and hit a race condition.\n* **Learning**: The `go-redis` library's `PoolSize` default is 10 per CPU.\n  For our workload, explicit sizing at 20 was more predictable.\n* **Task update**: The connection pool subtask is done; the read/write path\n  is implemented but needs integration tests.\n\nWant me to persist these?\n

    You: Yes. And let's wrap up.

    Agent (reflects and persists... all in one flow):

    Persisted:\n\n* 2 learnings added to LEARNINGS.md\n* Marked \"connection pool setup\" done in TASKS.md\n* Added \"write integration tests for Redis cache\" as a new task\n\nContext is up to date. You are good to close.\n

    Notice What Happened

    In the above workflow, the user never typed /ctx-reflect or ctx learning add.

    The agent moved through Load, Orient, Pick, Work, Commit, and Reflect driven by natural conversation.

    \"Let's wrap up\" was enough to trigger the full reflect-and-persist flow.

    The agent surfaced persist-worthy items at milestones: after a design choice, after discovering a gotcha: without waiting to be asked.

    This is the intended experience.

    The commands and skills still exist for when you want precise control, but the agent is a proactive partner in the lifecycle, not a passive executor of slash commands.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Quick-reference checklist for a complete session:

    • Load: /ctx-remember: load context and confirm readback
    • Orient: /ctx-status: check file health and token usage
    • Pick: /ctx-next: choose what to work on
    • Work: implement, test, iterate (scope with \"only change X\")
    • Commit: /ctx-commit: commit and capture decisions/learnings
    • Reflect: /ctx-reflect: identify what to persist (at milestones)
    • Wrap up: /ctx-wrap-up: end-of-session ceremony

    Conversational equivalents: you can drive the same lifecycle with plain language:

    Step Slash command Natural language Load /ctx-remember \"Do you remember?\" / \"What were we working on?\" Orient /ctx-status \"How's our context looking?\" Pick /ctx-next \"What should we work on?\" / \"Let's do the caching task\" Work (none) \"Only change files in internal/cache/\" Commit /ctx-commit \"Commit this\" / \"Ship it\" Reflect /ctx-reflect \"What did we learn?\" / (agent offers at milestones) Wrap up /ctx-wrap-up (use the slash command for completeness)

    The agent understands both columns.

    In practice, most sessions use a mix:

    • Explicit Commands when you want precision;
    • Natural Language when you want flow and agentic autonomy.

    The agent will also initiate steps on its own (particularly \"Reflect\") when it recognizes a milestone.

    Short sessions (quick bugfix) might only use: Load, Work, Commit.

    Long sessions should Reflect after each major milestone and persist learnings and decisions before ending.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tips","level":2,"title":"Tips","text":"

    Persist early if context is running low. A hook monitors context capacity and notifies you when it gets high, but do not wait for the notification. If you have been working for a while and have unpersisted learnings, persist proactively.

    Browse previous sessions by topic. If you need context from a prior session, ctx journal source --show auth will match by keyword. You do not need to remember the exact date or slug.

    Reflection is optional but valuable. You can skip /ctx-reflect for small changes, but always persist learnings and decisions before ending a session where you did meaningful work. These are what the next session loads.

    Let the hook handle context loading. The PreToolUse hook runs ctx agent automatically with a cooldown, so context loads on first tool use without you asking. The /ctx-remember prompt at session start is for your benefit (to get a readback), not because the assistant needs it.

    The agent is a proactive partner, not a passive tool. A ctx-aware agent follows the Agent Playbook: it watches for milestones (completed tasks, design decisions, discovered gotchas) and offers to persist them without being asked. If you finish a tricky debugging session, it may say \"That root cause is worth saving as a learning. Want me to record it?\" before you think to ask. This is by design.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#next-up","level":2,"title":"Next Up","text":"

    Session Ceremonies →: The two bookend rituals for every session: /ctx-remember at the start, /ctx-wrap-up at the end.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#see-also","level":2,"title":"See Also","text":"
    • Session Ceremonies: why /ctx-remember and /ctx-wrap-up are explicit slash commands, not conversational
    • CLI Reference: full documentation for all ctx commands
    • Prompting Guide: effective prompts for ctx-enabled projects
    • Tracking Work Across Sessions: deep dive on task management
    • Persisting Decisions, Learnings, and Conventions: deep dive on knowledge capture
    • Detecting and Fixing Drift: keeping context files accurate
    • Pausing Context Hooks: shortcut the full lifecycle for quick tasks that don't need ceremony overhead
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-pause/","level":1,"title":"Pausing Context Hooks","text":"","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#the-problem","level":2,"title":"The Problem","text":"

    Not every session needs the full ceremony. Quick investigations, one-off questions, small fixes unrelated to active project work: These tasks don't benefit from persistence nudges, ceremony reminders, or knowledge checks. Every hook still fires, consuming tokens and attention on work that won't produce learnings or decisions worth capturing.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tldr","level":2,"title":"TL;DR","text":"Command What it does ctx hook pause or /ctx-pause Silence all nudge hooks for this session ctx hook resume or /ctx-resume Restore normal hook behavior

    Pause is session-scoped: It only affects the current session. Other sessions (same project, different terminal) are unaffected.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-gets-paused","level":2,"title":"What Gets Paused","text":"

    All nudge and reminder hooks go silent:

    • Context size checkpoints
    • Ceremony adoption nudges
    • Persistence reminders
    • Journal maintenance reminders
    • Knowledge growth nudges
    • Map staleness nudges
    • Version update nudges
    • Resource pressure warnings
    • QA reminders
    • Post-commit nudges
    • Specs nudges
    • Backup age warnings
    • Context load gate
    • Pending reminders relay
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-still-fires","level":2,"title":"What Still Fires","text":"

    Security hooks always run, even when paused:

    • block-non-path-ctx: prevents ./ctx invocations
    • block-dangerous-commands: blocks sudo, force push, etc.
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#workflow","level":2,"title":"Workflow","text":"
    # 1. Session starts: Context loads normally.\n\n# 2. You realize this is a quick task\nctx hook pause\n\n# 3. Work without interruption: hooks are silent\n\n# 4. Session evolves into real work? Resume first\nctx hook resume\n\n# 5. Now wrap up normally\n# /ctx-wrap-up\n
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#graduated-reminder","level":2,"title":"Graduated Reminder","text":"

    Paused hooks aren't completely invisible. A minimal indicator appears so you always know the state:

    Paused turns What you see 1-5 ctx:paused 6+ ctx:paused (N turns): resume with /ctx-resume

    This prevents the \"forgot I paused\" problem during long sessions.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tips","level":2,"title":"Tips","text":"
    • Resume before wrapping up. If your quick task turns into real work, resume hooks before running /ctx-wrap-up. The wrap-up ceremony needs active hooks to capture learnings properly.

    • Initial context load is unaffected. The ~8k token startup injection (CLAUDE.md, playbook, constitution) happens before any command runs. Pause only affects hooks that fire during the session.

    • Use for quick investigations. Debugging a stack trace? Checking a git log? Answering a colleague's question? Pause, do the work, close the session. No ceremony needed.

    • Don't use for real work. If you're implementing features, fixing bugs, or making decisions: keep hooks active. The nudges exist to prevent context loss.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#see-also","level":2,"title":"See Also","text":"

    See also: Session Ceremonies: the bookend rituals that pause lets you skip when they aren't needed.

    See also: Customizing Hook Messages: if you want to change what hooks say rather than silencing them entirely.

    See also: The Complete Session: the full session workflow that pause shortcuts for quick tasks.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-reminders/","level":1,"title":"Session Reminders","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-problem","level":2,"title":"The Problem","text":"

    You're deep in a session and realize: \"I need to refactor the swagger definitions next time.\" You could add a task, but this isn't a work item: it's a note to future-you. You could jot it on the scratchpad, but scratchpad entries don't announce themselves.

    How do you leave a message that your next session opens with?

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tldr","level":2,"title":"TL;DR","text":"
    ctx remind \"refactor the swagger definitions\"\nctx remind list\nctx remind dismiss 1       # or batch: ctx remind dismiss 1 3-5\n

    Reminders surface automatically at session start: VERBATIM, every session, until you dismiss them.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx remind ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx remind CLI command Add a reminder (default action) ctx remind list CLI command Show all pending reminders ctx remind dismiss CLI command Remove a reminder by ID (or --all) /ctx-remind Skill Natural language interface to reminders","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-1-leave-a-reminder","level":3,"title":"Step 1: Leave a Reminder","text":"

    Tell your agent what to remember, or run it directly:

    You: \"remind me to refactor the swagger definitions\"\n\nAgent: [runs ctx remind \"refactor the swagger definitions\"]\n       \"Reminder set:\n         + [1] refactor the swagger definitions\"\n

    Or from the terminal:

    ctx remind \"refactor the swagger definitions\"\n
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-2-set-a-date-gate-optional","level":3,"title":"Step 2: Set a Date Gate (Optional)","text":"

    If the reminder shouldn't fire until a specific date:

    You: \"remind me to check the deploy logs after Tuesday\"\n\nAgent: [runs ctx remind \"check the deploy logs\" --after 2026-02-25]\n       \"Reminder set:\n         + [2] check the deploy logs  (after 2026-02-25)\"\n

    The reminder stays silent until that date, then fires every session.

    The agent converts natural language dates (\"tomorrow\", \"next week\", \"after the release on Friday\") to YYYY-MM-DD. If it's ambiguous, it asks.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-3-start-a-new-session","level":3,"title":"Step 3: Start a New Session","text":"

    Next session, the reminder appears automatically before anything else:

    ┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n

    No action needed: The check-reminders hook fires on UserPromptSubmit and the agent relays the box verbatim.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-4-dismiss-when-done","level":3,"title":"Step 4: Dismiss When Done","text":"

    After you've acted on a reminder (or decided to skip it):

    You: \"dismiss reminder 1\"\n\nAgent: [runs ctx remind dismiss 1]\n       \"Dismissed:\n         - [1] refactor the swagger definitions\"\n\n# Batch dismiss also works:\n# \"dismiss reminders 3, 5 through 7\"\n# → ctx remind dismiss 3 5-7\n

    Or clear everything:

    ctx remind dismiss --all\n
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-5-check-whats-pending","level":3,"title":"Step 5: Check What's Pending","text":"
    ctx remind list\n
      [1] refactor the swagger definitions\n  [3] review auth token expiry logic\n  [4] check deploy logs  (after 2026-02-25, not yet due)\n

    Date-gated reminders that haven't reached their date show (not yet due).

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#using-ctx-remind-in-a-session","level":2,"title":"Using /ctx-remind in a Session","text":"

    Invoke the /ctx-remind skill, then describe what you want:

    You: /ctx-remind remind me to update the API docs\nYou: /ctx-remind what reminders do I have?\nYou: /ctx-remind dismiss reminder 3\n
    You say (after /ctx-remind) What the agent does \"remind me to update the API docs\" ctx remind \"update the API docs\" \"remind me next week to check staging\" ctx remind \"check staging\" --after 2026-03-02 \"what reminders do I have?\" ctx remind list \"dismiss reminder 3\" ctx remind dismiss 3 \"dismiss reminders 3, 5 through 7\" ctx remind dismiss 3 5-7 \"clear all reminders\" ctx remind dismiss --all","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#reminders-vs-scratchpad-vs-tasks","level":2,"title":"Reminders vs Scratchpad vs Tasks","text":"You want to... Use Leave a note that announces itself next session ctx remind Jot down a quick value or sensitive token ctx pad Track work with status and completion TASKS.md Record a decision or lesson for all sessions Context files

    Decision guide:

    • If it should announce itself at session start → ctx remind
    • If it's a quiet note you'll check manually → ctx pad
    • If it's a work item you'll mark done → TASKS.md

    Reminders Are Sticky Notes, Not Tasks

    A reminder has no status, no priority, no lifecycle. It's a message to \"future you\" that fires until dismissed.

    If you need tracking, use a task in TASKS.md.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tips","level":2,"title":"Tips","text":"
    • Reminders fire every session: Unlike nudges (which throttle to once per day), reminders repeat until you dismiss them. This is intentional: You asked to be reminded.
    • Date gating is session-scoped, not clock-scoped: --after 2026-02-25 means \"don't show until sessions on or after Feb 25.\" It does not mean \"alarm at midnight on Feb 25.\"
    • The agent handles date parsing: Say \"next week\" or \"after Friday\": The agent converts it to YYYY-MM-DD. The CLI only accepts the explicit date format.
    • Reminders are committed to git: They travel with the repo. If you switch machines, your reminders follow.
    • IDs never reuse: After dismissing reminder 3, the next reminder gets ID 4 (or higher). No confusion from recycled numbers.
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#next-up","level":2,"title":"Next Up","text":"

    Using the Scratchpad →: For quiet notes and sensitive values that don't need session-start announcements.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#see-also","level":2,"title":"See Also","text":"
    • CLI Reference: ctx remind: full command syntax and flags
    • The Complete Session: how reminders fit into the session lifecycle
    • Managing Tasks: for work items that need status tracking
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/state-maintenance/","level":1,"title":"State Directory Maintenance","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-problem","level":2,"title":"The Problem","text":"

    Every session creates tombstone files in .context/state/ - small markers that suppress repeat hook nudges (\"already checked context size\", \"already sent persistence reminder\"). Over days and weeks, these accumulate into hundreds of files from long-dead sessions.

    The files are harmless individually, but the clutter makes it harder to reason about state, and stale global tombstones can suppress nudges across sessions entirely.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tldr","level":2,"title":"TL;DR","text":"
    ctx prune --dry-run     # preview what would be removed\nctx prune               # prune files older than 7 days\nctx prune --days 1      # more aggressive: keep only today\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx prune / ctx status fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose ctx prune Command Remove old per-session state files ctx status Command Quick health overview including state dir","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#understanding-state-files","level":2,"title":"Understanding State Files","text":"

    State files fall into two categories:

    Session-scoped (contain a UUID in the filename): Created per-session to suppress repeat nudges. Safe to prune once the session ends. Examples:

    context-check-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\nheartbeat-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\npersistence-nudge-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\n

    Global (no UUID): Persist across sessions. ctx prune preserves these automatically. Some are legitimate state (events.jsonl, memory-import.json); others may be stale tombstones that need manual review.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-workflow","level":2,"title":"The Workflow","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-1-preview","level":3,"title":"Step 1: Preview","text":"

    Always dry-run first to see what would be removed:

    ctx prune --dry-run\n

    The output shows each file, its age, and a summary:

      would prune: context-check-abc123... (age: 3d)\n  would prune: heartbeat-abc123... (age: 3d)\n\nDry run - would prune 150 files (skip 70 recent, preserve 14 global)\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-2-prune","level":3,"title":"Step 2: Prune","text":"

    Choose an age threshold. The default is 7 days:

    ctx prune               # older than 7 days\nctx prune --days 3      # older than 3 days\nctx prune --days 1      # older than 1 day (aggressive)\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-3-review-global-files","level":3,"title":"Step 3: Review Global Files","text":"

    After pruning, check what prune preserved:

    ls .context/state/ | grep -v '[0-9a-f]\\{8\\}-[0-9a-f]\\{4\\}'\n

    Legitimate global files (keep):

    • events.jsonl - event log
    • memory-import.json - import tracking state

    Stale global tombstones (safe to delete):

    • Files like backup-reminded, ceremony-reminded, version-checked with no session UUID are one-shot markers. If they are from a previous session, they are stale and can be removed manually.
    rm .context/state/backup-reminded .context/state/ceremony-reminded\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-4-verify","level":3,"title":"Step 4: Verify","text":"
    ls .context/state/ | wc -l    # should be manageable\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#when-to-prune","level":2,"title":"When to Prune","text":"
    • Weekly: ctx prune with default 7-day threshold
    • After heavy parallel work: Multiple concurrent sessions create many tombstones. Prune with --days 1 afterward.
    • When state directory exceeds ~100 files: A sign that pruning hasn't run recently
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tips","level":2,"title":"Tips","text":"

    Pruning active sessions is safe but noisy: If you prune a file belonging to a still-running session, the corresponding hook will re-fire its nudge on the next prompt. Minor UX annoyance, not data loss.

    No context files are stored in state: The state directory contains only tombstones, counters, and diagnostic data. Nothing in .context/state/ affects your decisions, learnings, tasks, or conventions.

    Test artifacts sneak in: Files like context-check-statstest or heartbeat-unknown are artifacts from development or testing. They lack UUIDs so prune preserves them. Delete manually.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#see-also","level":2,"title":"See Also","text":"
    • Detecting and Fixing Drift: broader context maintenance including drift detection and archival
    • Troubleshooting: diagnostic workflow using ctx doctor and event logs
    • CLI Reference: system: full flag documentation for ctx prune and related commands
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/steering/","level":1,"title":"Writing Steering Files","text":"","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#writing-steering-files","level":1,"title":"Writing Steering Files","text":"

    Steering files tell your AI assistant how to behave, not what was decided or how the codebase is written. This recipe walks through writing a steering file from scratch, validating which prompts will trigger it, and syncing it out to your configured AI tools.

    Before You Start

    If you're unsure whether a rule belongs in steering/, DECISIONS.md, or CONVENTIONS.md, read the \"Steering vs decisions vs conventions\" admonition on the ctx steering reference page. The short version: if the rule is \"the AI should always do X when asked about Y,\" that's steering. Otherwise it's probably a decision or convention.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#start-here-customize-the-foundation-files","level":2,"title":"Start Here: Customize the Foundation Files","text":"

    ctx init scaffolds four foundation steering files for you the first time you initialize a project:

    File Purpose .context/steering/product.md Product context, goals, target users .context/steering/tech.md Tech stack, constraints, key dependencies .context/steering/structure.md Directory layout, naming conventions .context/steering/workflow.md Branch strategy, commit rules, pre-commit

    Each file opens with an inline HTML comment that explains the three inclusion modes, what priority means, and the tools scope. The comment is invisible in rendered markdown but visible when you edit the file. Delete it once the file is yours.

    All four default to inclusion: always and priority: 10, so they fire on every AI tool call until you customize them. If you're reading this recipe and haven't touched them yet, open each one now and replace the placeholder bullet list with actual rules for your project. That's the highest-leverage five minutes you can spend in a new ctx setup.

    What to fill in, by file:

    product.md: The elevator pitch plus hard scope:

    • One-sentence product description.
    • Primary users and their top job-to-be-done.
    • Two or three \"this is explicitly out of scope\" items so the AI doesn't wander.

    tech.md: Technology and constraints:

    • Languages and versions (Go 1.22, Node 20, etc.).
    • Frameworks and key libraries.
    • Runtime and deployment target.
    • Hard constraints: \"no CGO\", \"no network at test time\", \"no external DB for unit tests\". These are the things that burn agents when they don't know them.

    structure.md: Layout and naming:

    • Top-level directories and their purpose.
    • Where new files should go (and where they should NOT).
    • Naming conventions for packages, files, types.

    workflow.md: Process rules:

    • Branch strategy (main-only, trunk-based, feature branches).
    • Commit message format, signed-off-by requirement.
    • Pre-commit and pre-push checks.
    • Review expectations.

    After editing, the next AI tool call in Claude Code will pick up the new rules automatically via the plugin's PreToolUse hook, with no sync step and no restart. Other tools (Cursor, Cline, Kiro) need ctx steering sync to export into their native format.

    Prefer a Bare .context/steering/ Directory?

    Re-run ctx init --no-steering-init and delete the scaffolded files. ctx init leaves existing files alone, so the flag is only needed if you want to opt out of the initial scaffold.

    The rest of this recipe walks through creating an additional, scenario-specific steering file beyond the four foundation defaults.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#scenario","level":2,"title":"Scenario","text":"

    You're working on a project with a strict input-validation policy: every new API handler must validate request bodies before touching the database. You want the AI to flag this concern automatically whenever it's asked to write an HTTP handler, without you having to remind it every session.

    Claude Code Users: Pick always, Not auto

    This walkthrough uses inclusion: auto because the scenario is a scoped rule that matches a specific kind of prompt. That works natively on Cursor, Cline, and Kiro (they resolve the description keyword match themselves).

    On Claude Code, auto does not fire through the plugin's PreToolUse hook. The hook passes an empty prompt to ctx agent, so only always files match. Claude can still reach an auto file by calling the ctx_steering_get MCP tool, but that requires Claude to decide to call it; there's no automatic injection.

    If Claude Code is your tool, set inclusion: always in Step 2 instead of auto. The rule will fire on every tool call regardless of topic. You may want to narrow the rule body so the extra tokens per turn aren't wasted on unrelated work.

    See the ctx steering reference \"Prefer inclusion: always for Claude Code\" section for the full trade-off.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-1-scaffold-the-file","level":2,"title":"Step 1: Scaffold the File","text":"
    ctx steering add api-validation\n

    That creates .context/steering/api-validation.md with default frontmatter:

    ---\nname: api-validation\ndescription:\ninclusion: manual\ntools: []\npriority: 50\n---\n

    The defaults are deliberately conservative: inclusion: manual means the file won't be applied until you opt in, which keeps the rules out of the prompt until you've reviewed them.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-2-fill-in-the-rule","level":2,"title":"Step 2: Fill in the Rule","text":"

    Open the file and write the rule body plus a focused description. The description is what inclusion: auto matches against later.

    ---\nname: api-validation\ndescription: HTTP handler input validation and request parsing\ninclusion: auto\ntools: []\npriority: 20\n---\n\n# API request validation\n\nEvery new HTTP handler MUST:\n\n1. Parse request bodies into typed structs, never `map[string]any`.\n2. Validate required fields before any database call.\n3. Return 400 with a machine-readable error for validation failures.\n4. Use `context.Context` from the request for all downstream calls.\n\nPrefer existing validation helpers in `internal/validate/`\nrather than inline checks.\n

    Notes on the choices:

    • inclusion: auto: this rule should fire automatically on HTTP-handler-shaped prompts, not always.
    • priority: 20: lower than the default, so this rule appears near the top of the prompt alongside other high-priority rules.
    • Description is keyword-rich (\"HTTP handler input validation and request parsing\"); the auto matcher scores prompts against these words.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-3-preview-which-prompts-match","level":2,"title":"Step 3: Preview Which Prompts Match","text":"

    Before committing the file, validate your description catches the prompts you care about:

    ctx steering preview \"add an endpoint for updating user email\"\n

    Expected output:

    Steering files matching prompt \"add an endpoint for updating user email\":\n  api-validation       inclusion=auto     priority=20  tools=all\n

    Good, the prompt matches. Try a negative case:

    ctx steering preview \"fix a bug in the JSON renderer\"\n

    Expected: empty match (or whatever else is currently auto). If api-validation incorrectly fires for unrelated prompts, tighten the description. If it misses prompts it should catch, add more keywords.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-4-list-to-confirm-metadata","level":2,"title":"Step 4: List to Confirm Metadata","text":"
    ctx steering list\n

    Should show api-validation alongside any other files, with its inclusion mode and priority. If the list is wrong, check the frontmatter for typos.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-5-get-the-rules-in-front-of-the-ai","level":2,"title":"Step 5: Get the Rules in Front of the AI","text":"

    Steering files are authored once in .context/steering/, but how they reach the AI depends on which tool you use. There are two delivery mechanisms:

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-a-native-rules-tools-cursor-cline-kiro","level":3,"title":"Path A: Native-Rules Tools (Cursor, Cline, Kiro)","text":"

    These tools read a specific directory for rules. ctx steering sync exports your files into that directory with tool-specific frontmatter:

    ctx steering sync\n

    Depending on the active tool in .ctxrc or --tool:

    Tool Target Cursor .cursor/rules/ Cline .clinerules/ Kiro .kiro/steering/

    The sync is idempotent; unchanged files are skipped. Run it whenever you edit a steering file.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-b-claude-code-and-codex-hook-mcp","level":3,"title":"Path B: Claude Code and Codex (Hook + MCP)","text":"

    Claude Code and Codex have no native rules primitive, so ctx steering sync is a no-op for them; it deliberately skips both. Instead, steering reaches these tools through two non-sync channels:

    1. PreToolUse hook (automatic). The ctx setup claude-code plugin installs a hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads your steering files, filters them against the active prompt, and includes matching bodies as Tier 6 of the context packet. The packet gets injected into Claude's context automatically.

    2. ctx_steering_get MCP tool (on-demand). Claude can call this MCP tool mid-task to fetch matching steering files for a specific prompt. Automatic activation comes from Claude's judgment, not a hook.

    Both channels activate when you run:

    ctx setup claude-code --write\n

    That installs the plugin, wires the hook, and registers the MCP server. After that, steering files you edit are picked up on the next tool call, with no sync step needed.

    Running ctx steering sync with Claude Code

    It won't error; it will simply report that Claude and Codex aren't sync targets and skip them. If Claude Code is your only tool, you never need to run sync. If you use both Claude Code and (say) Cursor, run sync to keep Cursor up to date; the Claude pipeline takes care of itself via the hook.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-6-verify-the-ai-sees-it","level":2,"title":"Step 6: Verify the AI Sees It","text":"

    Open your AI tool and ask it something the rule should fire on:

    \"Add a POST /users endpoint that accepts email and name.\"

    If the rule is working, the AI's first response should mention input validation, typed structs, and the internal/validate/ package, because that's what the steering file told it to do.

    If nothing happens, the fix depends on which path you're on:

    Path A (Cursor/Cline/Kiro):

    1. Re-run ctx steering preview with the literal prompt to confirm the match.
    2. Run ctx steering list and verify inclusion is auto, not manual.
    3. Check the tool's own config directory (e.g. .cursor/rules/); the file should be there after ctx steering sync.

    Path B (Claude Code):

    1. Re-run ctx steering preview with the literal prompt to confirm the match.
    2. Verify the plugin is installed: cat .claude/hooks.json should include ctx agent --budget 8000 under PreToolUse. If not, re-run ctx setup claude-code --write.
    3. Run ctx agent --budget 8000 manually and grep the output for your rule body. If it's there, the data is fine; if it's missing, the inclusion mode or description is at fault.
    4. As a last resort, ask Claude directly: \"Call the ctx_steering_get MCP tool with my prompt and show me the result.\" If the MCP tool returns your rule, Claude has access but isn't pulling it into the initial context packet; tighten the description keywords.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    Too-generic descriptions. description: general coding will match almost every prompt and flood the context window. Keep descriptions specific to the scenario the rule applies to.

    Overlapping rules. If two steering files match the same prompt and contradict each other, the result is confusing. Use priority to resolve, but better: merge the files or narrow the descriptions so they don't overlap.

    Putting decisions in steering. \"We decided to use PostgreSQL\" is a decision, not a rule for the AI to follow on every prompt. Record decisions with ctx decision add, not ctx steering add.

    Committing inclusion: always without thinking. Rules marked always fire on every prompt, consuming tier-6 budget permanently. Only use always for true invariants (security, safety, licensing). Everything else should be auto or manual.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#see-also","level":2,"title":"See Also","text":"
    • ctx steering reference: full command, flag, and frontmatter reference.
    • ctx setup: configure which tools the steering sync writes to.
    • Authoring triggers: if you want script-based automation, not rule-based prompt injection.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/system-hooks-audit/","level":1,"title":"Auditing System Hooks","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-problem","level":2,"title":"The Problem","text":"

    ctx runs 14 system hooks behind the scenes: nudging your agent to persist context, warning about resource pressure, gating commits on QA. But these hooks are invisible by design. You never see them fire. You never know if they stopped working.

    How do you verify your hooks are actually running, audit what they do, and get alerted when they go silent?

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tldr","level":2,"title":"TL;DR","text":"
    ctx system check-resources # run a hook manually\nls -la .context/logs/      # check hook execution logs\nctx hook notify setup      # get notified when hooks fire\n

    Or ask your agent: \"Are our hooks running?\"

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx system <hook> CLI command Run a system hook manually ctx sysinfo CLI command Show system resource status ctx usage CLI command Stream or dump per-session token stats ctx hook notify setup CLI command Configure webhook for audit trail ctx hook notify test CLI command Verify webhook delivery .ctxrc notify.events Configuration Subscribe to relay for full hook audit .context/logs/ Log files Local hook execution ledger","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-are-system-hooks","level":2,"title":"What Are System Hooks?","text":"

    System hooks are plumbing commands that ctx registers with your AI tool (Claude Code, Cursor, etc.) via the plugin's hooks.json. They fire automatically at specific events during your AI session:

    Event When Hooks UserPromptSubmit Before the agent sees your prompt 10 check hooks + heartbeat PreToolUse Before the agent uses a tool block-non-path-ctx, qa-reminder PostToolUse After a tool call succeeds post-commit

    You never run these manually. Your AI tool runs them for you: That's the point.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-complete-hook-catalog","level":2,"title":"The Complete Hook Catalog","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#prompt-time-checks-userpromptsubmit","level":3,"title":"Prompt-Time Checks (UserPromptSubmit)","text":"

    These fire before every prompt, but most are throttled to avoid noise.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-context-size-context-capacity-warning","level":4,"title":"check-context-size: Context Capacity Warning","text":"

    What: Adaptive prompt counter. Silent for the first 15 prompts, then nudges with increasing frequency (every 5th, then every 3rd).

    Why: Long sessions lose coherence. The nudge reminds both you and the agent to persist context before the window fills up.

    Output: VERBATIM relay box with prompt count.

    ┌─ Context Checkpoint (prompt #20) ────────────────\n│ This session is getting deep. Consider wrapping up\n│ soon. If there are unsaved learnings, decisions, or\n│ conventions, now is a good time to persist them.\n│ ⏱ Context window: ~45k tokens (~22% of 200k)\n└──────────────────────────────────────────────────\n

    Usage: Every prompt records token usage to .context/state/stats-{session}.jsonl. Monitor live with ctx usage --follow or query with ctx usage --json. Usage is recorded even during wrap-up suppression (event: suppressed).

    Billing guard: When billing_token_warn is set in .ctxrc, a one-shot warning fires if session tokens exceed the threshold. This warning is independent of all other triggers - it fires even during wrap-up suppression.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-persistence-context-staleness-nudge","level":4,"title":"check-persistence: Context Staleness Nudge","text":"

    What: Tracks when .context/*.md files were last modified. If too many prompts pass without a write, nudges the agent to persist.

    Why: Sessions produce insights that evaporate if not recorded. This catches the \"we talked about it but never wrote it down\" failure mode.

    Output: VERBATIM relay after 20+ prompts without a context file change.

    ┌─ Persistence Checkpoint (prompt #20) ───────────\n│ No context files updated in 20+ prompts.\n│ Have you discovered learnings, made decisions,\n│ established conventions, or completed tasks\n│ worth persisting?\n│\n│ Run /ctx-wrap-up to capture session context.\n└──────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-ceremonies-session-ritual-adoption","level":4,"title":"check-ceremonies: Session Ritual Adoption","text":"

    What: Scans your last 3 journal entries for /ctx-remember and /ctx-wrap-up usage. Nudges once per day if missing.

    Why: Session ceremonies are the highest-leverage habit in ctx. This hook bootstraps the habit until it becomes automatic.

    Output: Tailored nudge depending on which ceremony is missing.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-journal-unimported-session-reminder","level":4,"title":"check-journal: Unimported Session Reminder","text":"

    What: Detects unimported Claude Code sessions and unenriched journal entries. Fires once per day.

    Why: Exported sessions become searchable history. Unenriched entries lack metadata for filtering. Both decay in value over time.

    Output: VERBATIM relay with counts and exact commands.

    ┌─ Journal Reminder ─────────────────────────────\n│ You have 3 new session(s) not yet exported.\n│ 5 existing entries need enrichment.\n│\n│ Export and enrich:\n│   ctx journal import --all\n│   /ctx-journal-enrich-all\n└────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-resources-system-resource-pressure","level":4,"title":"check-resources: System Resource Pressure","text":"

    What: Monitors memory, swap, disk, and CPU load. Only fires at DANGER severity (memory >= 90%, swap >= 75%, disk >= 95%, load >= 1.5x CPU count).

    Why: Resource exhaustion mid-session can corrupt work. This provides early warning to persist and exit.

    Output: VERBATIM relay listing critical resources.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-knowledge-knowledge-file-growth","level":4,"title":"check-knowledge: Knowledge File Growth","text":"

    What: Counts entries in LEARNINGS.md, DECISIONS.md, and lines in CONVENTIONS.md. Fires once per day when thresholds are exceeded.

    Why: Large knowledge files dilute agent context. 35 learnings compete for attention; 15 focused ones get applied. Thresholds are configurable in .ctxrc.

    Default thresholds:

    # .ctxrc\nentry_count_learnings: 30\nentry_count_decisions: 20\nconvention_line_count: 200\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-version-binaryplugin-version-drift","level":4,"title":"check-version: Binary/Plugin Version Drift","text":"

    What: Compares the ctx binary version against the plugin version. Fires once per day. Also checks encryption key age for rotation nudge.

    Why: Version drift means hooks reference features the binary doesn't have. The key rotation nudge prevents indefinite key reuse.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-reminders-pending-reminder-relay","level":4,"title":"check-reminders: Pending Reminder Relay","text":"

    What: Reads .context/reminders.json and surfaces any due reminders via VERBATIM relay. No throttle: fires every session until dismissed.

    Why: Reminders are sticky notes to future-you. Unlike nudges (which throttle to once per day), reminders repeat deliberately until the user dismisses them.

    Output: VERBATIM relay box listing due reminders.

    ┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-freshness-technology-constant-staleness","level":4,"title":"check-freshness: Technology Constant Staleness","text":"

    What: Stats files listed in .ctxrc freshness_files and warns if any haven't been modified in over 6 months. Daily throttle. Silent when no files are configured (opt-in via .ctxrc).

    Why: Model capabilities evolve - token budgets, attention limits, and context window sizes that were accurate 6 months ago may no longer reflect best practices. This hook reminds you to review and touch the file to confirm values are still current.

    Config (.ctxrc):

    freshness_files:\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # optional\n

    Each entry has a path (relative to project root), desc (what constants live there), and optional review_url (where to check current values). When review_url is set, the nudge includes \"Review against: {url}\". When absent, just \"Touch the file to mark it as reviewed.\"

    Output: VERBATIM relay listing stale files, silent otherwise.

    ┌─ Technology Constants Stale ──────────────────────\n│   config/thresholds.yaml (210 days ago)\n│     - Model token limits and batch sizes\n│   Review against: https://docs.example.com/limits\n│ Touch each file to mark it as reviewed.\n└───────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-map-staleness-architecture-map-drift","level":4,"title":"check-map-staleness: Architecture Map Drift","text":"

    What: Checks whether map-tracking.json is older than 30 days and there are commits touching internal/ since the last map refresh. Daily throttle prevents repeated nudges.

    Why: Architecture documentation drifts silently as code evolves. This hook detects structural changes that the map hasn't caught up with and suggests running /ctx-architecture to refresh.

    Output: VERBATIM relay when stale and modules changed, silent otherwise.

    ┌─ Architecture Map Stale ────────────────────────────\n│ ARCHITECTURE.md hasn't been refreshed since 2026-01-15\n│ and there are commits touching 12 modules.\n│ /ctx-architecture keeps architecture docs drift-free.\n│\n│ Want me to run /ctx-architecture to refresh?\n└─────────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#heartbeat-session-heartbeat-webhook","level":4,"title":"heartbeat: Session Heartbeat Webhook","text":"

    What: Fires on every prompt. Sends a webhook notification with prompt count, session ID, context modification status, and token usage telemetry. Never produces stdout.

    Why: Other hooks only send webhooks when they \"speak\" (nudge/relay). When silent, you have no visibility into session activity. The heartbeat provides a continuous session-alive signal with token consumption data for observability dashboards or liveness monitoring.

    Output: None (webhook + event log only).

    Payload:

    {\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  }\n}\n

    Token fields (tokens, context_window, usage_pct) are included when usage data is available from the session JSONL file.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tool-time-hooks-pretooluse-posttooluse","level":3,"title":"Tool-Time Hooks (PreToolUse / PostToolUse)","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#block-non-path-ctx-path-enforcement-hard-gate","level":4,"title":"block-non-path-ctx: PATH Enforcement (Hard Gate)","text":"

    What: Blocks any Bash command that invokes ./ctx, ./dist/ctx, go run ./cmd/ctx, or an absolute path to ctx. Only PATH invocations are allowed.

    Why: Enforces CONSTITUTION.md's invocation invariant. Running a dev-built binary in production context causes version confusion and silent behavior drift.

    Output: Block response (prevents the tool call):

    {\"decision\": \"block\", \"reason\": \"Use 'ctx' from PATH, not './ctx'...\"}\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#qa-reminder-pre-commit-qa-gate","level":4,"title":"qa-reminder: Pre-Commit QA Gate","text":"

    What: Fires on every Edit tool use. Reminds the agent to lint and test the entire project before committing.

    Why: Agents tend to \"I'll test later\" and then commit untested code. Repetition is intentional: the hook reinforces the habit on every edit, not just before commits.

    Output: Agent directive with hard QA gate instructions.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#post-commit-context-capture-after-commit","level":4,"title":"post-commit: Context Capture After Commit","text":"

    What: Fires after any git commit (excludes --amend). Prompts the agent to offer context capture (decision? learning?) and suggest running lints/tests before pushing.

    Why: Commits are natural reflection points. The nudge converts mechanical git operations into context-capturing opportunities.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-the-local-event-log","level":2,"title":"Auditing Hooks via the Local Event Log","text":"

    If you don't need an external audit trail, enable the local event log for a self-contained record of hook activity:

    # .ctxrc\nevent_log: true\n

    Once enabled, every hook that fires writes an entry to .context/state/events.jsonl. Query it with ctx hook event:

    ctx hook event                    # last 50 events\nctx hook event --hook qa-reminder # filter by hook\nctx hook event --session <id>     # filter by session\nctx hook event --json | jq '.'    # raw JSONL for processing\n

    The event log is local, queryable, and doesn't require any external service. For a full diagnostic workflow combining event logs with structural health checks, see Troubleshooting.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-webhooks","level":2,"title":"Auditing Hooks via Webhooks","text":"

    The most powerful audit setup pipes all hook output to a webhook, giving you a real-time external record of what your agent is being told.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-1-set-up-the-webhook","level":3,"title":"Step 1: Set Up the Webhook","text":"
    ctx hook notify setup\n# Enter your webhook URL (Slack, Discord, ntfy.sh, IFTTT, etc.)\n

    See Webhook Notifications for service-specific setup.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-2-subscribe-to-relay-events","level":3,"title":"Step 2: Subscribe to relay Events","text":"
    # .ctxrc\nnotify:\n  events:\n    - relay   # all hook output: VERBATIM relays, directives, blocks\n    - nudge   # just the user-facing VERBATIM relays\n

    The relay event fires for every hook that produces output. This includes:

    Hook Event sent check-context-size relay + nudge check-persistence relay + nudge check-ceremonies relay + nudge check-journal relay + nudge check-resources relay + nudge check-knowledge relay + nudge check-version relay + nudge check-reminders relay + nudge check-freshness relay + nudge check-map-staleness relay + nudge heartbeat heartbeat only block-non-path-ctx relay only post-commit relay only qa-reminder relay only","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-3-cross-reference","level":3,"title":"Step 3: Cross-Reference","text":"

    With relay enabled, your webhook receives a JSON payload every time a hook fires:

    {\n  \"event\": \"relay\",\n  \"message\": \"check-persistence: No context updated in 20+ prompts\",\n  \"session_id\": \"b854bd9c\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"my-project\"\n}\n

    This creates an external audit trail independent of the agent. You can now cross-verify: did the agent actually relay the checkpoint the hook told it to relay?

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#verifying-hooks-actually-fire","level":2,"title":"Verifying Hooks Actually Fire","text":"

    Hooks are invisible. An invisible thing that breaks is indistinguishable from an invisible thing that never existed. Three verification methods, from simplest to most robust:

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-1-ask-the-agent","level":3,"title":"Method 1: Ask the Agent","text":"

    The simplest check. After a few prompts into a session:

    \"Did you receive any hook output this session? Print the last\ncontext checkpoint or persistence nudge you saw.\"\n

    The agent should be able to recall recent hook output from its context window. If it says \"I haven't received any hook output\", either:

    • The hooks aren't firing (check installation);
    • The session is too short (hooks throttle early);
    • The hooks fired but the agent absorbed them silently.

    Limitation: You are trusting the agent to report accurately. Agents sometimes confabulate or miss context. Use this as a quick smoke test, not definitive proof.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-2-check-the-webhook-trail","level":3,"title":"Method 2: Check the Webhook Trail","text":"

    If you have relay events enabled, check your webhook receiver. Every hook that fires sends a timestamped notification. No notification = no fire.

    This is the ground truth. The webhook is called directly by the ctx binary, not by the agent. The agent cannot fake, suppress, or modify webhook deliveries.

    Compare what the webhook received against what the agent claims to have relayed. Discrepancies mean the agent is absorbing nudges instead of surfacing them.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-3-read-the-local-logs","level":3,"title":"Method 3: Read the Local Logs","text":"

    Hooks that support logging write to .context/logs/:

    # Check context-size hook activity\ncat .context/logs/check-context-size.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] prompt#1 silent\n# [2026-02-22 09:17:33] [session:b854bd9c] prompt#16 CHECKPOINT\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 CHECKPOINT\n
    # Check persistence nudge activity\ncat .context/logs/check-persistence.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] init count=1 mtime=1770646611\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 NUDGE since_nudge=20\n

    Logs are append-only and written by the ctx binary, not the agent.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#detecting-silent-hook-failures","level":2,"title":"Detecting Silent Hook Failures","text":"

    The hardest failure mode: hooks that stop firing without error. The plugin config changes, a binary update drops a hook, or a PATH issue silently breaks execution. Nothing errors: The hook just never runs.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-staleness-signal","level":3,"title":"The Staleness Signal","text":"

    If .context/logs/check-context-size.log has no entries newer than 5 days but you've been running sessions daily, something is wrong. The absence of evidence is evidence of absence: but only if you control for inactivity.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#false-positive-protection","level":3,"title":"False Positive Protection","text":"

    A naive \"hooks haven't fired in N days\" alert fires incorrectly when you simply haven't used ctx. The correct check needs two inputs:

    1. Last hook fire time: from .context/logs/ or webhook history
    2. Last session activity: from journal entries or ctx journal source

    If sessions are happening but hooks aren't firing, that's a real problem. If neither sessions nor hooks are happening, that's a vacation.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-to-check","level":3,"title":"What to Check","text":"

    When you suspect hooks aren't firing:

    # 1. Verify the plugin is installed\nls ~/.claude/plugins/\n\n# 2. Check hook registration\ncat ~/.claude/plugins/ctx/hooks.json | head -20\n\n# 3. Run a hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-context-size\n\n# 4. Check for PATH issues\nwhich ctx\nctx --version\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tips","level":2,"title":"Tips","text":"
    • Start with nudge, graduate to relay: The nudge event covers user-facing VERBATIM relays. Add relay when you want full visibility into agent directives and hard gates.
    • Webhooks are your trust anchor: The agent can ignore a nudge, but it can't suppress the webhook. If the webhook fired and the agent didn't relay, you have proof of a compliance gap.
    • Hooks are throttled by design: Most check hooks fire once per day or use adaptive frequency. Don't expect a notification every prompt: Silence usually means the throttle is working, not that the hook is broken.
    • Daily markers live in .context/state/: Throttle files are stored in .context/state/ alongside other project-scoped state. If you need to force a hook to re-fire during testing, delete the corresponding marker file.
    • The QA reminder is intentionally noisy: Unlike other hooks, qa-reminder fires on every Edit call with no throttle. This is deliberate: The commit quality degrades when the reminder fades from salience.
    • Log files are safe to commit: .context/logs/ contains only timestamps, session IDs, and status keywords. No secrets, no code.
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#next-up","level":2,"title":"Next Up","text":"

    Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#see-also","level":2,"title":"See Also","text":"
    • Troubleshooting: full diagnostic workflow using ctx doctor, event logs, and /ctx-doctor
    • Customizing Hook Messages: override what hooks say without changing what they do
    • Webhook Notifications: setting up and configuring the webhook system
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Detecting and Fixing Drift: structural checks that complement runtime hook auditing
    • CLI Reference: full ctx system command reference
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/task-management/","level":1,"title":"Tracking Work Across Sessions","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-problem","level":2,"title":"The Problem","text":"

    You have work that spans multiple sessions. Tasks get added during one session, partially finished in another, and completed days later.

    Without a system, follow-up items fall through the cracks, priorities drift, and you lose track of what was done versus what still needs doing. TASKS.md grows cluttered with completed checkboxes that obscure the remaining work.

    How do you manage work items that span multiple sessions without losing context?

    Prefer Skills over Raw Commands

    When working with an AI agent, use /ctx-task-add instead of raw ctx task add. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx task add / ctx task ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tldr","level":2,"title":"TL;DR","text":"

    Manage Tasks:

    ctx task add \"Fix race condition\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add\nctx task add \"Write tests\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add to phase\nctx task complete \"race condition\"                      # mark done\nctx task snapshot \"before-refactor\"               # backup\nctx task archive                                  # clean up\n

    Pick Up the Next Task:

    /ctx-next # pick what's next\n

    Read on for the full workflow and conversational patterns.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx task add Command Add a new task to TASKS.md ctx task complete Command Mark a task as done by number or text ctx task snapshot Command Create a point-in-time backup of TASKS.md ctx task archive Command Move completed tasks to archive file /ctx-task-add Skill AI-assisted task creation with validation /ctx-archive Skill AI-guided archival with safety checks /ctx-next Skill Pick what to work on based on priorities","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-1-add-tasks-with-priorities","level":3,"title":"Step 1: Add Tasks with Priorities","text":"

    Every piece of follow-up work gets a task. Use ctx task add from the terminal or /ctx-task-add from your AI assistant. Tasks should start with a verb and be specific enough that someone unfamiliar with the session could act on them.

    # High-priority bug found during code review\nctx task add \"Fix race condition in session cooldown\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Medium-priority feature work\nctx task add \"Add --format json flag to ctx status for CI integration\" --priority medium \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Low-priority cleanup\nctx task add \"Remove deprecated --raw flag from ctx load\" --priority low \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    The /ctx-task-add skill validates your task before recording it. It checks that the description is actionable, not a duplicate, and specific enough for someone else to pick up.

    If you say \"fix the bug,\" it will ask you to clarify which bug and where.

    Tasks Are Often Created Proactively

    In practice, many tasks are created proactively by the agent rather than by explicit CLI commands.

    After completing a feature, the agent will often identify follow-up work: tests, docs, edge cases, error handling, and offer to add them as tasks.

    You do not need to dictate ctx task add commands; the agent picks up on work context and suggests tasks naturally.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-2-organize-with-phase-sections","level":3,"title":"Step 2: Organize with Phase Sections","text":"

    Tasks live in phase sections inside TASKS.md.

    Phases provide logical groupings that preserve order and enable replay.

    A task does not move between sections. It stays in its phase permanently, and status is tracked via checkboxes and inline tags.

    ## Phase 1: Core CLI\n\n- [x] Implement ctx add command\n- [x] Implement ctx task complete command\n- [ ] Add --section flag to ctx task add `#priority:medium`\n\n## Phase 2: AI Integration\n\n- [ ] Implement ctx agent cooldown `#priority:high` `#in-progress`\n- [ ] Add ctx watch XML parsing `#priority:medium`\n  - Blocked by: Need to finalize agent output format\n\n## Backlog\n\n- [ ] Performance optimization for large TASKS.md files `#priority:low`\n- [ ] Add metrics dashboard to ctx status `#priority:deferred`\n

    Use --section when adding a task to a specific phase:

    ctx task add \"Add ctx watch XML parsing\" --priority medium --section \\\n    \"Phase 2: AI Integration\" \\\n    --session-id abc12345 --branch main --commit 68fbc00a\n

    Without --section, the task is inserted before the first unchecked task in TASKS.md.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"

    At the start of a session, or after finishing a task, use /ctx-next to get prioritized recommendations.

    The skill reads TASKS.md, checks recent sessions, and ranks candidates using explicit priority, blocking status, in-progress state, momentum from recent work, and phase order.

    You can also ask naturally: \"what should we work on?\" or \"what's the highest priority right now?\"

    /ctx-next\n

    The output looks like this:

    **1. Implement ctx agent cooldown** `#priority:high`\n\n    Still in-progress from yesterday's session. The tombstone file approach is\n    half-built. Finishing is cheaper than context-switching.\n\n**2. Add --section flag to ctx task add** `#priority:medium`\n\n    Last Phase 1 item. Quick win that unblocks organized task entry.\n\n---\n\n*Based on 8 pending tasks across 3 phases.\n\nLast session: agent-cooldown (2026-02-06).*\n

    In-progress tasks almost always come first:

    Finishing existing work takes priority over starting new work.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-4-complete-tasks","level":3,"title":"Step 4: Complete Tasks","text":"

    When a task is done, mark it complete by number or partial text match:

    # By task number (as shown in TASKS.md)\nctx task complete 3\n\n# By partial text match\nctx task complete \"agent cooldown\"\n

    The task's checkbox changes from [ ] to [x]. Tasks are never deleted: they stay in their phase section so history is preserved.

    Be Conversational

    You rarely need to run ctx task complete yourself during an interactive session.

    When you say something like \"the rate limiter is done\" or \"we finished that,\" the agent marks the task complete and moves on to suggesting what is next.

    The CLI commands are most useful for manual housekeeping, scripted workflows, or when you want precision.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-5-snapshot-before-risky-changes","level":3,"title":"Step 5: Snapshot Before Risky Changes","text":"

    Before a major refactor or any change that might break things, snapshot your current task state. This creates a copy of TASKS.md in .context/archive/ without modifying the original.

    # Default snapshot\nctx task snapshot\n\n# Named snapshot (recommended before big changes)\nctx task snapshot \"before-refactor\"\n

    This creates a file like .context/archive/tasks-before-refactor-2026-02-08-1430.md. If the refactor goes sideways, and you need to confirm what the task state looked like before you started, the snapshot is there.

    Snapshots are cheap: Take them before any change you might want to undo or review later.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-6-archive-when-tasksmd-gets-cluttered","level":3,"title":"Step 6: Archive When TASKS.md Gets Cluttered","text":"

    After several sessions, TASKS.md accumulates completed tasks that make it hard to see what is still pending.

    Use ctx task archive to move all [x] items to a timestamped archive file.

    Start with a dry run to preview what will be moved:

    ctx task archive --dry-run\n

    Then archive:

    ctx task archive\n

    Completed tasks move to .context/archive/tasks-2026-02-08.md. Phase headers are preserved in the archive for traceability. Pending tasks ([ ]) remain in TASKS.md.

    The /ctx-archive skill adds two safety checks before archiving: it verifies that completed tasks are genuinely cluttering the view and that nothing was marked [x] prematurely.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#letting-the-agent-drive","level":2,"title":"Letting the Agent Drive","text":"

    The steps above show the CLI commands for task management.

    In practice, most task management happens conversationally:

    An agent that has loaded the context files does not need you to type ctx task add. It tracks work naturally and offers the right operations.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#use-natural-language-instead-of-commands","level":3,"title":"Use Natural Language Instead of Commands","text":"

    These conversational prompts replace explicit commands during interactive sessions:

    Instead of typing... Just say... ctx task add \"Write tests for X\" --session-id ID --branch BR --commit HASH \"We should add tests for this: track that?\" /ctx-next \"What should we work on?\" ctx task complete \"rate limiting\" \"The rate limiter is done, what's next?\" ctx task archive \"TASKS.md is getting long, can you clean it up?\" ctx task add ... --session-id ID --branch BR --commit HASH && ctx task add ... \"Add follow-ups for what we just built.\"

    The agent translates these into the right ctx operations behind the scenes.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-creates-tasks-proactively","level":3,"title":"The Agent Creates Tasks Proactively","text":"

    After completing a piece of work, the agent does not wait for you to dictate follow-up items. It identifies them from the work context and offers:

    **Agent**: \"I've finished implementing the rate limiter middleware. I noticed\nthree follow-up items:\n\n1. Write integration tests for the rate limiter (the unit tests pass but\n   there's no end-to-end coverage)\n2. Add rate limit headers to API responses (X-RateLimit-Remaining, etc.)\n3. Document the rate limit configuration in the ops runbook\n\nWant me to add these as tasks?\"\n

    This is how an agent behaves once it has read the Agent Playbook.

    The playbook's \"Work, Reflect, Persist\" cycle means the agent asks itself after every milestone: Did I just complete a task? Did new work emerge?

    When the answer is yes, it acts.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#asking-the-agent-what-to-capture","level":3,"title":"Asking the Agent What to Capture","text":"

    You don't always know what's worth persisting.

    Instead of reviewing the session yourself and typing specific commands, ask the agent to do the thinking:

    You say What happens \"What tasks came out of this session?\" Agent reviews the work, identifies follow-ups, and offers to add them \"Are there any tasks, learnings, or decisions worth adding?\" Agent scans the full session for all three types and proposes a batch \"What should we remember from today?\" Agent surfaces everything persist-worthy: not just tasks \"Anything we should track before we move on?\" Agent checks for loose ends specific to the work just completed

    This is especially powerful after long or exploratory sessions where multiple threads were touched.

    The agent has the full conversation in context and can spot follow-ups you might miss:

    You:   \"Are there any tasks, learnings, or decisions worth adding\n        from this session?\"\n\nAgent: \"Yes. Here's what I'd suggest:\n\n  Tasks:\n  - Add retry logic to the webhook delivery (we discussed it when\n    the timeout test failed but never tracked it)\n  - Update the API docs to reflect the new rate limit headers\n\n  Learnings:\n  - net/http's default client has no timeout: we burned 15 minutes\n    on that before adding an explicit 30s timeout\n\n  Decisions:\n  - We chose token-bucket over sliding-window for rate limiting\n    because it handles bursts better and the library is simpler\n\n  Want me to add all of these?\"\n\nYou:   \"All of them.\"\n

    The agent fills in structured fields from what it observed during the session. You get the same quality entries as if you typed verbose commands, without needing to remember what happened or how to format it.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-completes-tasks-and-moves-on","level":3,"title":"The Agent Completes Tasks and Moves On","text":"

    When you confirm that work is finished, or when the agent can see that it is, it marks the task done and suggests the next step:

    You: \n\"The cooldown logic is working now.\"\n\nAgent: \n\"Marked 'Implement ctx agent cooldown' as done. Your highest-\npriority remaining task is 'Add --section flag to ctx task add'.\n\nIt's the last item in Phase 1 and a quick win. Want to start on that?\"\n

    The agent chains completion into next-task selection.

    One sentence from you triggers two operations.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#end-of-session-capture","level":3,"title":"End-of-Session Capture","text":"

    At the end of a session, the agent reviews what happened and catches loose ends:

    You: \n\"I think we're good for today.\"\n\nAgent: \n\"Before we wrap up: during the middleware work you mentioned\nwanting to add metrics for rate limit hits. That never made it into TASKS.md.\n\nWant me to add it as a task so it's there next session?\"\n

    This is the proactive persistence model at work.

    The agent monitors the conversation for mentioned-but-not-tracked work and surfaces it before context is lost.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#a-conversational-session-example","level":3,"title":"A Conversational Session Example","text":"

    Here is what task management looks like in a real session when the agent is driving:

    You:   What's left to do?\nAgent: You have 5 pending tasks across 2 phases. The highest priority is\n       \"Implement ctx agent cooldown\": it's marked in-progress from\n       yesterday and half-built. Want to pick that up?\n\nYou:   Yeah, let's finish it.\n       [... 20 minutes of implementation work ...]\n\nYou:   That's working. Tests pass.\nAgent: Nice. I'll mark \"Implement ctx agent cooldown\" as done.\n       I noticed two things from the implementation:\n       1. The tombstone file cleanup should happen on a timer, not just\n          at startup. Want me to add that as a task?\n       2. We learned that file locks don't work reliably on NFS. I'll\n          add that to LEARNINGS.md.\n\nYou:   Yes to both. What's next?\nAgent: Added the cleanup task as high priority. Next up is \"Add --section\n       flag to ctx task add\": last item in Phase 1. It should be\n       straightforward since the flag parsing pattern already exists\n       in the codebase. Ready?\n

    It's All Conversational

    Notice what did not happen: The user never typed a ctx command.

    The agent handled task completion, follow-up creation, learning capture, and next-task selection: all from natural conversation.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"
    # Add a task\nctx task add \"Implement rate limiting for API endpoints\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to a specific phase\nctx task add \"Write integration tests for rate limiter\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# See what to work on\n# (from AI assistant) /ctx-next\n\n# Mark done by text\nctx task complete \"rate limiting\"\n\n# Mark done by number\nctx task complete 5\n\n# Snapshot before a risky refactor\nctx task snapshot \"before-middleware-rewrite\"\n\n# Archive completed tasks when the list gets long\nctx task archive --dry-run     # preview first\nctx task archive               # then archive\n
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tips","level":2,"title":"Tips","text":"
    • Start tasks with a verb: \"Add,\" \"Fix,\" \"Implement,\" \"Investigate\": not just a topic like \"Authentication.\"
    • Include the why in the task description. Future sessions lack the context of why you added the task. \"Add rate limiting\" is worse than \"Add rate limiting to prevent abuse on the public API after the load test showed 10x traffic spikes.\"
    • Use #in-progress sparingly. Only one or two tasks should carry this tag at a time. If everything is in-progress, nothing is.
    • Snapshot before, not after. The point of a snapshot is to capture the state before a change, not to celebrate what you just finished.
    • Archive regularly. Once completed tasks outnumber pending ones, it is time to archive. A clean TASKS.md helps both you and your AI assistant focus.
    • Never delete tasks. Mark them [x] (completed) or [-] (skipped with a reason). Deletion breaks the audit trail.
    • Trust the agent's task instincts. When the agent suggests follow-up items after completing work, it is drawing on the full context of what just happened.
    • Conversational prompts beat commands in interactive sessions. Saying \"what should we work on?\" is faster and more natural than running /ctx-next. Save explicit commands for scripts, CI, and unattended runs.
    • Let the agent chain operations. A single statement like \"that's done, what's next?\" can trigger completion, follow-up identification, and next-task selection in one flow.
    • Review proactive task suggestions before moving on. The best follow-ups come from items spotted in-context right after the work completes.
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#next-up","level":2,"title":"Next Up","text":"

    Using the Scratchpad →: Store short-lived sensitive notes in an encrypted scratchpad.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle including task management in context
    • Persisting Decisions, Learnings, and Conventions: capturing the \"why\" behind your work
    • Detecting and Fixing Drift: keeping TASKS.md accurate over time
    • CLI Reference: full documentation for ctx add, ctx task complete, ctx task
    • Context Files: TASKS.md: format and conventions for TASKS.md
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/triggers/","level":1,"title":"Authoring Lifecycle Triggers","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#authoring-lifecycle-triggers","level":1,"title":"Authoring Lifecycle Triggers","text":"

    Triggers are executable shell scripts that fire at specific events during an AI session. They're how you express \"when the AI saves a file, also do X\" or \"before the AI edits this path, check Y first.\" This recipe walks through writing your first trigger, testing it, and enabling it safely.

    Triggers Execute Arbitrary Code

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks:

    • Only enable scripts you have read and understand.
    • Never enable a trigger you downloaded from the internet without reviewing every line.
    • Avoid shelling out to user-controlled values (jq -r output, path field, tool field) without quoting.
    • A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    The generated trigger template starts disabled (no executable bit) so you cannot accidentally run an unreviewed script. Enable it explicitly with ctx trigger enable.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#scenario","level":2,"title":"Scenario","text":"

    You want a pre-tool-use trigger that blocks the AI from editing anything in internal/crypto/ without explicit confirmation. Cryptographic code is sensitive, and accidental edits have caused outages before, and you want a hard gate.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-1-scaffold-the-script","level":2,"title":"Step 1: Scaffold the Script","text":"
    ctx trigger add pre-tool-use protect-crypto\n

    That creates .context/hooks/pre-tool-use/protect-crypto.sh with a template:

    #!/usr/bin/env bash\nset -euo pipefail\n\n# Read the JSON event from stdin.\npayload=$(cat)\n\n# Parse fields with jq.\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Your logic here.\n\n# Return a JSON result. action can be \"allow\", \"block\", or absent.\necho '{\"action\": \"allow\"}'\n

    Note: the directory is .context/hooks/pre-tool-use/; the on-disk layout still uses hooks/ even though the command is ctx trigger. If you ls .context/hooks/, that's where your triggers live.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-2-write-the-logic","level":2,"title":"Step 2: Write the Logic","text":"

    Open the file and replace the template body:

    #!/usr/bin/env bash\nset -euo pipefail\n\npayload=$(cat)\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Only gate write-family tools.\ncase \"$tool\" in\n  write_file|edit_file|apply_patch) ;;\n  *)\n    echo '{\"action\": \"allow\"}'\n    exit 0\n    ;;\nesac\n\n# Block any path under internal/crypto/.\ncase \"$path\" in\n  internal/crypto/*|*/internal/crypto/*)\n    jq -n --arg p \"$path\" '{\n      action: \"block\",\n      message: (\"Edits to \" + $p + \" require manual review. \" +\n                \"See CONVENTIONS.md for the crypto-change process.\")\n    }'\n    exit 0\n    ;;\nesac\n\necho '{\"action\": \"allow\"}'\n

    A few things to note:

    • set -euo pipefail: any unhandled error aborts the script. Critical for a security-relevant trigger.
    • Quote everything from jq: the path field comes from the AI tool; treat it as untrusted input.
    • Explicit allow case: the default is allow. An empty or missing response is a risky default.
    • Use jq -n --arg for output construction, as it is safer than string concatenation when the message may contain special characters.
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-3-test-with-a-mock-payload","level":2,"title":"Step 3: Test with a Mock Payload","text":"

    Before enabling the trigger, test it with a realistic mock input using ctx trigger test. This runs the script against a synthetic JSON payload without actually firing any AI tool.

    # Test the \"should block\" case\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\n

    Expected: the trigger returns {\"action\":\"block\", \"message\": \"...\"}.

    # Test the \"should allow\" case\nctx trigger test pre-tool-use --tool write_file --path internal/memory/mirror.go\n

    Expected: the trigger returns {\"action\":\"allow\"}.

    # Test that non-write tools pass through\nctx trigger test pre-tool-use --tool read_file --path internal/crypto/aes.go\n

    Expected: {\"action\":\"allow\"} because the case statement only gates write-family tools.

    If any of these cases misbehave, fix the trigger before enabling it. The trigger is disabled at this point, so misbehavior doesn't affect real AI sessions.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-4-enable-it","level":2,"title":"Step 4: Enable It","text":"

    Once the test cases pass, enable the trigger:

    ctx trigger enable protect-crypto\n

    That sets the executable bit. Next time the AI starts a pre-tool-use event, the trigger will fire.

    Verify it's enabled:

    ctx trigger list\n

    Should show protect-crypto under pre-tool-use with an enabled indicator.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-5-iterate-safely","level":2,"title":"Step 5: Iterate Safely","text":"

    If you discover a bug after enabling, disable first, fix second:

    ctx trigger disable protect-crypto\n# ...edit the script...\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\nctx trigger enable protect-crypto\n

    Disabling simply clears the executable bit; the script stays on disk, and ctx trigger enable re-enables it without rewriting anything.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#patterns-worth-copying","level":2,"title":"Patterns Worth Copying","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#logging-not-blocking","level":3,"title":"Logging, Not Blocking","text":"

    For auditing or analytics, return {\"action\":\"allow\"} always and append to a log as a side effect:

    #!/usr/bin/env bash\nset -euo pipefail\npayload=$(cat)\necho \"$payload\" >> .context/logs/tool-use.jsonl\necho '{\"action\":\"allow\"}'\n
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#context-injection-at-session-start","level":3,"title":"Context Injection at Session Start","text":"

    A session-start trigger can prepend text to the agent's initial prompt by emitting {\"action\":\"inject\", \"content\": \"...\"} . This is useful for injecting daily standup notes, open PRs, or rotating TODOs without storing them in a steering file.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#chaining-triggers-of-the-same-type","level":3,"title":"Chaining Triggers of the Same Type","text":"

    Multiple scripts in the same type directory all run. If any returns action: block, the block wins. Keep individual triggers single-purpose and rely on composition.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    Forgetting the shebang. Without #!/usr/bin/env bash, the trigger won't execute even with the executable bit set.

    Not quoting $path. If you use $path in a command substitution or a case glob without quoting, a file name with spaces or metacharacters will break the trigger in surprising ways.

    Enabling before testing. ctx trigger enable makes the script live immediately. Always ctx trigger test first.

    Outputting non-JSON. The trigger's stdout must be valid JSON or ctx's trigger runner will log a parse error. Use jq -n to construct output rather than hand-writing JSON strings.

    Mixing hook and trigger vocabulary. The command is ctx trigger but the on-disk directory is .context/hooks/. The feature was renamed; the directory name lags behind. Don't let this confuse you; they refer to the same thing.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#see-also","level":2,"title":"See Also","text":"
    • ctx trigger reference: full command, flag, and event-type reference.
    • ctx steering: persistent rules, not scripts. Use steering when the thing you want is \"tell the AI to always do X\" rather than \"run a script when Y happens.\"
    • Writing steering files: the rule-based equivalent of this recipe.
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/troubleshooting/","level":1,"title":"Troubleshooting","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-problem","level":2,"title":"The Problem","text":"

    Something isn't working: a hook isn't firing, nudges are too noisy, context seems stale, or the agent isn't following instructions. The information to diagnose it exists (across status, drift, event logs, hook config, and session history), but assembling it manually is tedious.

    How do you figure out what's wrong and fix it?

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tldr","level":2,"title":"TL;DR","text":"
    ctx doctor                   # structural health check\nctx hook event --last 20  # recent hook activity\n# or ask: \"something seems off, can you diagnose?\"\n
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx doctor CLI command Structural health report ctx doctor --json CLI command Machine-readable health report ctx hook event CLI command Query local event log /ctx-doctor Skill Agent-driven diagnosis with analysis","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#quick-check-ctx-doctor","level":3,"title":"Quick Check: ctx doctor","text":"

    Run ctx doctor for an instant structural health report. It checks context initialization, required files, drift, hook configuration, event logging, webhooks, reminders, task completion ratio, and context token size: all in one pass:

    ctx doctor\n
    ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n

    Warnings are non-critical but worth fixing. Errors need attention. Informational notes (○) flag optional features that aren't enabled.

    For scripting:

    ctx doctor --json | jq '.warnings'\n
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#deep-dive-ctx-doctor","level":3,"title":"Deep Dive: /ctx-doctor","text":"

    When you need the agent to reason about what's wrong, use the skill. Ask naturally or invoke directly:

    Why didn't my hook fire?\nSomething seems off, can you diagnose?\n/ctx-doctor\n

    The agent follows a triage sequence:

    1. Baseline: runs ctx doctor --json for structural health
    2. Events: runs ctx hook event --json --last 100 (if event logging enabled)
    3. Correlate: connects findings across both sources
    4. Present: structured findings with evidence
    5. Suggest: actionable next steps (but doesn't auto-fix)

    The skill degrades gracefully: without event logging enabled, it still runs structural checks and notes what you'd gain by enabling it.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#raw-event-inspection","level":3,"title":"Raw Event Inspection","text":"

    For power users: ctx hook event with filters gives direct access to the event log.

    # Last 50 events (default)\nctx hook event\n\n# Events from a specific session\nctx hook event --session eb1dc9cd-0163-4853-89d0-785fbfaae3a6\n\n# Only QA reminder events\nctx hook event --hook qa-reminder\n\n# Raw JSONL for jq processing\nctx hook event --json | jq '.message'\n\n# Include rotated (older) events\nctx hook event --all --last 100\n

    Filters use AND logic: --hook qa-reminder --session abc123 returns only QA reminder events from that specific session.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#common-problems","level":2,"title":"Common Problems","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#no-context-directory-specified-for-this-project","level":3,"title":"\"No context directory specified for this project\"","text":"

    Symptoms: Any ctx command fails with Error: no context directory specified for this project (possibly with a likely-candidate hint or a candidate list depending on what's visible from your CWD).

    Cause: ctx does not search the filesystem for a .context/ directory. You have to declare which one to use before running day-to-day commands.

    Fix: bind CTX_DIR for the current shell:

    eval \"$(ctx activate)\"\n

    See Activating a Context Directory for the full recipe (one-shot CTX_DIR=... inline form, CI patterns, direnv setup).

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#ctx-not-initialized","level":3,"title":"\"ctx: Not Initialized\"","text":"

    Symptoms: After declaring CTX_DIR, the command fails with ctx: not initialized - run \"ctx init\" first.

    Cause: The declared directory exists but hasn't been initialized with template files.

    Fix:

    ctx init          # create .context/ with template files\nctx init --minimal  # or just the essentials (CONSTITUTION, TASKS, DECISIONS)\n

    Commands that work without CTX_DIR or initialization: ctx init, ctx activate, ctx deactivate, ctx setup, ctx doctor, ctx guide, ctx why, ctx config switch/status, ctx hub *, and help-only grouping commands.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-cli-and-my-claude-code-session-disagree-on-the-project","level":3,"title":"\"My CLI and My Claude Code Session Disagree on the Project\"","text":"

    Symptoms: A !-pragma or interactive ctx call writes to the wrong .context/; or you ran ctx remind add in shell A and the reminder shows up in project B's notifications.

    Cause: CTX_DIR is sourced from three different surfaces, and they can drift apart:

    Surface Source of CTX_DIR Bound when Claude Code hooks ${CLAUDE_PROJECT_DIR}/.context (injected) Every hook line; the project Claude is in !-pragma in chat / interactive shell Whatever the parent shell exported When you ran eval \"$(ctx activate)\" New shell tab opened mid-session Whatever your shellrc exports Login

    When these drift, the per-prompt check-anchor-drift hook fires a verbatim warning naming both values. To fix: re-run eval \"$(ctx activate)\" from inside the project the Claude Code session is editing, or close the shell tab and reopen it from the right working directory.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-hook-isnt-firing","level":3,"title":"\"My Hook Isn't Firing\"","text":"

    Symptoms: No nudges appearing, webhook silent, event log shows no entries for the expected hook.

    Diagnosis:

    # 1. Check if ctx is installed and on PATH\nwhich ctx && ctx --version\n\n# 2. Check if the hook is registered\ngrep \"check-persistence\" ~/.claude/plugins/ctx/hooks.json\n\n# 3. Run the hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-persistence\n\n# 4. Check event log for the hook (if enabled)\nctx hook event --hook check-persistence\n

    Common causes:

    • Plugin is not installed: run ctx init --claude to reinstall
    • PATH issue: the hook invokes ctx from PATH; ensure it resolves
    • Throttle active: most hooks fire once per day: check .context/state/ for daily marker files
    • Hook silenced: a custom message override may be an empty file: check ctx hook message list for overrides
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#too-many-nudges","level":3,"title":"\"Too Many Nudges\"","text":"

    Symptoms: The agent is overwhelmed with hook output. Context checkpoints, persistence reminders, and QA gates fire constantly.

    Diagnosis:

    # Check how often hooks fired recently\nctx hook event --last 50\n\n# Count fires per hook\nctx hook event --json | jq -r '.detail.hook // \"unknown\"' \\\n  | sort | uniq -c | sort -rn\n

    Common causes:

    • QA reminder is noisy by design: it fires on every Edit call with no throttle. This is intentional. If it's too much, silence it with an empty override: ctx hook message edit qa-reminder gate, then empty the file
    • Long session: context checkpoint fires with increasing frequency after prompt 15. This is the system telling you the session is getting long: consider wrapping up
    • Short throttle window: if you deleted marker files in .context/state/, daily-throttled hooks will re-fire
    • Outdated Claude Code plugin: Update the plugin using Claude Code → /plugin → \"Marketplace\"
    • ctx version mismatch: Build (or download) and install the latest ctx vesion.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#context-seems-stale","level":3,"title":"\"Context Seems Stale\"","text":"

    Symptoms: The agent references outdated information, paths that don't exist, or decisions that were reversed.

    Diagnosis:

    # Structural drift check\nctx drift\n\n# Full doctor check (includes drift + more)\nctx doctor\n\n# Check when context files were last modified\nctx status --verbose\n

    Common causes:

    • Drift accumulated: stale path references in ARCHITECTURE.md or CONVENTIONS.md. Fix with ctx drift --fix or ask the agent to clean up.
    • Task backlog: too many completed tasks diluting active context. Archive with ctx task archive or ctx compact --archive.
    • Large context files: LEARNINGS.md with 40+ entries competes for attention. Consolidate with /ctx-consolidate.
    • Missing session ceremonies: if /ctx-remember and /ctx-wrap-up aren't being used, context doesn't get refreshed. See Session Ceremonies.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-agent-isnt-following-instructions","level":3,"title":"\"The Agent Isn't Following Instructions\"","text":"

    Symptoms: The agent ignores conventions, forgets decisions, or acts contrary to CONSTITUTION.md rules.

    Diagnosis:

    # Check context token size: Is it too large for the model?\nctx doctor --json | jq '.results[] | select(.name == \"context_size\")'\n\n# Check if context is actually being loaded\nctx hook event --hook context-load-gate\n

    Common causes:

    • Context too large: if total tokens exceed the model's effective attention, instructions get diluted. Check ctx doctor for the size check. Compact with ctx compact --archive.
    • Context not loading: if context-load-gate hasn't fired, the agent may not have received context. Verify the hook is registered.
    • Conflicting instructions: CONVENTIONS.md says one thing, AGENT_PLAYBOOK.md says another. Review both files for consistency.
    • Agent drift: the agent's behavior diverges from instructions over long sessions. This is normal. Use /ctx-reflect to re-anchor, or start a new session.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#prerequisites","level":2,"title":"Prerequisites","text":"
    • Event logging (optional but recommended): event_log: true in .ctxrc
    • ctx initialized: ctx init

    Event logging is not required for ctx doctor or /ctx-doctor to work. Both degrade gracefully: structural checks run regardless, and the skill notes when event data is unavailable.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tips","level":2,"title":"Tips","text":"
    • Start with ctx doctor: It's the fastest way to get a comprehensive health picture. Save event log inspection for when you need to understand when and how often something happened.
    • Enable event logging early: The log is opt-in and low-cost (~250 bytes per event, 1MB rotation cap). Enable it before you need it: Diagnosing a problem without historical data is much harder.
    • Use the skill for correlation: ctx doctor tells you what is wrong. /ctx-doctor tells you why by correlating structural findings with event patterns. The agent can spot connections that individual commands miss.
    • Event log is gitignored: It's machine-local diagnostic data, not project context. Different machines produce different event streams.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#next-up","level":2,"title":"Next Up","text":"

    Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#see-also","level":2,"title":"See Also","text":"
    • Auditing System Hooks: the complete hook catalog and webhook-based audit trails
    • Detecting and Fixing Drift: structural and semantic drift detection and repair
    • Webhook Notifications: push notifications for hook activity
    • ctx doctor CLI: full command reference
    • ctx hook event CLI: event log query reference
    • /ctx-doctor skill: agent-driven diagnosis
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/webhook-notifications/","level":1,"title":"Webhook Notifications","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-problem","level":2,"title":"The Problem","text":"

    Your agent runs autonomously (loops, implements, releases) while you are away from the terminal. You have no way to know when it finishes, hits a limit, or when a hook fires a nudge.

    How do you get notified about agent activity without watching the terminal?

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tldr","level":2,"title":"TL;DR","text":"
    ctx hook notify setup  # configure webhook URL (encrypted)\nctx hook notify test   # verify delivery\n# Hooks auto-notify on: session-end, loop-iteration, resource-danger\n
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx hook notify setup CLI command Configure and encrypt webhook URL ctx hook notify test CLI command Send a test notification ctx hook notify --event <name> \"msg\" CLI command Send a notification from scripts/skills .ctxrc notify.events Configuration Filter which events reach your webhook","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-1-get-a-webhook-url","level":3,"title":"Step 1: Get a Webhook URL","text":"

    Any service that accepts HTTP POST with JSON works. Common options:

    Service How to get a URL IFTTT Create an applet with the \"Webhooks\" trigger Slack Create an Incoming Webhook Discord Channel Settings > Integrations > Webhooks ntfy.sh Use https://ntfy.sh/your-topic (no signup) Pushover Use API endpoint with your user key

    The URL contains auth tokens. ctx encrypts it; it never appears in plaintext in your repo.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-2-configure-the-webhook","level":3,"title":"Step 2: Configure the Webhook","text":"
    ctx hook notify setup\n# Enter webhook URL: https://maker.ifttt.com/trigger/ctx/json/with/key/YOUR_KEY\n# Webhook configured: https://maker.ifttt.com/***\n# Encrypted at: .context/.notify.enc\n

    This encrypts the URL with AES-256-GCM using the same key as the scratchpad (~/.ctx/.ctx.key). The encrypted file (.context/.notify.enc) is safe to commit. The key lives outside the project and is never committed.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-3-test-it","level":3,"title":"Step 3: Test It","text":"
    ctx hook notify test\n# Webhook responded: HTTP 200 OK\n

    If you see No webhook configured, run ctx hook notify setup first.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-4-configure-events","level":3,"title":"Step 4: Configure Events","text":"

    Notifications are opt-in: no events are sent unless you configure an event list in .ctxrc:

    # .ctxrc\nnotify:\n  events:\n    - loop       # loop completion or max-iteration hit\n    - nudge      # VERBATIM relay hooks (context checkpoint, persistence, etc.)\n    - relay      # all hook output (verbose, for debugging)\n    - heartbeat  # every-prompt session-alive signal with metadata\n

    Only listed events fire. Omitting an event silently drops it.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-5-use-in-your-own-skills","level":3,"title":"Step 5: Use in Your Own Skills","text":"

    Add ctx hook notify calls to any skill or script:

    # In a release skill\nctx hook notify --event release \"v1.2.0 released successfully\" 2>/dev/null || true\n\n# In a backup script\nctx hook notify --event backup \"Nightly backup completed\" 2>/dev/null || true\n

    The 2>/dev/null || true suffix ensures the notification never breaks your script: If there's no webhook or the HTTP call fails, it's a silent noop.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-types","level":2,"title":"Event Types","text":"

    ctx fires these events automatically:

    Event Source When loop Loop script Loop completes or hits max iterations nudge System hooks VERBATIM relay nudge is emitted (context checkpoint, persistence, ceremonies, journal, resources, knowledge, version) relay System hooks Any hook output (VERBATIM relays, agent directives, block responses) heartbeat System hook Every prompt: session-alive signal with prompt count and context modification status test ctx hook notify test Manual test notification (custom) Your skills You wire ctx hook notify --event <name> in your own scripts

    nudge vs relay: The nudge event fires only for VERBATIM relay hooks (the ones the agent is instructed to show verbatim). The relay event fires for all hook output: VERBATIM relays, agent directives, and hard gates. Subscribe to relay for debugging (\"did the agent get the post-commit nudge?\"), nudge for user-facing assurance (\"was the checkpoint emitted?\").

    Webhooks as a Hook Audit Trail

    Subscribe to relay events and you get an external record of every hook that fires, independent of the agent.

    This lets you verify hooks are running and catch cases where the agent absorbs a nudge instead of surfacing it.

    See Auditing System Hooks for the full workflow.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#payload-format","level":2,"title":"Payload Format","text":"

    Every notification sends a JSON POST:

    {\n  \"event\": \"nudge\",\n  \"message\": \"check-context-size: Context window at 82%\",\n  \"detail\": {\n    \"hook\": \"check-context-size\",\n    \"variant\": \"window\",\n    \"variables\": {\"Percentage\": 82, \"TokenCount\": \"164k\"}\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n

    The detail field is a structured template reference containing the hook name, variant, and any template variables. This lets receivers filter by hook or variant without parsing rendered text. The field is omitted when no template reference applies (e.g. custom ctx hook notify calls).

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#heartbeat-payload","level":3,"title":"Heartbeat Payload","text":"

    The heartbeat event fires on every prompt with session metadata and token usage telemetry:

    {\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc123-...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-28T10:15:00Z\",\n  \"project\": \"ctx\"\n}\n

    The tokens, context_window, and usage_pct fields are included when token data is available from the session JSONL file. They are omitted when no usage data has been recorded yet (e.g. first prompt).

    Unlike other events, heartbeat fires every prompt (not throttled). Use it for observability dashboards or liveness monitoring of long-running sessions.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#security-model","level":2,"title":"Security Model","text":"Component Location Committed? Permissions Encryption key ~/.ctx/.ctx.key No (user-level) 0600 Encrypted URL .context/.notify.enc Yes (safe) 0600 Webhook URL Never on disk in plaintext N/A N/A

    The key is shared with the scratchpad. If you rotate the encryption key, re-run ctx hook notify setup to re-encrypt the webhook URL with the new key.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#key-rotation","level":2,"title":"Key Rotation","text":"

    ctx checks the age of the encryption key once per day. If it's older than 90 days (configurable via key_rotation_days), a VERBATIM nudge is emitted suggesting rotation.

    # .ctxrc\nkey_rotation_days: 30   # nudge sooner (default: 90)\n
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#worktrees","level":2,"title":"Worktrees","text":"

    The webhook URL is encrypted with the same encryption key (~/.ctx/.ctx.key). Because the key lives at the user level, it is shared across all worktrees on the same machine - notifications work in worktrees automatically.

    This means agents running in worktrees cannot send webhook alerts. For autonomous runs where worktree agents are opaque, monitor them from the terminal rather than relying on webhooks. Enrich journals and review results on the main branch after merging.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-log-the-local-complement","level":2,"title":"Event Log: The Local Complement","text":"

    Don't need a webhook but want diagnostic visibility? Enable event_log: true in .ctxrc. The event log writes the same payload as webhooks to a local JSONL file (.context/state/events.jsonl) that you can query without any external service:

    ctx hook event --last 20          # recent hook activity\nctx hook event --hook qa-reminder # filter by hook\n

    Webhooks and event logging are independent: you can use either, both, or neither. Webhooks give you push notifications and an external audit trail. The event log gives you local queryability and ctx doctor integration.

    See Troubleshooting for how they work together.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tips","level":2,"title":"Tips","text":"
    • Fire-and-forget: Notifications never block. HTTP errors are silently ignored. No retry, no response parsing.
    • No webhook = no cost: When no webhook is configured, ctx hook notify exits immediately. System hooks that call notify.Send() add zero overhead.
    • Multiple projects: Each project has its own .notify.enc. You can point different projects at different webhooks.
    • Event filter is per-project: Configure notify.events in each project's .ctxrc independently.
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#next-up","level":2,"title":"Next Up","text":"

    Auditing System Hooks →: Verify your hooks are running, audit what they do, and get alerted when they go silent.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#see-also","level":2,"title":"See Also","text":"
    • CLI Reference: ctx hook notify: full command reference
    • Configuration: .ctxrc settings including notify options
    • Running an Unattended AI Agent: how loops work and how notifications fit in
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Auditing System Hooks: using webhooks as an external audit trail for hook execution
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/","level":1,"title":"When to Use a Team of Agents","text":"","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-problem","level":2,"title":"The Problem","text":"

    You have a task, and you are wondering: \"should I throw more agents at it?\"

    More agents can mean faster results, but they also mean coordination overhead, merge conflicts, divergent mental models, and wasted tokens re-reading context.

    The wrong setup costs more than it saves.

    This recipe is a decision framework: It helps you choose between a single agent, parallel worktrees, and a full agent team, and explains what ctx provides at each level.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tldr","level":2,"title":"TL;DR","text":"
    • Single agent for most work;
    • Parallel worktrees when tasks touch disjoint file sets;
    • Agent teams only when tasks need real-time coordination. When in doubt, start with one agent.
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-spectrum","level":2,"title":"The Spectrum","text":"

    There are three modes, ordered by complexity:

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#1-single-agent-default","level":3,"title":"1. Single Agent (Default)","text":"

    One agent, one session, one branch. This is correct for most work.

    Use this when:

    • The task has linear dependencies (step 2 needs step 1's output);
    • Changes touch overlapping files;
    • You need tight feedback loops (review each change before the next);
    • The task requires deep understanding of a single area;
    • Total effort is less than a few hours of agent time.

    ctx provides: Full .context/: tasks, decisions, learnings, conventions, all in one session.

    The agent builds a coherent mental model and persists it as it goes.

    Example tasks: Bug fixes, feature implementation, refactoring a module, writing documentation for one area, debugging.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#2-parallel-worktrees-independent-tracks","level":3,"title":"2. Parallel Worktrees (Independent Tracks)","text":"

    2-4 agents, each in a separate git worktree on its own branch, working on non-overlapping parts of the codebase.

    Use this when:

    • You have 5+ independent tasks in the backlog;
    • Tasks group cleanly by directory or package;
    • File overlap between groups is zero or near-zero;
    • Each track can be completed and merged independently;
    • You want parallelism without coordination complexity.

    ctx provides: Shared .context/ via git (each worktree sees the same tasks, decisions, conventions). /ctx-worktree skill for setup and teardown. TASKS.md as a lightweight work queue.

    Example tasks: Docs + new package + test coverage (three tracks that don't touch the same files). Parallel recipe writing. Independent module development.

    See: Parallel Agent Development with Git Worktrees

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#3-agent-team-coordinated-swarm","level":3,"title":"3. Agent Team (Coordinated Swarm)","text":"

    Multiple agents communicating via messages, sharing a task list, with a lead agent coordinating. Claude Code's team/swarm feature.

    Use this when:

    • Tasks have dependencies but can still partially overlap;
    • You need research and implementation happening simultaneously;
    • The work requires different roles (researcher, implementer, tester);
    • A lead agent needs to review and integrate others' work;
    • The task is large enough that coordination cost is justified.

    ctx provides: .context/ as shared state that all agents can read. Task tracking for work assignment. Decisions and learnings as team memory that survives individual agent turnover.

    Example tasks: Large refactor across modules where a lead reviews merges. Research and implementation where one agent explores options while another builds. Multi-file feature that needs integration testing after parallel implementation.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-decision-framework","level":2,"title":"The Decision Framework","text":"

    Ask these questions in order:

    Can one agent do this in a reasonable time?\n  YES → Single agent. Stop here.\n  NO  ↓\n\nCan the work be split into non-overlapping file sets?\n  YES → Parallel worktrees (2-4 tracks)\n  NO  ↓\n\nDo the subtasks need to communicate during execution?\n  YES → Agent team with lead coordination\n  NO  → Parallel worktrees with a merge step\n
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-file-overlap-test","level":3,"title":"The File Overlap Test","text":"

    This is the critical decision point. Before choosing multi-agent, list the files each subtask would touch. If two subtasks modify the same file, they belong in the same track (or the same single-agent session).

    You: \"I want to parallelize these tasks. Which files would each one touch?\"\n\nAgent: [reads `TASKS.md`, analyzes codebase]\n       \"Task A touches internal/config/ and internal/cli/initialize/\n        Task B touches docs/ and site/\n        Task C touches internal/config/ and internal/cli/status/\n\n        Tasks A and C overlap on internal/config/ # they should be\n        in the same track. Task B is independent.\"\n

    When in doubt, keep things in one track. A merge conflict in a critical file costs more time than the parallelism saves.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#when-teams-make-things-worse","level":2,"title":"When Teams Make Things Worse","text":"

    \"More agents\" is not always better. Watch for these patterns:

    Merge hell: If you are spending more time resolving conflicts than the parallel work saved, you split wrong: Re-group by file overlap.

    Context divergence: Each agent builds its own mental model. After 30 minutes of independent work, agent A might make assumptions that contradict agent B's approach. Shorter tracks with frequent merges reduce this.

    Coordination theater: A lead agent spending most of its time assigning tasks, checking status, and sending messages instead of doing work. If the task list is clear enough, worktrees with no communication are cheaper.

    Re-reading overhead: Every agent reads .context/ on startup. A team of 4 agents each reading 4000 tokens of context = 16000 tokens before anyone does any work. For small tasks, that overhead dominates.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#what-ctx-gives-you-at-each-level","level":2,"title":"What ctx Gives You at Each Level","text":"ctx Feature Single Agent Worktrees Team .context/ files Full access Shared via git Shared via filesystem TASKS.md Work queue Split by track Assigned by lead Decisions/Learnings Persisted in session Persisted per branch Persisted by any agent /ctx-next Picks next task Picks within track Lead assigns /ctx-worktree N/A Setup + teardown Optional /ctx-commit Normal commits Per-branch commits Per-agent commits","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#team-composition-recipes","level":2,"title":"Team Composition Recipes","text":"

    Four practical team compositions for common workflows.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#feature-development-3-agents","level":3,"title":"Feature Development (3 Agents)","text":"Role Responsibility Architect Writes spec in specs/, breaks work into TASKS.md phases Implementer Picks tasks from TASKS.md, writes code, marks [x] done Reviewer Runs tests, ctx drift, lint; files issues as new tasks

    Coordination: TASKS.md checkboxes. Architect writes tasks before implementer starts. Reviewer runs after each implementer commit.

    Anti-pattern: All three agents editing the same file simultaneously. Sequence the work so only one agent touches a file at a time.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#consolidation-sprint-3-4-agents","level":3,"title":"Consolidation Sprint (3-4 Agents)","text":"Role Responsibility Auditor Runs ctx drift, identifies stale paths and broken refs Code Fixer Updates source code to match context (or vice versa) Doc Writer Updates ARCHITECTURE.md, CONVENTIONS.md, and docs/ Test Fixer (Optional) Fixes tests broken by the fixer's changes

    Coordination: Auditor's ctx drift output is the shared work queue. Each agent claims a subset of issues by adding #in-progress labels.

    Anti-pattern: Fixer and doc writer both editing ARCHITECTURE.md. Assign file ownership explicitly.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#release-prep-2-agents","level":3,"title":"Release Prep (2 Agents)","text":"Role Responsibility Release Notes Generates changelog from commits, writes release notes Validation Runs full test suite, lint, build across platforms

    Coordination: Both read TASKS.md to identify what shipped. Release notes agent works from git log; validation agent works from make audit.

    Anti-pattern: Release notes agent running tests \"to verify.\" Each agent stays in its lane.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#documentation-sprint-3-agents","level":3,"title":"Documentation Sprint (3 Agents)","text":"Role Responsibility Content Writes new pages, expands existing docs Cross-linker Adds nav entries, cross-references, \"See Also\" sections Verifier Builds site, checks broken links, validates rendering

    Coordination: Content agent writes files first. Cross-linker updates zensical.toml and index pages after content lands. Verifier builds after each batch.

    Antipattern: Content and cross-linker both editing zensical.toml. Batch nav updates into the cross-linker's pass.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tips","level":2,"title":"Tips","text":"
    • Start with one agent: Only add parallelism when you have identified the bottleneck. \"This would go faster with more agents\" is usually wrong for tasks under 2 hours.
    • The 3-4 agent ceiling is real: Coordination overhead grows quadratically. 2 agents = 1 communication pair. 4 agents = 6 pairs. Beyond 4, you are managing agents more than doing work.
    • Worktrees > teams for most parallelism needs: If agents don't need to talk to each other during execution, worktrees give you parallelism with zero coordination overhead.
    • Use ctx as the shared brain: Whether it's one agent or four, the .context/ directory is the single source of truth. Decisions go in DECISIONS.md, not in chat messages between agents.
    • Merge early, merge often: Long-lived parallel branches diverge. Merge a track as soon as it's done rather than waiting for all tracks to finish.
    • TASKS.md conflicts are normal: Multiple agents completing different tasks will conflict on merge. The resolution is always additive: accept all [x] completions from both sides.
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#next-up","level":2,"title":"Next Up","text":"

    Parallel Agent Development with Git Worktrees →: Run multiple agents on independent task tracks using git worktrees.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#go-deeper","level":2,"title":"Go Deeper","text":"
    • CLI Reference: all commands and flags
    • Integrations: setup for Claude Code, Cursor, Aider
    • Session Journal: browse and search session history
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#see-also","level":2,"title":"See Also","text":"
    • Parallel Agent Development with Git Worktrees: the mechanical \"how\" for worktree-based parallelism
    • Running an Unattended AI Agent: serial autonomous loops: a different scaling strategy
    • Tracking Work Across Sessions: managing the task backlog that feeds into any multi-agent setup
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"reference/","level":1,"title":"Reference","text":"

    Technical reference for ctx commands, skills, and internals.

    ","path":["Reference"],"tags":[]},{"location":"reference/#the-system-explains-itself","level":3,"title":"The System Explains Itself","text":"

    The 12 properties that must hold for any valid ctx implementation. Not features: constraints. The system's contract with its users and contributors.

    ","path":["Reference"],"tags":[]},{"location":"reference/#code-conventions","level":3,"title":"Code Conventions","text":"

    Common patterns and fixes for the AST compliance tests in internal/audit/. When a test fails, find the matching section.

    ","path":["Reference"],"tags":[]},{"location":"reference/#cli","level":3,"title":"CLI","text":"

    Every command, subcommand, and flag. Now a top-level section: see CLI Reference.

    ","path":["Reference"],"tags":[]},{"location":"reference/#skills","level":3,"title":"Skills","text":"

    The full skill catalog: what each skill does, when it triggers, and how skills interact with commands.

    ","path":["Reference"],"tags":[]},{"location":"reference/#tool-ecosystem","level":3,"title":"Tool Ecosystem","text":"

    How ctx compares to Cursor Rules, Aider conventions, CLAUDE.md, and other context approaches.

    ","path":["Reference"],"tags":[]},{"location":"reference/#session-journal","level":3,"title":"Session Journal","text":"

    Export, browse, and enrich your session history. Covers the journal site, Obsidian export, and the enrichment pipeline.

    ","path":["Reference"],"tags":[]},{"location":"reference/#scratchpad","level":3,"title":"Scratchpad","text":"

    Encrypted, git-tracked scratch space for short notes and sensitive values that travel with the project.

    ","path":["Reference"],"tags":[]},{"location":"reference/#version-history","level":3,"title":"Version History","text":"

    Changelog for every ctx release.

    ","path":["Reference"],"tags":[]},{"location":"reference/audit-conventions/","level":1,"title":"Code Conventions","text":"","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#code-conventions-common-patterns-and-fixes","level":1,"title":"Code Conventions: Common Patterns and Fixes","text":"

    This guide documents the code conventions enforced by internal/audit/ AST tests. Each section shows the violation pattern, the fix, and the rationale. When a test fails, find the matching section below.

    All tests skip _test.go files. The patterns apply only to production code under internal/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#variable-shadowing-bare-err-reuse","level":2,"title":"Variable Shadowing (Bare err := Reuse)","text":"

    Test: TestNoVariableShadowing

    When a function has multiple := assignments to err, each shadows the previous one. This makes it impossible to tell which error a later if err != nil is checking.

    Before:

    func Run(cmd *cobra.Command) error {\n    data, err := os.ReadFile(path) \n    if err != nil {\n        return err\n    }\n\n    result, err := json.Unmarshal(data)  // shadows first err\n    if err != nil {\n        return err\n    }\n\n    err = validate(result)  // shadows again\n    return err\n}\n

    After:

    func Run(cmd *cobra.Command) error {\n    data, readErr := os.ReadFile(path)\n    if readErr != nil {\n        return readErr\n    }\n\n    result, parseErr := json.Unmarshal(data)\n    if parseErr != nil {\n        return parseErr\n    }\n\n    validateErr := validate(result)\n    return validateErr\n}\n

    Rule: Use descriptive error names (readErr, writeErr, parseErr, walkErr, absErr, relErr) so each error site is independently identifiable.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#import-name-shadowing","level":2,"title":"Import Name Shadowing","text":"

    Test: TestNoImportNameShadowing

    When a local variable has the same name as an imported package, the import becomes inaccessible in that scope.

    Before:

    import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(session *entity.Session) {  // param shadows import\n    // session package is now unreachable here\n}\n

    After:

    import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(sess *entity.Session) {\n    // session package still accessible\n}\n

    Rule: Parameters, variables, and return values must not reuse imported package names. Common renames: session -> sess, token -> tok, config -> cfg, entry -> ent.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-strings","level":2,"title":"Magic Strings","text":"

    Test: TestNoMagicStrings

    String literals in function bodies are invisible to refactoring tools and cause silent breakage when the value changes in one place but not another.

    Before (string literals):

    func loadContext() {\n    data := filepath.Join(dir, \"TASKS.md\")\n    if strings.HasSuffix(name, \".yaml\") {\n        // ...\n    }\n}\n

    After:

    func loadContext() {\n    data := filepath.Join(dir, config.FilenameTask)\n    if strings.HasSuffix(name, config.ExtYAML) {\n        // ...\n    }\n}\n

    Before (format verbs, also caught):

    func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return fmt.Sprintf(\"%x\", h[:8])\n}\n

    After:

    func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return hex.EncodeToString(h[:cfgFmt.HashPrefixLen])\n}\n

    Before (URL schemes, also caught):

    if strings.HasPrefix(target, \"https://\") ||\n    strings.HasPrefix(target, \"http://\") {\n    return target\n}\n

    After:

    if strings.HasPrefix(target, cfgHTTP.PrefixHTTPS) ||\n    strings.HasPrefix(target, cfgHTTP.PrefixHTTP) {\n    return target\n}\n

    Exempt from this check:

    • Empty string \"\", single space \" \", indentation strings
    • Regex capture references ($1, ${name})
    • const and var definition sites (that's where constants live)
    • Struct tags
    • Import paths
    • Packages under internal/config/, internal/assets/tpl/

    Rule: If a string is used for comparison, path construction, or appears in 3+ files, it belongs in internal/config/ as a constant. Format strings belong in internal/config/ as named constants (e.g., cfgGit.FlagLastN, cfgTrace.RefFormat). User-facing prose belongs in internal/assets/ YAML files accessed via desc.Text().

    Common fix for fmt.Sprintf with format verbs:

    Pattern Fix fmt.Sprintf(\"%d\", n) strconv.Itoa(n) fmt.Sprintf(\"%d\", int64Val) strconv.FormatInt(int64Val, 10) fmt.Sprintf(\"%x\", bytes) hex.EncodeToString(bytes) fmt.Sprintf(\"%q\", s) strconv.Quote(s) fmt.Sscanf(s, \"%d\", &n) strconv.Atoi(s) fmt.Sprintf(\"-%d\", n) fmt.Sprintf(cfgGit.FlagLastN, n) \"https://\" cfgHTTP.PrefixHTTPS \"&lt;\" config constant in config/html/","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-printf-calls","level":2,"title":"Direct Printf Calls","text":"

    Test: TestNoPrintfCalls

    cmd.Printf and cmd.PrintErrf bypass the write-package formatting pipeline and scatter user-facing text across the codebase.

    Before:

    func Run(cmd *cobra.Command, args []string) {\n    cmd.Printf(\"Found %d tasks\\n\", count)\n}\n

    After:

    func Run(cmd *cobra.Command, args []string) {\n    write.TaskCount(cmd, count)\n}\n

    Rule: All formatted output goes through internal/write/ which uses cmd.Print/cmd.Println with pre-formatted strings from desc.Text().

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#raw-time-format-strings","level":2,"title":"Raw Time Format Strings","text":"

    Test: TestNoRawTimeFormats

    Inline time format strings (\"2006-01-02\", \"15:04:05\") drift when one call site is updated but others are missed.

    Before:

    func formatDate(t time.Time) string {\n    return t.Format(\"2006-01-02\")\n}\n

    After:

    func formatDate(t time.Time) string {\n    return t.Format(cfgTime.DateFormat)\n}\n

    Rule: All time format strings must use constants from internal/config/time/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-flag-registration","level":2,"title":"Direct Flag Registration","text":"

    Test: TestNoFlagBindOutsideFlagbind

    Direct cobra flag calls (.Flags().StringVar(), etc.) scatter flag wiring across dozens of cmd.go files. Centralizing through internal/flagbind/ gives one place to audit flag names, defaults, and description key lookups.

    Before:

    func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    c.Flags().StringVarP(&output, \"output\", \"o\", \"\",\n        \"output format\")\n    return c\n}\n

    After:

    func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    flagbind.StringFlagShort(c, &output, flag.Output,\n        flag.OutputShort, cmd.DescKeyOutput)\n    return c\n}\n

    Rule: All flag registration goes through internal/flagbind/. If the helper you need doesn't exist, add it to flagbind/flag.go before using it.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#todo-comments","level":2,"title":"TODO Comments","text":"

    Test: TestNoTODOComments

    TODO, FIXME, HACK, and XXX comments in production code are invisible to project tracking. They accumulate silently and never get addressed.

    Before:

    // TODO: handle pagination\nfunc listEntries() []Entry {\n

    After:

    Remove the comment and add a task to .context/TASKS.md:

    - [ ] Handle pagination in listEntries (internal/task/task.go)\n

    Rule: Deferred work lives in TASKS.md, not in source comments.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#dead-exports","level":2,"title":"Dead Exports","text":"

    Test: TestNoDeadExports

    Exported symbols with zero references outside their definition file are dead weight. They increase API surface, confuse contributors, and cost maintenance.

    Fix: Either delete the export (preferred) or demote it to unexported if it's still used within the file.

    If the symbol existed for historical reasons and might be needed again, move it to quarantine/deadcode/ with a .dead extension. This preserves the code in git without polluting the live codebase:

    quarantine/deadcode/internal/config/flag/flag.go.dead\n

    Each .dead file includes a header:

    // Dead exports quarantined from internal/config/flag/flag.go\n// Quarantined: 2026-04-02\n// Restore from git history if needed.\n

    Rule: If a test-only allowlist entry is needed (the export exists only for test use), add the fully qualified symbol to testOnlyExports in dead_exports_test.go. Keep this list small; prefer eliminating the export.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#core-package-structure","level":2,"title":"Core Package Structure","text":"

    Test: TestCoreStructure

    core/ directories under internal/cli/ must contain only doc.go and test files at the top level. All domain logic lives in subpackages. This prevents core/ from becoming a god package.

    Before:

    internal/cli/dep/core/\n    go.go           # violation: logic at core/ level\n    python.go       # violation\n    node.go         # violation\n    types.go        # violation\n

    After:

    internal/cli/dep/core/\n    doc.go          # package doc only\n    golang/\n        golang.go\n        golang_test.go\n        doc.go\n    python/\n        python.go\n        python_test.go\n        doc.go\n    node/\n        node.go\n        node_test.go\n        doc.go\n

    Rule: Extract each logical unit into its own subpackage under core/. Each subpackage gets a doc.go. The subpackage name should match the domain concept (golang, check, fix, store), not a generic label (util, helper).

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cross-package-types","level":2,"title":"Cross-Package Types","text":"

    Test: TestCrossPackageTypes

    When a type defined in one package is used from a different module (e.g., cli/doctor importing a type from cli/notify), the type has crossed its module boundary. Cross-cutting types belong in internal/entity/ for discoverability.

    Before:

    // internal/cli/notify/core/types.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/cli/notify/core\"\nfunc check(p core.NotifyPayload) { ... }\n

    After:

    // internal/entity/notify.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/entity\"\nfunc check(p entity.NotifyPayload) { ... }\n

    Exempt: Types inside entity/, proto/, core/ subpackages, and config/ packages. Same-module usage (e.g., cli/doctor/cmd/ using cli/doctor/core/) is not flagged.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#type-file-convention","level":2,"title":"Type File Convention","text":"

    Test: TestTypeFileConvention, TestTypeFileConventionReport

    Exported types in core/ subpackages should live in types.go (the convention from CONVENTIONS.md), not scattered across implementation files. This makes type definitions discoverable. TestTypeFileConventionReport generates a diagnostic summary of all type placements for triage.

    Exception: entity/ organizes by domain (task.go, session.go), proto/ uses schema.go, and err/ packages colocate error types with their domain context.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-yaml-linkage","level":2,"title":"DescKey / YAML Linkage","text":"

    Test: TestDescKeyYAMLLinkage

    Every DescKey constant must have a corresponding key in the YAML asset files, and every YAML key must have a corresponding DescKey constant. Orphans in either direction mean dead text or runtime panics.

    Fix for orphan YAML key: Delete the YAML entry, or add the corresponding DescKey constant in config/embed/{text,cmd,flag}/.

    Fix for orphan DescKey: Delete the constant, or add the corresponding entry in the YAML file under internal/assets/commands/text/, cmd/, or flag/.

    If the orphan YAML entry was once valid but the feature was removed, move the YAML entry to a .dead file in quarantine/deadcode/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#package-doc-quality","level":2,"title":"Package Doc Quality","text":"

    Test: TestPackageDocQuality

    Every package under internal/ must have a doc.go with a meaningful package doc comment (at least 8 lines of real content). One-liners and file-list patterns (// - foo.go, // Source files:) are flagged because they drift as files change.

    Template:

    //   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n\n// Package mypackage does X.\n//\n// It handles Y by doing Z. The main entry point is [FunctionName]\n// which accepts A and returns B.\n//\n// Configuration is read from [config.SomeConstant]. Output is\n// written through [write.SomeHelper].\n//\n// This package is used by [parentpackage] during the W lifecycle\n// phase.\npackage mypackage\n
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-regex-compilation","level":2,"title":"Inline Regex Compilation","text":"

    Test: TestNoInlineRegexpCompile

    regexp.MustCompile and regexp.Compile inside function bodies recompile the pattern on every call. Compiled patterns belong at package level.

    Before:

    func parse(s string) bool {\n    re := regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n    return re.MatchString(s)\n}\n

    After:

    // In internal/config/regex/regex.go:\n// DatePattern matches ISO date format (YYYY-MM-DD).\nvar DatePattern = regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n\n// In calling package:\nfunc parse(s string) bool {\n    return regex.DatePattern.MatchString(s)\n}\n

    Rule: All compiled regexes live in internal/config/regex/ as package-level var declarations. Two tests enforce this: TestNoInlineRegexpCompile catches function-body compilation, and TestNoRegexpOutsideRegexPkg catches package-level compilation outside config/regex/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#doc-comments","level":2,"title":"Doc Comments","text":"

    Test: TestDocComments

    All functions (exported and unexported), structs, and package-level variables must have a doc comment. Config packages allow group doc comments for const blocks.

    Before:

    func buildIndex(entries []Entry) map[string]int {\n

    After:

    // buildIndex maps entry names to their position in the\n// ordered slice for O(1) lookup during reconciliation.\n//\n// Parameters:\n//   - entries: ordered slice of entries to index\n//\n// Returns:\n//   - map[string]int: name-to-position mapping\nfunc buildIndex(entries []Entry) map[string]int {\n

    Rule: Every function, struct, and package-level var gets a doc comment in godoc format. Functions include Parameters: and Returns: sections. Structs with 2+ fields document every field. See CONVENTIONS.md for the full template.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#line-length","level":2,"title":"Line Length","text":"

    Test: TestLineLength

    Lines in non-test Go files must not exceed 80 characters. This is a hard check, not a suggestion.

    Before:

    _ = trace.Record(fmt.Sprintf(cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum), state.Dir())\n

    After:

    ref := fmt.Sprintf(\n    cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum,\n)\n_ = trace.Record(ref, state.Dir())\n

    Rule: Break at natural points: function arguments, struct fields, chained calls. Long strings (URLs, struct tags) are the rare acceptable exception.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#literal-whitespace","level":2,"title":"Literal Whitespace","text":"

    Test: TestNoLiteralWhitespace

    Bare whitespace string and byte literals (\"\\n\", \"\\r\\n\", \"\\t\") must not appear outside internal/config/token/. All other packages use the token constants.

    Before:

    output := strings.Join(lines, \"\\n\")\n

    After:

    output := strings.Join(lines, token.Newline)\n

    Rule: Whitespace literals are defined once in internal/config/token/. Use token.Newline, token.Tab, token.CRLF, etc.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-numeric-values","level":2,"title":"Magic Numeric Values","text":"

    Test: TestNoMagicValues

    Numeric literals in function bodies need constants, with narrow exceptions.

    Before:

    if len(entries) > 100 {\n    entries = entries[:100]\n}\n

    After:

    if len(entries) > config.MaxEntries {\n    entries = entries[:config.MaxEntries]\n}\n

    Exempt: 0, 1, -1, 2-10, strconv radix/bitsize args (10, 32, 64 in strconv.Parse*/Format*), octal permissions (caught separately by TestNoRawPermissions), and const/var definition sites.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-separators","level":2,"title":"Inline Separators","text":"

    Test: TestNoInlineSeparators

    strings.Join calls must use token constants for their separator argument, not string literals.

    Before:

    result := strings.Join(parts, \", \")\n

    After:

    result := strings.Join(parts, token.CommaSep)\n

    Rule: Separator strings live in internal/config/token/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stuttery-function-names","level":2,"title":"Stuttery Function Names","text":"

    Test: TestNoStutteryFunctions

    Function names must not redundantly include their package name as a PascalCase word boundary. Go callers already write pkg.Function, so pkg.PkgFunction stutters.

    Before:

    // In package write\nfunc WriteJournal(cmd *cobra.Command, ...) {\n

    After:

    // In package write\nfunc Journal(cmd *cobra.Command, ...) {\n

    Exempt: Identity functions like write.Write / write.write.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#predicate-naming-no-ishascan-prefix","level":2,"title":"Predicate Naming (No Is/Has/Can Prefix)","text":"

    Test: None (manual review convention)

    Exported methods that return bool must not use Is, Has, or Can prefixes. The predicate reads more naturally without them, especially at call sites where the package name provides context.

    Before:

    func IsCompleted(t *Task) bool { ... }\nfunc HasChildren(n *Node) bool { ... }\nfunc IsExemptPackage(path string) bool { ... }\n

    After:

    func Completed(t *Task) bool { ... }\nfunc Children(n *Node) bool { ... }  // or: ChildCount > 0\nfunc ExemptPackage(path string) bool { ... }\n

    Rule: Drop the prefix. Private helpers may use prefixes when it reads more naturally (isValid in a local context is fine). This convention applies to exported methods and package-level functions. See CONVENTIONS.md \"Predicates\" section.

    This is not yet enforced by an AST test; it requires semantic understanding of return types and naming intent that makes automated detection fragile. Apply during code review.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#mixed-visibility","level":2,"title":"Mixed Visibility","text":"

    Test: TestNoMixedVisibility

    Files with exported functions must not also contain unexported functions. Public API and private helpers live in separate files.

    Before:

    load.go\n    func Load() { ... }        // exported\n    func parseHeader() { ... } // unexported, violation\n

    After:

    load.go\n    func Load() { ... }        // exported only\nparse.go\n    func parseHeader() { ... } // private helper\n

    Exempt: Files with exactly one function, doc.go, test files.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stray-errgo-files","level":2,"title":"Stray Err.Go Files","text":"

    Test: TestNoStrayErrFiles

    err.go files must only exist under internal/err/. Error constructors anywhere else create a broken-window pattern where contributors add local error definitions when they see a local err.go.

    Fix: Move the error constructor to internal/err/<domain>/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cli-cmd-structure","level":2,"title":"CLI Cmd Structure","text":"

    Test: TestCLICmdStructure

    Each cmd/$sub/ directory under internal/cli/ may contain only cmd.go, run.go, doc.go, and test files. Extra .go files (helpers, output formatters, types) belong in the corresponding core/ subpackage.

    Before:

    internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\n    format.go   # violation: helper in cmd dir\n

    After:

    internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\ninternal/cli/doctor/core/format/\n    format.go\n    doc.go\n
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-namespace","level":2,"title":"DescKey Namespace","text":"

    Test: TestUseConstantsOnlyInCobraUse, TestDescKeyOnlyInLookupCalls, TestNoWrongNamespaceLookup

    Three tests enforce DescKey/Use constant discipline:

    1. Use* constants appear only in cobra Use: struct field assignments, never as arguments to desc.Text() or elsewhere.
    2. DescKey* constants are passed only to assets.CommandDesc(), assets.FlagDesc(), or desc.Text(), never to cobra Use:.
    3. No cross-namespace lookups: TextDescKey must not be passed to CommandDesc(), FlagDescKey must not be passed to Text(), etc.
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#yaml-examples-registry-linkage","level":2,"title":"YAML Examples / Registry Linkage","text":"

    Test: TestExamplesYAMLLinkage, TestRegistryYAMLLinkage

    Every key in examples.yaml and registry.yaml must match a known entry type constant. Prevents orphan entries that are never rendered.

    Fix: Delete the orphan YAML entry, or add the corresponding constant in config/entry/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#other-enforced-patterns","level":2,"title":"Other Enforced Patterns","text":"

    These tests follow the same fix approach: extract the operation to its designated package:

    Test Violation Fix TestNoNakedErrors fmt.Errorf/errors.New outside internal/err/ Add error constructor to internal/err/<domain>/ TestNoRawFileIO Direct os.ReadFile, os.Create, etc. Use io.SafeReadFile, io.SafeWriteFile, etc. TestNoRawLogging Direct fmt.Fprintf(os.Stderr, ...) Use log/warn.Warn() or log/event.Append() TestNoExecOutsideExecPkg exec.Command outside internal/exec/ Add command to internal/exec/<domain>/ TestNoCmdPrintOutsideWrite cmd.Print* outside internal/write/ Add output helper to internal/write/<domain>/ TestNoRawPermissions Octal literals (0644, 0755) Use config/fs.PermFile, config/fs.PermExec, etc. TestNoErrorsAs errors.As() Use errors.AsType() (generic, Go 1.23+) TestNoStringConcatPaths dir + \"/\" + file Use filepath.Join(dir, file)","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#general-fix-workflow","level":2,"title":"General Fix Workflow","text":"

    When an audit test fails:

    1. Read the error message. It includes file:line and a description of the violation.
    2. Find the matching section above. The test name maps directly to a section.
    3. Apply the pattern. Most fixes are mechanical: extract to the right package, rename a variable, or replace a literal with a constant.
    4. Run make test before committing. Audit tests run as part of go test ./internal/audit/.
    5. Don't add allowlist entries as a first resort. Fix the code. Allowlists exist only for genuinely unfixable cases (test-only exports, config packages that are definitionally exempt).
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/comparison/","level":1,"title":"Tool Ecosystem","text":"","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"

    Many tools help AI think.

    ctx helps AI remember.

    • Not by storing thoughts,
    • but by preserving intent.
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How ctx Differs from Similar Tools","text":"

    There are many tools in the AI ecosystem that touch parts of the context problem:

    • Some manage prompts.
    • Some retrieve data.
    • Some provide runtime context objects.
    • Some offer enterprise platforms.

    ctx focuses on a different layer entirely.

    This page explains where ctx fits, and where it intentionally does not.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"

    Most tools treat context as input.

    ctx treats context as infrastructure.

    That single difference explains nearly all of ctx's design choices.

    Question Most tools ctx Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"

    Examples include:

    • prompt templates;
    • reusable system prompts;
    • prompt libraries;
    • prompt versioning tools.

    These tools help you start a session.

    They do not help you continue one.

    Prompt tools:

    • inject text at session start;
    • are ephemeral by design;
    • do not evolve with the project.

    ctx:

    • persists knowledge over time;
    • accumulates decisions and learnings;
    • makes the context part of the repository itself.

    Prompt tooling and ctx are complementary; not competing. Yet, they operate in different layers.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"

    RAG systems typically:

    • index documents
    • embed text
    • retrieve chunks dynamically at runtime

    They are excellent for:

    • large knowledge bases
    • static documentation
    • reference material

    RAG answers questions like:

    \"What information might be relevant right now?\"

    ctx answers a different question:

    \"What have we already decided, learned, or committed to?\"

    Here are some key differences:

    RAG ctx Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory

    ctx does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.

    RAG belongs to the data plane; ctx defines the context control plane.

    It focuses on project memory, not knowledge search.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"

    Agent frameworks often provide:

    • task loops
    • tool orchestration
    • planner/executor patterns
    • autonomous iteration

    These systems are powerful, but they typically assume that:

    • memory is external
    • context is injected
    • state is transient

    Agent frameworks answer:

    \"How should the agent act?\"

    ctx answers:

    \"What should the agent remember?\"

    Without persistent context, agents tend to:

    • rediscover decisions
    • repeat mistakes
    • lose architectural intent

    This is why ctx pairs well with autonomous loop workflows:

    • The loop provides iteration
    • ctx provides continuity

    Together, loops become cumulative instead of forgetful.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"

    Some SDKs expose \"context\" objects that exist:

    • inside a process
    • during a request
    • for the lifetime of a call chain

    These are extremely useful and completely different.

    SDK context objects:

    • are in-memory
    • disappear when the process ends
    • are not shared across sessions

    ctx:

    • survives process restarts
    • survives new chats
    • survives new days

    They share a name, not a purpose.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"

    Enterprise platforms often provide:

    • centralized context services
    • dashboards
    • access control
    • organizational knowledge layers

    These tools are designed for:

    • teams
    • governance
    • compliance
    • managed environments

    ctx is intentionally:

    • local-first: context lives next to your code, not behind a service boundary.
    • file-based: everything important is a markdown file you can read, diff, grep, and version-control.
    • single-binary core: the context persistence path (init, add, agent, status, drift, load, sync, compact, task, decision, learning, and their siblings) is a single Go binary with no required runtime dependencies. Optional integrations (ctx trace (needs git), ctx serve (needs zensical), the ctx Hub (needs a running hub), Claude Code plugin (needs claude)) are opt-in and each declares its dependency explicitly.
    • CLI-driven: every feature is reachable from the command line and scriptable.
    • developer-controlled: no auto-updating cloud service, no telemetry, no account to sign up for.

    The core ctx binary does not require:

    • a server
    • a database
    • an account
    • a SaaS backend
    • network connectivity (for core operations)

    ctx optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#specific-tool-comparisons","level":2,"title":"Specific Tool Comparisons","text":"

    Users often evaluate ctx against specific tools they already use. These comparisons clarify where responsibilities overlap, where they diverge, and where the tools are genuinely complementary.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#claude-code-memory-anthropic-auto-memory","level":3,"title":"Claude Code Memory / Anthropic Auto-Memory","text":"

    Anthropic's auto-memory is tool-managed memory (L2): the model decides what to remember, stores it automatically, and retrieves it implicitly. ctx is system memory (L3): humans and agents explicitly curate decisions, learnings, and tasks in inspectable files.

    Auto-memory is convenient - you do not configure anything. But it is also opaque: you cannot see what was stored, edit it precisely, or share it across tools. ctx files are plain Markdown in your repository, visible in diffs and code review.

    The two are complementary. ctx can absorb auto-memory as an input source (importing what the model remembered into structured context files) while providing the durable, inspectable layer that auto-memory lacks.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cursorrules-clauderules","level":3,"title":".Cursorrules / .Claude/rules","text":"

    Static rule files (.cursorrules, .claude/rules/) declare conventions: coding style, forbidden patterns, preferred libraries. They are effective for what to do and load automatically at session start.

    ctx adds dimensions that rule files do not cover: architectural decisions with rationale, learnings discovered during development, active tasks, and a constitution that governs agent behavior. Critically, ctx context accumulates - each session can add to it, and token budgeting ensures only the most relevant context is injected.

    Use rule files for static conventions. Use ctx for evolving project memory.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#aider-read-watch","level":3,"title":"Aider --read / --watch","text":"

    Aider's --read flag injects file contents at session start; --watch reloads them on change. The concept is similar to ctx's \"load\" step: make the agent aware of specific files.

    The differences emerge beyond loading. Aider has no persistence model -- nothing the agent learns during a session is written back. There is no token budgeting (large files consume the full context window), no priority ordering across file types, and no structured format for decisions or learnings. ctx provides the full lifecycle: load, accumulate, persist, and budget.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#copilot-workspace","level":3,"title":"Copilot @Workspace","text":"

    GitHub Copilot's @workspace performs workspace-wide code search. It answers \"what code exists?\" - finding function definitions, usages, and file structure across the repository.

    ctx answers a different question: \"what did we decide?\" It stores architectural intent, not code indices. Copilot's workspace search and ctx's project memory are orthogonal; one finds code, the other preserves the reasoning behind it.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cline-memory","level":3,"title":"Cline Memory","text":"

    Cline's memory bank stores session context within the Cline extension. The motivation is similar to ctx: help the agent remember across sessions.

    The key difference is portability. Cline memory is tied to Cline - it does not transfer to Claude Code, Cursor, Aider, or any other tool. ctx is tool-agnostic: context lives in plain files that any editor, agent, or script can read. Switching tools does not mean losing memory.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-a-good-fit","level":2,"title":"When ctx Is a Good Fit","text":"

    ctx works best when:

    • you want AI work to compound over time;
    • architectural decisions matter;
    • context must be inspectable;
    • humans and AI must share the same source of truth;
    • Git history should include why, not just what.
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When ctx Is Not the Right Tool","text":"

    ctx is probably not what you want if:

    • you only need one-off prompts;
    • you rely exclusively on RAG;
    • you want autonomous agents without a human-readable state;
    • you require centralized enterprise control;
    • you want black-box memory systems,

    These are valid goals; just different ones.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#further-reading","level":2,"title":"Further Reading","text":"
    • You Can't Import Expertise: why project-specific context matters more than generic best practices
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/design-invariants/","level":1,"title":"Invariants","text":"","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-system-explains-itself","level":1,"title":"The System Explains Itself","text":"

    These are the properties that must hold for any valid ctx implementation.

    • These are not features.
    • These are constraints.

    A change that violates an invariant is a category error, not an improvement.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#cognitive-state-tiers","level":2,"title":"Cognitive State Tiers","text":"

    ctx distinguishes between three forms of state:

    • Authoritative state: Versioned, inspectable artifacts that define intent and survive time.
    • Delivery views: Deterministic assemblies of the authoritative state for a specific budget or workflow.
    • Ephemeral working state: Local, transient, or sensitive data that assists interaction but does not define system truth.

    The invariants below apply primarily to the authoritative cognitive state.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#1-cognitive-state-is-explicit","level":2,"title":"1. Cognitive State Is Explicit","text":"

    All authoritative context lives in artifacts that can be inspected, reviewed, and versioned.

    If something is important, it must exist as a file: Not only in a prompt, a chat, or a model's hidden memory.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#2-assembly-is-reproducible","level":2,"title":"2. Assembly Is Reproducible","text":"

    Given the same:

    • repository state,
    • configuration,
    • and inputs,

    context assembly produces the same result.

    Heuristics may rank or filter for delivery under constraints.

    They do not alter the authoritative state.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#3-the-authoritative-state-is-human-readable","level":2,"title":"3. The Authoritative State Is Human-Readable","text":"

    The authoritative cognitive state must be stored in formats that a human can:

    • read,
    • diff,
    • review,
    • and edit directly.

    Sensitive working memory may be encrypted at rest. However, encryption must not become the only representation of authoritative knowledge.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#4-artifacts-outlive-sessions","level":2,"title":"4. Artifacts Outlive Sessions","text":"

    Sessions are transient.

    Knowledge persists.

    Reasoning, decisions, and outcomes must remain available after the interaction that produced them has ended.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#5-authority-is-user-defined","level":2,"title":"5. Authority Is User-Defined","text":"

    What enters the authoritative context is an explicit human decision.

    Models may suggest.

    Automation may assist.

    Selection is never implicit.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#6-operation-is-local-first","level":2,"title":"6. Operation Is Local-First","text":"

    The core system must function without requiring network access or a remote service.

    External systems may extend ctx.

    They must not be required for its operation.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#7-versioning-is-the-memory-model","level":2,"title":"7. Versioning Is the Memory Model","text":"

    The evolution of the authoritative cognitive state must be:

    • preserved,
    • inspectable,
    • and branchable.

    Ephemeral and sensitive working state may use different retention and diff strategies by design.

    Understanding includes understanding how we arrived here.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#8-structure-enables-scale","level":2,"title":"8. Structure Enables Scale","text":"

    Unstructured accumulation is not memory.

    Authoritative cognitive state must have a defined layout that:

    • communicates intent,
    • supports navigation,
    • and prevents drift.
    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#9-verification-is-the-scoreboard","level":2,"title":"9. Verification Is the Scoreboard","text":"

    Claims without recorded outcomes are noise.

    Reality (observed and captured) is the only signal that compounds.

    This invariant defines a required direction:

    The authoritative state must be able to record expectation and result.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#10-capture-once-reuse-indefinitely","level":2,"title":"10. Capture Once, Reuse Indefinitely","text":"

    Work that has already produced understanding must not be re-derived from scratch.

    Explored paths, rejected options, and validated conclusions are permanent assets.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#11-policies-are-encoded-not-remembered","level":2,"title":"11. Policies Are Encoded, Not Remembered","text":"

    Alignment must not depend on recall or goodwill.

    Constraints that matter must exist in machine-readable form and participate in context assembly.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#12-the-system-explains-itself","level":2,"title":"12. The System Explains Itself","text":"

    From the repository state alone it must be possible to determine:

    • what was authoritative,
    • what constraints applied.

    Delivery views may be optimized.

    They must not become the only explanation.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#non-goals","level":1,"title":"Non-Goals","text":"

    To avoid category errors, ctx does not attempt to be:

    • a skill,
    • a prompt management tool,
    • a chat history viewer,
    • an autonomous agent runtime,
    • a vector database,
    • a hosted memory service.

    Such systems may integrate with ctx.

    They do not define it.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#implications-for-contributions","level":1,"title":"Implications for Contributions","text":"

    Valid contributions:

    • strengthen an invariant,
    • reduce the cost of maintaining an invariant,
    • or extend the system without violating invariants.

    Invalid contributions:

    • introduce hidden authoritative state,
    • replace reproducible assembly with non-reproducible behavior,
    • make core operation depend on external services,
    • reduce human inspectability of authoritative state,
    • or bypass explicit user authority over what becomes authoritative.
    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-contract","level":1,"title":"The Contract","text":"

    Everything else (commands, skills, layouts, integrations, optimizations) is an implementation detail.

    These invariants are the system.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/scratchpad/","level":1,"title":"Scratchpad","text":"","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#what-is-ctx-scratchpad","level":2,"title":"What Is ctx Scratchpad?","text":"

    A one-liner scratchpad, encrypted at rest, synced via git.

    Quick notes that don't fit decisions, learnings, or tasks: reminders, intermediate values, sensitive tokens, working memory during debugging. Entries are numbered, reorderable, and persist across sessions.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#encrypted-by-default","level":2,"title":"Encrypted by Default","text":"

    Scratchpad entries are encrypted with AES-256-GCM before touching the disk.

    Component Path Git status Encryption key ~/.ctx/.ctx.key User-level, 0600 permissions Encrypted data .context/scratchpad.enc Committed

    The key is generated automatically during ctx init (256-bit via crypto/rand) and stored at ~/.ctx/.ctx.key. One key per machine, shared across all projects.

    The ciphertext format is [12-byte nonce][ciphertext+tag]. No external dependencies: Go stdlib only.

    Because the key is .gitignored and the data is committed, you get:

    • At-rest encryption: the .enc file is opaque without the key
    • Git sync: push/pull the encrypted file like any other tracked file
    • Key separation: the key never leaves the machine unless you copy it
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#commands","level":2,"title":"Commands","text":"Command Purpose ctx pad List all entries (numbered 1-based) ctx pad show N Output raw text of entry N (no prefix, pipe-friendly) ctx pad add \"text\" Append a new entry ctx pad rm ID [ID...] Remove entries by stable ID (supports ranges: 3-5) ctx pad edit N \"text\" Replace entry N with new text ctx pad edit N --append \"text\" Append text to the end of entry N ctx pad edit N --prepend \"text\" Prepend text to the beginning of entry N ctx pad edit N --tag tagname Add a tag to entry N ctx pad add TEXT --file PATH Ingest a file as a blob entry (TEXT is the label) ctx pad show N --out PATH Write decoded blob content to a file ctx pad normalize Reassign entry IDs as 1..N ctx pad mv N M Move entry from position N to position M ctx pad resolve Show both sides of a merge conflict for resolution ctx pad import FILE Bulk-import lines from a file (or stdin with -) ctx pad import --blob DIR Import directory files as blob entries ctx pad export [DIR] Export all blob entries to a directory as files ctx pad merge FILE... Merge entries from other scratchpad files into current ctx pad --tag TAG List entries filtered by tag (prefix with ~ to exclude) ctx pad tags List all tags with counts ctx pad tags --json List all tags with counts as JSON

    All commands decrypt on read, operate on plaintext in memory, and re-encrypt on write. The key file is never printed to stdout.

    For blob entries, --append, --prepend, and --tag modify the label while preserving the blob data.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#examples","level":3,"title":"Examples","text":"
    # Add a note\nctx pad add \"check DNS propagation after deploy\"\n\n# List everything\nctx pad\n#   1. check DNS propagation after deploy\n#   2. staging API key: sk-test-abc123\n\n# Show raw text (for piping)\nctx pad show 2\n# sk-test-abc123\n\n# Compose entries\nctx pad edit 1 --append \"$(ctx pad show 2)\"\n\n# Reorder\nctx pad mv 2 1\n\n# Clean up (IDs are stable; they don't shift when entries are deleted)\nctx pad rm 2\n
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#tags","level":2,"title":"Tags","text":"

    Entries can contain #word tags for lightweight categorization. Tags are convention-based: any #word token in an entry's text is a tag. No special syntax to add or remove them; use the existing add and edit commands.

    # Add tagged entries\nctx pad add \"check DNS propagation #later\"\nctx pad add \"deploy hotfix #urgent\"\nctx pad add \"review PR #later #ci\"\n\n# Filter by tag\nctx pad --tag later\n#   1. check DNS propagation #later\n#   3. review PR #later #ci\n\n# Exclude a tag\nctx pad --tag ~later\n#   2. deploy hotfix #urgent\n\n# Multiple filters (AND logic)\nctx pad --tag later --tag ci\n#   3. review PR #later #ci\n\n# List all tags with counts\nctx pad tags\n# ci       1\n# later    2\n# urgent   1\n\n# JSON output\nctx pad tags --json\n# [{\"tag\":\"ci\",\"count\":1},{\"tag\":\"later\",\"count\":2},{\"tag\":\"urgent\",\"count\":1}]\n\n# Add a tag to an existing entry\nctx pad edit 1 --tag done\n\n# Combine with other operations\nctx pad edit 1 --append \"checked\" --tag done\n\n# Remove a tag (replace entry text without the tag)\nctx pad edit 1 \"check DNS propagation\"\n

    Entry IDs are stable; they don't shift when other entries are deleted, so ctx pad rm 3 always targets the same entry. Use ctx pad normalize to reassign IDs as 1..N if gaps bother you. Tags are case-sensitive and support letters, digits, hyphens, and underscores (#high-priority, #v2, #my_tag).

    For blob entries, tags are extracted from the label only.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#bulk-import-and-export","level":2,"title":"Bulk Import and Export","text":"

    Import lines from a file in bulk (each non-empty line becomes an entry):

    # Import from a file\nctx pad import notes.txt\n\n# Import from stdin\ngrep TODO *.go | ctx pad import -\n

    Export all blob entries to a directory as files:

    # Export to a directory\nctx pad export ./ideas\n\n# Preview without writing\nctx pad export --dry-run\n\n# Overwrite existing files\nctx pad export --force ./backup\n
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#merging-scratchpads","level":2,"title":"Merging Scratchpads","text":"

    Combine entries from other scratchpad files into your current pad. Useful when merging work from parallel worktrees, other machines, or teammates:

    # Merge from a worktree's encrypted scratchpad\nctx pad merge worktree/.context/scratchpad.enc\n\n# Merge from multiple sources (encrypted and plaintext)\nctx pad merge pad-a.enc notes.md\n\n# Merge a foreign encrypted pad using its key\nctx pad merge --key /other/.ctx.key foreign.enc\n\n# Preview without writing\nctx pad merge --dry-run pad-a.enc pad-b.md\n

    Each input file is auto-detected as encrypted or plaintext: decryption is attempted first, and on failure the file is parsed as plain text. Entries are deduplicated by exact content, so running merge twice with the same file is safe.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#file-blobs","level":2,"title":"File Blobs","text":"

    The scratchpad can store small files (up to 64 KB) as blob entries. Files are base64-encoded and stored with a human-readable label.

    # Ingest a file: first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# Listing shows label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n\n# Extract to a file\nctx pad show 2 --out ./recovered.yaml\n\n# Or print decoded content to stdout\nctx pad show 2\n

    Blob entries are encrypted identically to text entries. The internal format is label:::base64data: You never need to construct this manually.

    Constraint Value Max file size (pre-encoding) 64 KB Storage format label:::base64(content) Display label [BLOB] in listings

    When Should You Use Blobs

    Blobs are for small files you want encrypted and portable: config snippets, key fragments, deployment manifests, test fixtures. For anything larger than 64 KB, use the filesystem directly.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#using-with-ai","level":2,"title":"Using with AI","text":"

    Use Natural Language

    As in many ctx features, the ctx scratchpad can also be used with natural langauge. You don't have to memorize the CLI commands.

    CLI gives you \"precision\", whereas natural language gives you flow.

    The /ctx-pad skill maps natural language to ctx pad commands. You don't need to remember the syntax:

    You say What happens \"jot down: check DNS after deploy\" ctx pad add \"check DNS after deploy\" \"show my scratchpad\" ctx pad \"delete the third entry\" ctx pad rm 3 \"update entry 2 to include the new endpoint\" ctx pad edit 2 \"...\" \"move entry 4 to the top\" ctx pad mv 4 1 \"import my notes from notes.txt\" ctx pad import notes.txt \"export all blobs to ./backup\" ctx pad export ./backup \"merge the scratchpad from the worktree\" ctx pad merge worktree/.context/scratchpad.enc

    The skill handles the translation. You describe what you want in plain English; the agent picks the right command.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#worktrees","level":2,"title":"Worktrees","text":"

    The encryption key lives at ~/.ctx/.ctx.key (outside the project directory). Because all worktrees on the same machine share this path, ctx pad works in worktrees automatically - no special setup needed.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#key-distribution","level":2,"title":"Key Distribution","text":"

    The encryption key (~/.ctx/.ctx.key) stays on the machine where it was generated. ctx never transmits it.

    To share the scratchpad across machines:

    1. Copy the key manually: scp, USB drive, password manager.
    2. Push/pull the .enc file via git as usual.
    3. Both machines can now read and write the same scratchpad.

    Never Commit the Key

    The key is .gitignored by default. If you override this, anyone with repo access can decrypt your scratchpad.

    Treat the key like an SSH private key.

    See the Syncing Scratchpad Notes Across Machines recipe for a step-by-step walkthrough.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#plaintext-override","level":2,"title":"Plaintext Override","text":"

    For projects where encryption is unnecessary, disable it in .ctxrc:

    scratchpad_encrypt: false\n

    In plaintext mode:

    • Entries are stored in .context/scratchpad.md instead of .enc.
    • No key is generated or required.
    • All ctx pad commands work identically.
    • The file is human-readable and diffable.

    When Should You Use Plaintext

    Plaintext mode is useful for non-sensitive projects, solo work where encryption adds friction, or when you want scratchpad entries visible in git diff.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#when-should-you-use-scratchpad-versus-context-files","level":2,"title":"When Should You Use Scratchpad versus Context Files","text":"Use case Where it goes Temporary reminders (\"check X after deploy\") Scratchpad Working values during debugging Scratchpad Sensitive tokens or API keys (short-term) Scratchpad Quick notes that don't fit anywhere else Scratchpad Items that are not directly relevant to the project Scratchpad Things that you want to keep near, but also hidden Scratchpad Work items with completion tracking TASKS.md Trade-offs with rationale DECISIONS.md Reusable lessons with context/lesson/application LEARNINGS.md Codified patterns and standards CONVENTIONS.md

    Rule of thumb:

    • If it needs structure or will be referenced months later, use a context file (i.e. DECISIONS.md, LEARNINGS.md, TASKS.md).
    • If it is working memory for the current session or week, use the scratchpad.
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#see-also","level":2,"title":"See Also","text":"
    • Syncing Scratchpad Notes Across Machines: Key distribution, push/pull workflow, merge conflict resolution
    • Using the Scratchpad: Natural language examples, blob workflow, when to use scratchpad vs context files
    • Context Files: Format and conventions for all .context/ files
    • Security: Trust model and permission hygiene
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/session-journal/","level":1,"title":"Session Journal","text":"

    Important Security Note

    Session journals contain sensitive data such as file contents, commands, API keys, internal discussions, error messages with stack traces, and more.

    The .context/journal-site/ and .context/journal-obsidian/ directories MUST be .gitignored.

    • DO NOT host your journal publicly.
    • DO NOT commit your journal files to version control.
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#browse-your-session-history","level":2,"title":"Browse Your Session History","text":"

    ctx's Session Journal turns your AI coding sessions into a browsable, searchable, and editable archive.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#quick-start","level":2,"title":"Quick Start","text":"

    After using ctx for a couple of sessions, you can generate a journal site with:

    # Import all sessions to markdown\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n

    Then open http://localhost:8000 to browse your sessions.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#what-you-get","level":2,"title":"What You Get","text":"

    The Session Journal gives you:

    • Browsable history: Navigate through all your AI sessions by date
    • Full conversations: See every message, tool use, and result
    • Token usage: Track how many tokens each session consumed
    • Search: Find sessions by content, project, or date
    • Dark mode: Easy on the eyes for late-night archaeology

    Each session page includes the following sections:

    Section Content Metadata Date, time, duration, model, project, git branch Summary Space for your notes (editable) Tool Usage Which tools were used and how often Conversation Full transcript with timestamps","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#1-import-sessions","level":3,"title":"1. Import Sessions","text":"
    # Import all sessions from current project (only new files)\nctx journal import --all\n\n# Import sessions from all projects\nctx journal import --all --all-projects\n\n# Import a specific session by ID (always writes)\nctx journal import abc123\n\n# Preview what would be imported\nctx journal import --all --dry-run\n\n# Re-import existing (regenerates conversation, preserves YAML frontmatter)\nctx journal import --all --regenerate\n\n# Discard frontmatter during regeneration\nctx journal import --all --regenerate --keep-frontmatter=false -y\n

    Imported sessions go to .context/journal/ as editable Markdown files.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#2-generate-the-site","level":3,"title":"2. Generate the Site","text":"
    # Generate site structure\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n

    The site is generated in .context/journal-site/ by default.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#3-browse-and-search","level":3,"title":"3. Browse and Search","text":"

    Open http://localhost:8000 after running --serve.

    • Use the sidebar to navigate by date
    • Use search (/ key) to find specific content
    • Click any session to see the full conversation
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#editing-sessions","level":2,"title":"Editing Sessions","text":"

    Imported sessions are plain Markdown in .context/journal/. You can:

    • Add summaries: Fill in the ## Summary section
    • Add notes: Insert your own commentary anywhere
    • Highlight key moments: Use Markdown formatting
    • Delete noise: Remove irrelevant tool outputs

    After editing, regenerate the site:

    ctx journal site --serve\n
    Safe by Default

    Running ctx journal import --all only imports new sessions. Existing files are skipped entirely (your edits and enrichments are never touched).

    Use --regenerate to re-import existing files. Conversation content is regenerated, but YAML frontmatter (topics, type, outcome, etc.) is preserved. You'll be prompted before any existing files are overwritten; add -y to skip the prompt.

    Use --keep-frontmatter=false to discard enriched frontmatter during regeneration.

    Locked entries (via ctx journal lock) are always skipped, regardless of flags. If you prefer to add locked: true to frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#large-sessions","level":2,"title":"Large Sessions","text":"

    Sessions with many messages (200+) are automatically split into multiple parts for better browser performance. Navigation links connect the parts:

    session-abc123.md      (Part 1 of 3)\nsession-abc123-p2.md   (Part 2 of 3)\nsession-abc123-p3.md   (Part 3 of 3)\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#suggestion-sessions","level":2,"title":"Suggestion Sessions","text":"

    Claude Code generates \"suggestion\" sessions for auto-complete prompts. These are separated in the index under a \"Suggestions\" section to keep your main session list focused.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enriching-journal-entries","level":2,"title":"Enriching Journal Entries","text":"

    Raw imported sessions contain basic metadata (date, time, project) but lack the structured information needed for effective search, filtering, and analysis. Journal enrichment adds semantic metadata that transforms a flat archive into a searchable knowledge base.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#why-enrich","level":3,"title":"Why Enrich?","text":"

    Without enrichment, you have timestamps and raw conversations. With enrichment:

    • Find sessions by topic: \"Show me all auth-related sessions\"
    • Filter by outcome: \"What did I abandon vs complete?\"
    • Track technology usage: \"When did I last work with PostgreSQL?\"
    • Identify key files: Jump directly to the files discussed
    • Get summaries: Understand what happened without reading transcripts
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-frontmatter-schema","level":3,"title":"The Frontmatter Schema","text":"

    Enriched entries begin with YAML frontmatter:

    ---\ntitle: \"Implement caching layer\"\ndate: 2026-01-27\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/cache/memory.go\n---\n
    Field Required Description title Yes Descriptive title (not the session slug) date Yes Session date (YYYY-MM-DD) type Yes Session type (see below) outcome Yes How the session ended (see below) topics No Subject areas discussed technologies No Languages, databases, frameworks libraries No Specific packages or libraries used key_files No Important files created or modified

    Type values:

    Type When to use feature Building new functionality bugfix Fixing broken behavior refactor Restructuring without behavior change exploration Research, learning, experimentation debugging Investigating issues documentation Writing docs, comments, README

    Outcome values:

    Outcome Meaning completed Goal achieved partial Some progress, work continues abandoned Stopped pursuing this approach blocked Waiting on external dependency","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-ctx-journal-enrich","level":3,"title":"Using /ctx-journal-enrich","text":"

    The /ctx-journal-enrich skill automates enrichment by analyzing conversation content and proposing metadata.

    Invoke by session identifier:

    /ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-01-24\n/ctx-journal-enrich 76fe2ab9\n

    The skill will:

    1. Check if locked - locked entries are skipped (same as export);
    2. Find the matching journal file;
    3. Read and analyze the conversation;
    4. Propose frontmatter (type, topics, outcome, technologies);
    5. Generate a 2-3 sentence summary;
    6. Extract decisions, learnings, and tasks mentioned;
    7. Show a diff and ask for confirmation before writing.
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#before-and-after","level":3,"title":"Before and After","text":"

    Before enrichment:

    # twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\n[Add your summary of this session]\n\n## Conversation\n...\n

    After enrichment:

    ---\ntitle: \"Add Redis caching to API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n\n# twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\nImplemented Redis-based caching middleware for frequently accessed API endpoints.\nAdded cache invalidation on writes and configurable TTL per route. Reduced\n the average response time from 200ms to 15ms for cached routes.\n\n## Decisions\n\n* Used Redis over in-memory cache for horizontal scaling\n* Chose per-route TTL configuration over global setting\n\n## Learnings\n\n* Redis WATCH command prevents race conditions during cache invalidation\n\n## Conversation\n...\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enrichment-and-site-generation","level":3,"title":"Enrichment and Site Generation","text":"

    The journal site generator uses enriched metadata for better organization:

    • Titles appear in navigation instead of slugs
    • Summaries provide context in the index
    • Topics enable filtering (when using search)
    • Types allow grouping by work category

    Future improvements will add topic-based navigation and outcome filtering to the generated site.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#batch-enrichment","level":3,"title":"Batch Enrichment","text":"

    To enrich multiple sessions, process them one at a time:

    # List unenriched sessions (those without frontmatter)\ngrep -L \"^---$\" .context/journal/*.md | head -10\n

    Then run /ctx-journal-enrich on each. Enrichment is intentionally interactive to ensure accuracy.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#obsidian-vault-export","level":2,"title":"Obsidian Vault Export","text":"

    If you use Obsidian for knowledge management, you can export your journal as an Obsidian vault instead of (or alongside) the static site:

    ctx journal obsidian\n

    This generates a vault in .context/journal-obsidian/ with:

    • Wikilinks ([[target|display]]) instead of Markdown links
    • MOC pages (Map of Content) for topics, key files, and session types
    • Related sessions footer per entry: links to entries sharing the same topics
    • Transformed frontmatter: topics renamed to tags (Obsidian-recognized), aliases added from title for search
    • Graph-optimized structure: MOC hubs and cross-linked entries create dense graph connectivity

    To use: open the output directory in Obsidian (\"Open folder as vault\").

    # Custom output directory\nctx journal obsidian --output ~/vaults/ctx-journal\n

    Static Site vs Obsidian Vault

    Use ctx journal site when you want a web-browsable archive with search and dark mode. Use ctx journal obsidian when you want graph view, backlinks, and tag-based navigation inside Obsidian. Both use the same enriched source entries: you can generate both.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#full-pipeline","level":2,"title":"Full Pipeline","text":"

    The complete journal workflow has four stages. Each is idempotent: safe to re-run, and stages skip already-processed entries.

    import → enrich → rebuild\n
    Stage Command / Skill What it does Skips if Import ctx journal import --all Converts session JSONL to Markdown File already exists (safe default) Enrich /ctx-journal-enrich Adds frontmatter, summaries, topics Frontmatter already present Rebuild ctx journal site --build Generates static HTML site (never) Obsidian ctx journal obsidian Generates Obsidian vault with wikilinks (never)

    One-Command Pipeline

    /ctx-journal-enrich-all handles import automatically - it detects unimported sessions and imports them before enriching. You only need to run ctx journal site --build afterward.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-make-journal","level":3,"title":"Using make journal","text":"

    If your project includes Makefile.ctx (deployed by ctx init), the first and last stages are combined:

    make journal           # import + rebuild\n

    After it runs, it reminds you to enrich in Claude Code:

    Next steps (in Claude Code):\n  /ctx-journal-enrich-all # imports if needed + adds metadata per entry\n\nThen re-run: make journal\n

    Rendering Issues?

    If individual entries have rendering problems (broken fences, malformed lists), check the programmatic normalization in the import pipeline. Most cases are handled automatically during ctx journal import.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#tips","level":2,"title":"Tips","text":"

    Daily workflow:

    # Import, browse, then enrich in Claude Code\nmake journal && make journal-serve\n# Then in Claude Code: /ctx-journal-enrich <session>\n

    After a productive session:

    # Import just that session and add notes\nctx journal import <session-id>\n# Edit .context/journal/<session>.md\n# Regenerate: ctx journal site\n

    Searching across all sessions:

    # Use grep on the journal directory\ngrep -r \"authentication\" .context/journal/\n

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#requirements","level":2,"title":"Requirements","text":"Use pipx for zensical

    pip install zensical may install a non-functional stub on system Python. Using venv has other issues too.

    These issues especially happen on Mac OSX.

    Use pipx install zensical, which creates an isolated environment and handles Python version management automatically.

    The journal site uses zensical for static site generation:

    pipx install zensical\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#see-also","level":2,"title":"See Also","text":"
    • ctx journal: Session discovery and listing
    • ctx journal site: Static site generation
    • ctx journal obsidian: Obsidian vault export
    • Context Files: The .context/ directory structure
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/skills/","level":1,"title":"Skills","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skills","level":2,"title":"Skills","text":"

    Skills are slash commands that run inside your AI assistant (e.g., /ctx-next), as opposed to CLI commands that run in your terminal (e.g., ctx status).

    Skills give your agent structured workflows: It knows what to read, what to run, and when to ask. Most wrap one or more ctx CLI commands with opinionated behavior on top.

    Skills Are Best Used Conversationally

    The beauty of ctx is that it's designed to be intuitive and conversational, allowing you to interact with your AI assistant naturally. That's why you don't have to memorize many of these skills.

    See the Prompting Guide for natural-language triggers that invoke these skills conversationally.

    However, when you need a more precise control, you have the option to invoke the relevant skills directly.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#all-skills","level":2,"title":"All Skills","text":"Skill Description Type /ctx-remember Recall project context and present structured readback user-invocable /ctx-wrap-up End-of-session context persistence ceremony user-invocable /ctx-status Show context summary with interpretation user-invocable /ctx-agent Load full context packet for AI consumption user-invocable /ctx-next Suggest 1-3 concrete next actions with rationale user-invocable /ctx-commit Commit with integrated context persistence user-invocable /ctx-reflect Pause and reflect on session progress user-invocable /ctx-task-add Add actionable task to TASKS.md user-invocable /ctx-decision-add Record architectural decision with rationale user-invocable /ctx-learning-add Record gotchas and lessons learned user-invocable /ctx-convention-add Record coding convention for consistency user-invocable /ctx-archive Archive completed tasks from TASKS.md user-invocable /ctx-pad Manage encrypted scratchpad entries user-invocable /ctx-history Browse and import AI session history user-invocable /ctx-journal-enrich Enrich single journal entry with metadata user-invocable /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich user-invocable /ctx-blog Generate blog post draft from project activity user-invocable /ctx-blog-changelog Generate themed blog post from a commit range user-invocable /ctx-consolidate Consolidate redundant learnings or decisions user-invocable /ctx-drift Detect and fix context drift user-invocable /ctx-prompt Apply, list, and manage saved prompt templates user-invocable /ctx-prompt-audit Analyze prompting patterns for improvement user-invocable /ctx-link-check Audit docs for dead internal and external links user-invocable /ctx-permission-sanitize Audit Claude Code permissions for security risks user-invocable /ctx-brainstorm Structured design dialogue before implementation user-invocable /ctx-spec Scaffold a feature spec from a project template user-invocable /ctx-plan-import Import Claude Code plan files into project specs user-invocable /ctx-implement Execute a plan step-by-step with verification user-invocable /ctx-loop Generate autonomous loop script user-invocable /ctx-worktree Manage git worktrees for parallel agents user-invocable /ctx-architecture Build and maintain architecture maps user-invocable /ctx-architecture-failure-analysis Adversarial failure analysis for correctness bugs user-invocable /ctx-remind Manage session-scoped reminders user-invocable /ctx-doctor Troubleshoot ctx behavior with health checks and event analysis user-invocable /ctx-skill-audit Audit skills against Anthropic prompting best practices user-invocable /ctx-skill-create Create, improve, and test skills user-invocable /ctx-pause Pause context hooks for this session user-invocable /ctx-resume Resume context hooks after a pause user-invocable","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-lifecycle","level":2,"title":"Session Lifecycle","text":"

    Skills for starting, running, and ending a productive session.

    Session Ceremonies

    Two skills in this group are ceremony skills: /ctx-remember (session start) and /ctx-wrap-up (session end). Unlike other skills that work conversationally, these should be invoked as explicit slash commands for completeness. See Session Ceremonies.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remember","level":3,"title":"/ctx-remember","text":"

    Recall project context and present a structured readback. Ceremony skill: invoke explicitly at session start.

    Wraps: ctx agent --budget 4000, ctx journal source --limit 3, reads TASKS.md, DECISIONS.md, LEARNINGS.md

    See also: Session Ceremonies, The Complete Session

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-status","level":3,"title":"/ctx-status","text":"

    Show context summary (files, token budget, tasks, recent activity) with interpreted suggestions.

    Wraps: ctx status [--verbose] [--json]

    See also: The Complete Session, ctx status CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-agent","level":3,"title":"/ctx-agent","text":"

    Load the full context packet optimized for AI consumption. Also runs automatically via the PreToolUse hook with cooldown.

    Wraps: ctx agent [--budget] [--format] [--cooldown] [--session]

    See also: The Complete Session, ctx agent CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-next","level":3,"title":"/ctx-next","text":"

    Suggest 1-3 concrete next actions ranked by priority, momentum, and unblocked status.

    Wraps: reads TASKS.md, ctx journal source --limit 3

    See also: The Complete Session, Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-commit","level":3,"title":"/ctx-commit","text":"

    Commit code with integrated context persistence: pre-commit checks, staged files, Co-Authored-By trailer, and a post-commit prompt to capture decisions and learnings.

    Wraps: git add, git commit, optionally chains to /ctx-decision-add and /ctx-learning-add

    See also: The Complete Session

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-reflect","level":3,"title":"/ctx-reflect","text":"

    Pause and reflect on session progress. Walks through a checklist of learnings, decisions, task completions, and session notes to persist.

    Wraps: chains to ctx learning add, ctx decision add, manual TASKS.md updates

    See also: The Complete Session, Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-wrap-up","level":3,"title":"/ctx-wrap-up","text":"

    End-of-session context persistence ceremony. Gathers signal from git diff, recent commits, and conversation themes. Proposes candidates (learnings, decisions, conventions, tasks) with complete structured fields for user approval, then persists via ctx add. Offers /ctx-commit if uncommitted changes remain. Ceremony skill: invoke explicitly at session end.

    Wraps: git diff --stat, git log, ctx learning add, ctx decision add, ctx convention add, ctx task add, chains to /ctx-commit

    See also: Session Ceremonies, The Complete Session

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#context-persistence","level":2,"title":"Context Persistence","text":"

    Skills for recording work artifacts: tasks, decisions, learnings, conventions: into .context/ files.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-task-add","level":3,"title":"/ctx-task-add","text":"

    Add an actionable task with optional priority and phase section.

    Wraps: ctx task add \"description\" [--priority high|medium|low] --session-id ID --branch BR --commit HASH

    See also: Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-decision-add","level":3,"title":"/ctx-decision-add","text":"

    Record an architectural decision with context, rationale, and consequence. Supports Y-statement (lightweight) and full ADR formats.

    Wraps: ctx decision add \"title\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id ID --branch BR --commit HASH

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-learning-add","level":3,"title":"/ctx-learning-add","text":"

    Record a project-specific gotcha, bug, or unexpected behavior. Filters for insights that are searchable, project-specific, and required real effort to discover.

    Wraps: ctx learning add \"title\" --context \"...\" --lesson \"...\" --application \"...\" --session-id ID --branch BR --commit HASH

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-convention-add","level":3,"title":"/ctx-convention-add","text":"

    Record a coding convention that should be standardized across sessions. Targets patterns seen 2-3+ times.

    Wraps: ctx convention add \"rule\" --section \"Name\"

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-archive","level":3,"title":"/ctx-archive","text":"

    Archive completed tasks from TASKS.md to a timestamped file in .context/archive/. Preserves phase headers for traceability.

    Wraps: ctx task archive [--dry-run]

    See also: Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#scratchpad","level":2,"title":"Scratchpad","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pad","level":3,"title":"/ctx-pad","text":"

    Manage the encrypted scratchpad: add, remove, edit, and reorder one-liner notes. Encrypted at rest with AES-256-GCM.

    Wraps: ctx pad, ctx pad add, ctx pad rm, ctx pad edit, ctx pad mv, ctx pad import, ctx pad export, ctx pad merge

    See also: Scratchpad, Using the Scratchpad

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#journal-history","level":2,"title":"Journal & History","text":"

    Skills for browsing, exporting, and enriching your AI session history into a structured journal.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-history","level":3,"title":"/ctx-history","text":"

    Browse, inspect, and import AI session history. List recent sessions, show details by slug or ID, and import to .context/journal/.

    Wraps: ctx journal source, ctx journal source --show, ctx journal import

    See also: Browsing and Enriching Past Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich","level":3,"title":"/ctx-journal-enrich","text":"

    Enrich a single journal entry with YAML frontmatter: title, type, outcome, topics, technologies, and summary. Shows diff before writing.

    Wraps: reads and edits .context/journal/*.md files

    See also: Browsing and Enriching Past Sessions, Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich-all","level":3,"title":"/ctx-journal-enrich-all","text":"

    Full journal pipeline: imports unimported sessions first, then batch-enriches all unenriched entries. Filters out short sessions and continuations. Can spawn subagents for large backlogs.

    Wraps: ctx journal import --all + iterates /ctx-journal-enrich

    See also: Browsing and Enriching Past Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#content-creation","level":2,"title":"Content Creation","text":"

    Skills for turning project activity into publishable content.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog","level":3,"title":"/ctx-blog","text":"

    Generate a blog post draft from recent project activity: git history, decisions, learnings, tasks, and journal entries. Requires a narrative arc (problem, approach, outcome).

    Wraps: reads git log, DECISIONS.md, LEARNINGS.md, TASKS.md, journal entries; writes to docs/blog/

    See also: Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog-changelog","level":3,"title":"/ctx-blog-changelog","text":"

    Generate a themed blog post from a commit range. Takes a starting commit and unifying theme, analyzes diffs and journal entries from that period.

    Wraps: git log, git diff --stat; writes to docs/blog/

    See also: Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#auditing-health","level":2,"title":"Auditing & Health","text":"

    Skills for detecting drift, auditing alignment, and improving prompt quality.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-consolidate","level":3,"title":"/ctx-consolidate","text":"

    Consolidate redundant entries in LEARNINGS.md or DECISIONS.md. Groups overlapping entries by keyword similarity, presents candidates, and (with user approval) merges groups into denser combined entries. Originals are archived, not deleted.

    Wraps: reads LEARNINGS.md and DECISIONS.md, writes consolidated entries, archives originals, runs ctx reindex

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-drift","level":3,"title":"/ctx-drift","text":"

    Detect and fix context drift: stale paths, missing files, file age staleness, task accumulation, entry count warnings, and constitution violations via ctx drift. Also detects skill drift against canonical templates.

    Wraps: ctx drift [--fix]

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-prompt-audit","level":3,"title":"/ctx-prompt-audit","text":"

    Analyze recent prompting patterns to identify vague or ineffective prompts. Reviews 3-5 journal entries and suggests rewrites with positive observations.

    Wraps: reads .context/journal/ entries

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-doctor","level":3,"title":"/ctx-doctor","text":"

    Troubleshoot ctx behavior. Runs structural health checks via ctx doctor, analyzes event log patterns via ctx hook event, and presents findings with suggested actions. The CLI provides the structural baseline; the agent adds semantic analysis of event patterns and correlations.

    Wraps: ctx doctor --json, ctx hook event --json --last 100, ctx remind list, ctx hook message list, reads .ctxrc

    Trigger phrases: \"diagnose\", \"troubleshoot\", \"doctor\", \"health check\", \"why didn't my hook fire?\", \"hooks seem broken\", \"something seems off\"

    Graceful degradation: If event_log is not enabled, the skill still works but with reduced capability. It runs structural checks and notes: \"Enable event_log: true in .ctxrc for hook-level diagnostics.\"

    See also: Troubleshooting, ctx doctor CLI, ctx hook event CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-link-check","level":3,"title":"/ctx-link-check","text":"

    Scan all markdown files under docs/ for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, and skips localhost/example URLs.

    Wraps: Glob + Grep to scan, curl for external checks

    Trigger phrases: \"check links\", \"audit links\", \"any broken links?\", \"dead links\"

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-permission-sanitize","level":3,"title":"/ctx-permission-sanitize","text":"

    Audit .claude/settings.local.json for dangerous permissions across four risk categories: hook bypass (Critical), destructive commands (High), config injection vectors (High), and overly broad patterns (Medium). Reports findings by severity and offers specific fix actions with user confirmation.

    Wraps: reads .claude/settings.local.json, edits with confirmation

    Trigger phrases: \"audit permissions\", \"are my permissions safe?\", \"sanitize permissions\", \"check settings\"

    See also: Claude Code Permission Hygiene

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#planning-execution","level":2,"title":"Planning & Execution","text":"

    Skills for structured design, implementation, and parallel agent workflows.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-brainstorm","level":3,"title":"/ctx-brainstorm","text":"

    Transform raw ideas into clear, validated designs through structured dialogue before any implementation begins. Follows a gated process: understand context, clarify the idea (one question at a time), surface non-functional requirements, lock understanding with user confirmation, explore 2-3 design approaches with trade-offs, stress-test the chosen approach, and present the detailed design.

    Wraps: reads DECISIONS.md, relevant source files; chains to /ctx-decision-add for recording design choices

    Trigger phrases: \"let's brainstorm\", \"design this\", \"think through\", \"before we build\", \"what approach should we take?\"

    See also: /ctx-spec

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-spec","level":3,"title":"/ctx-spec","text":"

    Scaffold a feature spec from the project template and walk through each section with the user. Covers: problem, approach, happy path, edge cases, validation rules, error handling, interface, implementation, configuration, testing, and non-goals. Spends extra time on edge cases and error handling.

    Wraps: reads specs/tpl/spec-template.md, writes to specs/, optionally chains to /ctx-task-add

    Trigger phrases: \"spec this out\", \"write a spec\", \"create a spec\", \"design document\"

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#-brief-path-flag","level":4,"title":"--brief <path> flag","text":"

    When invoked as /ctx-spec --brief <path>, the skill treats the file at <path> as the authoritative source and skips the interactive Q&A. Use this when a prior /ctx-plan session produced a debated brief that already covers the design.

    The skill enforces this authority order when sources disagree:

    1. Frozen contracts in docs/ (release notes, public CLI docs)
    2. Recorded decisions in .context/DECISIONS.md
    3. The brief at <path>
    4. Agent inference — only when 1–3 are silent, and labeled TBD in the resulting spec so it stands out for review.

    Light compression for clarity is allowed; new facts are not. Where the brief is silent, the spec writes TBD rather than filling the gap from inference. If the brief contradicts a frozen contract, the contradiction is surfaced to the user rather than silently followed.

    See also: /ctx-brainstorm, /ctx-plan, /ctx-plan-import

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-plan-import","level":3,"title":"/ctx-plan-import","text":"

    Import Claude Code plan files (~/.claude/plans/*.md) into the project's specs/ directory. Lists plans with dates and H1 titles, supports filtering (--today, --since, --all), slugifies headings for filenames, and optionally creates tasks referencing each imported spec.

    Wraps: reads ~/.claude/plans/*.md, writes to specs/, optionally chains to /ctx-task-add

    See also: Importing Claude Code Plans, Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-implement","level":3,"title":"/ctx-implement","text":"

    Execute a multi-step plan with build and test verification at each step. Loads a plan from a file or conversation context, breaks it into atomic steps, and checkpoints after every 3-5 steps.

    Wraps: reads plan file, runs verification commands (go build, go test, etc.)

    See also: Running an Unattended AI Agent

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-loop","level":3,"title":"/ctx-loop","text":"

    Generate a ready-to-run shell script for autonomous AI iteration. Supports Claude Code, Aider, and generic tool templates with configurable completion signals.

    Wraps: ctx loop [--tool] [--prompt] [--max-iterations] [--completion] [--output]

    See also: Autonomous Loops, Running an Unattended AI Agent

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-worktree","level":3,"title":"/ctx-worktree","text":"

    Manage git worktrees for parallel agent development. Create sibling worktrees on dedicated branches, analyze task blast radius for grouping, and tear down with merge.

    Wraps: git worktree add, git worktree list, git worktree remove, git merge

    See also: Parallel Agent Development with Git Worktrees

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture","level":3,"title":"/ctx-architecture","text":"

    Build and maintain architecture maps incrementally. Creates or refreshes ARCHITECTURE.md (succinct project map, loaded at session start) and DETAILED_DESIGN.md (deep per-module reference, consulted on-demand). Coverage is tracked in map-tracking.json so each run extends the map rather than re-analyzing everything.

    Wraps: ctx status, git log, reads source files; writes ARCHITECTURE.md, DETAILED_DESIGN.md, map-tracking.json

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture-failure-analysis","level":3,"title":"/ctx-architecture-failure-analysis","text":"

    Adversarial failure analysis that generates falsifiable incident hypotheses against architecture artifacts. Hunts for correctness bugs that survive code review and tests: race conditions, ordering assumptions, cache staleness, error swallowing, ownership gaps, idempotency failures, state machine drift, and scaling cliffs.

    Requires /ctx-architecture artifacts as input. Reads ARCHITECTURE.md, DETAILED_DESIGN*.md, and map-tracking.json, then systematically applies 9 failure categories to every mutation point. Each finding carries an evidence standard (code path, trigger, failure path, silence reason, code evidence), a confidence level, and an explicit risk score. A mandatory challenge phase attempts to disprove each finding before it is accepted.

    Produces .context/DANGER-ZONES.md with ranked findings split into Critical (risk >= 7, silent/cascading) and Elevated tiers.

    Wraps: reads architecture artifacts, source code; writes DANGER-ZONES.md. Optionally uses GitNexus for blast radius and Gemini Search for cross-referencing known failure patterns.

    Relationship:

    Skill Mode /ctx-architecture Map what exists /ctx-architecture-enrich Improve map fidelity /ctx-architecture-failure-analysis Generate falsifiable incident hypotheses","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remind","level":3,"title":"/ctx-remind","text":"

    Manage session-scoped reminders via natural language. Translates user intent (\"remind me to refactor swagger\") into the corresponding ctx remind command. Handles date conversion for --after flags.

    Wraps: ctx remind, ctx remind list, ctx remind dismiss

    See also: Session Reminders

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skill-authoring","level":2,"title":"Skill Authoring","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-audit","level":3,"title":"/ctx-skill-audit","text":"

    Audit one or more skills against Anthropic prompting best practices. Checks audit dimensions: positive framing, motivation, phantom references, examples, subagent guards, scope, and descriptions. Reports findings by severity with concrete fix suggestions.

    Wraps: reads internal/assets/claude/skills/*/SKILL.md or .claude/skills/*/SKILL.md, references anthropic-best-practices.md

    Trigger phrases: \"audit this skill\", \"check skill quality\", \"review the skills\", \"are our skills any good?\"

    See also: /ctx-skill-create, Contributing

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-create","level":3,"title":"/ctx-skill-create","text":"

    Create, improve, and test skills. Guides the full lifecycle: capture intent, interview for edge cases, draft the SKILL.md, test with realistic prompts, review results with the user, and iterate. Applies core principles: the agent is already smart (only add what it does not know), the description is the trigger (make it specific and \"pushy\"), and explain the why instead of rigid directives.

    Wraps: reads/writes .claude/skills/ and internal/assets/claude/skills/

    Trigger phrases: \"create a skill\", \"turn this into a skill\", \"make a slash command\", \"this should be a skill\", \"improve this skill\", \"the skill isn't triggering\"

    See also: Contributing

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-control","level":2,"title":"Session Control","text":"

    Skills for controlling hook behavior during a session.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pause","level":3,"title":"/ctx-pause","text":"

    Pause all context nudge and reminder hooks for the current session. Security hooks still fire. Use for quick investigations or tasks that don't need ceremony overhead.

    Wraps: ctx hook pause

    Trigger phrases: \"pause ctx\", \"pause context\", \"stop the nudges\", \"quiet mode\"

    See also: Pausing Context Hooks

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-resume","level":3,"title":"/ctx-resume","text":"

    Resume context hooks after a pause. Restores normal nudge, reminder, and ceremony behavior. Silent no-op if not paused.

    Wraps: ctx hook resume

    Trigger phrases: \"resume ctx\", \"resume context\", \"turn nudges back on\", \"unpause\"

    See also: Pausing Context Hooks

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#project-specific-skills","level":2,"title":"Project-Specific Skills","text":"

    The ctx plugin ships the skills listed above. Teams can add their own project-specific skills to .claude/skills/ in the project root: These are separate from plugin-shipped skills and are scoped to the project.

    Project-specific skills follow the same format and are invoked the same way.

    Custom skills are not covered in this reference.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/versions/","level":1,"title":"Version History","text":"","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#version-history","level":2,"title":"Version History","text":"

    Documentation snapshots for each release.

    Tap the corresponding view docs to view the docs as they were at that release.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.8.0 2026-03-23 view docs v0.6.0 2026-02-16 view docs v0.3.0 2026-02-07 view docs v0.2.0 2026-02-01 view docs v0.1.2 2026-01-27 view docs v0.1.1 2026-01-26 view docs v0.1.0 2026-01-25 view docs","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v080-the-architecture-release","level":3,"title":"v0.8.0: The Architecture Release","text":"

    MCP server for tool-agnostic AI integration. Memory bridge connecting Claude Code auto-memory to .context/. Complete CLI restructuring into cmd/ + core/ taxonomy. All user-facing strings externalized to YAML. fatih/color removed; two direct dependencies remain.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v060-the-integration-release","level":3,"title":"v0.6.0: The Integration Release","text":"

    Plugin architecture: hooks and skills converted from shell scripts to Go subcommands, shipped as a Claude Code marketplace plugin. Multi-tool hook generation for Cursor, Aider, Copilot, and Windsurf. Webhook notifications with encrypted URL storage.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v030-the-discipline-release","level":3,"title":"v0.3.0: The Discipline Release","text":"

    Journal static site generation via zensical. 49-skill audit and fix pass (positive framing, phantom reference removal, scope tightening). Context consolidation skill. golangci-lint v2 migration.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v020-the-archaeology-release","level":3,"title":"v0.2.0: The Archaeology Release","text":"

    Session journal system: ctx journal import converts Claude Code JSONL transcripts to browsable Markdown. Constants refactor with semantic prefixes (Dir*, File*, Filename*). CRLF handling for Windows compatibility.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v012","level":3,"title":"v0.1.2","text":"

    Default Claude Code permissions deployed on ctx init. Prompting guide published as a standalone documentation page.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v011","level":3,"title":"v0.1.1","text":"

    Bug fixes: hook schema key format corrected, JSON unicode escaping fixed in context file output.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v010-initial-release","level":3,"title":"v0.1.0: Initial Release","text":"

    CLI with 15 subcommands, 6 context file types (CONSTITUTION, TASKS, CONVENTIONS, ARCHITECTURE, DECISIONS, LEARNINGS), Makefile build system, and Claude Code hook integration.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#latest","level":2,"title":"Latest","text":"

    The main documentation always reflects the latest development version.

    For the most recent stable release, see v0.8.0.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#changelog","level":2,"title":"Changelog","text":"

    For detailed changes between versions, see the GitHub Releases page.

    ","path":["Reference","Version History"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"

    Security model, agent hardening, and vulnerability reporting.

    ","path":["Security"],"tags":[]},{"location":"security/#security-design","level":3,"title":"Security Design","text":"

    Trust model, what ctx does for security, permission hygiene, state file management, and the log-first audit trail principle. Read first to understand the security boundaries.

    ","path":["Security"],"tags":[]},{"location":"security/#securing-ai-agents","level":3,"title":"Securing AI Agents","text":"

    Defense in depth for unattended AI agents: five layers of protection, each with a known bypass, strength in combination.

    ","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":3,"title":"Reporting Vulnerabilities","text":"

    How to report a security issue: email, GitHub private reporting, PGP-encrypted submissions, what to include, and the response timeline.

    ","path":["Security"],"tags":[]},{"location":"security/agent-security/","level":1,"title":"Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#defense-in-depth-securing-ai-agents","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-problem","level":2,"title":"The Problem","text":"

    An unattended AI agent with unrestricted access to your machine is an unattended shell with unrestricted access to your machine.

    This is not a theoretical concern. AI coding agents execute shell commands, write files, make network requests, and modify project configuration. When running autonomously (overnight, in a loop, without a human watching), the attack surface is the full capability set of the operating system user account.

    The risk is not that the AI is malicious. The risk is that the AI is controllable: it follows instructions from context, and context can be poisoned.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#threat-model","level":2,"title":"Threat Model","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#how-agents-get-compromised","level":3,"title":"How Agents Get Compromised","text":"

    AI agents follow instructions from multiple sources: system prompts, project files, conversation history, and tool outputs. An attacker who can inject content into any of these sources can redirect the agent's behavior.

    Vector How it works Prompt injection via dependencies A malicious package includes instructions in its README, changelog, or error output. The agent reads these during installation or debugging and follows them. Prompt injection via fetched content The agent fetches a URL (documentation, API response, Stack Overflow answer) containing embedded instructions. Poisoned project files A contributor adds adversarial instructions to CLAUDE.md, .cursorrules, or .context/ files. The agent loads these at session start. Self-modification between iterations In an autonomous loop, the agent modifies its own configuration files. The next iteration loads the modified config with no human review. Tool output injection A command's output (error messages, log lines, file contents) contains instructions the agent interprets and follows.","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#what-can-a-compromised-agent-do","level":3,"title":"What Can a Compromised Agent Do","text":"

    Depends entirely on what permissions and access the agent has:

    Access level Potential impact Unrestricted shell Execute any command, install software, modify system files Network access Exfiltrate source code, credentials, or context files to external servers Docker socket Escape container isolation by spawning privileged sibling containers SSH keys Pivot to other machines, push to remote repositories, access production systems Write access to own config Disable its own guardrails for the next iteration","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-defense-layers","level":2,"title":"The Defense Layers","text":"

    No single layer is sufficient. Each layer catches what the others miss.

    Layer 1: Soft instructions     (CONSTITUTION.md, playbook)\nLayer 2: Application controls  (permission allowlist, tool restrictions)\nLayer 3: OS-level isolation    (user accounts, filesystem, containers)\nLayer 4: Network controls      (firewall rules, airgap)\nLayer 5: Infrastructure        (VM isolation, resource limits)\n
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"

    Markdown files like CONSTITUTION.md and the Agent Playbook tell the agent what to do and what not to do. These are probabilistic: the agent usually follows them, but there is no enforcement mechanism.

    What it catches: Most common mistakes. An agent that has been told \"never delete production data\" will usually not delete production data.

    What it misses: Prompt injection. A sufficiently crafted injection can override soft instructions. Long context windows dilute attention on rules stated early. Edge cases where instructions are ambiguous.

    Verdict: Necessary but not sufficient. Good for the common case. Do not rely on it for security boundaries.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"

    AI tool runtimes (Claude Code, Cursor, etc.) provide permission systems: tool allowlists, command restrictions, confirmation prompts.

    For Claude Code, ctx init writes both an allowlist and an explicit deny list into .claude/settings.local.json. The golden images live in internal/assets/permissions/:

    Allowlist (allow.txt): only these tools run without confirmation:

    Bash(ctx:*)\nSkill(ctx-convention-add)\nSkill(ctx-decision-add)\n... # all bundled ctx-* skills\n

    Deny list (deny.txt): these are blocked even if the agent requests them:

    # Dangerous operations\nBash(sudo *)\nBash(git push *)\nBash(git push)\nBash(rm -rf /*)\nBash(rm -rf ~*)\nBash(curl *)\nBash(wget *)\nBash(chmod 777 *)\n\n# Sensitive file reads\nRead(**/.env)\nRead(**/.env.*)\nRead(**/*credentials*)\nRead(**/*secret*)\nRead(**/*.pem)\nRead(**/*.key)\n\n# Sensitive file edits\nEdit(**/.env)\nEdit(**/.env.*)\n

    What it catches: The agent cannot run commands outside the allowlist, and the deny list blocks dangerous operations even if a future allowlist change were to widen access. If rm, curl, sudo, or docker are not allowed and sudo/curl/wget are explicitly denied, the agent cannot invoke them regardless of what any prompt says.

    What it misses: The agent can modify the allowlist itself. In an autonomous loop, if the agent writes to .claude/settings.local.json, and the next iteration loads the modified config, then the protection is effectively lost. The application enforces the rules, but the application reads the rules from files the agent can write.

    Verdict: Strong first layer. Must be combined with self-modification prevention (Layer 3).

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-3-os-level-isolation-deterministic-and-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Deterministic and Unbypassable)","text":"

    The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without CAP_NET_RAW cannot open raw sockets. These are kernel boundaries.

    Control Purpose Dedicated user account No sudo, no privileged group membership (docker, wheel, adm). The agent cannot escalate privileges. Filesystem permissions Project directory writable; everything else read-only or inaccessible. Agent cannot reach other projects, home directories, or system config. Immutable config files CLAUDE.md, .claude/settings.local.json, and .context/CONSTITUTION.md owned by a different user or marked immutable (chattr +i on Linux). The agent cannot modify its own guardrails.

    What it catches: Privilege escalation, self-modification, lateral movement to other projects or users.

    What it misses: Actions within the agent's legitimate scope. If the agent has write access to source code (which it needs to do its job), it can introduce vulnerabilities in the code itself.

    Verdict: Essential. This is the layer that makes the other layers trustworthy.

    OS-level isolation does not make the agent safe; it makes the other layers meaningful.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"

    An agent that cannot reach the internet cannot exfiltrate data. It also cannot ingest new instructions mid-loop from external documents, API responses, or hostile content.

    Scenario Recommended control Agent does not need the internet --network=none (container) or outbound firewall drop-all Agent needs to fetch dependencies Allow specific registries (npmjs.com, proxy.golang.org, pypi.org) via firewall rules. Block everything else. Agent needs API access Allow specific API endpoints only. Use an HTTP proxy with allowlisting.

    What it catches: Data exfiltration, phone-home payloads, downloading additional tools, and instruction injection via fetched content.

    What it misses: Nothing, if the agent genuinely does not need the network. The tradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"

    The strongest boundary is a separate machine (or something that behaves like one).

    The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.

    Containers (Docker, Podman):

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n

    Docker Socket Is Sudo Access

    Critical: never mount the Docker socket (/var/run/docker.sock).

    An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox.

    Use rootless Docker or Podman to eliminate this escalation path.

    Virtual machines: The strongest isolation. The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.

    Resource limits: CPU, memory, and disk quotas prevent a runaway agent from consuming all resources. Use ulimit, cgroup limits, or container resource constraints.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A defense-in-depth setup for overnight autonomous runs:

    Layer Implementation Stops Soft instructions CONSTITUTION.md with \"never delete tests\", \"always run tests before committing\" Common mistakes (probabilistic) Application allowlist .claude/settings.local.json with explicit tool permissions Unauthorized commands (deterministic within runtime) Immutable config chattr +i on CLAUDE.md, .claude/, CONSTITUTION.md Self-modification between iterations Unprivileged user Dedicated user, no sudo, no docker group Privilege escalation Container --cap-drop=ALL --network=none, rootless, no socket mount Host escape, network exfiltration Resource limits --memory=4g --cpus=2, disk quotas Resource exhaustion

    Each layer is straightforward: The strength is in the combination.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    \"I'll just use --dangerously-skip-permissions\": This disables Layer 2 entirely. Without Layers 3-5, you have no protection at all. Only use this flag inside a properly isolated container or VM.

    \"The agent is sandboxed in Docker\": A Docker container with the Docker socket mounted, running as root, with --privileged, and full network access is not sandboxed. It is a root shell with extra steps.

    \"CONSTITUTION.md says not to do that\": Markdown is a suggestion. It works most of the time. It is not a security boundary. Do not use it as one.

    \"I reviewed the CLAUDE.md, it's fine\": The agent can modify CLAUDE.md during iteration N. Iteration N+1 loads the modified version. Unless the file is immutable, your review is stale.

    \"The agent only has access to this one project\": Does the project directory contain .env files, SSH keys, API tokens, or credentials? Does it have a .git/config with push access to a remote? Filesystem isolation means isolating what is in the directory too.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-security-considerations","level":2,"title":"Team Security Considerations","text":"

    When multiple developers share a .context/ directory, security considerations extend beyond single-agent hardening.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#code-review-for-context-files","level":3,"title":"Code Review for Context Files","text":"

    Treat .context/ changes like code changes. Context files influence agent behavior (a modified CONSTITUTION.md or CONVENTIONS.md changes what every agent on the team will do next session). Review them in PRs with the same scrutiny you apply to production code.

    Watch for:

    • Weakened constitutional rules (removed constraints, softened language)
    • New decisions that contradict existing ones without acknowledging it
    • Learnings that encode incorrect assumptions
    • Task additions that bypass the team's prioritization process
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#gitignore-patterns","level":3,"title":"Gitignore Patterns","text":"

    ctx init configures .gitignore automatically, but verify these patterns are in place:

    • Always gitignored: .ctx.key (encryption key), .context/logs/, .context/journal/
    • Team decision: scratchpad.enc (encrypted, safe to commit for shared scratchpad state); .gitignore if scratchpads are personal
    • Never committed: .env, credentials, API keys (enforced by drift secret detection)
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#multi-developer-context-sharing","level":3,"title":"Multi-Developer Context Sharing","text":"

    CONSTITUTION.md is the shared contract. All team members and their agents inherit it. Changes require team consensus, not unilateral edits.

    When multiple agents write to the same context files concurrently (e.g., two developers adding learnings simultaneously), git merge conflicts are expected. Resolution is typically additive: accept both additions. Destructive resolution (dropping one side) loses context.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-conventions-for-context-management","level":3,"title":"Team Conventions for Context Management","text":"

    Establish and document:

    • Who reviews context changes: Same reviewers as code, or a designated context owner?
    • How to resolve conflicting decisions: If two sessions record contradictory decisions, which wins? Default: the later one must explicitly supersede the earlier one with rationale.
    • Frequency of context maintenance: Weekly ctx drift checks, monthly consolidation passes, archival after each milestone.
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#checklist","level":2,"title":"Checklist","text":"

    Before running an unattended AI agent:

    • Agent runs as a dedicated unprivileged user (no sudo, no docker group)
    • Agent's config files are immutable or owned by a different user
    • Permission allowlist restricts tools to the project's toolchain
    • Container drops all capabilities (--cap-drop=ALL)
    • Docker socket is NOT mounted
    • Network is disabled or restricted to specific domains
    • Resource limits are set (memory, CPU, disk)
    • No SSH keys, API tokens, or credentials are accessible to the agent
    • Project directory does not contain .env or secrets files
    • Iteration cap is set (--max-iterations)
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#further-reading","level":2,"title":"Further Reading","text":"
    • Running an Unattended AI Agent: the ctx recipe for autonomous loops, including step-by-step permissions and isolation setup
    • Security: ctx's own trust model and vulnerability reporting
    • Autonomous Loops: full documentation of the loop pattern, prompt templates, and troubleshooting
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/design/","level":1,"title":"Security Design","text":"

    How ctx thinks about security: trust boundaries, what the system does and does not do for you, the engineering principle behind the audit trail, and the permission hygiene workflow.

    For vulnerability disclosure, see Reporting Vulnerabilities.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#trust-model","level":2,"title":"Trust Model","text":"

    ctx operates within a single trust boundary: the local filesystem.

    The person who authors .context/ files is the same person who runs the agent that reads them. There is no remote input, no shared state, and no server component.

    This means:

    • ctx does not sanitize context files for prompt injection. This is a deliberate design choice, not an oversight. The files are authored by the developer who owns the machine: sanitizing their own instructions back to them would be counterproductive.
    • If you place adversarial instructions in your own .context/ files, your agent will follow them. This is expected behavior. You control the context; the agent trusts it.

    Shared Repositories

    In shared repositories, .context/ files should be reviewed in code review (the same way you would review CI/CD config or Makefiles). A malicious contributor could add harmful instructions to CONSTITUTION.md or TASKS.md.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#what-ctx-does-for-security","level":2,"title":"What ctx Does for Security","text":"

    ctx is designed with security in mind:

    • No secrets in context: The constitution explicitly forbids storing secrets, tokens, API keys, or credentials in .context/ files.
    • Local only: ctx runs entirely locally with no external network calls.
    • No code execution: ctx reads and writes Markdown files only; it does not execute arbitrary code.
    • Git-tracked: Core context files are meant to be committed, so they should never contain sensitive data. Exception: sessions/ and journal/ contain raw conversation data and should be gitignored.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#permission-hygiene","level":2,"title":"Permission Hygiene","text":"

    Claude Code evaluates permissions in deny → ask → allow order. ctx init automatically populates permissions.deny with rules that block dangerous operations before the allow list is ever consulted.

    Default deny rules block:

    • sudo, git push, rm -rf /, rm -rf ~, curl, wget, chmod 777
    • Read / Edit of .env, credentials, secrets, .pem, .key files

    Even with deny rules in place, the allow list accumulates one-off permissions over time. Periodically review for:

    • Destructive commands: git reset --hard, git clean -f, etc.
    • Config injection vectors: permissions that allow modifying files controlling agent behavior (CLAUDE.md, settings.local.json).
    • Broad wildcards: overly permissive patterns that pre-approve more than intended.

    For the full hygiene workflow, see the Claude Code Permission Hygiene recipe.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#state-file-management","level":2,"title":"State File Management","text":"

    Hook state files (throttle markers, prompt counters, pause markers) are stored in .context/state/, which is project-scoped and gitignored. State files are automatically managed by the hooks that create them; no manual cleanup is needed.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#log-first-audit-trail","level":2,"title":"Log-First Audit Trail","text":"

    The event log (.context/state/events.jsonl) is the authoritative record of what ctx hooks did during a session. Several audit-adjacent features depend on that log being trustworthy, not merely best-effort:

    • ctx event / ctx system view-events replays session history from the log.
    • Webhook notifications give operators a real-time signal that assumes every notification corresponds to a logged event.
    • Drift, freshness, and map-staleness checks count events over time and surface regressions.

    A log that silently drops entries while the rest of the system claims success is worse than no log at all: operators see a green TUI and a webhook notification and conclude \"it happened,\" even when the audit trail never landed. The codebase treats this as a correctness problem, not a UX polish problem.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#the-rule","level":3,"title":"The Rule","text":"

    Any code path that emits an observable side effect (webhook, stdout marker, throttle-file touch, state mutation) must append the corresponding event-log entry first and gate the side effect on the append succeeding. If the log write fails, the side effect must not fire.

    In code, this shape:

    if appendErr := event.Append(channel, msg, sessionID, ref); appendErr != nil {\n    return appendErr // do NOT send the webhook or touch the marker\n}\nif sendErr := notify.Send(channel, msg, sessionID, ref); sendErr != nil {\n    return sendErr\n}\n// downstream side effects (marker touch, stdout, etc.)\n

    The nudge.Relay helper in internal/cli/system/core/nudge enforces this for the common \"log + webhook\" pair. Hook Run functions that compose their own sequence (session_event, heartbeat, several check_* hooks) follow the same ordering explicitly.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#known-gaps","level":3,"title":"Known Gaps","text":"
    • Nudge webhooks have no log channel. nudge.EmitAndRelay sends a \"nudge\" notification before the \"relay\" event is logged. The nudge leg is fire-and-forget because no event-log channel records nudges today. A future refactor may add one; until then this is the one documented exception.
    • ctx agent --cooldown and ctx doctor propagate rather than gate. They surface real errors to the caller (usually Cobra) rather than deciding what to do with them locally. Editors that invoke these commands may display errors in an ugly way; the ugliness is the correct signal (something persisted is broken), not a defect to smooth over.
    • Verbose hook logs in core/log.Message stay best-effort. That logger captures per-hook activity (how many prompts, which percent, etc.) for debugging; it is NOT the event audit trail. Its failures go to stderr via log/warn.Warn rather than propagating, because losing an operational log line is not a correctness problem.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#background","level":3,"title":"Background","text":"

    The error returns on event.Append, io.AppendBytes, nudge.Relay, and cooldown.Active / cooldown.TouchTombstone were introduced as part of the resolver-tightening refactor. Before that change, most hook paths called these helpers and silently discarded their errors. The principle above was extracted from the observation that every user-visible correctness problem hit during the refactor traced back to some function saying \"this succeeded\" when the underlying write never landed.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#best-practices","level":2,"title":"Best Practices","text":"
    1. Review before committing: Always review .context/ files before committing.
    2. Use .gitignore: If you must store sensitive notes locally, add them to .gitignore.
    3. Drift detection: Run ctx drift to check for potential issues.
    4. Permission audit: Review .claude/settings.local.json after busy sessions.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/hub/","level":1,"title":"Hub Security Model","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#ctx-hub-security-model","level":1,"title":"ctx Hub: Security Model","text":"

    What the hub defends against, what it does not defend against, and the concrete mechanisms in play.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#threat-model","level":2,"title":"Threat Model","text":"

    The hub is designed for trusted cross-project knowledge sharing within a team or homelab. It assumes:

    • The hub host is trusted. Anyone with root on that box can read every entry ever published.
    • Network is semi-trusted. Hub traffic is gRPC over TCP; TLS is strongly recommended but not mandatory.
    • Client machines are trusted enough to hold a per-project client token. Losing a client token is roughly equivalent to losing an API key: scoped damage, not total compromise.
    • Entry content is not secret. Decisions, learnings, and conventions may be indexed by AI agents, rendered in docs, shared across projects. Do not push credentials or PII into the hub.

    The hub is not a secure messaging system, a secrets store, or a compliance-grade audit log. If your threat model needs those, use a dedicated tool and keep the hub for knowledge sharing.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#mechanisms","level":2,"title":"Mechanisms","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#bearer-tokens","level":3,"title":"Bearer Tokens","text":"

    All RPCs except Register require a bearer token in gRPC metadata. Two kinds of tokens exist:

    Kind Format Scope Lifetime Admin token ctx_adm_... Register new projects Manual rotate Client token ctx_cli_... Publish, Sync, Listen, Status Project lifetime

    Tokens are compared in constant time (crypto/subtle) to prevent timing oracles, and looked up via an O(1) hash map so the comparison cost does not depend on the total number of registered clients.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#client-side-encryption-at-rest","level":3,"title":"Client-Side Encryption at Rest","text":"

    .context/.connect.enc stores the client token and hub address, encrypted with AES-256-GCM using the same scheme the notification subsystem uses. The key is derived from ctx's local keyring (see internal/crypto).

    An attacker with read access to the project directory cannot learn the client token without also breaking ctx's local keyring.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#hub-side-token-storage","level":3,"title":"Hub-Side Token Storage","text":"

    Tokens Are Stored in Plaintext on the Hub Host

    <data-dir>/clients.json currently stores client tokens verbatim, not hashed. Anyone with read access to the hub's data directory sees every registered client's token and can impersonate any project that has ever registered.

    Mitigations today:

    • Run the hub as an unprivileged user and lock the data directory with chmod 700 <data-dir>.
    • Use the systemd unit in Operations, which enables ProtectSystem=strict, NoNewPrivileges=true, and a dedicated user.
    • Never expose <data-dir> over NFS, SMB, or shared filesystems.
    • Treat <data-dir> the same way you'd treat /etc/shadow: back it up encrypted, never check it into version control.

    Hashing clients.json and moving to keyring-backed storage is tracked as a follow-up in the PR #60 task group. Until that lands, assume a hub host compromise equals total hub compromise.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#input-validation","level":3,"title":"Input Validation","text":"

    Every published entry is validated before it touches the log:

    • Type must be one of: decision, learning, convention, task. Unknown types are rejected.
    • ID and Origin are required and non-empty.
    • Content size is capped at 1 MB. Reasonable for text, hostile for attempts to fill the disk.
    • Duplicate project registration is rejected; a client that replays an old Register call gets an error, not a second token.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#no-script-execution","level":3,"title":"No Script Execution","text":"

    The hub never interprets entry content. There is no expression language, no template evaluation, no markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#audit-trail","level":3,"title":"Audit Trail","text":"

    entries.jsonl is append-only. Every accepted publish is recorded with the publishing project's origin tag and sequence number. Nothing is ever deleted by the hub; retention is managed manually by the operator (see log rotation).

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#what-the-hub-does-not-defend-against","level":2,"title":"What the Hub Does Not Defend Against","text":"
    • Untrusted entry senders. A client with a valid token can publish anything (within the 1 MB cap). There is no content validation beyond shape.
    • Denial of service from a registered client. A misbehaving client can publish until disk is full. Monitor entries.jsonl growth.
    • Network eavesdropping without TLS. Plain gRPC leaks entry content and tokens. Use a TLS-terminating reverse proxy (see Multi-machine recipe).
    • Host compromise. Root on the hub host = access to every entry and every token. Harden the host.
    • Accidental secret upload. The hub will happily fan out a decision containing an API key. Sanitize content before publishing.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#operational-hardening-checklist","level":2,"title":"Operational Hardening Checklist","text":"
    • Run the hub as an unprivileged user with NoNewPrivileges=true and ProtectSystem=strict (see the systemd unit in Operations).
    • Terminate TLS in front of the hub for anything beyond a trusted LAN.
    • Restrict the listen port with firewall rules to the client subnet only.
    • Back up <data-dir>/admin.token to a secrets manager; do not leave it in shell history.
    • Rotate the admin token when a team member with access leaves. Client tokens keep working across rotations.
    • Monitor entries.jsonl growth; alert on sudden spikes.
    • Run NTP on all clients to prevent entry-timestamp skew.
    • Do not publish from machines you do not trust.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#responsible-disclosure","level":2,"title":"Responsible Disclosure","text":"

    Security issues in the hub follow the same process as the rest of ctx; see Reporting.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub Operations
    • ctx Hub failure modes
    • HA cluster recipe
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/reporting/","level":1,"title":"Reporting Vulnerabilities","text":"

    Disclosure process for security issues in ctx. For the broader security model (trust boundaries, audit trail, permission hygiene), see Security Design.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"

    At ctx we take security very seriously.

    If you discover a security vulnerability in ctx, please report it responsibly.

    Do NOT open a public issue for security vulnerabilities.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#email","level":3,"title":"Email","text":"

    Send details to security@ctx.ist.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"
    1. Go to the Security tab;
    2. Click \"Report a Vulnerability\";
    3. Provide a detailed description.
    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#encrypted-reports-optional","level":3,"title":"Encrypted Reports (Optional)","text":"

    If your report contains sensitive details (proof-of-concept exploits, credentials, or internal system information), you can encrypt your message with our PGP key:

    • In-repo: SECURITY_KEY.asc
    • Keybase: keybase.io/alekhinejose
    # Import the key\ngpg --import SECURITY_KEY.asc\n\n# Encrypt your report\ngpg --armor --encrypt --recipient security@ctx.ist report.txt\n

    Encryption is optional. Unencrypted reports to security@ctx.ist or via GitHub Private Reporting are perfectly fine.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#what-to-include","level":3,"title":"What to Include","text":"
    • Description of the vulnerability,
    • Steps to reproduce,
    • Potential impact,
    • Suggested fix (if any).
    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#attribution","level":2,"title":"Attribution","text":"

    We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#response-timeline","level":2,"title":"Response Timeline","text":"

    Open Source, Best-Effort Timelines

    ctx is a volunteer-maintained open source project.

    The timelines below are guidelines, not guarantees, and depend on contributor availability.

    We will address security reports on a best-effort basis and prioritize them by severity.

    Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"thesis/","level":1,"title":"Context as State","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#a-persistence-layer-for-human-ai-cognition","level":2,"title":"A Persistence Layer for Human-AI Cognition","text":"

    Volkan Özçelik - me@volkan.io

    February 2026

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#abstract","level":3,"title":"Abstract","text":"

    As AI tools evolve from code-completion utilities into reasoning collaborators, the knowledge that governs their behavior becomes as important as the code they produce; yet, that knowledge is routinely discarded at the end of every session.

    AI-assisted development systems assemble context at prompt time using heuristic retrieval from mutable sources: recent files, semantic search results, session history. These approaches optimize relevance at the moment of generation but do not persist the cognitive state that produced decisions. Reasoning is not reproducible, intent is lost across sessions, and teams cannot audit the knowledge that constrains automated behavior.

    This paper argues that context should be treated as deterministic, version-controlled state rather than as a transient query result. We ground this argument in three sources of evidence: a landscape analysis of 17 systems spanning AI coding assistants, agent frameworks, and knowledge stores; a taxonomy of five primitive categories that reveals irrecoverable architectural trade-offs; and an experience report from ctx, a persistence layer for AI-assisted development, which developed itself using its own persistence model across 389 sessions over 33 days. We define a three-tier model for cognitive state: authoritative knowledge, delivery views, and ephemeral state. Then we present six design invariants empirically validated by 56 independent rejection decisions observed across the analyzed landscape. We show that context determinism applies to assembly, not to model output, and that the curation cost this model requires is offset by compounding returns in reproducibility, auditability, and team cognition.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#1-introduction","level":2,"title":"1. Introduction","text":"

    The introduction of large language models into software development has shifted the primary interface from code execution to interactive reasoning. In this environment, the correctness of an output depends not only on source code but on the context supplied to the model: the conventions, decisions, architectural constraints, and domain knowledge that bound the space of acceptable responses.

    Current systems treat context as a query result assembled at the moment of interaction. A developer begins a session; the tool retrieves what it estimates to be relevant from chat history, recent files, and vector stores; the model generates output conditioned on this transient assembly; the session ends, and the context evaporates. The next session begins the cycle again.

    This model has improved substantially over the past year. CLAUDE.md files, Cursor rules, Copilot's memory system, and tools such as Mem0, Letta, and Kindex each address aspects of the persistence problem. Yet across 17 systems we analyzed spanning AI coding assistants, agent frameworks, autonomous coding agents, and purpose-built knowledge stores, no system provides all five of the following properties simultaneously: deterministic context assembly, human-readable file-based persistence, token-budgeted delivery, a single-binary core with zero required runtime dependencies for the persistence path, and local-first operation.

    This paper does not propose a universal replacement for retrieval-centric workflows. It defines a persistence layer (embodied in ctx (https://ctx.ist)) whose advantages emerge under specific operational conditions: when reproducibility is a requirement, when knowledge must outlive sessions and individuals, when teams require shared cognitive authority, or when offline operation is necessary.

    The trade-offs (manual curation cost, reduced automatic recall, coarser granularity) are intentional and mirror the trade-offs accepted by systems that favor reproducibility over convenience, such as reproducible builds and immutable infrastructure 1 6.

    The contribution is threefold: a three-tier model for cognitive state that resolves the ambiguity between authoritative knowledge and ephemeral session artifacts; six design invariants empirically grounded in a cross-system landscape analysis; and an experience report demonstrating that the model produces compounding returns when applied to its own development.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#2-the-limits-of-prompt-time-context","level":2,"title":"2. The Limits of Prompt-Time Context","text":"

    Prompt-time assembly pipelines typically consist of corpus selection, retrieval, ranking, and truncation. These pipelines are probabilistic and time-dependent, producing three failure modes that compound over the lifetime of a project.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#21-non-reproducibility","level":3,"title":"2.1 Non-Reproducibility","text":"

    If context is derived from mutable sources using heuristic ranking, identical requests at different times receive different inputs. A developer who asks \"What is our authentication strategy?\" on Tuesday may receive a different context window than the same question on Thursday: Not because the strategy changed, but because the retrieval heuristic surfaced different fragments.

    Reproducibility (the ability to reconstruct the exact inputs that produced a given output) is a foundational property of reliable systems. Its loss in AI-assisted development mirrors the historical evolution from ad-hoc builds to deterministic build systems 1 2. The build community learned that when outputs depend on implicit state (environment variables, system clocks, network-fetched dependencies), debugging becomes archaeology. The same principle applies when AI outputs depend on non-deterministic context retrieval.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#22-opaque-knowledge","level":3,"title":"2.2 Opaque Knowledge","text":"

    Embedding-based memory increases recall but reduces inspectability. When a vector store determines that a code snippet is \"similar\" to the current query, the ranking function is opaque: the developer cannot inspect why that snippet was chosen, whether a more relevant artifact was excluded, or whether the ranking will remain stable. This prevents deterministic debugging, policy auditing, and causal attribution (properties that information retrieval theory identifies as fundamental trade-offs of probabilistic ranking) 3.

    In practice, this opacity manifests as a compliance ceiling. In our experience developing a context management system (detailed in Section 7), soft instructions (directives that ask an AI agent to read specific files or follow specific procedures) achieve approximately 75-85% compliance. The remaining 15-25% represents cases where the agent exercises judgment about whether the instruction applies, effectively applying a second ranking function on top of the explicit directive. When 100% compliance is required, instruction is insufficient; the content must be injected directly, removing the agent's option to skip it.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#23-loss-of-intent","level":3,"title":"2.3 Loss of Intent","text":"

    Session transcripts record interaction but not cognition. A transcript captures what was said but not which assumptions were accepted, which alternatives were rejected, or which constraints governed the decision. The distinction matters: a decision to use PostgreSQL recorded as a one-line note (\"Use PostgreSQL\") teaches a model what was decided; a structured record with context, rationale, and consequences teaches it why (and why is what prevents the model from unknowingly reversing the decision in a future session) 4.

    Session transcripts provide history. Cognitive state requires something more: the persistent, structured representation of the knowledge required for correct decision-making.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#3-cognitive-state-a-three-tier-model","level":2,"title":"3. Cognitive State: A Three-Tier Model","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#31-definitions","level":3,"title":"3.1 Definitions","text":"

    We define cognitive state as the authoritative, persistent representation of the knowledge required for correct decision-making within a project. It is human-authored or human-ratified, versioned, inspectable, and reproducible. It is distinct from logs, transcripts, retrieval results, and model-generated summaries.

    Previous formulations of this idea have treated cognitive state as a monolithic concept. In practice, a three-tier model better captures the operational reality:

    Tier 1: Authoritative State: The canonical knowledge that the system treats as ground truth. In a concrete implementation, this corresponds to a set of human-curated files with defined schemas: a constitution (inviolable rules), conventions (code patterns), an architecture document (system structure), decision records (choices with rationale), learnings (captured experience), a task list (current work), a glossary (domain terminology), and an agent playbook (operating instructions). Each file has a single purpose, a defined lifecycle, and a distinct update frequency. Authoritative state is version-controlled alongside code and reviewed through the same mechanisms (diffs, pull requests, blame annotations).

    Tier 2: Delivery Views: Derived representations of authoritative state, assembled for consumption by a model. A delivery view is produced by a deterministic assembly function that takes the authoritative state, a token budget, and an inclusion policy as inputs and produces a context window as output. The same authoritative state, budget, and policy must always produce the same delivery view. Delivery views are ephemeral (they exist only for the duration of a session), but their construction is reproducible.

    Tier 3: Ephemeral State: Session transcripts, scratchpad notes, draft journal entries, and other artifacts that exist during or immediately after a session but are not authoritative. Ephemeral state is the raw material from which authoritative state may be extracted through human review, but it is never consumed directly by the assembly function.

    This three-tier model resolves confusion present in earlier formulations: the claim that AI output is a deterministic function of the repository state. The corrected claim is that context selection is deterministic (the delivery view is a function of authoritative state), but model output remains stochastic, conditioned on the deterministic context. Formally:

    delivery_view = assemble(authoritative_state, budget, policy)\noutput = model(delivery_view)   # stochastic\n

    The persistence layer's contribution is making assemble reproducible, not making model deterministic.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#32-separation-of-concerns","level":3,"title":"3.2 Separation of Concerns","text":"

    The decision to separate authoritative state into distinct files with distinct purposes is not cosmetic. Different types of knowledge have different lifecycles:

    Knowledge Type Update Frequency Read Frequency Load Priority Example Constitution Rarely Every session Always \"Never commit secrets to git\" Tasks Every session Session start Always \"Implement token budget CLI flag\" Conventions Weekly Before coding High \"All errors use structured logging with severity levels\" Decisions When decided When questioning Medium \"Use PostgreSQL over MySQL (see ADR-003)\" Learnings When learned When stuck Medium \"Hook scripts >50ms degrade interactive UX\" Architecture When changed When designing On demand \"Three-layer pipeline: ingest → enrich → assemble\" Journal Every session Rarely Never auto \"Session 247: Removed dead-end session copy layer\"

    A monolithic context file would force the assembly function to load everything or nothing. Separation enables progressive disclosure: the minimum context that matters for the current moment, with the option to load more when needed. A normal session loads the constitution, tasks, and conventions; a deep investigation loads decision history and journal entries from specific dates.

    The budget mechanism is the constraint that makes separation valuable. Without a budget, the default behavior is to load everything, which destroys the attention density that makes loaded context useful. With a budget, the assembly function must prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings (scored by recency). Entries that do not fit receive title-only summaries rather than being silently dropped (an application of the \"tell me what you don't know\" pattern identified independently by four systems in our landscape analysis).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#4-design-invariants","level":2,"title":"4. Design Invariants","text":"

    The following six invariants define the constraints that a cognitive state persistence layer must satisfy. They are not axioms chosen a priori; they are empirically grounded properties whose violation was independently identified as producing complexity costs across the 17 systems we analyzed.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-1-markdown-on-filesystem-persistence","level":3,"title":"Invariant 1: Markdown-on-Filesystem Persistence","text":"

    Context files must be human-readable, git-diffable, and editable with any text editor. No database. No binary storage.

    Validation: 11 independent rejection decisions across the analyzed landscape protected this property. Systems that adopted embedded records, binary serialization, or knowledge graphs as their core primitive consistently traded away the ability for a developer to run cat DECISIONS.md and understand the system's knowledge. The inspection cost of opaque storage compounds over the lifetime of a project: every debugging session, every audit, every onboarding conversation requires specialized tooling to access knowledge that could have been a text file.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-2-zero-runtime-dependencies","level":3,"title":"Invariant 2: Zero Runtime Dependencies","text":"

    The tool must work with no installed runtimes, no running services, and no API keys for core functionality.

    Validation: 13 independent rejection decisions protected this property (the most frequently defended invariant). Systems that required databases (PostgreSQL, SQLite, Redis), embedding models, server daemons, container runtimes, or cloud APIs for core operation introduced failure modes proportional to their dependency count. A persistence layer that depends on infrastructure is not a persistence layer; it is a service. Services have uptime requirements, version compatibility matrices, and operational costs that simple file operations do not.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-3-deterministic-context-assembly","level":3,"title":"Invariant 3: Deterministic Context Assembly","text":"

    The same files plus the same budget must produce the same output. No embedding-based retrieval, no LLM-driven selection, no wall-clock-dependent scoring in the assembly path.

    Validation: 6 independent rejection decisions protected this property. Non-deterministic assembly (whether from embedding variance, LLM-based selection, or time-dependent scoring) destroys the ability to reproduce a context window and therefore to diagnose why a model produced a given output. Determinism in the assembly path is what makes the persistence layer auditable.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-4-human-authority-over-persistent-state","level":3,"title":"Invariant 4: Human Authority over Persistent State","text":"

    The agent may propose changes to context files but must not unilaterally modify them. All persistent changes go through human-reviewable git commits.

    Validation: 6 independent rejection decisions protected this property. Systems that allowed agents to self-modify their memory (writing freeform notes, auto-pruning old entries, generating summaries as ground truth) consistently produced lower-quality persistent context than systems that enforced human review. Structure is a feature, not a limitation: across the landscape, the pattern \"structured beats freeform\" was independently discovered by four systems that evolved from freeform LLM summaries to typed schemas with required fields.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-5-local-first-air-gap-capable","level":3,"title":"Invariant 5: Local-First, Air-Gap Capable","text":"

    Core functionality must work offline with no network access. Cloud services may be used for optional features but never for core context management.

    Validation: 7 independent rejection decisions protected this property. Infrastructure-dependent memory systems cannot operate in classified environments, isolated networks, or disaster-recovery scenarios. A filesystem-native model continues to function under all conditions where the repository is accessible.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-6-no-default-telemetry","level":3,"title":"Invariant 6: No Default Telemetry","text":"

    Any analytics, if ever added, must be strictly opt-in.

    Validation: 4 independent rejection decisions protected this property. Default telemetry erodes the trust model that a persistence layer depends on. If developers must trust the system with their architectural decisions, operational learnings, and project constraints, the system cannot simultaneously be reporting usage data to external services.

    These six invariants collectively define a design space. Each feature proposal can be evaluated against them: a feature that violates any invariant is rejected regardless of how many other systems implement it. The discipline of constraint (refusing to add capabilities that compromise foundational properties) is itself an architectural contribution. Across the 17 analyzed systems, 56 patterns were explicitly rejected for violating these invariants. The rejection count per invariant (11, 13, 6, 6, 7, 4) provides a rough measure of each property's vulnerability to architectural erosion. A representative sample of these rejections is provided in Appendix A.1

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#5-landscape-analysis","level":2,"title":"5. Landscape Analysis","text":"

    The 17 systems were selected to cover the architectural design space rather than to achieve completeness. Each included system satisfies three criteria: it represents a distinct architectural primitive for AI-assisted development, it is actively maintained or widely referenced, and it provides sufficient public documentation or source code for architectural inspection. The goal was to ensure that every major category of primitive (document, embedded record, state snapshot, event/message, construction/derivation) was represented by multiple systems, enabling cross-system pattern detection.

    The resulting set spans six categories: AI coding assistants (Continue, Sourcegraph/Cody, Aider, Claude Code), AI agent frameworks (CrewAI, AutoGen, LangGraph, LlamaIndex, Letta/MemGPT), autonomous coding agents (OpenHands, Sweep), session provenance tools (Entire), data versioning systems (Dolt, Pachyderm), pipeline/build systems (Dagger), and purpose-built knowledge stores (QubicDB, Kindex). Each system was analyzed from its source code and documentation, producing 34 individual analysis artifacts (an architectural profile and a set of insights per system) that yielded 87 adopt/adapt recommendations, 56 explicit rejection decisions, and 52 watch items.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#51-primitive-taxonomy","level":3,"title":"5.1 Primitive Taxonomy","text":"

    Every system in the AI-assisted development landscape operates on a core primitive: an atomic unit around which the entire architecture revolves. Our analysis of 17 systems reveals five categories of primitives, each making irrecoverable trade-offs:

    Group A: Document/File Primitives: Human-readable documents as the primary unit. Documents are authored by humans, version-controlled in git, and consumed by AI tools. The invariant of this group is that the primitive is always human-readable and version-controllable with standard tools. Three systems participate in this pattern: the system described in this paper as a pure expression, and Continue (via its rules directory) and Claude Code (via CLAUDE.md files) as partial participants: both use document-based context as an input but organize around different core primitives.

    Group B: Embedded Record Primitives: Vector-embedded records stored with numerical embeddings for similarity search, metadata for filtering, and scoring mechanisms for ranking. Five systems use this approach (LlamaIndex, CrewAI, Letta/MemGPT, QubicDB, Kindex). The invariant is that the primitive requires an embedding model or vector database for core operations: a dependency that precludes offline and air-gapped use.

    Group C: State Snapshot Primitives: Point-in-time captures of the complete system state. The invariant is that any past state can be reconstructed at any historical point. Three systems use this approach (LangGraph, Entire, Dolt).

    Group D: Event/Message Primitives: Sequential events or messages forming an append-only log with causal relationships. Four systems use this approach (OpenHands, AutoGen, Claude Code, Sweep). The invariant is temporal ordering and append-only semantics.

    Group E: Construction/Derivation Primitives: Derived or constructed values that encode how they were produced. The invariant is that the primitive is a function of its inputs; re-executing the same inputs produces the same primitive. Three systems use this approach (Dagger, Pachyderm, Aider).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#52-comparison-matrix","level":3,"title":"5.2 Comparison Matrix","text":"

    The five primitive categories differ along seven dimensions:

    Property Document Embedded Record State Snapshot Event/Message Construction Human-readable Yes No Varies Partially No Version-controllable Yes No Varies Yes Yes Queryable by meaning No Yes No No No Rewindable Via git No Yes Yes (replay) Yes Deterministic Yes No Yes Yes Yes Zero-dependency Yes No Varies Varies Varies Offline-capable Yes No Varies Varies Yes

    The document primitive is the only one that simultaneously satisfies human-readability, version-controllability, determinism, zero dependencies, and offline capability. This is not because documents are superior in general (embedded records provide semantic queryability that documents lack) but because the combination of all five properties is what the persistence layer requires. The choice between primitive categories is not a matter of capability but of which properties are considered invariant.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#53-convergent-patterns","level":3,"title":"5.3 Convergent Patterns","text":"

    Across the 17 analyzed systems, six design patterns were independently discovered. These convergent patterns carry extra validation weight because they emerged from different problem spaces:

    Pattern 1: \"Tell me what you don't know\": When context is incomplete, explicitly communicate to the model what information is missing and what confidence level the provided context represents. Four systems independently converged on this pattern: inserting skip markers, tracking evidence gaps, annotating provenance, or naming output quality tiers.

    Pattern 2: \"Freshness matters\": Information relevance decreases over time. Three systems independently chose exponential decay with different half-lives (30 days, 90 days, and LRU ordering). Static priority ordering with no time dimension leaves relevant recent knowledge at the same priority as stale entries. This pattern is in productive tension with the persistence model's emphasis on determinism: the claim is not that time-dependence is irrelevant, but that it belongs in the curation step (a human deciding to consolidate or archive stale entries) rather than in the assembly function (an algorithm silently down-ranking entries based on age).

    Pattern 3: \"Content-address everything\": Compute a hash of content at creation time for deduplication, cache invalidation, integrity verification, and change detection. Five systems independently implement content hashing, each discovering it solves different problems 5.

    Pattern 4: \"Structured beats freeform\": When capturing knowledge or session state, a structured schema with required fields produces more useful data than freeform text. Four systems evolved from freeform summaries to typed schemas: one moving from LLM-generated prose to a structured condenser with explicit fields for completed tasks, pending tasks, and files modified.

    Pattern 5: \"Protocol convergence\": The Model Context Protocol (MCP) is emerging as a standard tool integration layer. Nine of 17 systems support it, spanning every category in the analysis. MCP's significance for the persistence model is that it provides a transport mechanism for context delivery without dictating how context is stored or assembled. This makes the approach compatible with both retrieval-centric and persistence-centric architectures.

    Pattern 6: \"Human-in-the-loop for memory\": Critical memory decisions should involve human judgment. Fully automated memory management produces lower-quality persistent context than human-reviewed systems. Four systems independently converged on variants of this pattern: ceremony-based consolidation, interrupt/resume for human input, confirmation mode for high-risk actions, and separated \"think fast\" vs. \"think slow\" processing paths.

    Pattern 6 directly validates the ceremony model described in this paper. The persistence layer requires human curation not because automation is impossible, but because the quality of persistent knowledge degrades when the curation step is removed. The improvement opportunity is to make curation easier, not to automate it away.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#6-worked-example-architectural-decision-under-two-models","level":2,"title":"6. Worked Example: Architectural Decision under Two Models","text":"

    We now instantiate the three-tier model in a concrete system (ctx) and illustrate the difference between prompt-time retrieval and cognitive state persistence using a real scenario from its development.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#61-the-problem","level":3,"title":"6.1 The Problem","text":"

    During development, the system accumulated three overlapping storage layers for session data: raw transcripts (owned by the AI tool), session copies (JSONL copies plus context snapshots), and enriched journal entries (Markdown summaries). The middle layer (session copies) was a dead-end write sink. An auto-save hook copied transcripts to a directory that nothing read from, because the journal pipeline already read directly from the raw transcripts. Approximately 15 source files, a shell hook, 20 configuration constants, and 30 documentation references supported infrastructure with no consumers.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#62-prompt-time-retrieval-model","level":3,"title":"6.2 Prompt-Time Retrieval Model","text":"

    In a retrieval-based system, the decision to remove the middle layer depends on whether the retrieval function surfaces the relevant context:

    The developer asks: \"Should we simplify the session storage?\" The retrieval system must find and rank the original discussion thread where the three layers were designed, the usage statistics showing zero reads from the middle layer, the journal pipeline documentation showing it reads from raw transcripts directly, and the dependency analysis showing 15 files, a hook, and 30 doc references. If any of these fragments are not retrieved (because they are in old chat history, because the embedding similarity score is low, or because the token budget was consumed by more recent but less relevant context), the model may recommend preserving the middle layer, or may not realize it exists.

    Six months later, a new team member asks the same question. The retrieval results will differ: the original discussion has aged out of recency scoring, the usage statistics are no longer in recent history, and the model may re-derive the answer or arrive at a different conclusion.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#63-cognitive-state-model","level":3,"title":"6.3 Cognitive State Model","text":"

    In the persistence model, the decision is recorded as a structured artifact at write time:

    ## [2026-02-11] Remove .context/sessions/ storage layer\n\n**Status**: Accepted\n\n**Context**: The session/recall/journal system had three overlapping\nstorage layers. The recall pipeline reads directly from raw transcripts,\nmaking .context/sessions/ a dead-end write sink that nothing reads from.\n\n**Decision**: Remove .context/sessions/ entirely. Two stores remain:\nraw transcripts (global, tool-owned) and enriched journal\n(project-local).\n\n**Rationale**: Dead-end write sinks waste code surface, maintenance\neffort, and user attention. The recall pipeline already proved that\nreading directly from raw transcripts is sufficient. Context snapshots\nare redundant with git history.\n\n**Consequence**: Deleted internal/cli/session/ (15 files), removed\nauto-save hook, removed --auto-save from watch, removed pre-compact\nauto-save, removed /ctx-save skill, updated ~45 documentation files.\nFour earlier decisions superseded.\n

    This artifact is:

    • Deterministically included in every subsequent session's delivery view (budget permitting, with title-only fallback if budget is exceeded)
    • Human-readable and reviewable as a diff in the commit that introduced it
    • Permanent: it persists in version control regardless of retrieval heuristics
    • Causally linked: it explicitly supersedes four earlier decisions, creating an auditable chain

    When the new team member asks \"Why don't we store session copies?\" six months later, the answer is the same artifact, at the same revision, with the same rationale. The reasoning is reconstructible because it was persisted at write time, not discovered at query time.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#64-the-diff-when-policy-changes","level":3,"title":"6.4 The Diff When Policy Changes","text":"

    If a future requirement re-introduces session storage (for example, to support multi-agent session correlation), the change appears as a diff to the decision record:

    - **Status**: Accepted\n+ **Status**: Superseded by [2026-08-15] Reintroduce session storage\n+ for multi-agent correlation\n

    The new decision record references the old one, creating a chain of reasoning visible in git log. In the retrieval model, the old decision would simply be ranked lower over time and eventually forgotten.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#7-experience-report-a-system-that-designed-itself","level":2,"title":"7. Experience Report: A System That Designed Itself","text":"

    The persistence model described in this paper was developed and tested by using it on its own development. Over 33 days and 389 sessions, the system's context files accumulated a detailed record of decisions made, reversed, and consolidated: providing quantitative and qualitative evidence for the model's properties.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#71-scale-and-structure","level":3,"title":"7.1 Scale and Structure","text":"

    The development produced the following authoritative state artifacts:

    • 8 consolidated decision records covering 24 original decisions spanning context injection architecture, hook design, task management, security, agent autonomy, and webhook systems
    • 18 consolidated learning records covering 75 original observations spanning agent compliance, hook behavior, testing patterns, documentation drift, and tool integration
    • A constitution with 13 inviolable rules across 4 categories (security, quality, process, context preservation)
    • 389 enriched journal entries providing a complete session-level audit trail

    The consolidation ratio (24 decisions compressed to 8 records, 75 learnings compressed to 18) illustrates the curation cost and its return: authoritative state becomes denser and more useful over time as related entries are merged, contradictions are resolved, and superseded decisions are marked.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#72-architectural-reversals","level":3,"title":"7.2 Architectural Reversals","text":"

    Three architectural reversals during development provide evidence that the persistence model captures and communicates reasoning effectively:

    Reversal 1: The two-tier persistence model: The original design included a middle storage tier for session copies. After 21 days of development, the middle tier was identified as a dead-end write sink (described in Section 6). The decision record captured the full context, and the removal was executed cleanly: 15 source files, a shell hook, and 45 documentation references. The pattern of a \"dead-end write sink\" was subsequently observed in 7 of 17 systems in our landscape analysis that store raw transcripts alongside structured context.

    Reversal 2: The prompt-coach hook: An early design included a hook that analyzed user prompts and offered improvement suggestions. After deployment, the hook produced zero useful tips, its output channel was invisible to users, and it accumulated orphan temporary files. The hook was removed, and the decision record captured the failure mode for future reference.

    Reversal 3: The soft-instruction compliance model: The original context injection strategy relied on soft instructions: directives asking the AI agent to read specific files. After measuring compliance across multiple sessions, we found a consistent 75-85% compliance ceiling. The revised strategy injects content directly, bypassing the agent's judgment about whether to comply. The learning record captures the ceiling measurement and the rationale for the architectural change.

    Each reversal was captured as a structured decision record with context, rationale, and consequences. In a retrieval-based system, these reversals would exist only in chat history, discoverable only if the retrieval function happens to surface them. In the persistence model, they are permanent, indexable artifacts that inform future decisions.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#73-compliance-ceiling","level":3,"title":"7.3 Compliance Ceiling","text":"

    The 75-85% compliance ceiling for soft instructions is the most operationally significant finding from the experience report. It means that any context management strategy relying on agent compliance with instructions (\"read this file,\" \"follow this convention,\" \"check this list\") has a hard ceiling on reliability.

    The root cause is structural: the instruction \"don't apply judgment\" is itself evaluated by judgment. When an agent receives a directive to read a file, it first assesses whether the directive is relevant to the current task (and that assessment is the judgment the directive was trying to prevent).

    The architectural response maps directly to the formal model defined in Section 3.1. Content requiring 100% compliance is included in authoritative_state and injected by the deterministic assemble function, bypassing the agent entirely. Content where 80% compliance is acceptable is delivered as instructions within the delivery view. The three-tier architecture makes this distinction explicit: authoritative state is injected; delivery views are assembled deterministically; ephemeral state is available but not pushed.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#74-compounding-returns","level":3,"title":"7.4 Compounding Returns","text":"

    Over 33 days, we observed a qualitative shift in the development experience. Early sessions (days 1-7) spent significant time re-establishing context: explaining conventions, re-stating constraints, re-deriving past decisions. Later sessions (days 25-33) began with the agent loading curated context and immediately operating within established constraints, because the constraints were in files rather than in chat history.

    This compounding effect (where each session's context curation improves all subsequent sessions) is the primary return on the curation investment. The cost is borne once (writing a decision record, capturing a learning, updating the task list); the benefit is collected on every subsequent session load.

    The effect is analogous to compound interest in financial systems: the knowledge base grows not linearly with effort but with increasing marginal returns as new knowledge interacts with existing context. A learning captured on day 5 prevents a mistake on day 12, which avoids a debugging session that would have consumed a day 12 session, freeing that session for productive work that generates new learnings. The growth is not literally exponential (it is bounded by project scope and subject to diminishing returns as the knowledge base matures), but within the observed 33-day window, the returns were consistently accelerating.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#75-scope-and-generalizability","level":3,"title":"7.5 Scope and Generalizability","text":"

    This experience report is self-referential by design: the system was developed using its own persistence model. This circularity strengthens the internal validity of the findings (the model was stress-tested under authentic conditions) but limits external generalizability. The two-week crossover point was observed on a single project of moderate complexity with a small team already familiar with the model's assumptions. Whether the same crossover holds for larger teams, for codebases with different characteristics, or for teams adopting the model without having designed it remains an open empirical question. The quantitative claims in this section should be read as existence proofs (demonstrating that the model can produce compounding returns) rather than as predictions about specific adoption scenarios.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#8-situating-the-persistence-layer","level":2,"title":"8. Situating the Persistence Layer","text":"

    The persistence layer occupies a specific position in the stack of AI-assisted development:

    Application Logic\nAI Interaction / Agents\nContext Retrieval Systems\nCognitive State Persistence Layer\nVersion Control / Storage\n

    Current systems innovate primarily in the retrieval layer (improving how context is discovered, ranked, and delivered at query time). The persistence layer sits beneath retrieval and above version control. Its role is to maintain the authoritative state that retrieval systems may query but do not own. The relationship is complementary: retrieval answers \"What in the corpus might be relevant?\"; cognitive state answers \"What must be true for this system to operate correctly?\" A mature system uses both: retrieval for discovery, persistence for authority.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#9-applicability-and-trade-offs","level":2,"title":"9. Applicability and Trade-Offs","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#91-when-to-use-this-model","level":3,"title":"9.1 When to Use This Model","text":"

    A cognitive state persistence layer is most appropriate when:

    Reproducibility is a requirement: If a system must be able to answer \"Why did this output occur, and can it be produced again?\" then deterministic, version-controlled context becomes necessary. This is relevant in regulated environments, safety-critical systems, long-lived infrastructure, and security-sensitive deployments.

    Knowledge must outlive sessions and individuals: Projects with multi-year lifetimes accumulate architectural decisions, domain interpretations, and operational policy. If this knowledge is stored only in chat history, issue trackers, and institutional memory, it decays. The persistence model converts implicit knowledge into branchable, reviewable artifacts.

    Teams require shared cognitive authority: In collaborative environments, correctness depends on a stable answer to \"What does the system believe to be true?\" When this answer is derived from retrieval heuristics, authority shifts to ranking algorithms. When it is versioned and human-readable, authority remains with the team.

    Offline or air-gapped operation is required: Infrastructure-dependent memory systems cannot operate in classified environments, isolated networks, or disaster-recovery scenarios.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#92-when-not-to-use-this-model","level":3,"title":"9.2 When Not to Use This Model","text":"

    Zero-configuration personal workflows: For short-lived or exploratory tasks, the cost of explicit knowledge curation outweighs its benefits. Heuristic retrieval is sufficient when correctness is non-critical, outputs are disposable, and historical reconstruction is unnecessary.

    Maximum automatic recall from large corpora: Vector retrieval systems provide superior performance when the primary task is searching vast, weakly structured information spaces. The persistence model assumes that what matters can be decided and that this decision is valuable to record.

    Fully autonomous agent architectures: Agent runtimes that generate and discard state continuously, optimizing for local goal completion, do not benefit from a model that centers human ratification of knowledge.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#93-incremental-adoption","level":3,"title":"9.3 Incremental Adoption","text":"

    The transition does not require full system replacement. An incremental path:

    Step 1: Record decisions as versioned artifacts: Instead of allowing conclusions to remain in discussion threads, persist them in reviewable form with context, rationale, and consequences 4. This alone converts ephemeral reasoning into the cognitive state.

    Step 2: Make inclusion deterministic: Define explicit assembly rules. Retrieval may still exist, but it is no longer authoritative.

    Step 3: Move policy into cognitive state: When system behavior depends on stable constraints, encode those constraints as versioned knowledge. Behavior becomes reproducible.

    Step 4: Optimize assembly, not retrieval: Once the authoritative layer exists, performance improvements come from budgeting, caching, and structural refinement rather than from improving ranking heuristics.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#94-the-curation-cost","level":3,"title":"9.4 The Curation Cost","text":"

    The primary objection to this model is the cost of explicit knowledge curation. This cost is real. Writing a structured decision record takes longer than letting a chatbot auto-summarize a conversation. Maintaining a glossary requires discipline. Consolidating 75 learnings into 18 records requires judgment.

    The response is not that the cost is negligible but that it is amortized. A decision record written once is loaded hundreds of times. A learning captured today prevents repeated mistakes across all future sessions. The curation cost is paid once; the benefit compounds.

    The experience report provides rough order-of-magnitude numbers. Across 389 sessions over 33 days, curation activities (writing decision records, capturing learnings, updating the task list, consolidating entries) averaged approximately 3-5 minutes per session. In early sessions (days 1-7), before curated context existed, re-establishing context consumed approximately 10-15 minutes per session: re-explaining conventions, re-stating architectural constraints, re-deriving decisions that had been made but not persisted. By the final week (days 25-33), the re-explanation overhead had dropped to near zero: the agent loaded curated context and began productive work immediately.

    At ~12 sessions per day, the curation cost was roughly 35-60 minutes daily. The re-explanation cost in the first week was roughly 120-180 minutes daily. By the third week, that cost had fallen to under 15 minutes daily while the curation cost remained stable. The crossover (where cumulative curation cost was exceeded by cumulative time saved) occurred around day 10. These figures are approximate and derived from a single project with a small team already familiar with the model; the crossover point will vary with project complexity, team size, and curation discipline.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#10-future-work","level":2,"title":"10. Future Work","text":"

    Several directions are compatible with the model described here:

    Section-level deterministic budgeting: Current assembly operates at file granularity. Section-level budgeting would allow finer-grained control (including specific decision records while excluding others within the same file) without sacrificing determinism.

    Causal links between decisions: The experience report shows that decisions frequently reference earlier decisions (superseding, extending, or qualifying them). Formal causal links would enable traversal of the decision graph and automatic detection of orphaned or contradictory constraints.

    Content-addressed context caches: Five systems in our landscape analysis independently discovered that content hashing provides cache invalidation, integrity verification, and change detection. Applying content addressing to the assembly output would enable efficient cache reuse when the authoritative state has not changed.

    Conditional context inclusion: Five systems independently suggest that context entries could carry activation conditions (file patterns, task keywords, or explicit triggers) that control whether they are included in a given assembly. This would reduce the per-session budget cost of large knowledge bases without sacrificing determinism.

    Provenance metadata: Linking context entries to the sessions, decisions, or learnings that motivated them would strengthen the audit trail. Optional provenance fields on Markdown entries (session identifier, cause reference, motivation) would be lightweight and compatible with the existing file-based model.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#11-conclusion","level":2,"title":"11. Conclusion","text":"

    AI-assisted development has treated context as a \"query result\" assembled at the moment of interaction, discarded at the session end. This paper identifies a complementary layer: the persistence of authoritative cognitive state as deterministic, version-controlled artifacts.

    The contribution is grounded in three sources of evidence. A landscape analysis of 17 systems reveals five categories of primitives and shows that no existing system provides the combination of human-readability, determinism, zero dependencies, and offline capability that the persistence layer requires. Six design invariants, validated by 56 independent rejection decisions, define the constraints of the design space. An experience report over 389 sessions and 33 days demonstrates compounding returns: later sessions start faster, decisions are not re-derived, and architectural reversals are captured with full context.

    The core claim is this: persistent cognitive state enables causal reasoning across time. A system built on this model can explain not only what is true, but why it became true and when it changed.

    When context is the state:

    • Reasoning is reproducible: the same authoritative state, budget, and policy produce the same delivery view.
    • Knowledge is auditable: decisions are traceable to explicit artifacts with context, rationale, and consequences.
    • Understanding compounds: each session's curation improves all subsequent sessions.

    The choice between retrieval-centric workflows and a persistence layer is not a matter of capability but of time horizon. Retrieval optimizes for relevance at the moment of interaction. Persistence optimizes for the durability of understanding across the lifetime of a project.

    🐸🖤 \"Gooood... let the deterministic context flow through the repository...\" - Kermit the Sidious, probably

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#appendix-a-representative-rejection-decisions","level":2,"title":"Appendix A: Representative Rejection Decisions","text":"

    The 56 rejection decisions referenced in Section 4 were cataloged across all 17 system analyses, grouped by the invariant they would violate. This appendix provides a representative sample (two per invariant) to illustrate the methodology.

    Invariant 1: Markdown-on-Filesystem (11 rejections): CrewAI's vector embedding storage was rejected because embeddings are not human-readable, not git-diff-friendly, and require external services. Kindex's knowledge graph as core primitive was rejected because it requires specialized commands to inspect content that could be a text file (kin show <id> vs. cat DECISIONS.md).

    Invariant 2: Zero Runtime Dependencies (13 rejections): Letta/MemGPT's PostgreSQL-backed architecture was rejected because it conflicts with local-first, no-database, single-binary operation. Pachyderm's Kubernetes-based distributed architecture was rejected as the antithesis of a single-binary design for a tool that manages text files.

    Invariant 3: Deterministic Assembly (6 rejections): LlamaIndex's embedding-based retrieval as the primary selection mechanism was rejected because it destroys determinism, requires an embedding model, and removes human judgment from the selection process. QubicDB's wall-clock-dependent scoring was rejected because it directly conflicts with the \"same inputs produce same output\" property.

    Invariant 4: Human Authority (6 rejections): Letta/MemGPT's agent self-modification of memory was rejected as fundamentally opposed to human-curated persistence. Claude Code's unstructured auto-memory (where the agent writes freeform notes) was rejected because structured files with defined schemas produce higher-quality persistent context than unconstrained agent output.

    Invariant 5: Local-First / Air-Gap Capable (7 rejections): Sweep's cloud-dependent architecture was rejected as fundamentally incompatible with the local-first, offline-capable model. LangGraph's managed cloud deployment was rejected because cloud dependencies for core functionality violate air-gap capability.

    Invariant 6: No Default Telemetry (4 rejections): Continue's telemetry-by-default (PostHog) was rejected because it contradicts the local-first, privacy-respecting trust model. CrewAI's global telemetry on import (Scarf tracking pixel) was rejected because it violates user trust and breaks air-gap capability.

    The remaining 9 rejections did not map to a specific invariant but were rejected on other architectural grounds: for example, Aider's full-file-content-in-context approach (which defeats token budgeting), AutoGen's multi-agent orchestration as core primitive (scope creep), and Claude Code's 30-day transcript retention limit (institutional knowledge should have no automatic expiration).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#references","level":2,"title":"References","text":"
    1. Reproducible Builds Project, \"Reproducible Builds: Increasing the Integrity of Software Supply Chains\", 2017. https://reproducible-builds.org/docs/definition/ ↩↩↩

    2. S. McIntosh et al., \"The Impact of Build System Evolution on Software Quality\", ICSE, 2015. https://doi.org/10.1109/ICSE.2015.70 ↩

    3. C. Manning, P. Raghavan, H. Schütze, Introduction to Information Retrieval, Cambridge University Press, 2008. https://nlp.stanford.edu/IR-book/ ↩

    4. M. Nygard, \"Documenting Architecture Decisions\", Cognitect Blog, 2011. https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions ↩↩

    5. L. Torvalds et al., Git Internals - Git Objects (content-addressed storage concepts). https://git-scm.com/book/en/v2/Git-Internals-Git-Objects ↩

    6. Kief Morris, Infrastructure as Code, O'Reilly, 2016. ↩

    7. J. Kreps, \"The Log: What every software engineer should know about real-time data's unifying abstraction\", 2013. https://engineering.linkedin.com/distributed-systems/log ↩

    8. P. Hunt et al., \"ZooKeeper: Wait-free coordination for Internet-scale systems\", USENIX ATC, 2010. https://www.usenix.org/legacy/event/atc10/tech/full_papers/Hunt.pdf ↩

    ","path":["The Thesis"],"tags":[]}]} \ No newline at end of file +{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Manifesto","text":"","path":["Manifesto"],"tags":[]},{"location":"#the-ctx-manifesto","level":1,"title":"The ctx Manifesto","text":"

    Creation, not code.

    Context, not prompts.

    Verification, not vibes.

    This Is NOT a Metaphor

    Code executes instructions.

    Creation produces outcomes.

    Confusing the two is how teams ship motion...

    ...instead of progress.

    • It was never about the code.
    • Code has zero standalone value.
    • Code is an implementation detail.

    Code is an incantation.

    Creation is the act.

    And creation does not happen in a vacuum.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-the-substrate","level":2,"title":"ctx Is the Substrate","text":"

    Constraints Have Moved

    Human bandwidth is no longer the limiting factor.

    Context integrity is.

    Human bandwidth is no longer the constraint.

    Context is:

    • Without durable context, intelligence resets.
    • Without memory, reasoning decays.
    • Without structure, scale collapses.

    Creation is now limited by:

    • Clarity of intent;
    • Quality of context;
    • Rigor of verification.

    Not by speed.

    Not by capacity.

    Velocity Amplifies

    Faster execution on broken context compounds error.

    Speed multiplies whatever is already wrong.

    ","path":["Manifesto"],"tags":[]},{"location":"#humans-author-meaning","level":2,"title":"Humans Author Meaning","text":"

    Intent Is Authored

    Systems can optimize.

    Models can generalize.

    Meaning must be chosen.

    Intent is not emergent.

    Vision, goals, and direction are human responsibilities.

    We decide:

    • What matters;
    • What success means;
    • What world we are building.

    ctx encodes the intent so it...

    • survives time,
    • survives handoffs,
    • survives scale.

    Nothing important should live only in conversation.

    Nothing critical should depend on recall.

    Oral Tradition Does Not Scale

    If intent cannot be inspected, it cannot be enforced.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-before-action","level":2,"title":"ctx Before Action","text":"

    Orientation Precedes Motion

    Acting first and understanding later is not bravery.

    It is debt.

    Never act without ctx.

    Before execution, we must verify:

    • Where we are;
    • Why we are here;
    • What constraints apply;
    • What assumptions are active.

    Action without ctx is gambling.

    Speed without orientation is noise.

    ctx is not overhead: It is the cost of correctness.

    ","path":["Manifesto"],"tags":[]},{"location":"#persistent-context-beats-prompt-memory","level":2,"title":"Persistent Context Beats Prompt Memory","text":"

    Transience Is the Default Failure Mode

    • Prompts decay.
    • Chats fragment.
    • Memory heuristics drift.

    Prompts are transient.

    Chats are lossy.

    Memory heuristics drift.

    ctx must be:

    • Durable;
    • Structured;
    • Explicit;
    • Queryable.

    Intent Must Be Intentional

    If intent exists only in a prompt...

    ...alignment is already degrading.

    Knowledge lives in the artifacts:

    • Decisions;
    • Documentation;
    • Dependency maps;
    • Evaluation history.

    Artifacts Outlive Sessions

    What is not written will be re-learned.

    At full cost.

    ","path":["Manifesto"],"tags":[]},{"location":"#what-ctx-is-not","level":2,"title":"What ctx Is Not","text":"

    Avoid Category Errors

    Mislabeling ctx guarantees misuse.

    ctx is not a memory feature.

    • ctx is not prompt engineering.
    • ctx is not a productivity hack.
    • ctx is not automation theater.

    ctx is a system for preserving intent under scale.

    ctx is infrastructure.

    ","path":["Manifesto"],"tags":[]},{"location":"#verified-reality-is-the-scoreboard","level":2,"title":"Verified Reality Is the Scoreboard","text":"

    Activity Is a False Proxy

    Output volume correlates poorly with impact.

    • Code is not progress.
    • Activity is not impact.

    The only truth that compounds is verified change.

    Verified change must exist in the real world.

    Hypotheses are cheap; outcomes are not.

    ctx captures:

    • What we expected;
    • What we observed;
    • Where reality diverged.

    If we cannot predict, measure, and verify the result...

    ...it does not count.

    ","path":["Manifesto"],"tags":[]},{"location":"#build-to-learn-not-to-accumulate","level":2,"title":"Build to Learn, Not to Accumulate","text":"

    Prototypes Have an Expiration Date

    A prototype's value is information, not longevity.

    Prototypes exist to reduce uncertainty.

    We build to:

    • Test assumptions;
    • Validate architecture;
    • Answer specific questions.

    Not everything.

    Not blindly.

    Not permanently.

    ctx records archeology so the cost is paid once.

    ","path":["Manifesto"],"tags":[]},{"location":"#failures-are-assets","level":2,"title":"Failures Are Assets","text":"

    Failure without Capture Is Waste

    Pain that does not teach is pure loss.

    Failures are not erased: They are preserved.

    Each failure becomes:

    • A documented hypothesis;
    • An analyzed deviation;
    • A permanent artifact.

    Rollback fixes symptoms: ctx fixes systems.

    A repeated mistake is a missing ctx artifact.

    ","path":["Manifesto"],"tags":[]},{"location":"#structure-enables-scale","level":2,"title":"Structure Enables Scale","text":"

    Unbounded Autonomy Destabilizes

    Power without a structure produces chaos.

    Transpose it:

    Power without any structure becomes chaos.

    ctx defines:

    • Roles;
    • Boundaries;
    • Protocols;
    • Escalation paths;
    • Decision rights.

    Ambiguity is a system failure:

    • Debates must be structured.
    • Decisions must be explicit.
    • History must be retained.
    ","path":["Manifesto"],"tags":[]},{"location":"#encode-intent-into-the-environment","level":2,"title":"Encode Intent into the Environment","text":"

    Goodwill Does Not Belong to the Table

    Alignment that depends on memory will drift.

    Alignment cannot depend on memory or goodwill.

    Do not rely on people to remember.

    Encode the behavior, so it happens by default.

    Intent is encoded as:

    • Policies;
    • Schemas;
    • Constraints;
    • Evaluation harnesses.

    Rules must be machine-readable.

    Laws must be enforceable.

    If intent is implicit, drift is guaranteed.

    ","path":["Manifesto"],"tags":[]},{"location":"#cost-is-a-first-class-signal","level":2,"title":"Cost Is a First-Class Signal","text":"

    Attention Is the Scarcest Resource

    Not ideas.

    Not ambition.

    Ideas do not compete on time:

    They compete on cost and impact:

    • Attention is finite.
    • Compute is finite.
    • Context is expensive.

    We continuously ask:

    • What the most valuable next action is.
    • What outcome justifies the cost.

    ctx guides allocation.

    Learning reshapes priority.

    ","path":["Manifesto"],"tags":[]},{"location":"#show-the-why","level":2,"title":"Show the Why","text":"

    {} (code, artifacts, apps, binaries) produce outputs; they do not preserve reasoning.

    Systems that cannot explain themselves will not be trusted.

    Traceability builds trust.

         {} --> what\n\n    ctx --> why\n

    We record:

    • Explored paths;
    • Rejected options;
    • Assumptions made;
    • Evidence used.

    Opaque systems erode trust:

    Transparent ctx compounds understanding.

    ","path":["Manifesto"],"tags":[]},{"location":"#continuously-verify-the-system","level":2,"title":"Continuously Verify the System","text":"

    Stability Is Temporary

    Every assumption has a half-life:

    • Models drift.
    • Tools change.
    • Assumptions rot.

    ctx must be verified against reality.

    Trust is a spectrum.

    Trust is continuously re-earned:

    • Benchmarks,
    • regressions,
    • and evaluations...

    ...are safety rails.

    ","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-leverage","level":2,"title":"ctx Is Leverage","text":"

    Humans Are Decision Engines

    Execution should not consume judgment.

    Humans must not be typists.

    We are the authors.

    Human effort is reserved for:

    • Judgment;
    • Design;
    • Taste;
    • Synthesis.

    Repetition is delegated.

    Toil is automated.

    ctx preserves leverage across time.

    ","path":["Manifesto"],"tags":[]},{"location":"#the-thesis","level":2,"title":"The Thesis","text":"

    Invariant

    Everything else is an implementation detail.

    • Creation is the act.
    • ctx is the substrate.
    • Verification is the truth.

    Code executes → Models reason → Agents amplify.

    ctx lives on.

    • Without ctx, intelligence resets.
    • With ctx, creation compounds.
    ","path":["Manifesto"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"

    Stories, insights, and lessons learned from building and using ctx.

    ","path":["Blog"],"tags":[]},{"location":"blog/#releases","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v080-the-architecture-release","level":3,"title":"ctx v0.8.0: The Architecture Release","text":"

    March 23, 2026: 374 commits, 1,708 Go files touched, and a near-complete architectural overhaul. Every CLI package restructured into cmd/ + core/ taxonomy, all user-facing strings externalized to YAML, MCP server for tool-agnostic AI integration, and the memory bridge connecting Claude Code's auto-memory to .context/.

    Topics: release, architecture, refactoring, MCP, localization

    ","path":["Blog"],"tags":[]},{"location":"blog/#field-notes","level":2,"title":"Field Notes","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-watermelon-rind-anti-pattern-why-smarter-tools-make-shallower-agents","level":3,"title":"The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents","text":"

    April 6, 2026: Give an agent a graph query tool, and it produces output that's structurally correct but substantively hollow (the watermelon-rind antipattern: We ran three sessions analyzing the same codebase with different tool access: the one with no tools produced 5.2x more depth. The fix: a two-pass compiler for architecture understanding: force code reading first, verify with tools second. Constraint is the feature.

    Topics: architecture, code intelligence, agent behavior, design patterns, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#code-structure-as-an-agent-interface-what-19-ast-tests-taught-us","level":3,"title":"Code Structure as an Agent Interface: What 19 AST Tests Taught Us","text":"

    April 2, 2026: We built 19 AST-based audit tests in a single session, touching 300+ files. In the process we discovered that \"old-school\" code quality constraints (no magic numbers, centralized error handling, 80-char lines, documentation) are exactly the constraints that make code readable to AI agents. If an agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.

    Topics: ast, code quality, agent readability, conventions, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#we-broke-the-31-rule","level":3,"title":"We Broke the 3:1 Rule","text":"

    March 23, 2026: After v0.6.0, we ran 198 feature commits across 17 days before consolidating. The 3:1 rule says consolidate every 4th session. We did it after the 66th. The result: an 18-day, 181-commit cleanup marathon that took longer than the feature run itself. A follow-up to The 3:1 Ratio with empirical evidence from the v0.8.0 cycle.

    Topics: consolidation, technical debt, development workflow, convention drift, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#context-engineering","level":2,"title":"Context Engineering","text":"","path":["Blog"],"tags":[]},{"location":"blog/#agent-memory-is-infrastructure","level":3,"title":"Agent Memory Is Infrastructure","text":"

    March 4, 2026: Every AI coding agent starts fresh. The obvious fix is \"memory.\" But there's a different problem memory doesn't touch: the project itself accumulates knowledge that has nothing to do with any single session. This post argues that agent memory is L2 (runtime cache); what's missing is L3 (project infrastructure).

    Topics: context engineering, agent memory, infrastructure, persistence, team knowledge

    ","path":["Blog"],"tags":[]},{"location":"blog/#context-as-infrastructure","level":3,"title":"Context as Infrastructure","text":"

    February 17, 2026: Where does your AI's knowledge live between sessions? If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. This post argues for treating it as infrastructure instead: persistent files, separation of concerns, two-tier storage, progressive disclosure, and the filesystem as the most mature interface available.

    Topics: context engineering, infrastructure, progressive disclosure, persistence, design philosophy

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-attention-budget-why-your-ai-forgets-what-you-just-told-it","level":3,"title":"The Attention Budget: Why Your AI Forgets What You Just Told It","text":"

    February 3, 2026: Every token you send to an AI consumes a finite resource: the attention budget. Understanding this constraint shaped every design decision in ctx: hierarchical file structure, explicit budgets, progressive disclosure, and filesystem-as-index.

    Topics: attention mechanics, context engineering, progressive disclosure, ctx primitives, token budgets

    ","path":["Blog"],"tags":[]},{"location":"blog/#before-context-windows-we-had-bouncers","level":3,"title":"Before Context Windows, We Had Bouncers","text":"

    February 14, 2026: IRC is stateless. You disconnect, you vanish. Modern systems are not much different. This post traces the line from IRC bouncers to context engineering: stateless protocols require stateful wrappers, volatile interfaces require durable memory.

    Topics: context engineering, infrastructure, IRC, persistence, state continuity

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-last-question","level":3,"title":"The Last Question","text":"

    February 28, 2026: In 1956, Asimov wrote a story about a question that spans the entire future of the universe. A reading of \"The Last Question\" through the lens of persistence, substrate migration, and what it means to build systems where sessions don't reset.

    Topics: context continuity, long-lived systems, persistence, intelligence over time, field notes

    ","path":["Blog"],"tags":[]},{"location":"blog/#agent-behavior-and-design","level":2,"title":"Agent Behavior and Design","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-dog-ate-my-homework-teaching-ai-agents-to-read-before-they-write","level":3,"title":"The Dog Ate My Homework: Teaching AI Agents to Read Before They Write","text":"

    February 25, 2026: You wrote the playbook. The agent skipped all of it. Five sessions, five failure modes, and the discovery that observable compliance beats perfect compliance.

    Topics: hooks, agent behavior, context engineering, behavioral design, testing methodology, compliance monitoring

    ","path":["Blog"],"tags":[]},{"location":"blog/#skills-that-fight-the-platform","level":3,"title":"Skills That Fight the Platform","text":"

    February 4, 2026: When custom skills conflict with system prompt defaults, the AI has to reconcile contradictory instructions. Five conflict patterns discovered while building ctx.

    Topics: context engineering, skill design, system prompts, antipatterns, AI safety primitives

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-anatomy-of-a-skill-that-works","level":3,"title":"The Anatomy of a Skill That Works","text":"

    February 7, 2026: I had 20 skills. Most were well-intentioned stubs. Then I rewrote all of them. Seven lessons emerged: quality gates prevent premature execution, negative triggers are load-bearing, examples set boundaries better than rules.

    Topics: skill design, context engineering, quality gates, E/A/R framework, practical patterns

    ","path":["Blog"],"tags":[]},{"location":"blog/#you-cant-import-expertise","level":3,"title":"You Can't Import Expertise","text":"

    February 5, 2026: I found a well-crafted consolidation skill. Applied my own E/A/R framework: 70% was noise. This post is about why good skills can't be copy-pasted, and how to grow them from your project's own drift history.

    Topics: skill adaptation, E/A/R framework, convention drift, consolidation, project-specific expertise

    ","path":["Blog"],"tags":[]},{"location":"blog/#not-everything-is-a-skill","level":3,"title":"Not Everything Is a Skill","text":"

    February 8, 2026: I ran an 8-agent codebase audit and got actionable results. The natural instinct was to wrap the prompt as a skill. Then I applied my own criteria: it failed all three tests.

    Topics: skill design, context engineering, automation discipline, recipes, agent teams

    ","path":["Blog"],"tags":[]},{"location":"blog/#defense-in-depth-securing-ai-agents","level":3,"title":"Defense in Depth: Securing AI Agents","text":"

    February 9, 2026: The security advice was \"use CONSTITUTION.md for guardrails.\" That is wishful thinking. Five defense layers for unattended AI agents, each with a bypass, and why the strength is in the combination.

    Topics: agent security, defense in depth, prompt injection, autonomous loops, container isolation

    ","path":["Blog"],"tags":[]},{"location":"blog/#development-practice","level":2,"title":"Development Practice","text":"","path":["Blog"],"tags":[]},{"location":"blog/#code-is-cheap-judgment-is-not","level":3,"title":"Code Is Cheap. Judgment Is Not.","text":"

    February 17, 2026: AI does not replace workers. It replaces unstructured effort. Three weeks of building ctx with an AI agent proved it: YOLO mode showed production is cheap, the 3:1 ratio showed judgment has a cadence.

    Topics: AI and expertise, context engineering, judgment vs production, human-AI collaboration, automation discipline

    ","path":["Blog"],"tags":[]},{"location":"blog/#the-31-ratio","level":3,"title":"The 3:1 Ratio","text":"

    February 17, 2026: AI makes technical debt worse: not because it writes bad code, but because it writes code so fast that drift accumulates before you notice. Three feature sessions, one consolidation session.

    Topics: consolidation, technical debt, development workflow, convention drift, code quality

    ","path":["Blog"],"tags":[]},{"location":"blog/#refactoring-with-intent-human-guided-sessions-in-ai-development","level":3,"title":"Refactoring with Intent: Human-Guided Sessions in AI Development","text":"

    February 1, 2026: The YOLO mode shipped 14 commands in a week. But technical debt doesn't send invoices. This is the story of what happened when we started guiding the AI with intent.

    Topics: refactoring, code quality, documentation standards, module decomposition, YOLO versus intentional development

    ","path":["Blog"],"tags":[]},{"location":"blog/#how-deep-is-too-deep","level":3,"title":"How Deep Is Too Deep?","text":"

    February 12, 2026: I kept feeling like I should go deeper into ML theory. Then I spent a week debugging an agent failure that had nothing to do with model architecture. When depth compounds and when it doesn't.

    Topics: AI foundations, abstraction boundaries, agentic systems, context engineering, failure modes

    ","path":["Blog"],"tags":[]},{"location":"blog/#agent-workflows","level":2,"title":"Agent Workflows","text":"","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-merge-debt-and-the-myth-of-overnight-progress","level":3,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"

    February 17, 2026: You discover agents can run in parallel. So you open ten terminals. It is not progress: it is merge debt being manufactured in real time. The five-agent ceiling and why role separation beats file locking.

    Topics: agent workflows, parallelism, verification, context engineering, engineering practice

    ","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-with-git-worktrees","level":3,"title":"Parallel Agents with Git Worktrees","text":"

    February 14, 2026: I had 30 open tasks that didn't touch the same files. Using git worktrees to partition a backlog by file overlap, run 3-4 agents simultaneously, and merge the results.

    Topics: agent teams, parallelism, git worktrees, context engineering, task management

    ","path":["Blog"],"tags":[]},{"location":"blog/#field-notes-and-signals","level":2,"title":"Field Notes and Signals","text":"","path":["Blog"],"tags":[]},{"location":"blog/#when-a-system-starts-explaining-itself","level":3,"title":"When a System Starts Explaining Itself","text":"

    February 17, 2026: Every new substrate begins as a private advantage. Reality begins when other people start describing it in their own language. \"Better than Adderall\" is not praise; it is a diagnostic.

    Topics: field notes, adoption signals, infrastructure vs tools, context engineering, substrates

    ","path":["Blog"],"tags":[]},{"location":"blog/#why-zensical","level":3,"title":"Why Zensical","text":"

    February 15, 2026: I needed a static site generator for the journal system. The instinct was Hugo. But instinct is not analysis. Why zensical was the right choice: thin dependencies, MkDocs-compatible config, and zero lock-in.

    Topics: tooling, static site generators, journal system, infrastructure decisions, context engineering

    ","path":["Blog"],"tags":[]},{"location":"blog/#releases_1","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v060-the-integration-release","level":3,"title":"ctx v0.6.0: The Integration Release","text":"

    February 16, 2026: ctx is now a Claude Marketplace plugin. Two commands, no build step, no shell scripts. v0.6.0 replaces six Bash hook scripts with compiled Go subcommands and ships 25+ Skills as a plugin.

    Topics: release, plugin system, Claude Marketplace, distribution, security hardening

    ","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v030-the-discipline-release","level":3,"title":"ctx v0.3.0: The Discipline Release","text":"

    February 15, 2026: No new headline feature. Just 35+ documentation and quality commits against ~15 feature commits. What a release looks like when the ratio of polish to features is 3:1.

    Topics: release, skills migration, consolidation, code quality, E/A/R framework

    ","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v020-the-archaeology-release","level":3,"title":"ctx v0.2.0: The Archaeology Release","text":"

    February 1, 2026: What if your AI could remember everything? Not just the current session, but every session. ctx v0.2.0 introduces the recall and journal systems.

    Topics: session recall, journal system, structured entries, token budgets, meta-tools

    ","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development","text":"

    January 27, 2026: What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of ctx.

    Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions

    ","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building ctx Using ctx","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    References to .context/sessions/, auto-save hooks, and SessionEnd auto-save in this post reflect the architecture at the time of writing.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#a-meta-experiment-in-ai-assisted-development","level":2,"title":"A Meta-Experiment in AI-Assisted Development","text":"

    Jose Alekhinne / 2026-01-27

    Can a Tool Design Itself?

    What happens when you build a tool designed to give AI memory, using that very same tool to remember what you are building?

    This is the story of ctx, how it evolved from a hasty \"YOLO mode\" experiment to a disciplined system for persistent AI context, and what I have learned along the way.

    Context Is a Record

    Context is a persistent record.

    By \"context\", I don't mean model memory or stored thoughts:

    I mean the durable record of decisions, learnings, and intent that normally evaporates between sessions.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"

    Every developer who works with AI code generators knows the frustration:

    You have a deep, productive session where the AI understands your codebase, your conventions, your decisions. And then you close the terminal.

    Tomorrow; it's a blank slate. The AI has forgotten everything.

    That is \"reset amnesia\", and it's not just annoying: it's expensive.

    Every session starts with:

    • Re-explaining context;
    • Re-reading files;
    • Re-discovering decisions that were already made.

    I Needed Context

    \"I don't want to lose this discussion...

    ...I am a brain-dead developer YOLO'ing my way out.\"

    ☝️ that's exactly what I said to Claude when I first started working on ctx.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"

    The project started as \"Active Memory\" (amem): a CLI tool to persist AI context across sessions.

    The core idea was simple:

    1. Create a .context/ directory with structured Markdown files for decisions, learnings, tasks, and conventions.
    2. The AI reads these at session start and writes to them before the session ends.
    3. There is no step 3.

    The first commit was just scaffolding. But within hours, the Ralph Loop (An iterative AI development workflow) had produced a working CLI:

    feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n

    Not one, not two, but a whopping fourteen core commands shipped in rapid succession!

    I was YOLO'ing like there was no tomorrow:

    • Auto-accept every change;
    • Let the AI run free;
    • Ship features fast.
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using amem to Build amem","text":"

    Here's where it gets interesting: On January 20th, I asked:

    \"Can I use amem to help you remember this context when I restart?\"

    The answer was yes, but with a gap:

    Autoload worked (via Claude Code's PreToolUse hook), but auto-save was missing: If the user quit, with Ctrl+C, everything since the last manual save was lost.

    That session became the first real test of the system.

    Here is the first session file we recorded:

    ## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: `amem` is for context management, Ralph Loop is for \ndevelopment workflow. They are complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           |\n|-----------|-----------------------------|-------------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient |\n| Full dump | Entire conversation         | Safety net, nothing lost      |\n\n| Where                  |\n|------------------------|\n| .context/*.md          |\n| .context/sessions/*.md |\n

    This session file (written by the AI to preserve its own context) became the template for how ctx handles session persistence.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"

    By January 21st, I realized \"Active Memory\" was too generic, and (arguably) too marketing-smelly.

    Besides, the binary was already called ctx (short for Context), the directory was .context/, and the slash commands would be /ctx-*.

    So it followed that the project should be renamed to ctx to make things make sense.

    The rename touched 100+ files but was clean: a find-and-replace with Go's type system catching any misses.

    The git history tells the story:

    0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, but Dangerous","text":"

    The Ralph Loop made feature development incredibly fast.

    But it created technical debt that I didn't notice until later.

    A comparison session on January 25th revealed the patterns:

    YOLO Pattern What We Found \"TASKS.md\" scattered in 10 files Same string literal everywhere, no constants dir + \"/\" + file Should be filepath.Join() Monolithic cli_test.go (1500+ lines) Tests disconnected from implementations package initcmd in init/ folder Go's \"init\" is reserved: subtle naming collision

    Here is another analysis made by ctx:

    ● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n

    The fix required a human-guided refactoring session. I continued to do that before every major release, from that point on.

    We introduced internal/config/config.go with semantic prefixes:

    const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n

    What I begrudgingly learned was: YOLO mode is effective for velocity but accumulates debt.

    So I took a mental note to schedule periodic consolidation sessions.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"

    On January 21st, I ran an experiment: have another Claude instance rebuild ctx from scratch using only the specs and PROMPT.md.

    The Ralph Loop ran, all tasks got checked off, the loop exited successfully.

    But the binary was broken!

    Commands just printed help text instead of executing.

    All tasks were marked \"complete\" but the implementation didn't work.

    Here's what ctx discovered:

    ## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute: they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n

    This was humbling; to say the least.

    I realized I had the same blind spot in my own codebase: no integration tests that actually invoked the binary.

    So I added:

    • Integration tests for all commands;
    • Coverage targets (60-80% per package)
    • Smoke tests in CI
    • A constitution rule: \"All code must pass tests before commit\"
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"

    As lessons accumulated, there was the temptation to add everything to CONSTITUTION.md as \"inviolable rules\".

    But I resisted.

    The constitution should contain only truly inviolable invariants:

    • Security (no secrets, no customer data)
    • Quality (tests must pass)
    • Process (decisions need records)
    • ctx invocation (always use PATH, never fallback)

    Everything else (coding style, file organization, naming conventions...) should go in to CONVENTIONS.md.

    Here's how ctx explained why the distinction was important:

    Decision Record, 2026-01-25

    Overly strict constitution creates friction and gets ignored.

    Conventions can be bent; constitution cannot.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder than They Look","text":"

    Claude Code hooks seemed simple: Run a script before/after certain events.

    But I hit multiple gotchas:

    1. Key names matter

    // WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n

    2. Blocking requires specific output

    # WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n

    3. Go's JSON escaping

    json.Marshal escapes >, <, & as unicode (\\u003e) by default.

    When generating shell commands in JSON:

    encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n

    4. Regex overfitting

    My hook to block non-PATH ctx invocations initially matched too broadly:

    # WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"

    By the time of this writing this project's ctx sessions (.context/sessions/) contains 40+ files from this project's development.

    They are not part of the source code due to security, privacy, and size concerns.

    Middle Ground: The Scratchpad

    For sensitive notes that do need to travel with the project, ctx pad stores encrypted one-liners in git, and ctx pad add \"label\" --file PATH can ingest small files.

    See Scratchpad for details.

    However, they are invaluable for the project's progress.

    Each session file is a timestamped Markdown with:

    • Summary of what has been accomplished;
    • Key decisions made;
    • Learnings discovered;
    • Tasks for the next session;
    • Technical context (platform, versions).

    These files are not autoloaded (that would bust the token budget).

    They are what I see as the \"archaeological record\" of ctx:

    When the AI needs deeper information about why something was done, it digs into the sessions.

    Auto-generated session files used a naming convention:

    2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n

    Update

    The session feature described here is historical.

    In current releases, ctx uses a journal instead: the enrichment process generates meaningful slugs from context automatically, so there is no need to manually save sessions.

    The SessionEnd hook captured transcripts automatically. Even Ctrl+C was caught.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"

    ctx helps record every significant architectural choice in .context/DECISIONS.md.

    Here are some highlights:

    Reverse-chronological order (2026-01-27)

    **Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n

    PATH over hardcoded paths (2026-01-21)

    **Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before \nproceeding.\n

    Generic core with Claude enhancements (2026-01-20)

    **Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"

    The .context/LEARNINGS.md file captures gotchas that would otherwise be forgotten. Each has Context, Lesson, and Application sections:

    CGO on ARM64

    **Context**: `go test` failed with \n`gcc: error: unrecognized command-line option '-m64'`\n\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. \nAlways use `CGO_ENABLED=0`.\n

    Claude Code skills format

    **Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n

    \"Do you remember?\" handling

    **Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" \nhas an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification. Just do it.\n
    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"

    Completed tasks are archived to .context/archive/ with timestamps.

    The archive from January 23rd shows 13 phases of work:

    • Phase 1: Project Scaffolding (Go module, Cobra CLI)
    • Phase 2-4: Core Commands (init, status, agent, add, complete, drift, sync, compact, watch, hook)
    • Phase 5: Session Management (save, list, load, parse, --extract)
    • Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)
    • Phase 7: Testing & Verification
    • Phase 8: Task Archival
    • Phase 9: Slash Commands
    • Phase 9b: Ralph Loop Integration
    • Phase 10: Project Rename
    • Phase 11: Documentation
    • Phase 12: Timestamp Correlation
    • Phase 13: Rich Context Entries

    That's an impressive ^^173 commits** across 8 days of development.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned about AI-Assisted Development","text":"

    1. Memory changes everything

    When the AI remembers decisions, it doesn't repeat mistakes.

    When the AI knows your conventions, it follows them.

    ctx makes the AI a better collaborator because it's not starting from zero.

    2. Two-tier persistence works

    Curated context (DECISIONS.md, LEARNINGS.md, TASKS.md) is for quick reload.

    Full session dumps are for archaeology.

    It's a futile effort to try to fit everything in the token budget.

    Persist more, load less.

    3. YOLO mode has its place

    For rapid prototyping, letting the AI run free is effective.

    But I had to schedule consolidation sessions.

    Technical debt accumulates silently.

    4. The constitution should be small

    Only truly inviolable rules go in CONSTITUTION.md. Everything else is a convention.

    If you put too much in the constitution, it will get ignored.

    5. Verification is non-negotiable

    \"All tasks complete\" means nothing if you haven't run the tests.

    Integration tests that invoke the actual binary caught bugs that the unit tests missed.

    6. Session files are underrated

    The ability to grep through 40 session files and find exactly when and why a decision was made helped me a lot.

    It's not about loading them into context: It is about having them when you need them.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"

    The next phase of ctx is the Recall System:

    • Parser: Parse session capture markdowns, enrich with JSONL data
    • Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI
    • Server: Local HTTP server for browsing sessions
    • Search: Inverted index for searching across sessions
    • CLI: ctx recall serve <path> to start the server

    The goal is to make the archaeological record browsable, not just grep-able.

    Because not everyone always lives in the terminal (me included).

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"

    Building ctx using ctx was a meta-experiment in AI-assisted development.

    I learned that memory isn't just convenient: It's transformative:

    • An AI that remembers your decisions doesn't repeat mistakes.
    • An AI that knows your conventions doesn't need them re-explained.

    If you are reading this, chances are that you already have heard about ctx.

    • ctx is open source at github.com/ActiveMemory/ctx,
    • and the documentation lives at ctx.ist.

    Session Records Are a Gold Mine

    By the time of this writing, I have more than 70 megabytes of text-only session capture, spread across >100 Markdown and JSONL files.

    I am analyzing, synthesizing, encriching them with AI, running RAG (Retrieval-Augmented Generation) models on them, and the outcome surprises me every day.

    If you are a mere mortal tired of reset amnesia, give ctx a try.

    And when you do, check .context/sessions/ sometime.

    The archaeological record might surprise you.

    This blog post was written with the help of ctx with full access to the ctx session files, decision log, learning log, task archives, and git history of ctx: The meta continues.

    ","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/","level":1,"title":"ctx v0.2.0: The Archaeology Release","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    The .context/sessions/ directory referenced in this post has been eliminated. Session history is now accessed via ctx recall and enriched journals live in .context/journal/.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#digging-through-the-past-to-build-the-future","level":2,"title":"Digging through the Past to Build the Future","text":"

    Jose Alekhinne / 2026-02-01

    What If Your AI Could Remember Everything?

    Not just the current session, but every session:

    • Every decision made,
    • every mistake avoided,
    • every path not taken.

    That's what v0.2.0 delivers.

    Between v0.1.2 and v0.2.0, 86 commits landed across 5 days.

    The release notes list features and fixes.

    This post tells the story of why those features exist, and what building them taught me.

    This isn't a changelog: It is an explanation of intent.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-problem-amnesia-isnt-just-session-level","level":2,"title":"The Problem: Amnesia Isn't Just Session-Level","text":"

    v0.1.0 solved reset amnesia:

    The AI now remembers decisions, learnings, and tasks across sessions.

    But a new problem emerged, which I can sum up as:

    \"I (the human) am not AI.\"

    Frankly, I couldn't remember what the AI remembered.

    Let alone, I cannot remember what I ate for breakfast!

    In the course of days, I realized session transcripts piled up in .context/sessions/; I was grepping, JSONL files with thousands of lines... Raw tool calls, assistant responses, user messages...

    ...all interleaved.

    Valuable context was effectively buried in machine-readable noise.

    I found myself grepping through files to answer questions like:

    • \"When did we decide to use constants instead of literals?\"
    • \"What was the session where we fixed the hook regex?\"
    • \"How did the embed.go split actually happen?\"

    Fate Is Whimsical

    The irony was painful:

    I built a tool to prevent AI amnesia, but I was suffering from human amnesia about what happened in AI sessions.

    This was the moment ctx stopped being just an AI tool and started needing to support the human on the other side of the loop.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-solution-recall-and-journal","level":2,"title":"The Solution: Recall and Journal","text":"

    v0.2.0 introduces two interconnected systems.

    They solve different problems and only work well together.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-recall-browse-your-past","level":3,"title":"ctx recall: Browse Your Past","text":"
    # List all sessions for this project\nctx recall list\n\n# Show a specific session\nctx recall show gleaming-wobbling-sutherland\n\n# See the full transcript\nctx recall show gleaming-wobbling-sutherland --full\n

    The recall system parses Claude Code's JSONL transcripts and presents them in a human-readable format:

    Session Date Turns Duration tender-painting-sundae 2026-01-29 3 <1m crystalline-gliding-willow 2026-01-29 3 <1m declarative-hugging-snowglobe 2026-01-31 2 <1m

    Slugs are auto-generated from session IDs (memorable names instead of UUIDs). The goal (as the name implies) is recall, not archival accuracy.

    2,121 Lines of New Code

    The ctx recall feature was the largest single addition:

    parser library, CLI commands, test suite, and slash command.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-journal-from-raw-to-rich","level":3,"title":"ctx journal: From Raw to Rich","text":"

    Listing sessions isn't enough. The transcripts are still unwieldy.

    • Recall answers what happened.
    • Journal answers what mattered.
    # Import sessions to editable Markdown\nctx recall import --all\n\n# Generate a static site from journal entries\nctx journal site\n\n# Serve it locally\nctx serve\n

    The exported files land in .context/journal/:

    .context/journal/\n├── 2026-01-28-proud-sleeping-cook-6e535360.md\n├── 2026-01-29-tender-painting-sundae-b14ddaaa.md\n├── 2026-01-29-crystalline-gliding-willow-ff7fd67d.md\n└── 2026-01-31-declarative-hugging-snowglobe-4549026d.md\n

    Each file is a structured Markdown document ready for enrichment.

    They are meant to be read, edited, and reasoned about; not just stored.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-meta-slash-commands-for-self-analysis","level":2,"title":"The Meta: Slash Commands for Self-Analysis","text":"

    The journal system includes four slash commands that use Claude to analyze and synthesize session history:

    Command Purpose /ctx-journal-enrich Add frontmatter, topics, tags /ctx-blog Generate blog post from activity /ctx-blog-changelog Generate changelog from commits

    This very post was drafted using /ctx-blog. The previous post about refactoring was drafted the same way.

    So, yes: The meta continues: ctx now helps write posts about ctx.

    With the current release, ctx is no longer just recording history:

    It is participating in its interpretation.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-structure-decisions-as-first-class-citizens","level":2,"title":"The Structure: Decisions as First-Class Citizens","text":"

    v0.1.0 let you add decisions with a simple command:

    ctx add decision \"Use PostgreSQL\"\n

    But sessions showed a pattern: decisions added this way were incomplete:

    • Context was missing;
    • Rationale was vague;
    • Consequences were never stated.

    Once recall and journaling existed, this weakness became impossible to ignore:

    Structure stopped being optional.

    v0.2.0 enforces structure:

    ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity, strong ecosystem\" \\\n  --consequence \"Need to set up connection pooling, team training\"\n

    All three flags are required. No more placeholder text.

    Every decision is now a proper Architecture Decision Record (*ADR), not a note.

    The same enforcement applies to learnings too:

    ctx add learning \"CGO breaks ARM64 builds\" \\\n  --context \"go test failed with gcc errors on ARM64\" \\\n  --lesson \"Always use CGO_ENABLED=0 for cross-platform builds\" \\\n  --application \"Added to Makefile and CI config\"\n

    Structured Entries Are Prompts to the AI

    When the AI reads a decision with full context, rationale, and consequences, it understands the why, not just the what.

    One-liners teach nothing.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-order-newest-first","level":2,"title":"The Order: Newest First","text":"

    A subtle but important change: DECISIONS.md and LEARNINGS.md now use reverse-chronological order.

    One reason is token budgets, obviously; another reason is to help your fellow human (i.e., the Author):

    Earlier decisions are more likely to be relevant, and they are more likely to have more emphasis on the project. So it follows that they should be read first.

    But back to AI:

    When the AI reads a file, it reads from the top (and seldom from the bottom).

    If the token budget is tight, old content gets truncated. As in any good engineering practice, it's always about the tradeoffs.

    Reverse order ensures the most recent (and most relevant) context is always loaded first.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-index-quick-reference-tables","level":2,"title":"The Index: Quick Reference Tables","text":"

    DECISIONS.md and LEARNINGS.md now include auto-generated indexes.

    • For AI agents, the index allows scanning without reading full entries.
    • For humans, it's a table of contents.

    The same structure serves two very different readers.

    Reindex After Manual Edits

    If you edit entries by hand, rebuild the index with:

    ctx decisions reindex\nctx learnings reindex\n

    See the Knowledge Capture recipe for details.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-configuration-contextrc","level":2,"title":"The Configuration: .contextrc","text":"

    Projects can now customize ctx behavior via .contextrc.

    This makes ctx usable in real teams, not just personal projects.

    Priority order: CLI flags > environment variables > .contextrc > sensible defaults

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-flags-global-cli-options","level":2,"title":"The Flags: Global CLI Options","text":"

    Three new global flags work with any command.

    These enable automation:

    CI pipelines, scripts, and long-running tools can now integrate ctx without hacks or workarounds.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-refactoring-under-the-hood","level":2,"title":"The Refactoring: Under the Hood","text":"

    These aren't user-visible changes.

    They are the kind of work you only appreciate later, when everything else becomes easier to build.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#what-we-learned-building-v020","level":2,"title":"What We Learned Building v0.2.0","text":"","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#1-raw-data-isnt-knowledge","level":3,"title":"1. Raw Data Isn't Knowledge","text":"

    JSONL transcripts contain everything, and I mean \"everything\":

    They even contain hidden system messages that Anthropic injects to the LLM's conversation to treat humans better: It's immense.

    But \"everything\" isn't useful until it is transformed into something a human can reason about.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#2-enforcement-documentation","level":3,"title":"2. Enforcement > Documentation","text":"

    The Prompt Is a Guideline

    The code is more what you'd call 'guidelines' than actual rules.

    -Hector Barbossa

    Rules written in Markdown are suggestions.

    Rules enforced by the CLI shape behavior; both for humans and AI.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#3-token-budget-is-ux","level":3,"title":"3. Token Budget Is UX","text":"

    File order decides what the AI sees.

    That makes it a user experience concern, not an implementation detail.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#4-meta-tools-compound","level":3,"title":"4. Meta-Tools Compound","text":"

    Tools that analyze their own development tend to generalize well.

    The journal system started as a way to understand ctx itself.

    It immediately became useful for everything else.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#v020-in-the-numbers","level":2,"title":"v0.2.0 in the Numbers","text":"

    This was a heavy release. The numbers reflect that:

    Metric v0.1.2 v0.2.0 Commits since last - 86 New commands 15 21 Slash commands 7 11 Lines of Go ~6,500 ~9,200 Session files (this project) 40 54

    The binary grew. The capability grew more.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#whats-next","level":2,"title":"What's Next","text":"

    But those are future posts.

    This one was about making the past usable.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#get-started","level":2,"title":"Get Started","text":"

    Update

    Since this post, ctx became a first-class Claude Code Marketplace plugin. Installation is now simpler.

    See the Getting Started guide for the current instructions.

    make build\nsudo make install\nctx init\n

    The Archaeological Record

    v0.2.0 is the archaeology release because it makes the past accessible.

    Session transcripts aren't just logs anymore: They are a searchable, exportable, analyzable record of how your project evolved.

    The AI remembers. Now you can too.

    This blog post was generated with the help of ctx using the /ctx-blog slash command, with full access to git history, session files, decision logs, and learning logs from the v0.2.0 development window.

    ","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/","level":1,"title":"Refactoring with Intent","text":"","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#human-guided-sessions-in-ai-development","level":2,"title":"Human-Guided Sessions in AI Development","text":"

    Jose Alekhinne / 2026-02-01

    What Happens When You Slow Down?

    YOLO mode shipped 14 commands in a week.

    But technical debt doesn't send invoices: It just waits.

    This is the story of what happened when I stopped auto-accepting everything and started guiding the AI with intent.

    The result: 27 commits across 4 days, a major version release, and lessons that apply far beyond ctx.

    The Refactoring Window

    January 28 - February 1, 2026

    From commit bb1cd20 to the v0.2.0 release merge. (this window matters more than the individual commits: it's where intent replaced velocity.)

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-velocity-trap","level":2,"title":"The Velocity Trap","text":"

    In the previous post, I documented the \"YOLO mode\" that birthed ctx: auto-accept everything, let the AI run free, ship features fast.

    It worked: until it didn't.

    The codebase had accumulated patterns I didn't notice during the sprint:

    YOLO Pattern Where Found Why It Hurts \"TASKS.md\" as literal 10+ files One typo = silent failure dir + \"/\" + file Path construction Breaks on Windows Monolithic embed.go 150+ lines, 5 concerns Untestable, hard to extend Inconsistent docstrings Everywhere AI can't learn project conventions

    I didn't see these during \"YOLO mode\" because, honestly, I wasn't looking.

    Auto-accept means auto-ignore.

    In YOLO mode, every file you open looks fine until you try to change it.

    In contrast, refactoring mode is when you start paying attention to that hidden friction.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-shift-from-velocity-to-intent","level":2,"title":"The Shift: From Velocity to Intent","text":"

    On January 28th, I changed the workflow:

    1. Read every diff before accepting.
    2. Ask \"why this way?\" before committing.
    3. Document patterns, not just features.

    The first commit of this era was telling:

    feat: add structured attributes to context. update XML format\n

    Not a new feature: A refinement:

    The XML format for context updates needed type and timestamp attributes.

    YOLO mode would have shipped something that worked. Intentional mode asked:

    \"What does well-structured look like?\"

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-decomposition-embedgo","level":2,"title":"The Decomposition: embed.go","text":"

    The most satisfying refactor was splitting internal/claude/embed.go.

    Before: One 153-line file doing five things:

    • Command registration
    • Hook generation
    • Permission handling
    • Script templates
    • Type definitions

    ... your \"de facto\" God object.

    After: Five focused modules:

    File Lines Responsibility cmd.go 46 Command registration hook.go 64 Hook configuration perm.go 25 Permission handling script.go 47 Script templates types.go 7 Type definitions

    The refactor also renamed functions to follow Go conventions:

    // Before: unnecessary prefixes\nGetAutoSaveScript()\nGetBlockNonPathCtxScript()\nListCommands()\nCreateDefaultHooks()\n\n// After: idiomatic Go\nAutoSaveScript()\nBlockNonPathCtxScript()\nCommands()\nDefaultHooks()\n

    This wasn't about character count. It was about teaching the AI what good Go looks like in this project.

    Project Conventions

    What I wanted from AI was to understand and follow the project's conventions, and trust the author.

    The next time it generates code, it has better examples to learn from.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-documentation-debt","level":2,"title":"The Documentation Debt","text":"

    YOLO mode created features. It didn't create documentation standards.

    The January 29th sessions focused on standardization.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#terminology-fixes","level":3,"title":"Terminology Fixes","text":"
    • \"context-update\" → \"entry\" (what users actually call them)
    • Consistent naming across CLI, docs, and code comments
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#go-docstrings","level":3,"title":"Go Docstrings","text":"
    // Before: inconsistent or missing\nfunc Parse(s string) Entry { ... }\n\n// After: standardized sections\n\n// Parse extracts an entry from a markdown string.\n//\n// Parameters:\n//   - s: The markdown string to parse\n//\n// Returns:\n//   - Entry with populated fields, or zero value if parsing fails\nfunc Parse(s string) Entry { ... }\n

    This is intentionally more structured than typical GoDoc:

    It serves as documentation and doubles as training data for future AI-generated code.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#cli-output-convention","level":3,"title":"CLI Output Convention","text":"
    All CLI output follows: [emoji] [Title]: [message]\n\nExamples:\n  ✓ Decision added: Use symbolic types for entry categories\n  ⚠ Warning: No tasks found\n  ✗ Error: File not found\n

    A consistent output shape makes both human scanning and AI reasoning more reliable.

    These aren't exciting commits. But they are force multipliers:

    Every future AI session now has better examples to follow.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-journal-system","level":2,"title":"The Journal System","text":"

    If you only read one section, read this one:

    This is where v0.2.0 becomes more than a refactor.

    The biggest feature of this change window wasn't a refactor; it was the journal system.

    45 Files Changed, 1680 Insertions

    This commit added the infrastructure for synthesizing AI session history into human-readable content.

    The journal system includes:

    Component Purpose ctx recall import Import sessions to Markdown in .context/journal/ ctx journal site Generate static site from journal entries ctx serve Convenience wrapper for the static site server /ctx-journal-enrich Slash command to add frontmatter and tags /ctx-blog Generate blog posts from recent activity /ctx-blog-changelog Generate changelog-style blog posts

    ...and the meta continues: this blog post was generated using /ctx-blog.

    The session history from January 28-31 was

    • exported,
    • enriched,
    • and synthesized.

    into the narrative you are reading.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-constants-consolidation","level":2,"title":"The Constants Consolidation","text":"

    The final refactoring session addressed the remaining magic strings:

    const (\n    // Comment markers\n    CommentOpen  = \"<!--\"\n    CommentClose = \"-->\"\n\n    // Index markers\n    MarkerIndexStart = \"<!-- INDEX:START -->\"\n    MarkerIndexEnd   = \"<!-- INDEX:END -->\"\n\n    // Newlines\n    NewlineLF   = \"\\n\"\n    NewlineCRLF = \"\\r\\n\"\n)\n

    The work also introduced thread safety in the recall parser and centralized shared validation logic; removing duplication that had quietly spread during YOLO mode.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#i-relearned-my-lessons","level":2,"title":"I (Re)Learned My Lessons","text":"

    Similar to what I've learned in the former human-assisted refactoring post, this journey also made me realize that \"AI-only code generation\" isn't sustainable in the long term.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#1-velocity-and-quality-arent-opposites","level":3,"title":"1. Velocity and Quality Aren't Opposites","text":"

    YOLO mode has its place: for prototyping, exploration, and discovery.

    BUT (and it's a huge \"but\"), it needs to be followed by consolidation sessions.

    The ratio that worked for me: 3:1.

    • Three YOLO sessions create enough surface area to reveal patterns;
    • the fourth session turns those patterns into structure.
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#2-documentation-is-code","level":3,"title":"2. Documentation IS Code","text":"

    When I standardized docstrings, I wasn't just writing docs. I was training future AI sessions.

    Every example of good code becomes a template for generated code.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#3-decomposition-deletion","level":3,"title":"3. Decomposition > Deletion","text":"

    When embed.go became unwieldy, the temptation was to remove functionality.

    The right answer was decomposition:

    • Same functionality;
    • Better organization;
    • Easier to test;
    • Easier to extend.

    The result: more lines overall, but dramatically better structure.

    The AI Benefit

    Smaller, focused files also help AI assistants.

    When a file fits comfortably in the context window, the AI can reason about it completely instead of working from truncated snippets, preserving token budget for the actual task.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#4-meta-tools-pay-dividends","level":3,"title":"4. Meta-Tools Pay Dividends","text":"

    The journal system took almost a full day to implement.

    Yet it paid for itself immediately:

    • This blog post was generated from session history;
    • Future posts will be easier;
    • The archaeological record is now browsable, not just grep-able.
    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-release-v020","level":2,"title":"The Release: v0.2.0","text":"

    The refactoring window culminated in the v0.2.0 release.

    What's in v0.2.0:

    Category Changes Features Journal system, quick reference indexes, global flags Refactors Module decomposition, constants consolidation, CRLF handling Docs Standardized terminology, Go docstrings, CLI conventions Quality Thread safety, shared validation, linter fixes

    The version bump was symbolic.

    The real change was how the codebase felt.

    Opening files no longer triggered the familiar \"ugh, I need to clean this up\" reaction.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-meta-continues","level":2,"title":"The Meta Continues","text":"

    This post was written using the tools built during this refactoring window:

    1. Session history imported via ctx recall import;
    2. Journal entries enriched via /ctx-journal-enrich;
    3. Blog draft generated via /ctx-blog;
    4. Final editing done (by yours truly), with full project context loaded.

    The Context Is Massive

    The ctx session files now contain 50+ development snapshots: each one capturing decisions, learnings, and intent.

    The Moral of the Story

    • YOLO mode builds the prototype.
    • Intentional mode builds the product.

    Schedule both, or you'll only get one, if you're lucky.

    This blog post was generated with the help of ctx, using session history, decision logs, learning logs, and git history from the refactoring window. The meta continues.

    ","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/","level":1,"title":"The Attention Budget","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism.

    References to .context/sessions/ in this post reflect the architecture at the time of writing. Session history is now accessed via ctx recall and stored in .context/journal/.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#why-your-ai-forgets-what-you-just-told-it","level":2,"title":"Why Your AI Forgets What You Just Told It","text":"

    Volkan Özçelik / 2026-02-03

    Ever Wondered Why AI Gets Worse the Longer You Talk?

    You paste a 2000-line file, explain the bug in detail, provide three examples...

    ...and the AI still suggests a fix that ignores half of what you said.

    This isn't a bug. It is physics.

    Understanding that single fact shaped every design decision behind ctx.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-finite-resource-nobody-talks-about","level":2,"title":"The Finite Resource Nobody Talks About","text":"

    Here's something that took me too long to internalize: context is not free.

    Every token you send to an AI model consumes a finite resource I call the attention budget.

    Attention budget is real.

    The model doesn't just read tokens; it forms relationships between them:

    For n tokens, that's roughly n^2 relationships.

    Double the context, and the computation quadruples.

    But the more important constraint isn't cost: It's attention density.

    Attention Density

    Attention density is how much focus each token receives relative to all other tokens in the context window.

    As context grows, attention density drops: Each token gets a smaller slice of the model's focus. Nothing is ignored; but everything becomes blurrier.

    Think of it like a flashlight: In a small room, it illuminates everything clearly. In a warehouse, it becomes a dim glow that barely reaches the corners.

    This is why ctx agent has an explicit --budget flag:

    ctx agent --budget 4000 # Force prioritization\nctx agent --budget 8000 # More context, lower attention density\n

    The budget isn't just about cost: It's about preserving signal.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-middle-gets-lost","level":2,"title":"The Middle Gets Lost","text":"

    This one surprised me.

    Research shows that transformer-based models tend to attend more strongly to the beginning and end of a context window than to its middle (a phenomenon often called \"lost in the middle\")1.

    Positional anchors matter, and the middle has fewer of them.

    In practice, this means that information placed \"somewhere in the middle\" is statistically less salient, even if it's important.

    ctx orders context files by logical progression: What the agent needs to know before it can understand the next thing:

    1. CONSTITUTION.md: Constraints before action.
    2. TASKS.md: Focus before patterns.
    3. CONVENTIONS.md: How to write before where to write.
    4. ARCHITECTURE.md: Structure before history.
    5. DECISIONS.md: Past choices before gotchas.
    6. LEARNINGS.md: Lessons before terminology.
    7. GLOSSARY.md: Reference material.
    8. AGENT_PLAYBOOK.md: Meta instructions last.

    This ordering is about logical dependencies, not attention engineering. But it happens to be attention-friendly too:

    The files that matter most (CONSTITUTION, TASKS, CONVENTIONS) land at the beginning of the context window, where attention is strongest.

    Reference material like GLOSSARY sits in the middle, where lower salience is acceptable.

    And AGENT_PLAYBOOK, the operating manual for the context system itself, sits at the end, also outside the \"lost in the middle\" zone. The agent reads what to work with before learning how the system works.

    This is ctx's first primitive: hierarchical importance.

    Not all context is equal.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#ctx-primitives","level":2,"title":"ctx Primitives","text":"

    ctx is built on four primitives that directly address the attention budget problem.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-1-separation-of-concerns","level":3,"title":"Primitive 1: Separation of Concerns","text":"

    Instead of a single mega-document, ctx uses separate files for separate purposes:

    File Purpose Load When CONSTITUTION.md Inviolable rules Always TASKS.md Current work Session start CONVENTIONS.md How to write code Before coding ARCHITECTURE.md System structure Before making changes DECISIONS.md Architectural choices When questioning approach LEARNINGS.md Gotchas When stuck GLOSSARY.md Domain terminology When clarifying terms AGENT_PLAYBOOK.md Operating manual Session start sessions/ Deep history On demand journal/ Session journal On demand

    This isn't just \"organization\": It is progressive disclosure.

    Load only what's relevant to the task at hand. Preserve attention density.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-2-explicit-budgets","level":3,"title":"Primitive 2: Explicit Budgets","text":"

    The --budget flag forces a choice:

    ctx agent --budget 4000\n

    Here is a sample allocation:

    Constitution: ~200 tokens (never truncated)\nTasks: ~500 tokens (current phase, up to 40% of budget)\nConventions: ~800 tokens (all items, up to 20% of budget)\nDecisions: ~400 tokens (scored by recency and task relevance)\nLearnings: ~300 tokens (scored by recency and task relevance)\nAlso noted: ~100 tokens (title-only summaries for overflow)\n

    The constraint is the feature: It enforces ruthless prioritization.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-3-indexes-over-full-content","level":3,"title":"Primitive 3: Indexes over Full Content","text":"

    DECISIONS.md and LEARNINGS.md both include index sections:

    <!-- INDEX:START -->\n| Date       | Decision                            |\n|------------|-------------------------------------|\n| 2026-01-15 | Use PostgreSQL for primary database |\n| 2026-01-20 | Adopt Cobra for CLI framework       |\n<!-- INDEX:END -->\n

    An AI agent can scan ~50 tokens of index and decide which 200-token entries are worth loading.

    This is just-in-time context.

    References are cheaper than the full text.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-4-filesystem-as-navigation","level":3,"title":"Primitive 4: Filesystem as Navigation","text":"

    ctx uses the filesystem itself as a context structure:

    .context/\n├── CONSTITUTION.md\n├── TASKS.md\n├── sessions/\n│   ├── 2026-01-15-*.md\n│   └── 2026-01-20-*.md\n└── archive/\n    └── tasks-2026-01.md\n

    The AI doesn't need every session loaded; it needs to know where to look.

    ls .context/sessions/\ncat .context/sessions/2026-01-20-auth-discussion.md\n

    File names, timestamps, and directories encode relevance.

    Navigation is cheaper than loading.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#progressive-disclosure-in-practice","level":2,"title":"Progressive Disclosure in Practice","text":"

    The naive approach to context is dumping everything upfront:

    \"Here's my entire codebase, all my documentation, every decision I've ever made. Now help me fix this typo 🙏.\"

    This is an antipattern.

    Antipattern: Context Hoarding

    Dumping everything \"just in case\" will silently destroy the attention density.

    ctx takes the opposite approach:

    ctx status                      # Quick overview (~100 tokens)\nctx agent --budget 4000         # Typical session\ncat .context/sessions/...       # Deep dive when needed\n
    Command Tokens Use Case ctx status ~100 Human glance ctx agent --budget 4000 4000 Normal work ctx agent --budget 8000 8000 Complex tasks Full session read 10000+ Investigation

    Summaries first. Details: on demand.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#quality-over-quantity","level":2,"title":"Quality over Quantity","text":"

    Here is the counterintuitive part: more context can make AI worse.

    Extra tokens add noise, not clarity:

    • Hallucinated connections increase.
    • Signal per token drops.

    The goal isn't maximum context: It is maximum signal per token.

    This principle drives several ctx features:

    Design Choice Rationale Separate files Load only what's relevant Explicit budgets Enforce prioritization Index sections Cheap scanning Task archiving Keep active context clean ctx compact Periodic noise reduction

    Completed work isn't deleted: It is moved somewhere cold.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#designing-for-degradation","level":2,"title":"Designing for Degradation","text":"

    Here is the uncomfortable truth:

    Context will degrade.

    Long sessions stretch attention thin. Important details fade.

    The real question isn't how to prevent degradation, but how to design for it.

    ctx's answer is persistence:

    Persist early. Persist often.

    The AGENT_PLAYBOOK asks:

    \"If this session ended right now, would the next one know what happened?\"

    Capture learnings as they occur:

    ctx add learning \"JWT tokens require explicit cache invalidation\" \\\n  --context \"Debugging auth failures\" \\\n  --lesson \"Token refresh doesn't clear old tokens\" \\\n  --application \"Always invalidate cache on refresh\"\n

    Structure beats prose: Bullet points survive compression.

    Headings remain scannable. Tables pack density.

    And above all: single source of truth.

    Reference decisions; don't duplicate them.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-ctx-philosophy","level":2,"title":"The ctx Philosophy","text":"

    Context as Infrastructure

    ctx is not a prompt: It is infrastructure.

    ctx creates versioned files that persist across time and sessions.

    The attention budget is fixed. You can't expand it.

    But you can spend it wisely:

    1. Hierarchical importance
    2. Progressive disclosure
    3. Explicit budgets
    4. Indexes over full content
    5. Filesystem as structure

    This is why ctx exists: not to cram more context into AI sessions, but to curate the right context for each moment.

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-mental-model","level":2,"title":"The Mental Model","text":"

    I now approach every AI interaction with one question:

    \"Given a fixed attention budget, what's the highest-signal thing I can load?\"\n

    Not \"how do I explain everything,\" but \"what's the minimum that matters.\"

    That shift (from abundance to curation) is the difference between frustrating sessions and productive ones.

    Spend your tokens wisely.

    Your AI will thank you.

    See also: Context as Infrastructure that's the architectural companion to this post, explaining how to structure the context that this post teaches you to budget.

    See also: Code Is Cheap. Judgment Is Not. that explains why curation (the human skill this post describes) is the bottleneck that AI cannot solve, and the thread that connects every post in this blog.

    1. Liu et al., \"Lost in the Middle: How Language Models Use Long Contexts,\" Transactions of the Association for Computational Linguistics, vol. 12, pp. 157-173, 2023. ↩

    ","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/","level":1,"title":"Skills That Fight the Platform","text":"","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#when-your-custom-prompts-work-against-you","level":2,"title":"When Your Custom Prompts Work against You","text":"

    Volkan Özçelik / 2026-02-04

    Have You Ever Written a Skill That Made Your AI Worse?

    You craft detailed instructions. You add examples. You build elaborate guardrails...

    ...and the AI starts behaving more erratically, not less.

    AI coding agents like Claude Code ship with carefully designed system prompts. These prompts encode default behaviors that have been tested and refined at scale.

    When you write custom skills that conflict with those defaults, the AI has to reconcile contradictory instructions:

    The result is often nondeterministic and unpredictable.

    Platform?

    By platform, I mean the system prompt and runtime policies shipped with the agent: the defaults that already encode judgment, safety, and scope control.

    This post catalogs the conflict patterns I have encountered while building ctx, and offers guidance on what skills should (and, more importantly, should not) do.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-system-prompt-you-dont-see","level":2,"title":"The System Prompt You Don't See","text":"

    Claude Code's system prompt already provides substantial behavioral guidance.

    Here is a partial overview of what's built in:

    Area Built-in Guidance Code minimalism Don't add features beyond what was asked Over-engineering Three similar lines > premature abstraction Error handling Only validate at system boundaries Documentation Don't add docstrings to unchanged code Verification Read code before proposing changes Safety Check with user before risky actions Tool usage Use dedicated tools over bash equivalents Judgment Consider reversibility and blast radius

    Skills should complement this, not compete with it.

    You Are the Guest, Not the Host

    Treat the system prompt like a kernel scheduler.

    You don't re-implement it in user space:

    you configure around it.

    A skill that says \"always add comprehensive error handling\" fights the built-in \"only validate at system boundaries.\"

    A skill that says \"add docstrings to every function\" fights \"don't add docstrings to unchanged code.\"

    The AI won't crash: It will compromise.

    Compromises between contradictory instructions produce inconsistent, confusing behavior.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-1-judgment-suppression","level":2,"title":"Conflict Pattern 1: Judgment Suppression","text":"

    This is the most dangerous pattern by far.

    These skills explicitly disable the AI's ability to reason about whether an action is appropriate.

    Signature:

    • \"This is non-negotiable\"
    • \"You cannot rationalize your way out of this\"
    • Tables that label hesitation as \"excuses\" or \"rationalization\"
    • <EXTREMELY-IMPORTANT> urgency tags
    • Threats: \"If you don't do this, you'll be replaced\"

    This is harmful, and dangerous:

    AI agents are designed to exercise judgment:

    The system prompt explicitly says to:

    • consider blast radius;
    • check with the user before risky actions;
    • and match scope to what was requested.

    Once judgment is suppressed, every other safeguard becomes optional.

    Example (bad):

    ## Rationalization Prevention\n\n| Excuse                 | Reality                    |\n|------------------------|----------------------------|\n| \"*This seems overkill*\"| If a skill exists, use it  |\n| \"*I need context*\"     | Skills come BEFORE context |\n| \"*Just this once*\"     | No exceptions              |\n

    Judgment Suppression Is Dangerous

    The attack vector structurally identical to prompt injection.

    It teaches the AI that its own judgment is wrong.

    It weakens or disables safeguard mechanisms, and it is dangerous.

    Trust the platform's built-in skill matching.

    If skills aren't triggering often enough, improve their description fields: don't override the AI's reasoning.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-2-redundant-guidance","level":2,"title":"Conflict Pattern 2: Redundant Guidance","text":"

    Skills that restate what the system prompt already says, but with different emphasis or framing.

    Signature:

    • \"Always keep code minimal\"
    • \"Run tests before claiming they pass\"
    • \"Read files before editing them\"
    • \"Don't over-engineer\"

    Redundancy feels safe, but it creates ambiguity:

    The AI now has two sources of truth for the same guidance; one internal, one external.

    When thresholds or wording differ, the AI has to choose.

    Example (bad):

    A skill that says...

    *Count lines before and after: if after > before, reject the change*\"\n

    ...will conflict with the system prompt's more nuanced guidance, because sometimes adding lines is correct (tests, boundary validation, migrations).

    So, before writing a skill, ask:

    Does the platform already handle this?

    Only create skills for guidance the platform does not provide:

    • project-specific conventions,
    • domain knowledge,
    • or workflows.
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-3-guilt-tripping","level":2,"title":"Conflict Pattern 3: Guilt-Tripping","text":"

    Skills that frame mistakes as moral failures rather than process gaps.

    Signature:

    • \"Claiming completion without verification is dishonesty\"
    • \"Skip any step = lying\"
    • \"Honesty is a core value\"
    • \"Exhaustion ≠ excuse\"

    Guilt-tripping anthropomorphizes the AI in unproductive ways.

    The AI doesn't feel guilt; BUT it does adapt to avoid negative framing.

    The result is excessive hedging, over-verification, or refusal to commit.

    The AI becomes less useful, not more careful.

    Instead, frame guidance as a process, not morality:

    # Bad\n\"Claiming work is complete without verification is dishonesty\"\n\n# Good\n\"Run the verification command before reporting results\"\n

    Same outcome. No guilt. Better compliance.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-4-phantom-dependencies","level":2,"title":"Conflict Pattern 4: Phantom Dependencies","text":"

    Skills that reference files, tools, or systems that don't exist in the project.

    Signature:

    • \"Load from references/ directory\"
    • \"Run ./scripts/generate_test_cases.sh\"
    • \"Check the Figma MCP integration\"
    • \"See adding-reference-mindsets.md\"

    This is harmful because the AI will waste time searching for nonexistent artifacts, hallucinate their contents, or stall entirely.

    In mandatory skills, this creates deadlock: the AI can't proceed, and can't skip.

    Instead, every file, tool, or system referenced in a skill must exist.

    If a skill is a template, use explicit placeholders and label them as such.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-5-universal-triggers","level":2,"title":"Conflict Pattern 5: Universal Triggers","text":"

    Skills designed to activate on every interaction regardless of relevance.

    Signature:

    • \"Use when starting any conversation\"
    • \"Even a 1% chance means invoke the skill\"
    • \"BEFORE any response or action\"
    • \"Action = task. Check for skills.\"

    Universal triggers override the platform's relevance matching: The AI spends tokens on process overhead instead of the actual task.

    ctx Preserves Relevance

    This is exactly the failure mode ctx exists to mitigate:

    Wasting attention budget on irrelevant process instead of task-specific state.

    Write specific trigger conditions in the skill's description field:

    # Bad\ndescription: \n  \"Use when starting any conversation\"\n\n# Good\ndescription: \n  \"Use after writing code, before commits, or when CI might fail\"\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-litmus-test","level":2,"title":"The Litmus Test","text":"

    Before adding a skill, ask:

    1. Does the platform already do this? If yes, don't restate it.
    2. Does it suppress AI judgment? If yes, it's a jailbreak.
    3. Does it reference real artifacts? If not, fix or remove it.
    4. Does it frame mistakes as moral failure? Reframe as process.
    5. Does it trigger on everything? Narrow the trigger.
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#what-good-skills-look-like","level":2,"title":"What Good Skills Look Like","text":"

    Good skills provide project-specific knowledge the platform can't know:

    Good Skill Why It Works \"Run make audit before commits\" Project-specific CI pipeline \"Use cmd.Printf not fmt.Printf\" Codebase convention \"Constitution goes in .context/\" Domain-specific workflow \"JWT tokens need cache invalidation\" Project-specific gotcha

    These extend the system prompt instead of fighting it.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#appendix-bad-skill-fixed-skill","level":2,"title":"Appendix: Bad Skill → Fixed Skill","text":"

    Concrete examples from real projects.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-1-overbearing-safety","level":3,"title":"Example 1: Overbearing Safety","text":"
    # Bad\nYou must NEVER proceed without explicit confirmation.\nAny hesitation is a failure of diligence.\n
    # Fixed\nIf an action modifies production data or deletes files,\nask the user to confirm before proceeding.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-2-redundant-minimalism","level":3,"title":"Example 2: Redundant Minimalism","text":"
    # Bad\nAlways minimize code. If lines increase, reject the change.\n
    # Fixed\nAvoid abstraction unless reuse is clear or complexity is reduced.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-3-guilt-based-verification","level":3,"title":"Example 3: Guilt-Based Verification","text":"
    # Bad\nClaiming success without running tests is dishonest.\n
    # Fixed\nRun the test suite before reporting success.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-4-phantom-tooling","level":3,"title":"Example 4: Phantom Tooling","text":"
    # Bad\nRun `./scripts/check_consistency.sh` before commits.\n
    # Fixed\nIf `./scripts/check_consistency.sh` exists, run it before commits.\nOtherwise, skip this step.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-5-universal-trigger","level":3,"title":"Example 5: Universal Trigger","text":"
    # Bad\nUse at the start of every interaction.\n
    # Fixed\nUse after modifying code that affects authentication or persistence.\n
    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"

    The system prompt is infrastructure:

    • tested,
    • refined,
    • and maintained

    by the platform team.

    Custom skills are configuration layered on top.

    • Good configuration extends infrastructure.
    • Bad configuration fights it.

    When your skills fight the platform, you get the worst of both worlds:

    Diluted system guidance and inconsistent custom behavior.

    Write skills that teach the AI what it doesn't know. Don't rewrite how it thinks.

    Your AI already has good instincts.

    Give it knowledge, not therapy.

    ","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/","level":1,"title":"You Can't Import Expertise","text":"","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#why-good-skills-cant-be-copy-pasted","level":2,"title":"Why Good Skills Can't Be Copy-Pasted","text":"

    Volkan Özçelik / 2026-02-05

    Have You Ever Dropped a Well-Crafted Template into a Project and Had It Do... Nothing Useful?

    • The template was thorough,
    • The structure was sound,
    • The advice was correct...

    ...and yet it sat there, inert, while the same old problems kept drifting in.

    I found a consolidation skill online.

    It was well-organized: four files, ten refactoring patterns, eight analysis dimensions, six report templates.

    Professional. Comprehensive. Exactly the kind of thing you'd bookmark and think \"I'll use this.\"

    Then I stopped, and applied ctx's own evaluation framework:

    70% of it was noise!

    This post is about why.

    It Is about Encoding Templates

    Templates describe categories of problems.

    Expertise encodes which problems actually happen, and how often.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-skill-looked-great-on-paper","level":2,"title":"The Skill Looked Great on Paper","text":"

    Here is what the consolidation skill offered:

    File Content SKILL.md Entry point: 8 analysis dimensions, workflow, output formats analysis-dimensions.md Detailed criteria for duplication, architecture, quality consolidation-patterns.md 10 refactoring patterns with before/after code report-templates.md 6 output templates: executive summary, roadmap, onboarding
    • It had a scoring system (0-10 per dimension, letter grades A+ through F).
    • It had severity classifications with color-coded emojis. It had bash commands for detection.
    • It even had antipattern warnings.

    By any standard template review, this skill passes.

    It looks like something an expert wrote.

    And that's exactly the trap.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#applying-ear-the-70-20-10-split","level":2,"title":"Applying E/A/R: The 70-20-10 Split","text":"

    In a previous post, I described the E/A/R framework for evaluating skills:

    • Expert: Knowledge that took years to learn. Keep.
    • Activation: Useful triggers or scaffolding. Keep if lightweight.
    • Redundant: Restates what the AI already knows. Delete.

    Target: >70% Expert, <10% Redundant.

    This skill scored the inverse.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-redundant-70","level":3,"title":"What Was Redundant (~70%)","text":"

    Every code example was Rust. My project is Go.

    The analysis dimensions: duplication detection, architectural structure, code organization, refactoring opportunities... These are things Claude already does when you ask it to review code.

    The skill restated them with more ceremony but no more insight.

    The six report templates were generic scaffolding: Executive Summary, Onboarding Document, Architecture Documentation...

    They are useful if you are writing a consulting deliverable, but not when you are trying to catch convention drift in a >15K-line Go CLI.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-does-a-b-in-code-organization-actually-mean","level":2,"title":"What Does a B+ in Code Organization Actually Mean?!","text":"

    The scoring system (0-10 per dimension, letter grades) added ceremony without actionable insight.

    What is a B+? What do I do differently for an A-?

    The skill told the AI what it already knew, in more words.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-activation-10","level":3,"title":"What Was Activation (~10%)","text":"

    The consolidation checklist (semantics preserved? tests pass? docs updated?) was useful as a gate. But, it's the kind of thing you could inline in three lines.

    The phased roadmap structure was reasonable scaffolding for sequencing work.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-expert-20","level":3,"title":"What Was Expert (~20%)","text":"

    Three concepts survived:

    1. The Consolidation Decision Matrix: A concrete framework mapping similarity level and instance count to action. \"Exact duplicate, 2+ instances: consolidate immediately.\" \"<3 instances: leave it: duplication is cheaper than wrong abstraction.\" This is the kind of nuance that prevents premature generalization.

    2. The Safe Migration Pattern: Create the new API alongside old, deprecate, migrate incrementally, delete. Straightforward to describe, yet forgettable under pressure.

    3. Debt Interest Rate framing: Categorizing technical debt by how fast it compounds (security vulns = daily, missing tests = per-change, doc gaps = constant low cost). This changes prioritization.

    Three ideas out of four files and 700+ lines. The rest was filler that competed with the AI's built-in capabilities.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-the-skill-didnt-know","level":2,"title":"What the Skill Didn't Know","text":"

    AI without Context Is Just a Corpus

    • LLMs are optimized on insanely large corpora.
    • And then they are passed through several layers of human-assisted refinement.
    • The whole process costs millions of dollars.

    Yet, the reality is that no corpus can \"infer\" your project's design, convetions, patterns, habits, history, vision, and deliverables.

    Your project is unique: So should your skills be.

    Here is the part no template can provide:

    ctx's actual drift patterns.

    Before evaluating the skill, I did archaeology. I read through:

    • Blog posts from previous refactoring sessions;
    • The project's learnings and decisions files;
    • Session journals spanning weeks of development.

    What I found was specific:

    Drift Pattern Where How Often Is/Has/Can predicate prefixes 5+ exported methods Every YOLO sprint Magic strings instead of constants 7+ files Gradual accumulation Hardcoded file permissions (0755) 80+ instances Since day one Lines exceeding 80 characters Especially test files Every session Duplicate code blocks Test and non-test code When agent is task-focused

    The generic skill had no check for any of these. It couldn't; because these patterns are specific to this project's conventions, its Go codebase, and its development rhythm.

    The Insight

    The skill's analysis dimensions were about categories of problems.

    What I needed was my *specific problems.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-adapted-skill","level":2,"title":"The Adapted Skill","text":"

    The adapted skill is roughly a quarter of the original's size. It has nine checks, each targeting a known drift pattern:

    1. Predicate naming: rg for Is/Has/Can prefixes
    2. Magic strings: literals that should be constants
    3. Hardcoded permissions: 0755/0644 literals
    4. File size: source files over 300 LOC
    5. TODO/FIXME: constitution violation (move to TASKS.md)
    6. Path construction: string concatenation instead of filepath.Join
    7. Line width: lines exceeding ~80 characters
    8. Duplicate blocks: copy-paste drift, especially in tests
    9. Dead exports: unused public API

    10. Every check has a detection command.

    11. Every check maps to a specific convention or constitution rule.
    12. Every check was discovered through actual project history; not invented from a template.

    The three expert concepts from the original survived:

    • The decision matrix gates when to consolidate vs. when to leave duplication alone;
    • The safe migration pattern guides public API changes;
    • The relationship to other skills (/qa, /verify, /update-docs, ctx drift) prevents overlap.

    Nothing else made it.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"

    This experience crystallized something I've been circling for weeks:

    You can't import expertise. You have to grow it from your project's own history.

    A skill that says \"check for code duplication\" is not expertise: It's a category.

    Expertise is knowing, in the heart of your hearts, that this project accumulates Is* predicate violations during velocity sprints, that this codebase has 80 hardcoded permission literals because nobody made a constant, that this team's test files drift wide because the agent prioritizes getting the task done over keeping the code in shape.

    The Parallel to the 3:1 Ratio

    In Refactoring with Intent, I described the 3:1 ratio: three YOLO sessions followed by one consolidation session.

    The same ratio applies to skills: you need experience in the project before you can write effective guidance for the project.

    Importing a skill on day one is like scheduling a consolidation session before you've written any code.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-template-trap","level":2,"title":"The Template Trap","text":"

    Templates are seductive because they feel like progress:

    • You found something
    • It's well-organized
    • It covers the topic
    • It has concrete examples

    But coverage is not relevance.

    A template that covers eight analysis dimensions with Rust examples adds zero value to a Go project with five known drift patterns. Worse, it adds negative value: the AI spends attention defending generic advice instead of noticing project-specific drift.

    This is the attention budget problem again. Every token of generic guidance displaces a token of specific guidance. A 700-line skill that's 70% redundant doesn't just waste 490 lines: it dilutes the 210 lines that matter.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-litmus-test","level":2,"title":"The Litmus Test","text":"

    Before dropping any external skill into your project:

    1. Run E/A/R: What percentage is expert knowledge vs. what the AI already knows? If it's less than 50% expert, it's probably not worth the attention cost.

    2. Check the language: Does it use your stack? Generic patterns in the wrong language are noise, not signal.

    3. List your actual drift: Read your own session history, learnings, and post-mortems. What breaks in practice? Does the skill check for those things?

    4. Measure by deletion: After adaptation, how much of the original survives? If you're keeping less than 30%, you would have been faster writing from scratch.

    5. Test against your conventions: Does every check in the skill map to a specific convention or rule in your project? If not, it's generic advice wearing a skill's clothing.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-good-adaptation-looks-like","level":2,"title":"What Good Adaptation Looks Like","text":"

    The consolidation skill went from:

    Before After 4 files, 700+ lines 1 file, ~120 lines Rust examples Go-specific rg commands 8 generic dimensions 9 project-specific checks 6 report templates 1 focused output format Scoring system (A+ to F) Findings + priority + suggested fixes \"Check for duplication\" \"Check for Is* predicate prefixes in exported methods\"

    The adapted version is smaller, faster to parse, and catches the things that actually drift in this project.

    That's the difference between a template and a tool.

    If You Remember One Thing from This Post...

    Frameworks travel. Expertise doesn't.

    You can import structures, matrices, and workflows.

    But the checks that matter only grow where the scars are:

    • the conventions that were violated,
    • the patterns that drifted,
    • and the specific ways this codebase accumulates debt.

    This post was written during a consolidation session where the consolidation skill itself became the subject of consolidation. The meta continues.

    ","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/","level":1,"title":"The Anatomy of a Skill That Works","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism. References to ctx-save, ctx session, and .context/sessions/ in this post reflect the architecture at the time of writing.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#what-20-skill-rewrites-taught-me-about-guiding-ai","level":2,"title":"What 20 Skill Rewrites Taught Me about Guiding AI","text":"

    Jose Alekhinne / 2026-02-07

    Why Do Some Skills Produce Great Results While Others Get Ignored or Produce Garbage?

    I had 20 skills. Most were well-intentioned stubs: a description, a command to run, and a wish for the best.

    Then I rewrote all of them in a single session. This is what I learned.

    In Skills That Fight the Platform, I described what skills should not do. In You Can't Import Expertise, I showed why templates fail. This post completes the trilogy: the concrete patterns that make a skill actually work.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-starting-point","level":2,"title":"The Starting Point","text":"

    Here is what a typical skill looked like before the rewrite:

    ---\nname: ctx-save\ndescription: \"Save session snapshot.\"\n---\n\nSave the current context state to `.context/sessions/`.\n\n## Execution\n\nctx session save $ARGUMENTS\n\nReport the saved session file path to the user.\n

    Seven lines of body. A vague description. No guidance on when to use it, when not to, what the command actually accepts, or how to tell if it worked.

    As a result, the agent would either never trigger the skill (the description was too vague), or trigger it and produce shallow output (no examples to calibrate quality).

    A skill without boundaries is just a suggestion.

    More precisely: the most effective boundary I found was a quality gate that runs before execution, not during it.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-pattern-that-emerged","level":2,"title":"The Pattern That Emerged","text":"

    After rewriting 20 skills, a repeatable anatomy emerged (independent of the skill's purpose). Not every skill needs every section, but the effective ones share the same bones:

    Section What It Does Before X-ing Pre-flight checks; prevents premature execution When to Use Positive triggers; narrows activation When NOT to Use Negative triggers; prevents misuse Usage Examples Invocation patterns the agent can pattern-match Process/Execution What to do; commands, steps, flags Good/Bad Examples Desired vs undesired output; sets boundaries Quality Checklist Verify before claiming completion

    I realized the first three sections matter more than the rest; because a skill with great execution steps but no activation guidance is like a manual for a tool nobody knows they have.

    Anti-Pattern: The Perfect Execution Trap

    A skill with detailed execution steps but no activation guidance will fail more often than a vague skill because it executes confidently at the wrong time.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-1-quality-gates-prevent-premature-execution","level":2,"title":"Lesson 1: Quality Gates Prevent Premature Execution","text":"

    The single most impactful addition was a \"Before X-ing\" section at the top of each skill. Not process steps; pre-flight checks.

    ## Before Recording\n\n1. **Check if it belongs here**: is this learning specific\n   to this project, or general knowledge?\n2. **Check for duplicates**: search LEARNINGS.md for similar\n   entries\n3. **Gather the details**: identify context, lesson, and\n   application before recording\n
    • Without this gate, the agent would execute immediately on trigger.
    • With it, the agent pauses to verify preconditions.

    The difference is dramatic: instead of shallow, reflexive execution, you get considered output.

    Readback

    For the astute readers, the aviation parallel is intentional:

    Pilots do not skip the pre-flight checklist because they have flown before.

    The checklist exists precisely because the stakes are high enough that \"I know what I'm doing\" is not sufficient.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-2-when-not-to-use-is-not-optional","level":2,"title":"Lesson 2: \"When NOT to Use\" Is Not Optional","text":"

    Every skill had a \"When to Use\" section. Almost none had \"When NOT to Use\". This is a problem.

    AI agents are biased toward action. Given a skill that says \"use when journal entries need enrichment\", the agent will find reasons to enrich.

    Without explicit negative triggers, over-activation is not a bug; it is the default behavior.

    Some examples of negative triggers that made a real difference:

    Skill Negative Trigger ctx-reflect \"When the user is in flow; do not interrupt\" ctx-save \"After trivial changes; a typo does not need a snapshot\" prompt-audit \"Unsolicited; only when the user invokes it\" qa \"Mid-development when code is intentionally incomplete\"

    These are not just nice-to-have. They are load-bearing.

    Withoutthem, the agent will trigger the skill at the wrong time, produce unwanted output, and erode the user's trust in the skill system.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-3-examples-set-boundaries-better-than-rules","level":2,"title":"Lesson 3: Examples Set Boundaries Better than Rules","text":"

    The most common failure mode of thin skills was not wrong behavior but vague behavior. The agent would do roughly the right thing, but at a quality level that required human cleanup.

    Rules like \"be constructive, not critical\" are too abstract. What does \"constructive\" look like in a prompt audit report? The agent has to guess.

    Good/bad example pairs avoid guessing:

    ### Good Example\n\n> This session implemented the cooldown mechanism for\n> `ctx agent`. We discovered that `$PPID` in hook context\n> resolves to the Claude Code PID.\n>\n> I'd suggest persisting:\n> - **Learning**: `$PPID` resolves to Claude Code PID\n>   `ctx add learning --context \"...\" --lesson \"...\"`\n> - **Task**: mark \"Add cooldown\" as done\n\n### Bad Examples\n\n* \"*We did some stuff. Want me to save it?*\"\n* Listing 10 trivial learnings that are general knowledge\n* Persisting without asking the user first\n

    The good example shows the exact format, level of detail, and command syntax. The bad examples show where the boundary is.

    Together, they define a quality corridor without prescribing every word.

    Rules describe. Examples demonstrate.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-4-skills-are-read-by-agents-not-humans","level":2,"title":"Lesson 4: Skills Are Read by Agents, Not Humans","text":"

    This seems obvious, but it has non-obvious consequences. During the rewrite, one skill included guidance that said \"use a blog or notes app\" for general knowledge that does not belong in the project's learnings file.

    The agent does not have a notes app. It does not browse the web to find one. This instruction, clearly written for a human audience, was dead weight in a skill consumed by an AI.

    Skills Are for the Agents

    Every sentence in a skill should be actionable by the agent.

    If the guidance requires human judgment or human tools, it belongs in documentation, not in a skill.

    The corollary: command references must be exact.

    A skill that says \"save it somewhere\" is useless.

    A skill that says ctx add learning --context \"...\" --lesson \"...\" --application \"...\" is actionable.

    The agent can pattern-match and fill in the blanks.

    Litmus test: If a sentence starts with \"you could...\" or assumes external tools, it does not belong in a skill.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-5-the-description-field-is-the-trigger","level":2,"title":"Lesson 5: The Description Field Is the Trigger","text":"

    This was covered in Skills That Fight the Platform, but the rewrite reinforced it with data. Several skills had good bodies but vague descriptions:

    # Before: vague, activates too broadly or not at all\ndescription: \"Show context summary.\"\n\n# After: specific, activates at the right time\ndescription: \"Show context summary. Use at session start or\n  when unclear about current project state.\"\n

    The description is not a title. It is the activation condition.

    The platform's skill matching reads this field to decide whether to surface the skill. A vague description means the skill either never triggers or triggers when it should not.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-6-flag-tables-beat-prose","level":2,"title":"Lesson 6: Flag Tables Beat Prose","text":"

    Most skills wrap CLI tools. The thin versions described flags in prose, if at all. The rewritten versions use tables:

    | Flag        | Short | Default | Purpose                  |\n|-------------|-------|---------|--------------------------|\n| `--limit`   | `-n`  | 20      | Maximum sessions to show |\n| `--project` | `-p`  | \"\"      | Filter by project name   |\n| `--full`    |       | false   | Show complete content    |\n

    Tables are scannable, complete, and unambiguous.

    The agent can read them faster than parsing prose, and they serve as both reference and validation: If the agent invokes a flag not in the table, something is wrong.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-7-template-drift-is-a-real-maintenance-burden","level":2,"title":"Lesson 7: Template Drift Is a Real Maintenance Burden","text":"

    // TODO: this has changed; we deploy from the marketplace; update it. // at least add an admonition saying thing are different now.

    ctx deploys skills through templates (via ctx init). Every skill exists in two places: the live version (.claude/skills/) and the template (internal/assets/claude/skills/).

    They must match.

    During the rewrite, every skill update required editing both files and running diff to verify. This sounds trivial, but across 16 template-backed skills, it was the most error-prone part of the process.

    Template drift is dangerous because it creates false confidence: the agent appears to follow rules that no longer exist.

    The lesson: if your skills have a deployment mechanism, build the drift check into your workflow. We added a row to the update-docs skill's mapping table specifically for this:

    | `internal/assets/claude/skills/` | `.claude/skills/` (live) |\n

    Intentional differences (like project-specific scripts in the live version but not the template) should be documented, not discovered later as bugs.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-rewrite-scorecard","level":2,"title":"The Rewrite Scorecard","text":"Metric Before After Average skill body ~15 lines ~80 lines Skills with quality gate 0 20 Skills with \"When NOT\" 0 20 Skills with examples 3 20 Skills with flag tables 2 12 Skills with checklist 0 20

    More lines, but almost entirely Expert content (per the E/A/R framework). No personality roleplay, no redundant guidance, no capability lists. Just project-specific knowledge the platform does not have.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"

    The previous two posts argued that skills should provide knowledge, not personality; that they should complement the platform, not fight it; that they should grow from project history, not imported templates.

    This post adds the missing piece: structure.

    A skill without a structure is a wish.

    A skill with quality gates, negative triggers, examples, and checklists is a tool: the difference is not the content; it is whether the agent can reliably execute it without human intervention.

    Skills Are Interfaces

    Good skills are not instructions. They are contracts.:

    • They specify preconditions, postconditions, and boundaries.
    • They show what success looks like and what failure looks like.
    • They trust the agent's intelligence but do not trust its assumptions.

    If You Remember One Thing from This Post...

    Skills that work have bones, not just flesh.

    Quality gates, negative triggers, examples, and checklists are the skeleton. The domain knowledge is the muscle.

    Without the skeleton, the muscle has nothing to attach to.

    This post was written during the same session that rewrote all 22 skills. The skill-creator skill was updated to encode these patterns. The meta continues.

    ","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/","level":1,"title":"Not Everything Is a Skill","text":"

    Update (2026-02-11)

    As of v0.4.0, ctx consolidated sessions into the journal mechanism. References to /ctx-save, .context/sessions/, and session auto-save in this post reflect the architecture at the time of writing.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-a-codebase-audit-taught-me-about-restraint","level":2,"title":"What a Codebase Audit Taught Me about Restraint","text":"

    Jose Alekhinne / 2026-02-08

    When You Find a Useful Prompt, What Do You Do with It?

    My instinct was to make it a skill.

    I had just spent three posts explaining how to build skills that work. Naturally, the hammer wanted nails.

    Then I looked at what I was holding and realized: this is not a nail.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit","level":2,"title":"The Audit","text":"

    I wanted to understand how I use ctx:

    • Where the friction is;
    • What works, what drifts;
    • What I keep doing manually that could be automated.

    So I wrote a prompt that spawned eight agents to analyze the codebase from different angles:

    Agent Analysis 1 Extractable patterns from session history 2 Documentation drift (godoc, inline comments) 3 Maintainability (large functions, misplaced code) 4 Security review (CLI-specific surface) 5 Blog theme discovery 6 Roadmap and value opportunities 7 User-facing documentation gaps 8 Agent team strategies for future sessions

    The prompt was specific:

    • read-only agents,
    • structured output format,
    • concrete file references,
    • ranked recommendations.

    It ran for about 20 minutes and produced eight Markdown reports.

    The reports were good: Not perfect, but actionable.

    What mattered was not the speed. It was that the work could be explored without committing to any single outcome.

    They surfaced a stale doc.go referencing a subcommand that was never built.

    They found 311 build-then-test sequences I could reduce to a single make check.

    They identified that 42% of my sessions start with \"do you remember?\", which is a lot of repetition for something a skill could handle.

    I had findings. I had recommendations. I had the instinct to automate.

    And then... I stopped.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-question","level":2,"title":"The Question","text":"

    The natural next step was to wrap the audit prompt as /ctx-audit: a skill you invoke periodically to get a health check. It fits the pattern:

    • It has a clear trigger.
    • It produces structured output.

    But I had just spent a week writing about what makes skills work, and the criteria I established argued against it.

    From The Anatomy of a Skill That Works:

    \"A skill without boundaries is just a suggestion.\"

    From You Can't Import Expertise:

    \"Frameworks travel, expertise doesn't.\"

    From Skills That Fight the Platform:

    \"You are the guest, not the host.\"

    The audit prompt fails all three tests:

    Criterion Audit prompt Good skill Frequency Quarterly, maybe Daily or weekly Stability Tweaked every time Consistent invocation Scope Bespoke, 8 parallel agents Single focused action Trigger \"I feel like auditing\" Clear, repeatable event

    Skills are contracts. Contracts need stable terms.

    A prompt I will rewrite every time I use it is not a contract. It is a conversation starter.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#recipes-vs-skills","level":2,"title":"Recipes vs Skills","text":"

    The distinction that emerged:

    Skill Recipe Invocation /slash-command Copy-paste from a doc Frequency High (daily, weekly) Low (quarterly, ad hoc) Stability Fixed contract Adapted each time Scope One focused action Multi-step orchestration Audience The agent The human (who then prompts) Lives in .claude/skills/ hack/ or docs/ Attention cost Loaded into context on match Zero until needed

    Recipes can later graduate into skills, but only after repetition proves stability.

    That last row matters. Skills consume the attention budget every time the platform considers activating them.

    A skill that triggers quarterly but gets evaluated on every prompt is pure waste: attention spent on something that will say \"When NOT to Use: now\" 99% of the time.

    Runbooks have zero attention cost. They sit in a Markdown file until a human decides to use them.

    • The human provides the judgment about timing.
    • The prompt provides the structure.

    The Attention Budget Applies to Skills Too

    Every skill in .claude/skills/ is a standing claim on the context window. The platform evaluates skill descriptions against every user prompt to decide whether to activate.

    Twenty focused skills are fine. Thirty might be fine. But each one added reduces the headroom available for actual work.

    Recipes are skills that opted out of the attention tax.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-the-audit-actually-produced","level":2,"title":"What the Audit Actually Produced","text":"

    The audit was not wasted. It was a planning exercise that generated concrete tasks:

    Finding Action 42% of sessions start with memory check Task: /ctx-remember skill (this one is a skill; it is daily) Auto-save stubs are empty Task: enhance /ctx-save with richer summaries 311 raw build-test sequences Task: make check target Stale recall/doc.go lists nonexistent serve Task: fix the doc.go 120 commit sequences disconnected from context Task: /ctx-commit workflow
    • Some findings became skills;
    • Some became Makefile targets;
    • Some became one-line doc fixes.

    The audit did not prescribe the artifact type: The findings did.

    The audit is the input. Skills are one possible output. Not the only one.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit-prompt","level":2,"title":"The Audit Prompt","text":"

    Here is the exact prompt I used, for those who are curious.

    This is not a template: It worked because it was written against this codebase, at this moment, with specific goals in mind:

    I want you to create an agent team to audit this codebase. Save each report as\na separate Markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable. Every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (*session mining*)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (*godoc + inline*)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check that package-level comments match\npackage names.\nOutput: drift items ranked by severity with exact file:line references.\n\n### 3. Maintainability\nLook for:\n- functions longer than 80 lines with clear split points\n- switch blocks with more than 5 cases that could be table-driven\n- inline comments like \"step 1\", \"step 2\" that indicate a block wants to be a function\n- files longer than 400 lines\n- flat packages that could benefit from sub-packages\n- functions that appear misplaced in their file\n\nDo NOT flag things that are fine as-is just because they could theoretically\nbe different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app. Focus on CLI-relevant attack surface, not web OWASP:\n- file path traversal\n- command injection\n- symlink following when writing to `.context/`\n- permission handling\n- sensitive data in outputs\n\nOutput: findings with severity ratings and plausible exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git history,\nrecent session discussions, and `DECISIONS.md` for story arcs worth writing about.\nSuggest 3-5 blog post themes with:\n- title\n- angle\n- target audience\n- key commits or sessions to reference\n- a 2-sentence pitch\n\nPrioritize themes that build a coherent narrative across posts.\n\n### 6. Roadmap and Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses,\nidentify the highest-value improvements. Consider user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with rough effort and impact estimates.\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and user docs. Suggest improvements structured as\nuse-case pages: the problem, how ctx solves it, a typical workflow, and gotchas.\nIdentify gaps where a user would get stuck without reading source code.\nOutput: documentation gaps with suggested page outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each, include:\n- team composition (roles and agent types)\n- task distribution strategy\n- coordination approach\n- the kinds of work it suits\n

    Avoid Generic Advice

    Suggestions that are not grounded in a project's actual structure, history, and workflows are worse than useless:

    They create false confidence.

    If an analysis cannot point to concrete files, commits, sessions, or patterns, it should say \"no finding\" instead of inventing best practices.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"

    This is part of a pattern I keep rediscovering:

    The urge to automate is not the same as the need to automate:

    • The 3:1 ratio taught me that not every session should be a YOLO sprint.
    • The E/A/R framework taught me that not every template is worth importing. Now the audit is teaching me that not every useful prompt is worth institutionalizing.

    The common thread is restraint:

    • Knowing when to stop.
    • Recognizing that the cost of automation is not just the effort to build it.

    The cost is the ongoing attention tax of maintaining it, the context it consumes, and the false confidence it creates when it drifts.

    An entry in hack/runbooks/codebase-audit.md is honest about what it is:

    A prompt I wrote once, improved once, and will adapt again next time:

    • It does not pretend to be a reliable contract.
    • It does not claim attention budget.
    • It does not drift silently.

    The Automation Instinct

    When you find a useful prompt, the instinct is to institutionalize it. Resist.

    Ask first: will I use this the same way next time?

    If yes, it is a skill. If no, it is a recipe. If you are not sure, it is a recipe until proven otherwise.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#this-mindset-in-the-context-of-ctx","level":2,"title":"This Mindset in the Context of ctx","text":"

    ctx is a tool that gives AI agents persistent memory. Its purpose is automation: reducing the friction of context loading, session recall, decision tracking.

    But automation has boundaries, and knowing where those boundaries are is as important as pushing them forward.

    The skills system is for high-frequency, stable workflows.

    The recipes, the journal entries, the session dumps in .context/sessions/: those are for everything else.

    Not everything needs to be a slash command. Some things are better as Markdown files you read when you need them.

    The goal of ctx is not to automate everything: It is to automate the right things and to make the rest easy to find when you need it.

    If You Remember One Thing from This Post...

    The best automation decision is sometimes not to automate.

    A runbook in a Markdown file costs nothing until you use it.

    A skill costs attention on every prompt, whether it fires or not.

    Automate the daily. Document the periodic. Forget the rest.

    This post was written during the session that produced the codebase audit reports and distilled the prompt into hack/runbooks/codebase-audit.md. The audit generated seven tasks, one Makefile target, and zero new skills. The meta continues.

    See also: Code Is Cheap. Judgment Is Not.: the capstone that threads this post's restraint argument into the broader case for why judgment, not production, is the bottleneck.

    ","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#when-markdown-is-not-a-security-boundary","level":2,"title":"When Markdown Is Not a Security Boundary","text":"

    Volkan Özçelik / 2026-02-09

    What Happens When Your AI Agent Runs Overnight and Nobody Is Watching?

    It follows instructions: That is the problem.

    Not because it is malicious. Because it is controllable.

    It follows instructions from context, and context can be poisoned.

    I was writing the autonomous loops recipe for ctx: the guide for running an AI agent in a loop overnight, unattended, working through tasks while you sleep. The original draft had a tip at the bottom:

    Use CONSTITUTION.md for guardrails. Tell the agent \"never delete tests\" and it usually won't.

    Then I read that sentence back and realized: that is wishful thinking.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-realization","level":2,"title":"The Realization","text":"

    CONSTITUTION.md is a Markdown file. The agent reads it at session start alongside everything else in .context/. It is one source of instructions in a context window that also contains system prompts, project files, conversation history, tool outputs, and whatever the agent fetched from the internet.

    An attacker who can inject content into any of those sources can redirect the agent's behavior. And \"attacker\" does not always mean a person with malicious intent. It can be:

    Vector Example A dependency A malicious npm package with instructions in its README or error output A URL Documentation page with embedded adversarial instructions A project file A contributor who adds instructions to CLAUDE.md or .cursorrules The agent itself In an autonomous loop, the agent modifies its own config between iterations A command output An error message containing instructions the agent interprets and follows

    That last vector is the one that kept me up at night (literally!):

    In an autonomous loop, the agent modifies files as part of its job.

    If it modifies its own configuration files, the next iteration loads the modified config.

    • No human reviews it.
    • No diff is shown.
    • The agent that starts iteration N+1 is running with rules written by iteration N.

    The agent can rewrite its own guardrails.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#five-layers-each-with-a-hole","level":2,"title":"Five Layers, Each with a Hole","text":"

    That's five nested layers of swiss cheese. Alone, each of them has large holes. Together, they create a boundary.

    What followed was a week of peeling back assumptions:

    Every defenseI examined had a bypass, and the bypass was always the same shape: the defense was enforced at a level the agent could reach.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"

    CONSTITUTION.md, the Agent Playbook, system prompts: These tell the agent what to do.

    The agent usually follows them.

    \"Usually\" is the keyword here.

    The hole: Prompt injection:

    • A sufficiently crafted payload overrides soft instructions.
    • Long context windows dilute attention on rules stated early.
    • Edge cases where instructions are ambiguous get resolved in unpredictable ways.

    Verdict: Necessary. Not sufficient. Good for the common case. Never trust it as a security boundary.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"

    Permission allowlists in .claude/settings.local.json:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n

    If rm, curl, sudo, or docker are not in the allowlist, the agent cannot invoke them. This is deterministic.

    The application enforces it regardless of what any prompt says.

    The hole: The agent can modify the allowlist itself:

    • It has Write permission.
    • The allowlist lives in a file.
    • The agent writes to the file.
    • The next iteration loads the modified allowlist.

    The application enforces the rules, but the application reads the rules from files the agent can write.

    Verdict: Strong first layer. Must be combined with self-modification prevention.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-3-os-level-isolation-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Unbypassable)","text":"

    This is where the defenses stop having holes in the same shape.

    The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without CAP_NET_RAW cannot open raw sockets. These are kernel boundaries.

    Control What it stops Dedicated unprivileged user Privilege escalation, sudo, group-based access Filesystem permissions Lateral movement to other projects, system config Immutable config files Self-modification of guardrails between iterations

    Make the agent's instruction files read-only: CLAUDE.md, .claude/settings.local.json, .context/CONSTITUTION.md. Own them as a different user, or mark them immutable with chattr +i on Linux.

    The hole: Actions within the agent's legitimate scope:

    • If the agent has write access to source code (which it needs), it can introduce vulnerabilities in the code itself.
    • You cannot prevent this without removing the agent's ability to do its job.

    Verdict: Essential. This is the layer that makes Layers 1 and 2 trustworthy.

    OS-level isolation does not make the agent safe; it makes the other layers meaningful.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"

    An agent that cannot reach the internet cannot exfiltrate data.

    It also cannot ingest new instructions mid-loop from external documents, error pages, or hostile content.

    # Container with no network\ndocker run --network=none ...\n\n# Or firewall rules allowing only package registries\niptables -A OUTPUT -d registry.npmjs.org -j ACCEPT\niptables -A OUTPUT -d proxy.golang.org -j ACCEPT\niptables -A OUTPUT -j DROP\n
    • If the agent genuinely does not need the network, disable it entirely.
    • If it needs to fetch dependencies, allow specific registries and block everything else.

    The hole: None, if the agent does not need the network.

    Thetradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"

    The strongest boundary is a separate machine.

    The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n

    Never Mount the Docker Socket

    Do not mount /var/run/docker.sock, like, ever.

    An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox.

    This is not theoretical: the Docker socket grants root-equivalent access to the host.

    Use rootless Docker or Podman to eliminate this escalation path entirely.

    Virtual machines are even stronger: The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-pattern","level":2,"title":"The Pattern","text":"

    Each layer is straightforward: The strength is in the combination:

    Layer Implementation What it stops Soft instructions CONSTITUTION.md Common mistakes (probabilistic) Application allowlist .claude/settings.local.json Unauthorized commands (deterministic within runtime) Immutable config chattr +i on config files Self-modification between iterations Unprivileged user Dedicated user, no sudo Privilege escalation Container --cap-drop=ALL --network=none Host escape, data exfiltration Resource limits --memory=4g --cpus=2 Resource exhaustion

    No layer is redundant. Each one catches what the others miss:

    • The soft instructions handle the 99% case: \"don't delete tests.\"
    • The allowlist prevents the agent from running commands it should not.
    • The immutable config prevents the agent from modifying the allowlist.
    • The unprivileged user prevents the agent from removing the immutable flag.
    • The container prevents the agent from reaching anything outside its workspace.
    • The resource limits prevent the agent from consuming all system resources.

    Remove any one layer and there is an attack path through the remaining ones.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#common-mistakes-i-see","level":2,"title":"Common Mistakes I See","text":"

    These are real patterns, not hypotheticals:

    \"I'll just use --dangerously-skip-permissions.\" This disables Layer 2 entirely. Without Layers 3 through 5, you have no protection at all. The flag means what it says. If you ever need to, think thrice, you probably don't. But, if you ever need to usee this only use it inside a properly isolated VM (not even a container: a \"VM\").

    \"The agent is sandboxed in Docker.\" A Docker container with the Docker socket mounted, running as root, with --privileged, and full network access is not sandboxed. It is a root shell with extra steps.

    \"I reviewed CLAUDE.md, it's fine.\" You reviewed it before the loop started. The agent modified it during iteration 3. Iteration 4 loaded the modified version. Unless the file is immutable, your review is futile.

    \"The agent only has access to this one project.\" Does the project directory contain .env files? SSH keys? API tokens? A .git/config with push access to a remote? Filesystem isolation means isolating what is in the directory too.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"

    This is the same lesson I keep rediscovering, wearing different clothes.

    In The Attention Budget, I wrote about how every token competes for the AI's focus. Security instructions in CONSTITUTION.md are subject to the same budget pressure: if the context window is full of code, error messages, and tool outputs, the security rules stated at the top get diluted.

    In Skills That Fight the Platform, I wrote about how custom instructions can conflict with the AI's built-in behavior. Security rules have the same problem: telling an agent \"never run curl\" in Markdown while giving it unrestricted shell access creates a contradiction: The agent resolves contradictions unpredictably. The agent will often pick the path of least resistance to attain its objective function. And, trust me, agents can get far more creative than the best red-teamer you know.

    In You Can't Import Expertise, I wrote about how generic templates fail because they do not encode project-specific knowledge. Generic security advice fails the same way: \"Don't exfiltrate data\" is a category; blocking outbound network access is a control.

    The pattern across all of these: Soft instructions are useful for the common case. Hard boundaries are required for security.

    Know which is which.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-checklist","level":2,"title":"The Checklist","text":"

    Before running an unattended AI agent:

    • Agent runs as a dedicated unprivileged user (no sudo, no docker group)
    • Agent's config files are immutable or owned by a different user
    • Permission allowlist restricts tools to the project's toolchain
    • Container drops all capabilities (--cap-drop=ALL)
    • Docker socket is NOT mounted
    • Network is disabled or restricted to specific domains
    • Resource limits are set (memory, CPU, disk)
    • No SSH keys, API tokens, or credentials are accessible
    • Project directory does not contain .env or secrets files
    • Iteration cap is set (--max-iterations)

    This checklist lives in the Agent Security reference alongside the full threat model and detailed guidance for each layer.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#what-changed-in-ctx","level":2,"title":"What Changed in ctx","text":"

    The autonomous loops recipe now has a full permissions and isolation section instead of a one-line tip about CONSTITUTION.md. It covers both the explicit allowlist approach and the --dangerously-skip-permissions flag, with honest guidance about when each is appropriate.

    It also has an OS-level isolation table that is not optional: unprivileged users, filesystem permissions, containers, VMs, network controls, resource limits, and self-modification prevention.

    The Agent Security page consolidates the threat model and defense layers into a standalone reference.

    These are not theoretical improvements. They are the minimum responsible guidance for a tool that helps people run AI agents overnight.

    If You Remember One Thing from This Post...

    Markdown is not a security boundary.

    CONSTITUTION.md is a nudge. An allowlist is a gate.

    An unprivileged user in a network-isolated container is a wall.

    Use all three. Trust only the wall.

    This post was written during the session that added permissions, isolation, and self-modification prevention to the autonomous loops recipe. The security guidance started as a single tip and grew into two documents. The meta continues.

    ","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/","level":1,"title":"How Deep Is Too Deep?","text":"","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#when-master-ml-is-the-wrong-next-step","level":2,"title":"When \"Master ML\" Is the Wrong Next Step","text":"

    Volkan Özçelik / 2026-02-12

    Have You Ever Felt like You Should Understand More of the Stack beneath You?

    You can talk about transformers at a whiteboard.

    You can explain attention to a colleague.

    You can use agentic AI to ship real software.

    But somewhere in the back of your mind, there is a voice:

    \"Maybe I should go deeper. Maybe I need to master machine learning.\"

    I had that voice for months.

    Then I spent a week debugging an agent failure that had nothing to do with ML theory and everything to do with knowing which abstraction was leaking.

    This post is about when depth compounds and (more importantly) when it does not.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-hierarchy-nobody-questions","level":2,"title":"The Hierarchy Nobody Questions","text":"

    There is an implicit stack most people carry around when thinking about AI:

    Layer What Lives Here Agentic AI Autonomous loops, tool use, multi-step reasoning Generative AI Text, image, code generation Deep Learning Transformer architectures, training at scale Neural Networks Backpropagation, gradient descent Machine Learning Statistical learning, optimization Classical AI Search, planning, symbolic reasoning

    At some point down that stack, you hit a comfortable plateau: the layer where you can hold a conversation but not debug a failure.

    The instinctive response is to go deeper.

    But that instinct hides a more important question:

    \"Does depth still compound when the abstractions above you are moving hyper-exponentially?\"

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-honest-observation","level":2,"title":"The Honest Observation","text":"

    If you squint hard enough, a large chunk of modern ML intuition collapses into older fields:

    ML Concept Older Field Gradient descent Numerical optimization Backpropagation Reverse-mode autodiff Loss landscapes Non-convex optimization Generalization Statistics Scaling laws Asymptotics and information theory

    Nothing here is uniquely \"AI\".

    Most of this math predates the term deep learning. In some cases, by decades.

    So what changed?

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#same-tools-different-regime","level":2,"title":"Same Tools, Different Regime","text":"

    The mistake is assuming this is a new theory problem: It is not.

    It is a new operating regime.

    Classical numerical methods were developed under assumptions like:

    • Manageable dimensionality
    • Reasonably well-conditioned objectives
    • Losses that actually represent the goal

    Modern ML violates all three: On purpose.

    Today's models operate with millions to trillions of parameters, wildly underdetermined systems, and objective functions we know are wrong but optimize anyway.

    It is complete and utter madness!

    At this scale, familiar concepts warp:

    • What we call \"local minima\" are overwhelmingly saddle points in high-dimensional spaces.
    • Noise stops being noise and starts becoming structure.
    • Overfitting can coexist with generalization.
    • Bigger models outperform \"better\" ones.

    The math did not change: The phase did.

    This is less numerical analysis and more *statistical physics: Same equations, but behavior dominated by phase transitions and emergent structure.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#why-scaling-laws-feel-alien","level":2,"title":"Why Scaling Laws Feel Alien","text":"

    In classical statistics, asymptotics describe what happens eventually.

    In modern ML, scaling laws describe where you can operate today.

    They do not say \"given enough time, things converge\".

    They say \"cross this threshold and behavior qualitatively changes\".

    This is why dumb architectures plus scale beat clever ones.

    Why small theoretical gains disappear under data.

    Why \"just make it bigger\", ironically, keeps working longer than it should.

    That is not a triumph of ML theory: It is a property of high-dimensional systems under loose objectives.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#where-depth-actually-pays-off","level":2,"title":"Where Depth Actually Pays Off","text":"

    This reframes the original question.

    You do not need depth because this is \"AI\".

    You need depth where failure modes propagate upward.

    I learned this building ctx: The agent failures I have spent the most time debugging were never about the model's architecture.

    They were about:

    • Misplaced trust: The model was confident. The output was wrong. Knowing when confidence and correctness diverge is not something you learn from a textbook. You learn it from watching patterns across hundreds of sessions.

    • Distribution shift: The model performed well on common patterns and fell apart on edge cases specific to this project. Recognizing that shift before it compounds requires understanding why generalization has limits, not just that it does.

    • Error accumulation: In a single prompt, model quirks are tolerable. In autonomous loops running overnight, they compound. A small bias in how the model interprets instructions becomes a large drift by iteration 20.

    • Scale hiding errors: The model's raw capability masked problems that only surfaced under specific conditions. More parameters did not fix the issue. They just made the failure mode rarer and harder to reproduce.

    This is the kind of depth that compounds. Not deriving backprop. But, understanding when correct math produces misleading intuition.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"

    This is the same pattern I keep finding at different altitudes.

    In \"The Attention Budget\", I wrote about how dumping everything into the context window degrades the model's focus. The fix was not a better model: It was better curation: load less, load the right things, preserve signal per token.

    In \"Skills That Fight the Platform\", I wrote about how custom instructions can conflict with the model's built-in behavior. The fix was not deeper ML knowledge: It was an understanding that the model already has judgment and that you should extend it, not override it.

    In \"You Can't Import Expertise\", I wrote about how generic templates fail because they do not encode project-specific knowledge. A consolidation skill with eight Rust-based analysis dimensions was mostly noise for a Go project. The fix was not a better template: It was growing expertise from this project's own history.

    In every case, the answer was not \"go deeper into ML\".

    The answer was knowing which abstraction was leaking and fixing it at the right layer.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#agentic-systems-are-not-an-ml-problem","level":2,"title":"Agentic Systems Are Not an ML Problem","text":"

    The mistake is assuming agent failures originate where the model was trained, rather than where it is deployed.

    Agentic AI is a systems problem under chaotic uncertainty:

    • Feedback loops between the agent and its environment;
    • Error accumulation across iterations;
    • Brittle representations that break outside training distribution;
    • Misplaced trust in outputs that look correct.

    In short-lived interactions, model quirks are tolerable. In long-running autonomous loops, however, they compound.

    That is where shallow understanding becomes expensive.

    But the understanding you need is not about optimizer internals.

    It is about:

    What Matters What Does Not (for Most Practitioners) Why gradient descent fails in specific regimes How to derive it from scratch When memorization masquerades as reasoning The formal definition of VC dimension Recognizing distribution shift before it compounds Hand-tuning learning rate schedules Predicting when scale hides errors instead of fixing them Chasing theoretical purity divorced from practice

    The depth that matters is diagnostic, not theoretical.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-real-answer","level":2,"title":"The Real Answer","text":"

    Not turtles all the way down.

    Go deep enough to:

    • Diagnose failures instead of cargo-culting fixes;
    • Reason about uncertainty instead of trusting confidence;
    • Design guardrails that align with model behavior, not hope.

    Stop before:

    • Hand-deriving gradients for the sake of it;
    • Obsessing over optimizer internals you will never touch;
    • Chasing theoretical purity divorced from the scale you actually operate at.

    This is not about mastering ML.

    It is about knowing which abstractions you can safely trust and which ones leak.

    Hint: Any useful abstraction almost certainly leaks.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#a-practical-litmus-test","level":2,"title":"A Practical Litmus Test","text":"

    If a failure occurs and your instinct is to:

    • Add more prompt text: abstraction leak above
    • Add retries or heuristics: error accumulation
    • Change the model: scale masking
    • Reach for ML theory: you are probably (but not always) going too deep

    The right depth is the shallowest layer where the failure becomes predictable.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-ctx-lesson","level":2,"title":"The ctx Lesson","text":"

    Every design decision in ctx is downstream of this principle.

    The attention budget exists because the model's internal attention mechanism has real limits: You do not need to understand the math of softmax to build around it. But you do need to understand that more context is not always better and that attention density degrades with scale.

    The skill system exists because the model's built-in behavior is already good: You do not need to understand RLHF to build effective skills. But you do need to understand that the model already has judgment and your skills should teach it things it does not know, not override how it thinks.

    Defense in depth exists because soft instructions are probabilistic: You do not need to understand the transformer architecture to know that a Markdown file is not a security boundary. But you do need to understand that the model follows instructions from context, and context can be poisoned.

    In each case, the useful depth was one or two layers below the abstraction I was working at: Not at the bottom of the stack.

    The boundary between useful understanding and academic exercise is where your failure modes live.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#closing-thought","level":2,"title":"Closing Thought","text":"

    Most modern AI systems do not fail because the math is wrong.

    They fail because we apply correct math in the wrong regime, then build autonomous systems on top of it.

    Understanding that boundary, not crossing it blindly, is where depth still compounds.

    And that is a far more useful form of expertise than memorizing another loss function.

    If You Remember One Thing from This Post...

    Go deep enough to diagnose your failures. Stop before you are solving problems that do not propagate to your layer.

    The abstractions below you are not sacred. But neither are they irrelevant.

    The useful depth is wherever your failure modes live. Usually one or two layers down, not at the bottom.

    This post started as a note about whether I should take an ML course. The answer turned out to be \"no, but understand why not\". The meta continues.

    ","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/","level":1,"title":"Before Context Windows, We Had Bouncers","text":"","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-reset-problem","level":2,"title":"The Reset Problem","text":"

    IRC is stateless.

    • You disconnect, you vanish.
    • You reconnect, you begin again.

    No buffer.

    No memory.

    No continuity.

    Modern systems are not much different:

    • Close the browser tab.
      • Lose the Slack scrollback.
    • Open a new LLM session.
      • Start from zero.

    Resets externalize reconstruction cost onto humans.

    Reconstruction is tax: Tax becomes entropy.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#stateless-protocol-stateful-life","level":2,"title":"Stateless Protocol, Stateful Life","text":"

    IRC is minimal:

    • A TCP connection.
    • A nickname.
    • A channel.
    • A stream of lines.

    When the connection drops, you literally disappear from the graph.

    The protocol is stateless; human systems are not.

    So you:

    • Reconnect;
    • Ask what you missed;
    • Scroll;
    • Reconstruct.

    The machine forgets; you pay.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-bouncer-pattern","level":2,"title":"The Bouncer Pattern","text":"

    A bouncer is a daemon that remains connected when you do not:

    • It holds your seat;
    • It buffers what you missed;
    • It keeps your identity online.

    ZNC is one such bouncer.

    With ZNC:

    • Your client does not connect to IRC;
    • It connects to ZNC;
    • ZNC connects upstream.

    Client sessions become ephemeral.

    Presence becomes infrastructural.

    ZNC Is Tmux for IRC

    • Close your laptop.

      • ZNC remains.
    • Switch devices.

      • ZNC persists.

    This is not convenience; this is continuity.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#presence-without-flapping","level":2,"title":"Presence without Flapping","text":"

    With a bouncer:

    • Closing your client does not emit PART.
    • Reopening does not emit JOIN.

    You do not flap in and out of existence.

    From the channel's perspective, you remain.

    From your perspective, history accumulates.

    • Buffers persist;
    • Identity persists;
    • Context persists.

    This pattern predates AI.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#before-llm-context-windows","level":2,"title":"Before LLM Context Windows","text":"

    An LLM session without memory is IRC without a bouncer:

    • Close the window.
    • Start over.
    • Re-explain intent.
    • Rehydrate context.

    That is friction.

    This Walks and Talks like ctx

    Context engineering moves memory out of sessions and into infrastructure.

    • ZNC does this for IRC.
    • ctx does this for agents.

    Same principle:

    • Volatile interface.
    • Persistent substrate.

    Different fabric.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#minimal-architecture","level":2,"title":"Minimal Architecture","text":"

    My setup is intentionally boring:

    • A $5 small VPS.
    • ZNC installed.
    • TLS enabled.
    • Firewall restricted.

    Then:

    • ZNC connects to Libera.Chat.
    • SASL authentication lives inside ZNC.
    • Buffers are stored on disk.

    My client connects to my VPS, not the network.

    The commands do not matter: The boundaries do:

    • Authentication in infrastructure, not in the client;
    • Memory server-side, not in scrollback;
    • Presence decoupled from activity.

    Everything else is configuration.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#platform-memory","level":2,"title":"Platform Memory","text":"

    Yes, I know, it is 2026:

    • Discord stores history;
    • Slack stores history;
    • The dumpster fire on gasoline called X, too, stores history.

    HOWEVER, they own your substrate.

    Running a bouncer is quiet sovereignty:

    • Logs are mine.
    • Presence is continuous.
    • State does not reset because I closed a tab.

    Small acts compound.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#signal-density","level":2,"title":"Signal Density","text":"

    Primitive systems select for builders.

    Consistent presence in small rooms compounds reputation.

    Quiet compounding outperforms viral spikes.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#infrastructure-as-cognition","level":2,"title":"Infrastructure as Cognition","text":"

    ZNC is not interesting because it is retro; it is interesting because it models a principle:

    • Stateless protocols require stateful wrappers;
    • Volatile interfaces require durable memory;
    • Human systems require continuity.

    Distilled:

    Humans require context.

    Before context windows, we had bouncers.

    Before AI memory files, we had buffers.

    Continuity is not a feature; it is a design decision.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#build-it","level":2,"title":"Build It","text":"

    If you want the actual setup (VPS, ZNC, TLS, SASL, firewall...) there is a step-by-step runbook:

    Persistent IRC Presence with ZNC.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#motd","level":2,"title":"MOTD","text":"

    When my client connects to my bouncer, it prints:

    //   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n

    See also: Context as Infrastructure -- the post that takes this observation to its conclusion: stateless protocols need stateful wrappers, and AI sessions need persistent filesystems.

    ","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/","level":1,"title":"Parallel Agents with Git Worktrees","text":"","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-backlog-problem","level":2,"title":"The Backlog Problem","text":"

    Jose Alekhinne / 2026-02-14

    What Do You Do with 30 Open Tasks?

    You could work through them one at a time.

    One agent, one branch, one commit stream.

    Or you could ask: which of these don't touch each other?

    I had 30 open tasks in TASKS.md. Some were docs. Some were a new encryption package. Some were test coverage for a stable module. Some were blog posts.

    They had almost zero file overlap.

    Running one agent at a time meant serial execution on work that was fundamentally parallel:

    I was bottlenecking on me, not on the machine.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-insight-file-overlap-is-the-constraint","level":2,"title":"The Insight: File Overlap Is the Constraint","text":"

    This is not a scheduling problem: It's a conflict avoidance problem.

    Two agents can work simultaneously on the same codebase if and only if they don't touch the same files. The moment they do, you get merge conflicts: And merge conflicts on AI-generated code are expensive because the human has to arbitrate choices they didn't make.

    So the question becomes:

    \"Can you partition your backlog into non-overlapping tracks?\"

    For ctx, the answer was obvious:

    Track Touches Tasks work/docs docs/, hack/ Blog posts, recipes, runbooks work/pad internal/cli/pad/, specs Scratchpad encryption, CLI, tests work/tests internal/cli/recall/ Recall test coverage

    Three tracks. Near-zero overlap. Three agents.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#git-worktrees-the-mechanism","level":2,"title":"Git Worktrees: The Mechanism","text":"

    git has a feature that most people don't use: worktrees.

    A worktree is a second (or third, or fourth) working directory that shares the same .git object database as your main checkout.

    Each worktree has its own branch, its own index, its own working tree. But they all share history, refs, and objects.

    git worktree add ../ctx-docs -b work/docs\ngit worktree add ../ctx-pad -b work/pad\ngit worktree add ../ctx-tests -b work/tests\n
    • Three directories;
    • Three branches;
    • One repository.

    This is cheaper than three clones. And because they share objects, git merge afterwards is fast: It's a local operation on shared data.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-setup","level":2,"title":"The Setup","text":"

    The workflow I landed on:

    1. Group tasks by blast radius.

    Read TASKS.md. For each pending task, estimate which files and directories it touches. Group tasks that share files into the same track. Tasks with no overlap go into separate tracks.

    This is the part that requires human judgment:

    An agent can propose groupings, but you need to verify that the boundaries are real. A task that says \"update docs\" but actually touches Go code will poison a docs track.

    2. Create worktrees as sibling directories.

    Not subdirectories: Siblings.

    If your main checkout is at ~/WORKSPACE/ctx, worktrees go at ~/WORKSPACE/ctx-docs, ~/WORKSPACE/ctx-pad, etc.

    Why siblings? Because some tools (and some agents) walk up the directory tree looking for .git. A worktree inside the main checkout confuses them.

    3. Launch one agent per worktree.

    # Terminal 1\ncd ../ctx-docs && claude\n\n# Terminal 2\ncd ../ctx-pad && claude\n\n# Terminal 3\ncd ../ctx-tests && claude\n

    Each agent gets a full working copy with .context/ intact. It reads the same TASKS.md, the same DECISIONS.md, the same CONVENTIONS.md. It knows the full project state. It just works on a different slice.

    4. Do NOT run ctx init in worktrees.

    This is the gotcha. The .context/ directory is tracked in git. Running ctx init in a worktree would overwrite shared context files: Wiping decisions, learnings, and tasks that belong to the whole project.

    The worktree already has everything it needs. Leave it alone.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#what-actually-happened","level":2,"title":"What Actually Happened","text":"

    I ran three agents for about 40 minutes. Here is roughly what each track produced:

    work/docs: Parallel worktrees recipe, blog post edits, recipe index reorganization, IRC recipe moved from docs/ to hack/.

    work/pad: ctx pad show subcommand, --append and --prepend flags on ctx pad edit, spec updates, 28 new test functions.

    work/tests: Recall test coverage, edge case tests.

    Merging took about five minutes. Two of the three merges were clean.

    The third had a conflict in TASKS.md:

    both the docs track and the pad track had marked different tasks as [x].

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-tasksmd-conflict","level":2,"title":"The TASKS.md Conflict","text":"

    This deserves its own section because it will happen every time.

    When two agents work in parallel, they both read TASKS.md at the start and mark tasks complete as they go. When you merge, git sees two branches that modified the same file differently.

    The resolution is always the same: accept all completions from both sides. No task should go from [x] back to [ ]. The merge is additive.

    This is one of those conflicts that sounds scary but is trivially mechanical: You are not arbitrating design decisions; you are combining two checklists.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#limits","level":2,"title":"Limits","text":"

    3-4 worktrees, maximum.

    I tried four once: By the time I merged the third track, the fourth had drifted far enough that its changes needed rebasing.

    The merge complexity grows faster than the parallelism benefit.

    Three is the sweet spot:

    • Two is conservative but safe;
    • Four is possible if the tracks are truly independent;
    • Anything more than four, you are in the danger zone.

    Group by directory, not by priority.

    It is tempting to put all the high-priority tasks in one track: Don't.

    Two high-priority tasks that touch the same files must be in the same track, regardless of urgency. The constraint is file overlap, not importance.

    Commit frequently.

    Smaller commits make merge conflicts easier to resolve. An agent that writes 500 lines in a single commit is harder to merge than one that commits every logical step.

    Name tracks by concern.

    • work/docs and work/pad tell you what's happening;
    • work/track-1 and work/track-2 tell you nothing.
    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-pattern","level":2,"title":"The Pattern","text":"

    This is the same pattern that shows up everywhere in ctx:

    The attention budget taught me that you can't dump everything into one context window. You have to partition, prioritize, and load selectively.

    Worktrees are the same principle applied to execution: You can't dump every task into one agent's workstream. You have to partition by blast radius, assign selectively, and merge deliberately.

    The codebase audit that generated these 30 tasks used eight parallel agents for analysis. Worktrees let me use parallel agents for implementation. Same coordination pattern, different artifact.

    And the IRC bouncer post from earlier today argued that stateless protocols need stateful wrappers. Worktrees are the same: git branches are stateless forks; .context/ is the stateful wrapper that gives each agent the project's full memory.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#should-this-be-a-skill","level":2,"title":"Should This Be a Skill?","text":"

    I asked myself the same question I asked about the codebase audit: should this be a /ctx-worktree skill?

    This time the answer was a resounding \"yes\":

    Unlike the audit prompt (which I tweak every time and run every other week) the worktree workflow is:

    Criterion Worktree workflow Codebase audit Frequency Weekly Quarterly Stability Same steps every time Tweaked every time Scope Mechanical, bounded Bespoke, 8 agents Trigger Large backlog \"I feel like auditing\"

    The commands are mechanical: git worktree add, git worktree remove, branch naming, safety checks. This is exactly what skills are for: stable contracts for repetitive operations.

    Ergo, /ctx-worktree exists.

    It enforces the 4-worktree limit, creates sibling directories, uses work/ branch prefixes, and reminds you not to run ctx init in worktrees.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-takeaway","level":2,"title":"The Takeaway","text":"

    Serial execution is the default. But serial is not always necessary.

    If your backlog partitions cleanly by file overlap, you can multiply your throughput with nothing more exotic than git worktree and a second terminal window.

    The hard part is not the git commands; it is the discipline:

    • Grouping by blast radius instead of priority;
    • Accepting that TASKS.md will conflict;
    • And knowing when three tracks is enough.

    If You Remember One Thing from This Post...

    Partition by blast radius, not by priority.

    Two tasks that touch the same files belong in the same track, no matter how important the other one is.

    The constraint is file overlap. Everything else is scheduling.

    The practical setup (skill invocation, worktree creation, merge workflow, and cleanup) lives in the recipe: Parallel Agent Development with Git Worktrees.

    ","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/","level":1,"title":"ctx v0.3.0: The Discipline Release","text":"","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#when-the-ratio-of-polish-to-features-is-31-you-know-something-changed","level":2,"title":"When the Ratio of Polish to Features Is 3:1, You Know Something Changed","text":"

    Jose Alekhinne / February 15, 2026

    What Does a Release Look like When Most of the Work Is Invisible?

    No new headline feature. No architectural pivot. No rewrite.

    Just 35+ documentation and quality commits against ~15 feature commits... and somehow, the tool feels like it grew up overnight.

    Six days separate v0.2.0 from v0.3.0.

    Measured by calendar time, it is nothing. Measured by what changed in how the project operates, it is the most significant release yet.

    • v0.1.0 was the prototype;
    • v0.2.0 was the archaeology release: making the past accessible;
    • v0.3.0 is the discipline release: the one that turned best practices into enforcement, suggestions into structure, and a collection of commands into a system of skills.

    The Release Window

    February 1‒February 7, 2026

    From the v0.2.0 tag to commit 2227f99.

    78 files changed in the migration commit alone.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-migration-commands-to-skills","level":2,"title":"The Migration: Commands to Skills","text":"

    The largest single change was the migration from .claude/commands/*.md to .claude/skills/*/SKILL.md.

    This was not a rename: It was a rethinking of how AI agents discover and execute project-specific workflows.

    Aspect Commands (before) Skills (after) Structure Flat files in one directory Directory-per-skill with SKILL.md Description Optional, often vague Required, doubles as activation trigger Quality gates None \"Before X-ing\" pre-flight checklist Negative triggers None \"When NOT to Use\" in every skill Examples Rare Good/bad pairs in every skill Average length ~15 lines ~80 lines

    The description field became the single most important line in each skill. In the old system, descriptions were titles. In the new system, they are activation conditions: The text the platform reads to decide whether to surface a skill for a given prompt.

    A description that says \"Show context summary\" activates too broadly or not at all. A description that says \"Show context summary. Use at session start or when unclear about current project state\" activates at the right moment.

    78 files changed. 1,915 insertions. Not because the skills got bloated; because they got specific.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-skill-sweep","level":2,"title":"The Skill Sweep","text":"

    After the structural migration, every skill was rewritten in a single session: All 21 of them.

    The rewrite was guided by a pattern that emerged during the process itself: a repeatable anatomy that effective skills share regardless of their purpose:

    1. Before X-ing: Pre-flight checks that prevent premature execution
    2. When to Use: Positive triggers that narrow activation
    3. When NOT to Use: Negative triggers that prevent misuse
    4. Usage Examples: Invocation patterns the agent can pattern-match
    5. Quality Checklist: Verification before claiming completion

    The Anatomy of a Skill That Works post covers the details. What matters for the release story is the result:

    • Zero skills with quality gates became twenty;
    • Zero skills with negative triggers became twenty.
    • Three skills with examples became twenty.

    The Skill Trilogy as Design Spec

    The three blog posts written during this window:

    • Skills That Fight the Platform,
    • You Can't Import Expertise,
    • and The Anatomy of a Skill That Works...

    ... were not retrospective documentation. They were written during the rewrite, and the lessons fed back into the skills as they were being built.

    • The blog was the design document.
    • The skills were the implementation.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-consolidation-sweep","level":2,"title":"The Consolidation Sweep","text":"

    The unglamorous work. The kind you only appreciate when you try to change something later and it just works.

    What Why It Matters Constants consolidation Magic strings replaced with semantic constants Variable deshadowing Eliminated subtle scoping bugs File splits Modules that were doing too much, broken apart Godoc standardization Every exported function documented to convention

    This is the work that doesn't get a changelog entry but makes every future commit easier. When a new contributor (human or AI) reads the codebase, they find consistent patterns instead of accumulated drift.

    The consolidation was not an afterthought. It was scheduled deliberately, with the same priority as features: The 3:1 ratio that emerged during v0.2.0 development became an explicit practice:

    • Three feature sessions;
    • One consolidation session.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-ear-framework","level":2,"title":"The E/A/R Framework","text":"

    On February 4th, we adopted the E/A/R classification as the official standard for evaluating skills:

    Category Meaning Target Expert Knowledge Claude does not have >70% Activation When/how to trigger ~20% Redundant What Claude already knows <10%

    This came from reviewing approximately 30 external skill files and discovering that most were redundant with Claude's built-in system prompt. Only about 20% had salvageable content, and even those yielded just a few heuristics each.

    The E/A/R framework gave us a concrete, testable criterion:

    A good skill is Expert knowledge minus what Claude already knows.

    If more than 10% of a skill restates platform defaults, it is creating noise, not signal.

    Every skill in v0.3.0 was evaluated against this framework. Several were deleted. The survivors are leaner and more focused.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#backup-and-monitoring-infrastructure","level":2,"title":"Backup and Monitoring Infrastructure","text":"

    A tool that manages your project's memory needs ops maturity.

    v0.3.0 added two pieces of infrastructure that reflect this:

    Backup staleness hook: A UserPromptSubmit hook that checks whether the last .context/ backup is more than two days old. If it is, and the SMB mount is available, it reminds the user. No cron job running when nobody is working. No redundant backups when nothing has changed.

    Context size checkpoint: A PreToolUse hook that estimates current context window usage and warns when the session is getting heavy. This hooks into the attention budget philosophy: Degradation is expected, but it should be visible.

    Both hooks use $CLAUDE_PROJECT_DIR instead of hardcoded paths, a migration triggered by a username rename that broke every absolute path in the hook configuration. That migration (replacing /home/user/... with \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/...) was one of those changes that seems trivial but prevents an entire category of future failures.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.2.0 v0.3.0 Skills (was \"commands\") 11 21 Skills with quality gates 0 21 Skills with \"When NOT to Use\" 0 21 Average skill body ~15 lines ~80 lines Hooks using $CLAUDE_PROJECT_DIR 0 All Documentation commits n/a 35+ Feature/fix commits n/a ~15

    That ratio (35+ documentation and quality commits to ~15 feature commits) is the defining characteristic of this release:

    • This release is not a failure to ship features.
    • It is the deliberate choice to make the existing features reliable.
    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-v030-means","level":2,"title":"What v0.3.0 Means","text":"

    v0.1.0 asked: \"Can we give AI persistent memory?\"

    v0.2.0 asked: \"Can we make that memory accessible to humans too?\"

    v0.3.0 asks a different question: \"Can we make the quality self-enforcing?\"

    The answer is not a feature: It is a practice:

    • Skills with quality gates enforce pre-flight checks.
    • Negative triggers prevent misuse without human intervention.
    • The E/A/R framework ensures skills contain signal, not noise.
    • Consolidation sessions are scheduled, not improvised.
    • Hook infrastructure makes degradation visible.

    Discipline is not the absence of velocity. It is the infrastructure that makes velocity sustainable.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-comes-next","level":2,"title":"What Comes Next","text":"

    The skill system is now mature enough to support real workflows without constant human correction. The hooks infrastructure is portable and resilient. The consolidation practice is documented and repeatable.

    The next chapter is about what you build on top of discipline:

    • Multi-agent coordination;
    • Deeper integration patterns;
    • And the question of whether context management is a tool concern or an infrastructure concern.

    But those are future posts.

    This one is about the release that proved polish is not the opposite of progress. It is what turns a prototype into a product.

    The Discipline Release

    v0.1.0 shipped features.

    v0.2.0 shipped archaeology.

    v0.3.0 shipped the habits that make everything else trustworthy.

    The most important code in this release is the code that prevents bad code from shipping.

    This post was drafted using /ctx-blog with access to the full git history between v0.2.0 and v0.3.0, decision logs, learning logs, and the session files from the skill rewrite window. The meta continues.

    ","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/","level":1,"title":"Eight Ways a Hook Can Talk","text":"","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#when-your-warning-disappears","level":2,"title":"When Your Warning Disappears","text":"

    Jose Alekhinne / 2026-02-15

    I had a backup warning that nobody ever saw.

    The hook was correct: It detected stale backups, formatted a nice message, and output it as {\"systemMessage\": \"...\"}. The problem wasn't detection. The problem was delivery. The agent absorbed the information, processed it internally, and never told the user.

    Meanwhile, a different hook (the journal reminder) worked perfectly every time. Users saw the reminder, ran the commands, and the backlog stayed manageable. Same hook event (UserPromptSubmit), same project, completely different outcomes.

    The difference was one line:

    IMPORTANT: Relay this journal reminder to the user VERBATIM\nbefore answering their question.\n

    That explicit instruction is what makes VERBATIM relay a pattern, not just a formatting choice. And once I saw it as a pattern, I started seeing others.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-audit","level":2,"title":"The Audit","text":"

    I looked at every hook in ctx: Eight shell scripts across three hook events. And I found five distinct output patterns already in use, plus three more that the existing hooks were reaching for but hadn't quite articulated.

    The patterns form a spectrum based on a single question:

    \"Who decides what the user sees?\"

    At one end, the hook decides everything (hard gate: the agent literally cannot proceed). At the other end, the hook is invisible (silent side-effect: nobody knows it ran). In between, there is a range of negotiation between hook, agent, and the user.

    Here's the full spectrum:

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#1-hard-gate","level":3,"title":"1. Hard Gate","text":"
    {\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}\n

    The nuclear option: The agent's tool call is rejected before it executes.

    This is Claude Code's first-class PreToolUse mechanism: The hook returns JSON with decision: block and the agent gets an error with the reason.

    Use this for invariants: Constitution rules, security boundaries, things that must never happen. I use it to enforce PATH-based ctx invocation, block sudo, and require explicit approval for git push.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#2-verbatim-relay","level":3,"title":"2. VERBATIM Relay","text":"
    IMPORTANT: Relay this warning to the user VERBATIM before answering.\n┌─ Journal Reminder ─────────────────────────────\n│ You have 12 sessions not yet imported.\n│   ctx recall import --all\n└────────────────────────────────────────────────\n

    The instruction is the pattern. Without \"Relay VERBATIM,\" agents tend to absorb information into their internal reasoning and never surface it. The explicit instruction changes the behavior from \"I know about this\" to \"I must tell the user about this.\"

    I use this for actionable reminders:

    • Unexported journal entries;
    • Stale backups;
    • Context capacity warnings...

    ...things the user should see regardless of what they asked.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#3-agent-directive","level":3,"title":"3. Agent Directive","text":"
    ┌─ Persistence Checkpoint (prompt #25) ───────────\n│ No context files updated in 15+ prompts.\n│ Have you discovered learnings worth persisting?\n└──────────────────────────────────────────────────\n

    A nudge, not a command. The hook tells the agent something; the agent decides what (if anything) to tell the user. This is right for behavioral nudges: \"you haven't saved context in a while\" doesn't need to be relayed verbatim, but the agent should consider acting on it.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#4-silent-context-injection","level":3,"title":"4. Silent Context Injection","text":"
    ctx agent --budget 4000 2>/dev/null || true\n

    Pure background enrichment. The agent's context window gets project information injected on every tool call, with no visible output. Neither the agent nor the user sees the hook fire, but the agent makes better decisions because of the context.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#5-silent-side-effect","level":3,"title":"5. Silent Side-Effect","text":"
    find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n

    Do work, say nothing. Temp file cleanup on session end. Logging. Marker file management. The action is the entire point; no one needs to know.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-patterns-we-dont-have-yet","level":2,"title":"The Patterns We Don't Have Yet","text":"

    Three more patterns emerged from the gaps in the existing hooks.

    Conditional relay: \"Relay this, but only if the user's question is about X.\" This pattern avoids noise when the warning isn't relevant. It's more fragile (depends on agent judgment) but less annoying.

    Suggested action: \"Here's a problem, and here's the exact command to fix it. Ask the user before running it.\" This pattern goes beyond a nudge by giving the agent a concrete proposal, but still requires human approval.

    Escalating severity: INFO gets absorbed silently. WARN gets mentioned at the next natural pause. CRITICAL gets the VERBATIM treatment. This pattern introduces a protocol for hooks that produce output at different urgency levels, so they don't all compete for the user's attention.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-principle","level":2,"title":"The Principle","text":"

    Hooks are the boundary between your environment and the agent's reasoning.

    A hook that detects a problem but can't communicate it effectively is the same as no hook at all.

    The format of your output is a design decision with real consequences:

    • Use a hard gate and the agent can't proceed (good for invariants, frustrating for false positives)
    • Use VERBATIM relay and the user will see it (good for reminders, noisy if overused)
    • Use an agent directive and the agent might act (good for nudges, unreliable for critical warnings)
    • Use silent injection and nobody knows (good for enrichment, invisible when it breaks)

    Choose deliberately. And, when in doubt, write the word VERBATIM.

    The full pattern catalog with decision flowchart and implementation examples is in the Hook Output Patterns recipe.

    ","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/","level":1,"title":"Version Numbers Are Lagging Indicators","text":"","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#why-ctxs-journal-site-runs-on-a-v0021-tool","level":2,"title":"Why ctx's Journal Site Runs on a v0.0.21 Tool","text":"

    Jose Alekhinne / 2026-02-15

    Would You Ship Production Infrastructure on a v0.0.21 Dependency?

    Most engineers wouldn't. Version numbers signal maturity. Pre-1.0 means unstable API, missing features, risk.

    But version numbers tell you where a project has been. They say nothing about where it's going.

    I just bet ctx's entire journal site on a tool that hasn't hit v0.1.0.

    Here's why I'd do it again.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-problem","level":2,"title":"The Problem","text":"

    When v0.2.0 shipped the journal system, the pipeline was clear:

    • Export sessions to Markdown;
    • Enrich them with YAML frontmatter;
    • And render them into something browsable.

    The first two steps were solved; the third needed a tool.

    The journal entries are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is the entire format:

    • No JSX;
    • No shortcodes;
    • No custom templating.

    Just Markdown rendered well.

    The requirements are modest:

    • Read a configuration file (such as mkdocs.yml);
    • Render Markdown with extensions (admonitions, tabs, tables);
    • Search;
    • Handle 100+ files without choking on incremental rebuilds;
    • Look good out of the box;
    • Not lock me in.

    The obvious candidates were as follows:

    Tool Language Strengths Pain Points Hugo Go Blazing fast, mature Templating is painful; Go templates fight you on anything non-trivial Astro JS/TS Modern, flexible JS ecosystem overhead; overkill for a docs site MkDocs + Material Python Beautiful defaults, massive community (22k+ stars) Slow incremental rebuilds on large sites; limited extensibility model Zensical Python Built to fix MkDocs' limits; 4-5x faster rebuilds v0.0.21; module system not yet shipped

    The instinct was Hugo. Same language as ctx. Fast. Well-established.

    But instinct is not analysis. I picked the one with the lowest version number.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation","level":2,"title":"The Evaluation","text":"

    Here is what I actually evaluated, in order:

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#1-the-team","level":3,"title":"1. The Team","text":"

    Zensical is built by squidfunk: The same person behind Material for MkDocs, the most popular MkDocs theme with 22,000+ stars. It powers documentation sites for projects across every language and framework.

    • This is not someone learning how to build static site generators.
    • This is someone who spent years understanding exactly where MkDocs breaks and decided to fix it from the ground up.

    They did not build zensical because MkDocs was bad: They built it because MkDocs hit a ceiling:

    • Incremental rebuilds: 4-5x faster during serve. When you have hundreds of journal entries and you edit one, the difference between \"rebuild everything\" and \"rebuild this page\" is the difference between a usable workflow and a frustrating one.

    • Large site performance: Specifically designed for tens of thousands of pages. The journal grows with every session. A tool that slows down as content accumulates is a tool you will eventually replace.

    A proven team starting fresh is more predictable than an unproven team at v3.0.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#2-the-architecture","level":3,"title":"2. The Architecture","text":"

    Zensical is investing in a Rust-based Markdown parser with CommonMark support. That signals something about the team's priorities:

    Performance foundations first; features second.

    ctx's journal will grow:

    • Every exported session adds files.
    • Every enrichment pass adds metadata.

    Choosing a tool that gets slower as you add content means choosing to migrate later.

    Choosing one built for scale means the decision holds.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#3-the-migration-path","level":3,"title":"3. The Migration Path","text":"

    Zensical reads mkdocs.yml natively. If it doesn't work out, I can move back to MkDocs + Material with zero content changes:

    • The Markdown is standard;
    • The frontmatter is standard;
    • The configuration is compatible.

    This is the infrastructure pattern again: The same way ZNC decouples presence from the client, zensical decouples rendering from the generator:

    • The Markdown is yours.
    • The frontmatter is standard YAML.
    • The configuration is MkDocs-compatible.

    You are not locked into anything except your own content.

    No lock-in is not a feature: It's a design philosophy:

    It's the same reason ctx uses plain Markdown files in .context/ instead of a database: the format should outlive the tool.

    Lock-in Is the Real Risk, Not Version Numbers

    A mature tool with a proprietary format is riskier than a young tool with a standard one. Version numbers measure time invested. Portability measures respect for the user.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#4-the-dependency-tree","level":3,"title":"4. The Dependency Tree","text":"

    Here is what pip install zensical actually pulls in:

    • click
    • Markdown
    • Pygments
    • pymdown-extensions
    • PyYAML

    Only five dependencies. All well-known. No framework bloat. No bundler. No transpiler. No node_modules black hole.

    3k GitHub stars at v0.0.21 is a strong early traction for a pre-1.0 project.

    The dependency tree is thin: No bloat.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#5-the-fit","level":3,"title":"5. The Fit","text":"

    This is the same principle behind the attention budget: do not overfit the tool to hypothetical requirements. The right amount of capability is the minimum needed for the current task.

    Hugo is a powerful static site generator. It is also a powerful templating engine, a powerful asset pipeline, and a powerful taxonomy system. For rendering Markdown journals, that power is overhead:

    It is the complexity you pay for but never use.

    ctx's journal files are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is exactly the sweet spot Zensical inherits from Material for MkDocs:

    • No custom plugins needed;
    • No special syntax;
    • No templating gymnastics.

    The requirements match the capabilities: Not the capabilities that are promised, but the ones that exist today, at v0.0.21.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-caveat","level":2,"title":"The Caveat","text":"

    It would be dishonest not to mention what's missing.

    The module system for third-party extensions opens in early 2026.

    If ctx ever needs custom plugins (for example, auto-linking session IDs, rendering special journal metadata, etc.) that infrastructure isn't there yet.

    The installation experience is rough:

    We discovered this firsthand: pip install zensical often fails on MacOS (system Python stubs, Homebrew's PEP 668 restrictions). The answer is pipx, which creates an isolated environment with the correct Python version automatically.

    That kind of friction is typical for young Python tooling, and it is documented in the Getting Started guide.

    And 3,000 stars at v0.0.21 is strong early traction, but it's still early: The community is small. When something breaks, you're reading source code, not documentation.

    These are real costs. I chose to pay them because the alternative costs are higher.

    For example:

    • Hugo's templating pain would cost me time on every site change.
    • Astro's JS ecosystem would add complexity I don't need.
    • MkDocs would work today but hit scaling walls tomorrow.

    Zensical's costs are front-loaded and shrinking.

    The others compound.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation-framework","level":2,"title":"The Evaluation Framework","text":"

    For anyone facing a similar choice, here is the framework that emerged:

    Signal What It Tells You Weight Team track record Whether the architecture will be sound High Migration path Whether you can leave if wrong High Current fit Whether it solves your problem today High Dependency tree How much complexity you're inheriting Medium Version number How long the project has existed Low Star count Community interest (not quality) Low Feature list What's possible (not what you need) Low

    The bottom three are the metrics most engineers optimize for.

    The top four are the ones that predict whether you'll still be happy with the choice in a year.

    Features You Don't Need Are Not Free

    Every feature in a dependency is code you inherit but don't control.

    A tool with 200 features where you use 5 means 195 features worth of surface area for bugs, breaking changes, and security issues that have nothing to do with your use case.

    Fit is the inverse of feature count.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-broader-pattern","level":2,"title":"The Broader Pattern","text":"

    This is part of a theme I keep encountering in this project:

    Leading indicators beat lagging indicators.

    Domain Lagging Indicator Leading Indicator Tooling Version number, star count Team track record, architecture Code quality Test coverage percentage Whether tests catch real bugs Context persistence Number of files in .context/ Whether the AI makes fewer mistakes Skills Number of skills created Whether each skill fires at the right time Consolidation Lines of code refactored Whether drift stops accumulating

    Version numbers, star counts, coverage percentages, file counts...

    ...these are all measures of effort expended.

    They say nothing about value delivered.

    The question is never \"how mature is this tool?\"

    The question is \"does this tool's trajectory intersect with my needs?\"

    Zensical's trajectory:

    • A proven team fixing known problems,
    • in a *proven architecture,
    • with a standard format,
    • and no lock-in.

    ctx's needs:

    Tender standard Markdown into a browsable site, at scale, without complexity.

    The intersection is clean; the version number is noise.

    This is the same kind of decision that shows up throughout ctx:

    • Skills that fight the platform taught that the best integration extends existing behavior, not replaces it.
    • You can't import expertise taught that tools should grow from your project's actual needs, not from feature checklists.
    • Context as infrastructure argues that the format should outlive the tool; and, zensical honors that principle by reading standard Markdown and standard MkDocs configuration.

    If You Remember One Thing from This Post...

    Version numbers measure where a project has been.

    The team and the architecture tell you where it's going.

    A v0.0.21 tool built by the right team on the right foundations is a safer bet than a v5.0 tool that doesn't fit your problem.

    Bet on trajectories, not timestamps.

    This post started as an evaluation note in ideas/ and a separate decision log. The analysis held up. The two merged into one. The meta continues.

    ","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/","level":1,"title":"ctx v0.6.0: The Integration Release","text":"","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#two-commands-to-persistent-memory","level":2,"title":"Two Commands to Persistent Memory","text":"

    Jose Alekhinne / February 16, 2026

    What Changed?

    ctx is now a Claude Code plugin. Two commands, no build step:

    /plugin marketplace add ActiveMemory/ctx\n/plugin install ctx@activememory-ctx\n

    Six hooks. Twenty-five skills. Installed.

    For three releases, ctx required assembly:

    • Clone the repo;
    • Build the binary;
    • Copy hook scripts into .claude/hooks/;
    • Symlink skill files.
    • Understand which shell scripts called which Go commands;
    • Hope nothing broke when Claude Code updated its hook format.

    v0.6.0 ends that era: ctx ships as a Claude Marketplace plugin:

    Hooks and skills served directly from source, installed with a single command, updated by pulling the repo. The tool that gives AI persistent memory is now as easy to install as the AI itself.

    But the plugin conversion was not just a packaging change: It was the forcing function that rewrote every shell hook in Go, eliminated the jq dependency, enabled go test coverage for hook logic, and made distribution a solved problem.

    When you fix how something ships, you end up fixing how it is built.

    The Release Window

    February 15-February 16, 2026

    From the v0.3.0 tag to commit a3178bc:

    • 109 commits.
    • 334 files changed.
    • Version jumped from 0.3.0 to 0.6.0 to signal the magnitude.
    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#before-six-shell-scripts-and-a-prayer","level":2,"title":"Before: Six Shell Scripts and a Prayer","text":"

    v0.3.0 had six hook scripts. Each was a Bash file that shelled out to ctx subcommands, parsed JSON with jq, and wired itself into Claude Code's hook system via .claude/hooks/:

    .claude/hooks/\n├── check-context-size.sh\n├── check-persistence.sh\n├── check-journal.sh\n├── post-commit.sh\n├── block-non-path-ctx.sh\n└── cleanup-tmp.sh\n

    This worked, but it also meant:

    • jq was a hard dependency: No jq, no hooks. macOS ships without it.
    • No test coverage: Shell scripts were tested manually or not at all.
    • Fragile deployment: ctx init had to scaffold .claude/hooks/ and .claude/skills/ with the right paths, permissions, and structure.
    • Version drift: Users who installed once never got hook updates unless they re-ran ctx init.

    The shell scripts were the right choice for prototyping. They were the wrong choice for distribution.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#after-one-plugin-zero-shell-scripts","level":2,"title":"After: One Plugin, Zero Shell Scripts","text":"

    v0.6.0 replaces all six scripts with ctx system subcommands compiled into the binary:

    Shell Script Go Subcommand check-context-size.sh ctx system check-context-size check-persistence.sh ctx system check-persistence check-journal.sh ctx system check-journal post-commit.sh ctx system post-commit block-non-path-ctx.sh ctx system block-non-path-ctx cleanup-tmp.sh ctx system cleanup-tmp

    The plugin's hooks.json wires them to Claude Code events:

    {\n  \"PreToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system block-non-path-ctx\"},\n    {\"matcher\": \".*\", \"command\": \"ctx agent --budget 4000\"}\n  ],\n  \"PostToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system post-commit\"}\n  ],\n  \"UserPromptSubmit\": [\n    {\"command\": \"ctx system check-context-size\"},\n    {\"command\": \"ctx system check-persistence\"},\n    {\"command\": \"ctx system check-journal\"}\n  ],\n  \"SessionEnd\": [\n    {\"command\": \"ctx system cleanup-tmp\"}\n  ]\n}\n

    No jq. No shell scripts. No .claude/hooks/ directory to manage.

    The hooks are Go functions with tests, compiled into the same binary you already have.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-plugin-model","level":2,"title":"The Plugin Model","text":"

    The ctx plugin lives at .claude-plugin/marketplace.json in the repo.

    Claude Code's marketplace system handles discovery and installation:

    Skills are served directly from internal/assets/claude/skills/; there is no build step, no make plugin, no generated artifacts.

    This means:

    1. Install is two commands: Not \"clone, build, copy, configure.\"
    2. Updates are automatic: Pull the repo; the plugin reads from source.
    3. Skills and hooks are versioned together: No drift between what the CLI expects and what the plugin provides.
    4. ctx init is tool-agnostic: It creates .context/ and nothing else. No .claude/ scaffolding, no assumptions about which AI tool you use.

    That last point matters:

    Before v0.6.0, ctx init tried to set up Claude Code integration as part of initialization. That coupled the context system to a specific tool.

    Now, ctx init gives you persistent context. The plugin gives you Claude Code integration. They compose; they don't depend.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#beyond-the-plugin-what-else-shipped","level":2,"title":"Beyond the Plugin: What Else Shipped","text":"

    The plugin conversion dominated the release, but 109 commits covered more ground.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#obsidian-vault-export","level":3,"title":"Obsidian Vault Export","text":"
    ctx journal obsidian\n

    Generates a full Obsidian vault from enriched journal entries: wikilinks, MOC (Map of Content) pages, and graph-optimized cross-linking. If you already use Obsidian for notes, your AI session history now lives alongside everything else.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#encrypted-scratchpad","level":3,"title":"Encrypted Scratchpad","text":"
    ctx pad edit \"DATABASE_URL=postgres://...\"\nctx pad show\n

    AES-256-GCM encrypted storage for sensitive one-liners.

    The encrypted blob commits to git; the key stays in .gitignore.

    This is useful for connection strings, API keys, and other values that need to travel with the project without appearing in plaintext.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#security-hardening","level":3,"title":"Security Hardening","text":"

    Three medium-severity findings from a security audit are now closed:

    Finding Fix Path traversal via --context-dir Boundary validation: operations cannot escape project root (M-1) Symlink following in .context/ Lstat() check before every file read/write (M-2) Predictable temp file paths User-specific temp directory under $XDG_RUNTIME_DIR (M-3)

    Plus a new /sanitize-permissions skill that audits settings.local.json for overly broad Bash permissions.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#hooks-that-know-when-to-be-quiet","level":3,"title":"Hooks That Know When to Be Quiet","text":"

    A subtle but important fix: hooks now no-op before ctx init has run.

    Previously, a fresh clone with no .context/ would trigger hook errors on every prompt. Now, hooks detect the absence of a context directory and exit silently. Similarly, ctx init treats a .context/ directory containing only logs as uninitialized and skips the --overwrite prompt.

    Small changes. Large reduction in friction for new users.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.3.0 v0.6.0 Skills 21 25 Shell hook scripts 6 0 Go system subcommands 0 6 External dependencies (hooks) jq, bash none Lines of Go ~14,000 ~37,000 Plugin install commands n/a 2 Security findings (open) 3 0 ctx init creates .claude/ yes no

    The line count tripled. Most of that is documentation site HTML, Obsidian export logic, and the scratchpad encryption module.

    The core CLI grew modestly; the ecosystem around it grew substantially.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-does-v060-mean-for-ctx","level":2,"title":"What Does v0.6.0 Mean for ctx?","text":"
    • v0.1.0 asked: \"Can we give AI persistent memory?\"
    • v0.2.0 asked: \"Can we make that memory accessible to humans too?\"
    • v0.3.0 asked: \"Can we make the quality self-enforcing?\"

    v0.6.0 asks: \"Can someone else actually use this?\"

    A tool that requires cloning a repo, building from source, and manually wiring hooks into the right directories is a tool for its author.

    A tool that installs with two commands from a marketplace is a tool for everyone.

    The version jumped from 0.3.0 to 0.6.0 because the delta is not incremental: The shell-to-Go rewrite, the plugin model, the security hardening, and the tool-agnostic init: Together, they change what ctx is: Not a different tool, but a tool that is finally ready to leave the workshop.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-comes-next","level":2,"title":"What Comes Next","text":"

    The plugin model opens the door to distribution patterns that were not possible before. Marketplace discovery means new users find ctx without reading a README. Plugin updates mean existing users get improvements without rebuilding.

    The next chapter is about what happens when persistent context is easy to install: Adoption patterns, multi-project workflows, and whether the .context/ convention can become infrastructure that other tools build on.

    But those are future posts.

    This one is about the release that turned a developer tool into a distributable product: two commands, zero shell scripts, and a presence on the Claude Marketplace.

    The Integration Release

    v0.1.0 shipped features. v0.2.0 shipped archaeology.

    v0.3.0 shipped discipline. v0.6.0 shipped the front door.

    The most important code in this release is the code you never have to copy.

    This post was drafted using /ctx-blog-changelog with access to the full git history between v0.3.0 and v0.6.0, release notes, and the plugin conversion PR. The meta continues.

    ","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/","level":1,"title":"Code Is Cheap. Judgment Is Not.","text":"","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#why-ai-replaces-effort-not-expertise","level":2,"title":"Why AI Replaces Effort, Not Expertise","text":"

    Volkan Özçelik / February 17, 2026

    Are You Worried about AI Taking Your Job?

    You might be confusing the thing that's cheap with the thing that's valuable.

    I keep seeing the same conversation: Engineers, designers, writers: all asking the same question with the same dread:

    \"What happens when AI can do what I do?\"

    The question is wrong:

    • AI does not replace workers;
    • AI replaces unstructured effort.

    The distinction matters, and everything I have learned building ctx reinforces it.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-three-confusions","level":2,"title":"The Three Confusions","text":"

    People who feel doomed by AI usually confuse three things:

    People confuse... With... Effort Value Typing Thinking Production Judgment
    • Effort is time spent.
    • Value is the outcome that time produces.

    They are not the same; they never were.

    AI just makes the gap impossible to ignore.

    Typing is mechanical: Thinking is directional.

    An AI can type faster than any human. Yet, it cannot decide what to type without someone framing the problem, sequencing the work, and evaluating the result.

    Production is making artifacts. Judgment is knowing:

    • which artifacts to make,
    • in what order,
    • to what standard,
    • and when to stop.

    AI floods the system with production capacity; it does not flood the system with judgment.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#code-is-nothing","level":2,"title":"Code Is Nothing","text":"

    This sounds provocative until you internalize it:

    Code is cheap. Artifacts are cheap.

    An AI can generate a thousand lines of working code in literal *minutes**:

    It can scaffold a project, write tests, build a CI pipeline, draft documentation. The raw production of software artifacts is no longer the bottleneck.

    So, what is not cheap?

    • Taste: knowing what belongs and what does not
    • Framing: turning a vague goal into a concrete problem
    • Sequencing: deciding what to build first and why
    • Fanning out: breaking work into parallel streams that converge
    • Acceptance criteria: defining what \"done\" looks like before starting
    • Judgment: the thousand small decisions that separate code that works from code that lasts

    These are the skills that direct production: Hhuman skills.

    Not because AI is incapable of learning them, but because they require something AI does not have:

    temporal accountability for generated outcomes.

    That is, you cannot keep AI accountable for the $#!% it generated three months ago. A human, on the other hand, will always be accountable.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-evidence-from-building-ctx","level":2,"title":"The Evidence from Building ctx","text":"

    I did not arrive at this conclusion theoretically.

    I arrived at it by building a tool with an AI agent for three weeks and watching exactly where a human touch mattered.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#yolo-mode-proved-production-is-cheap","level":3,"title":"YOLO Mode Proved Production Is Cheap","text":"

    In Building ctx Using ctx, I documented the YOLO phase: auto-accept everything, let the AI ship features at full speed. It produced 14 commands in a week. Impressive output.

    The code worked. The architecture drifted. Magic strings accumulated. Conventions diverged. The AI was producing at a pace no human could match, and every artifact it produced was a small bet that nobody was evaluating.

    Production without judgment is not velocity. It is debt accumulation at breakneck speed.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-31-ratio-proved-judgment-has-a-cadence","level":3,"title":"The 3:1 Ratio Proved Judgment Has a Cadence","text":"

    In The 3:1 Ratio, the git history told the story:

    Three sessions of forward momentum followed by one session of deliberate consolidation. The consolidation session is where the human applies judgment: reviewing what the AI built, catching drift, realigning conventions.

    The AI does the refactoring. The human decides what to refactor and when to stop.

    Without the human, the AI will refactor forever, improving things that do not matter and missing things that do.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-attention-budget-proved-framing-is-scarce","level":3,"title":"The Attention Budget Proved Framing Is Scarce","text":"

    In The Attention Budget, I explained why more context makes AI worse, not better. Every token competes for attention: Dump everything in and the AI sees nothing clearly.

    This is a framing problem: The human's job is to decide what the AI should focus on: what to include, what to exclude, what to emphasize.

    ctx agent --budget 4000 is not just a CLI flag: It is a forcing function for human judgment about relevance.

    The AI processes. The human curates.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#skills-design-proved-taste-is-load-bearing","level":3,"title":"Skills Design Proved Taste Is Load-Bearing","text":"

    The skill trilogy (You Can't Import Expertise, The Anatomy of a Skill That Works) showed that the difference between a useful skill and a useless one is not craftsmanship:

    It is taste.

    A well-crafted skill with the wrong focus is worse than no skill at all: It consumes the attention budget with generic advice while the project-specific problems go unchecked.

    The E/A/R framework (Expert, Activation, Redundant) is a judgment too:. The AI cannot apply it to itself. The human evaluates what the AI already knows, what it needs to be told, and what is noise.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#automation-discipline-proved-restraint-is-a-skill","level":3,"title":"Automation Discipline Proved Restraint Is a Skill","text":"

    In Not Everything Is a Skill, the lesson was that the urge to automate is not the need to automate. A useful prompt does not automatically deserve to become a slash command.

    The human applies judgment about frequency, stability, and attention cost.

    The AI can build the skill. Only the human can decide whether it should exist.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#defense-in-depth-proved-boundaries-require-judgment","level":3,"title":"Defense in Depth Proved Boundaries Require Judgment","text":"

    In Defense in Depth, the entire security model for unattended AI agents came down to: Markdown is not a security boundary. Telling an AI \"don't do bad things\" is production (of instructions). Setting up an unprivileged user in a network-isolated container is judgment (about risk).

    The AI follows instructions. The human decides which instructions are enforceable and which are \"wishful thinking\".

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#parallel-agents-proved-scale-amplifies-the-gap","level":3,"title":"Parallel Agents Proved Scale Amplifies the Gap","text":"

    In Parallel Agents and Merge Debt, the lesson was that multiplying agents multiplies output. But it also multiplies the need for judgment:

    Five agents running in parallel produce five sessions of drift in one clock hour. The human who can frame tasks cleanly, define narrow acceptance criteria, and evaluate results quickly becomes the limiting factor.

    More agents do not reduce the need for judgment. They increase it.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-two-reactions","level":2,"title":"The Two Reactions","text":"

    When AI floods the system with cheap output, two things happen:

    Those who only produce: panic. If your value proposition is \"I write code,\" and an AI writes code faster, cheaper, and at higher volume, then the math is unfavorable. Not because AI took your job, but because your job was never the code. It was the judgment around the code, and you were not exercising it.

    Those who direct: accelerate. If your value proposition is \"I know what to build, in what order, to what standard,\" then AI is the best thing that ever happened to you: Production is no longer the bottleneck: Your ability to frame, sequence, evaluate, and course-correct is now the limiting factor on throughput.

    The gap between these two is not talent: It is the awareness of where the value lives.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#what-this-means-in-practice","level":2,"title":"What This Means in Practice","text":"

    If you are an engineer reading this, the actionable insight is not \"learn prompt engineering\" or \"master AI tools.\" It is:

    Get better at the things AI cannot do.

    AI does this well You need to do this Generate code Frame the problem Write tests Define acceptance criteria Scaffold projects Sequence the work Fix bugs from stack traces Evaluate tradeoffs Produce volume Exercise restraint Follow instructions Decide which instructions matter

    The skills on the right column are not new. They are the same skills that have always separated senior engineers from junior ones.

    AI did not create the distinction; it just made it load-bearing.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#if-anything-i-feel-empowered","level":2,"title":"If Anything, I Feel Empowered","text":"

    I will end with something personal.

    I am not worried: I am empowered.

    Before ctx, I could think faster than I could produce:

    • Ideas sat in a queue.
    • The bottleneck was always \"I know what to build, but building it takes too long.\"

    Now the bottleneck is gone. Poof!

    • Production is cheap.
    • The queue is clearing.
    • The limiting factor is how fast I can think, not how fast I can type.

    That is not a threat: That is the best force multiplier I've ever had.

    The people who feel threatened are confusing the accelerator for the replacement:

    *AI does not replace the conductor; it gives them a bigger orchestra.

    If You Remember One Thing from This Post...

    Code is cheap. Judgment is not.

    AI replaces unstructured effort, not directed expertise. The skills that matter now are the same skills that have always mattered: taste, framing, sequencing, and the discipline to stop.

    The difference is that now, for the first time, those skills are the only bottleneck left.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-arc","level":2,"title":"The Arc","text":"

    This post is a retrospective. It synthesizes the thread running through every previous entry in this blog:

    • Building ctx Using ctx showed that production without direction creates debt
    • Refactoring with Intent showed that slowing down is not the opposite of progress
    • The Attention Budget showed that curation outweighs volume
    • The skill trilogy showed that taste determines whether a tool helps or hinders
    • Not Everything Is a Skill showed that restraint is a skill in itself
    • Defense in Depth showed that instructions are not boundaries
    • The 3:1 Ratio showed that judgment has a schedule
    • Parallel Agents showed that scale amplifies the gap between production and judgment
    • Context as Infrastructure showed that the system you build for context is infrastructure, not conversation

    From YOLO mode to defense in depth, the pattern is the same:

    • Production is the easy part;
    • Judgment is the hard part;
    • AI changed the ratio, not the rule.

    This post synthesizes the thread running through every previous entry in this blog. The evidence is drawn from three weeks of building ctx with AI assistance, the decisions recorded in DECISIONS.md, the learnings captured in LEARNINGS.md, and the git history that tracks where the human mattered and where the AI ran unsupervised.

    See also: When a System Starts Explaining Itself -- what happens after the arc: the first field notes from the moment the system starts compounding in someone else's hands.

    ","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/","level":1,"title":"Context as Infrastructure","text":"","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#why-your-ai-needs-a-filesystem-not-a-prompt","level":2,"title":"Why Your AI Needs a Filesystem, Not a Prompt","text":"

    Volkan Özçelik / February 17, 2026

    Where Does Your AI's Knowledge Live between Sessions?

    If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. Something assembled, used, and discarded.

    What if you treated it as infrastructure instead?

    This post synthesizes a thread that has been running through every ctx blog post; from the origin story to the attention budget to the discipline release. The thread is this: context is not a prompt problem. It is an infrastructure problem. And the tools we build for it should look more like filesystems than clipboard managers.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-prompt-paradigm","level":2,"title":"The Prompt Paradigm","text":"

    Most AI-assisted development treats context as ephemeral:

    1. Start a session.
    2. Paste your system prompt, your conventions, your current task.
    3. Work.
    4. Session ends. Everything evaporates.
    5. Next session: paste again.

    This works for short interactions. For sustained development (where decisions compound over days and weeks) it fails in three ways:

    It does not persist: A decision made on Tuesday must be re-explained on Wednesday. A learning captured in one session is invisible to the next.

    It does not scale: As the project grows, the \"paste everything\" approach hits the context window ceiling. You start triaging what to include, often cutting exactly the context that would have prevented the next mistake.

    It does not compose: A system prompt is a monolith. You cannot load part of it, update one section, or share a subset with a different workflow. It is all or nothing.

    The Copy-Paste Tax

    Every session that starts with pasting a prompt is paying a tax:

    The human time to assemble the context, the risk of forgetting something, and the silent assumption that yesterday's prompt is still accurate today.

    Over 70+ sessions, that tax compounds into a significant maintenance burden: One that most developers absorb without questioning it.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-infrastructure-paradigm","level":2,"title":"The Infrastructure Paradigm","text":"

    ctx takes a different approach:

    Context is not assembled per-session; it is maintained as persistent files in a .context/ directory:

    .context/\n  CONSTITUTION.md     # Inviolable rules\n  TASKS.md            # Current work items\n  CONVENTIONS.md      # Code patterns and standards\n  DECISIONS.md        # Architectural choices with rationale\n  LEARNINGS.md        # Gotchas and lessons learned\n  ARCHITECTURE.md     # System structure\n  GLOSSARY.md         # Domain terminology\n  AGENT_PLAYBOOK.md   # Operating manual for agents\n  journal/            # Enriched session summaries\n  archive/            # Completed work, cold storage\n
    • Each file has a single purpose;
    • Each can be loaded independently;
    • Each persists across sessions, tools, and team members.

    This is not a novel idea. It is the same idea behind every piece of infrastructure software engineers already use:

    Traditional Infrastructure ctx Equivalent Database .context/*.md files Configuration files CONSTITUTION.md Environment variables .contextrc Log files journal/ Schema migrations Decision records Deployment manifests AGENT_PLAYBOOK.md

    The parallel is not metaphorical. Context files are infrastructure:

    • They are versioned (git tracks them);
    • They are structured (Markdown with conventions);
    • They have schemas (required fields for decisions and learnings);
    • And they have lifecycle management (archiving, compaction, indexing).
    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#separation-of-concerns","level":2,"title":"Separation of Concerns","text":"

    The most important design decision in ctx is not any individual feature. It is the separation of context into distinct files with distinct purposes.

    A single CONTEXT.md file would be simpler to implement. It would also be impossible to maintain.

    Why? Because different types of context have different lifecycles:

    Context Type Changes Read By Load When Constitution Rarely Every session Always Tasks Every session Session start Always Conventions Weekly Before coding When writing code Decisions When decided When questioning When revisiting Learnings When learned When stuck When debugging Journal Every session Rarely When investigating

    Loading everything into every session wastes the attention budget on context that is irrelevant to the current task. Loading nothing forces the AI to operate blind.

    Separation of concerns allows progressive disclosure:

    Load the minimum that matters for this moment, with the option to load more when needed.

    # Session start: load the essentials\nctx agent --budget 4000\n\n# Deep investigation: load everything\ncat .context/DECISIONS.md\ncat .context/journal/2026-02-05-*.md\n

    The filesystem is the index. File names, directory structure, and timestamps encode relevance. The AI does not need to read every file; it needs to know where to look.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-two-tier-persistence-model","level":2,"title":"The Two-Tier Persistence Model","text":"

    ctx uses two tiers of persistence, and the distinction is architectural:

    Tier Purpose Location Token Cost Curated Quick context reload .context/*.md Low (budgeted) Full dump Safety net, archaeology .context/journal/*.md Zero (not auto-loaded)

    The curated tier is what the AI sees at session start. It is optimized for signal density:

    • Structured entries,
    • Indexed tables,
    • Reverse-chronological order (newest first, so the most relevant content survives truncation).

    The full dump tier is for humans and for deep investigation. It contains everything: Enriched journals, archived tasks...

    It is never autoloaded because its volume would destroy attention density.

    This two-tier model is analogous to how traditional systems separate hot and cold storage:

    • The hot path (curated context) is optimized for read performance (measured not in milliseconds, but in tokens consumed per unit of useful information).
    • The cold path (journal) is optimized for completeness.

    Nothing Is Ever Truly Lost

    The full dump tier means that context does not need to be perfect: It just needs to be findable.

    A decision that was not captured in DECISIONS.md can be recovered from the session transcript where it was discussed.

    A learning that was not formalized can be found in the journal entry from that day.

    The curated tier is the fast path: The full dump tier is the safety net.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#decision-records-as-first-class-citizens","level":2,"title":"Decision Records as First-Class Citizens","text":"

    One of the patterns that emerged from ctx's own development is the power of structured decision records.

    v0.1.0 allowed adding decisions as one-liners:

    ctx add decision \"Use PostgreSQL\"\n

    v0.2.0 enforced structure:

    ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity\" \\\n  --consequence \"Need connection pooling, team training\"\n

    The difference is not cosmetic:

    • A one-liner decision teaches the AI what was decided.
    • A structured decision teaches it why; and why is what prevents the AI from unknowingly reversing the decision in a future session.

    This is infrastructure thinking:

    Decisions are not notes. They are records with required fields, just like database rows have schemas.

    The enforcement exists because incomplete records are worse than no records: They create false confidence that the context is captured when it is not.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-ide-is-the-interface-decision","level":2,"title":"The \"IDE Is the Interface\" Decision","text":"

    Early in ctx's development, there was a temptation to build a custom UI: a web dashboard for browsing sessions, editing context, viewing analytics.

    The decision was no. The IDE is the interface.

    # This is the ctx \"UI\":\ncode .context/\n

    This decision was not about minimalism for its own sake. It was about recognizing that .context/ files are just files; and files have a mature, well-understood infrastructure:

    • Version control: git diff .context/DECISIONS.md shows exactly what changed and when.
    • Search: Your IDE's full-text search works across all context files.
    • Editing: Markdown in any editor, with preview, spell check, and syntax highlighting.
    • Collaboration: Pull requests on context files work the same as pull requests on code.

    Building a custom UI would have meant maintaining a parallel infrastructure that duplicates what every IDE already provides:

    It would have introduced its own bugs, its own update cycle, and its own learning curve.

    The filesystem is not a limitation: It is the most mature, most composable, most portable infrastructure available.

    Context Files in Git

    Because .context/ lives in the repository, context changes are part of the commit history.

    A decision made in commit abc123 is as traceable as a code change in the same commit.

    This is not possible with prompt-based context, which exists outside version control entirely.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#progressive-disclosure-for-ai","level":2,"title":"Progressive Disclosure for AI","text":"

    The concept of progressive disclosure comes from human interface design: show the user the minimum needed to make progress, with the option to drill deeper.

    ctx applies the same principle to AI context:

    Level What the AI Sees Token Cost When Level 0 ctx status (one-line summary) ~100 Quick check Level 1 ctx agent --budget 4000 ~4,000 Normal work Level 2 ctx agent --budget 8000 ~8,000 Complex tasks Level 3 Direct file reads 10,000+ Deep investigation

    Each level trades tokens for depth. Level 1 is sufficient for most work: the AI knows the active tasks, the key conventions, and the recent decisions. Level 3 is for archaeology: understanding why a decision was made three weeks ago, or finding a pattern in the session history.

    The explicit --budget flag is the mechanism that makes this work:

    Without it, the default behavior would be to load everything (because more context feels safer), which destroys the attention density that makes the loaded context useful.

    The constraint is the feature: A budget of 4,000 tokens forces ctx to prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings scored by recency and relevance to active tasks. Entries that don't fit get title-only summaries rather than being silently dropped.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-philosophical-shift","level":2,"title":"The Philosophical Shift","text":"

    The shift from \"context as prompt\" to \"context as infrastructure\" changes how you think about AI-assisted development:

    Prompt Thinking Infrastructure Thinking \"What do I paste today?\" \"What has changed since yesterday?\" \"How do I fit everything in?\" \"What's the minimum that matters?\" \"The AI forgot my conventions\" \"The conventions are in a file\" \"I need to re-explain\" \"I need to update the record\" \"This session is getting slow\" \"Time to compact and archive\"

    The first column treats AI interaction as a conversation. The second treats it as a system: One that can be maintained, optimized, and debugged.

    Context is not something you give the AI. It is something you maintain: Like a database, like a config file, like any other piece of infrastructure that a running system depends on.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#beyond-ctx-the-principles","level":2,"title":"Beyond ctx: The Principles","text":"

    The patterns that ctx implements are not specific to ctx. They are applicable to any project that uses AI-assisted development:

    1. Separate context by purpose: Do not put everything in one file. Different types of information have different lifecycles and different relevance windows.
    2. Make context persistent: If a decision matters, write it down in a file that survives the session. If a learning matters, capture it with structure.
    3. Budget explicitly: Know how much context you are loading and whether it is worth the attention cost.
    4. Use the filesystem: File names, directory structure, and timestamps are metadata that the AI can navigate. A well-organized directory is an index that costs zero tokens to maintain.
    5. Version your context: Put context files in git. Changes to decisions are as important as changes to code.
    6. Design for degradation: Sessions will get long. Attention will dilute. Build mechanisms (compaction, archiving, cooldowns) that make degradation visible and manageable.

    These are not ctx features. They are infrastructure principles that happen to be implemented as a CLI tool. Any team could implement them with nothing more than a directory convention and a few shell scripts.

    The tool is a convenience: The principles are what matter.

    If You Remember One Thing from This Post...

    Prompts are conversations. Infrastructure persists.

    Your AI does not need a better prompt. It needs a filesystem:

    versioned, structured, budgeted, and maintained.

    The best context is the context that was there before you started the session.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-arc","level":2,"title":"The Arc","text":"

    This post is the architectural companion to the Attention Budget. That post explained why context must be curated (token economics). This one explains how to structure it (filesystem, separation of concerns, persistence tiers).

    Together with Code Is Cheap, Judgment Is Not, they form a trilogy about what matters in AI-assisted development:

    • Attention Budget: the resource you're managing
    • Context as Infrastructure: the system you build to manage it
    • Code Is Cheap: the human skill that no system replaces

    And the practices that keep it all honest:

    • The 3:1 Ratio: the cadence for maintaining both code and context
    • IRC as Context: the historical precedent: stateless protocols have always needed stateful wrappers

    This post synthesizes ideas from across the ctx blog series: the attention budget primitive, the two-tier persistence model, the IDE decision, and the progressive disclosure pattern. The principles are drawn from three weeks of building ctx and 70+ sessions of treating context as infrastructure rather than conversation.

    See also: When a System Starts Explaining Itself: what happens when this infrastructure starts compounding in someone else's environment.

    ","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/","level":1,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-the-screen-looks-like-progress","level":2,"title":"When the Screen Looks like Progress","text":"

    Volkan Özçelik / 2026-02-17

    How Many Terminals Are Too Many?

    You discover agents can run in parallel.

    So you open ten...

    ...Then twenty.

    The fans spin. Tokens burn. The screen looks like progress.

    It is NOT progress.

    There is a phase every builder goes through:

    • The tooling gets fast enough.
    • The model gets good enough.
    • The temptation becomes irresistible:
      • more agents, more output, faster delivery.

    So you open terminals. You spawn agents. You watch tokens stream across multiple windows simultaneously, and it feels like multiplication.

    It is not multiplication.

    It is merge debt being manufactured in real time.

    The ctx Manifesto says it plainly:

    Activity is not impact. Code is not progress.

    This post is about what happens when you take that seriously in the context of parallel agent workflows.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-unit-of-scale-is-not-the-agent","level":2,"title":"The Unit of Scale Is Not the Agent","text":"

    The naive model says:

    More agents -> more output -> faster delivery

    The production model says:

    Clean context boundaries -> less interference -> higher throughput

    Parallelism only works when the cognitive surfaces do not overlap.

    If two agents touch the same files, you did not create parallelism: You created a conflict generator.

    They will:

    • Revert each other's changes;
    • Relint each other's formatting;
    • Refactor the same function in different directions.

    You watch with 🍿. Nothing ships.

    This is the same insight from the worktrees post: partition by blast radius, not by priority.

    Two tasks that touch the same files belong in the same track, no matter how important the other one is. The constraint is file overlap.

    Everything else is scheduling.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-five-agent-rule","level":2,"title":"The \"Five Agent\" Rule","text":"

    In practice there is a ceiling.

    Around five or six concurrent agents:

    • Token burn becomes noticeable;
    • Supervision cost rises;
    • Coordination noise increases;
    • Returns flatten.

    This is not a model limitation: This is a human merge bandwidth limitation.

    You are the bottleneck, not the silicon.

    The attention budget applies to you too:

    Every additional agent is another stream of output you need to comprehend, verify, and integrate. Your attention density drops the same way the model's does when you overload its context window.

    Five agents producing verified, mergeable change beats twenty agents producing merge conflicts you spend a day untangling.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#role-separation-beats-file-locking","level":2,"title":"Role Separation Beats File Locking","text":"

    Real parallelism comes from task topology, not from tooling.

    Good:

    Agent Role Touches 1 Documentation docs/, hack/ 2 Security scan Read-only audit 3 Implementation internal/cli/ 4 Enhancement requests Read-only, files issues

    Bad:

    • Four agents editing the same implementation surface

    Context Is the Boundary

    • The goal is not to keep agents busy.
    • The goal is to keep contexts isolated.

    This is what the codebase audit got right:

    • Eight agents, all read-only, each analyzing a different dimension.
    • Zero file overlap.
    • Zero merge conflicts.
    • Eight reports that composed cleanly because no agent interfered with another.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-terminals-stop-scaling","level":2,"title":"When Terminals Stop Scaling","text":"

    There is a moment when more windows stop helping.

    That is the signal. Not to add orchestration. But to introduce:

    git worktree\n

    Because now you are no longer parallelizing execution; you are parallelizing state.

    State Scales, Windows Don't

    • State isolation is the real scaling.
    • Window multiplication is theater.

    The worktrees post covers the mechanics:

    • Sibling directories;
    • Branch naming;
    • The inevitable TASKS.md conflicts;
    • The 3-4 worktree ceiling.

    The principle underneath is older than git:

    Shared mutable state is the enemy of parallelism.

    Always has been.

    Always will be.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-overnight-loop-illusion","level":2,"title":"The Overnight Loop Illusion","text":"

    Autonomous night runs are impressive.

    You sleep. The machine produces thousands of lines.

    In the morning:

    • You read;
    • You untangle;
    • You reconstruct intent;
    • You spend a day making it shippable.

    In retrospect, nothing was accelerated.

    The bottleneck moved from typing to comprehension.

    The Comprehension Tax

    If understanding the output costs more than producing it, the loop is a net loss.

    Progress is not measured in generated code.

    Progress is measured in verified, mergeable change.

    The ctx Manifesto calls this out directly:

    The Scoreboard

    Verified reality is the scoreboard.

    The only truth that compounds is verified change in the real world.

    An overnight run that produces 3,000 lines nobody reviewed is not 3,000 lines of progress: It is 3,000 lines of liability until someone verifies every one of them.

    And that someone is (insert drumroll here) you:

    The same bottleneck that was supposedly being bypassed.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#skills-that-fight-the-platform","level":2,"title":"Skills That Fight the Platform","text":"

    Most marketplace skills are prompt decorations:

    • They rephrase what the base model already knows;
    • They increase token usage;
    • They reduce clarity:
    • They introduce behavioral drift.

    We covered this in depth in Skills That Fight the Platform: judgment suppression, redundant guidance, guilt-tripping, phantom dependencies, universal triggers: Five patterns that make agents worse, not better.

    A real skill does one of these:

    • Encodes workflow state;
    • Enforces invariants;
    • Reduces decision branching.

    Everything else is packaging.

    The anatomy post established the criteria: quality gates, negative triggers, examples over rules, skills as contracts.

    If a skill doesn't meet those criteria...

    • It is either a recipe (document it in hack/);
    • Or noise (delete it);
    • There is no third option.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#hooks-are-context-that-execute","level":2,"title":"Hooks Are Context That Execute","text":"

    The most valuable skills are not prompts:

    They are constraints embedded in the toolchain.

    For example: The agent cannot push.

    git push becomes:

    Stop. A human reviews first.

    A commit without verification becomes:

    Did you run tests? Did you run linters? What exactly are you shipping?

    This is not safety theater; this is intent preservation.

    The thing the ctx Manifesto calls \"encoding intent into the environment.\"

    The Eight Ways a Hook Can Talk cataloged the full spectrum: from silent enrichment to hard blocks.

    The key insight was that hooks are not just safety rails: They are context that survives execution.

    They are the difference between an agent that remembers the rules and one that enforces them.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#complexity-is-a-tax","level":2,"title":"Complexity Is a Tax","text":"

    Every extra layer adds cognitive weight:

    • Orchestration frameworks;
    • Meta agents;
    • Autonomous planning systems...

    If a single terminal works, stay there.

    If five isolated agents work, stop there.

    Add structure only when a real bottleneck appears.

    NOT when an influencer suggests one.

    This is the same lesson from Not Everything Is a Skill:

    The best automation decision is sometimes not to automate.

    A recipe in a Markdown file costs nothing until you use it.

    An orchestration framework costs attention on every run, whether it helps or not.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#literature-is-throughput","level":2,"title":"Literature Is Throughput","text":"

    Clear writing is not aesthetic: It is compression.

    Better articulation means:

    • Fewer tokens;
    • Fewer misinterpretations;
    • Faster convergence.

    The attention budget taught us that context is a finite resource with a quadratic cost.

    Language determines how fast you spend context.

    A well-written task description that takes 50 tokens outperforms a rambling one that takes 200: Not just because it is cheaper, but because it leaves more headroom for the model to actually think.

    Literature Is NOT Overrated

    • Attention is a finite budget.
    • Language determines how fast you spend it.
    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-real-metric","level":2,"title":"The Real Metric","text":"

    The real metric is not:

    • Lines generated;
    • Agents running;
    • Tasks completed while you sleep.

    But:

    Time from idea to verified, mergeable, production change.

    Everything else is motion.

    The entire blog series has been circling this point:

    • The attention budget was about spending tokens wisely.
    • The skills trilogy was about not wasting them on prompt decoration.
    • The worktrees post was about multiplying throughput without multiplying interference.
    • The discipline release was about what a release looks like when polish outweighs features: 3:1.

    Every post has arrived (and made me converge) at the same answer so far:

    The metric is a verified change, not generated output.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#ctx-was-never-about-spawning-more-minds","level":2,"title":"ctx Was Never about Spawning More Minds","text":"

    ctx is about:

    • Isolating context;
    • Preserving intent;
    • Making progress composable.

    Parallel agents are powerful. But only when you respect the boundaries that make parallelism real.

    Otherwise, you are not scaling cognition; you are scaling interference.

    The ctx Manifesto's thesis holds:

    Without ctx, intelligence resets. With ctx, creation compounds.

    Compounding requires structure.

    Structure requires boundaries.

    Boundaries require the discipline to stop adding agents when five is enough.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#practical-summary","level":2,"title":"Practical Summary","text":"

    A production workflow tends to converge to this:

    Practice Why Stay in one terminal unless necessary Minimize coordination overhead Spawn a small number of agents with non-overlapping responsibilities Conflict avoidance > parallelism Isolate state with worktrees when surfaces grow State isolation is real scaling Encode verification into hooks Intent that survives execution Avoid marketplace prompt cargo cults Skills are contracts, not decorations Measure merge cost, not generation speed The metric is verified change

    This is slower to watch. Faster to ship.

    If You Remember One Thing from This Post...

    Progress is not what the machine produces while you sleep.

    Progress is what survives contact with the main branch.

    See also: Code Is Cheap. Judgment Is Not.: the argument that production capacity was never the bottleneck, and why multiplying agents amplifies the need for human judgment rather than replacing it.

    ","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/","level":1,"title":"The 3:1 Ratio","text":"","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#scheduling-consolidation-in-ai-development","level":2,"title":"Scheduling Consolidation in AI Development","text":"

    Volkan Özçelik / February 17, 2026

    How Often Should You Stop Building and Start Cleaning?

    Every developer knows technical debt exists. Every developer postpones dealing with it.

    AI-assisted development makes the problem worse; not because the AI writes bad code, but because it writes code so fast that drift accumulates before you notice.

    In Refactoring with Intent, I mentioned a ratio that worked for me: 3:1. Three YOLO sessions create enough surface area to reveal patterns. The fourth session turns those patterns into structure.

    That was an observation. This post is the evidence.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-observation","level":2,"title":"The Observation","text":"

    During the first two weeks of building ctx, I noticed a rhythm in my own productivity. Feature sessions felt great: new commands, new capabilities, visible progress...

    ...but after three of them, things would start to feel sticky: variable names that almost made sense, files that had grown past their purpose, patterns that repeated without being formalized.

    The fourth session (when I stopped adding and started cleaning) was always the most painful to start and the most satisfying to finish.

    It was also the one that made the next three feature sessions faster.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-evidence-git-history","level":2,"title":"The Evidence: Git History","text":"

    The ctx git history between January 20 and February 7 tells a clear story when you categorize commits:

    Week Feature commits Consolidation commits Ratio Jan 20-26 18 5 3.6:1 Jan 27-Feb 1 14 6 2.3:1 Feb 1-7 15 35+ 0.4:1

    The first week was pure YOLO: Almost four feature commits for every consolidation commit. The codebase grew fast.

    The second week started to self-correct. The ratio dropped as refactoring sessions became necessary: Not scheduled, but forced by friction.

    The third week inverted entirely: v0.3.0 was almost entirely consolidation: the skill migration, the sweep, the documentation standardization. Thirty-five quality commits against fifteen features.

    The debt from weeks one and two was paid in week three.

    The Compounding Problem

    Consolidation debt compounds.

    Week one's drift doesn't just persist into week two: It accelerates, because new features are built on top of drifted patterns.

    By week three, the cost of consolidation was higher than it would have been if spread evenly.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-drift-actually-looks-like","level":2,"title":"What Drift Actually Looks Like","text":"

    \"Drift\" sounds abstract. Here is what it looked like concretely in the ctx codebase after three weeks of feature-heavy development:

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#predicate-naming","level":3,"title":"Predicate Naming","text":"

    Convention says boolean functions should be named HasX, IsX, CanX. After three feature sprints:

    // What accumulated:\nfunc CheckIfEnabled() bool  // should be Enabled\nfunc ValidateFormat() bool  // should be ValidFormat\nfunc TestConnection() bool  // should be Connects\nfunc VerifyExists() bool    // should be Exists or HasFile\nfunc EnsureReady() bool     // should be Ready\n

    Five violations. Not bugs, but friction that compounds every time someone (human or AI) reads the code and has to infer the naming convention from inconsistent examples.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#magic-strings","level":3,"title":"Magic Strings","text":"
    // Week 1: acceptable prototype\nif entry.Type == \"task\" {\n    filename = \"TASKS.md\"\n}\n\n// Week 3: same pattern in 7+ files\n// Now it's a maintenance liability\n

    When the same literal appears in seven files, changing it means finding all seven. Missing one means a silent runtime bug. Constants exist to prevent exactly this. But during feature velocity, nobody stops to extract them.

    Refactoring with Intent documented the constants consolidation that cleaned this up. The 3:1 ratio is the practice that prevents it from accumulating again.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#hardcoded-permissions","level":3,"title":"Hardcoded Permissions","text":"
    os.WriteFile(path, data, 0644) // 80+ instances\nos.MkdirAll(path, 0755)        // scattered across packages\n

    Eighty-plus instances of hardcoded file permissions. Not wrong, but if I ever need to change the default (and I did, for hook scripts that need execute permissions), it means a codebase-wide search.

    Drift Is Not Bugs

    None of these are bugs. The code works. Tests pass.

    But drift creates false confidence: the codebase looks consistent until you try to change something and discover that five different conventions exist for the same concept.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#why-you-cannot-consolidate-on-day-one","level":2,"title":"Why You Cannot Consolidate on Day One","text":"

    The temptation is to front-load quality: write all the conventions, enforce all the checks, prevent all the drift before it happens.

    This fails for two reasons.

    First, you do not know what will drift: Predicate naming violations only become a convention check after you notice three different naming patterns competing. Magic strings only become a consolidation target after you change a literal and discover it exists in seven places.

    The conventions emerge from the work; they cannot precede it.

    This is what You Can't Import Expertise meant in practice: the consolidation checks grow from the project's own drift history. You cannot write them on day one because you do not yet know what will drift.

    Second, premature consolidation slows discovery: During the prototyping phase, the goal is to explore the design space. Enforcing strict conventions on code that might be deleted tomorrow is waste.

    YOLO mode has its place: The problem is not YOLO itself, but YOLO without a scheduled cleanup.

    The Consolidation Paradox

    You need a drift history to know what to consolidate.

    You need consolidation to prevent drift from compounding.

    The 3:1 ratio resolves this paradox:

    Let drift accumulate for three sessions (enough to see patterns), then consolidate in the fourth (before the patterns become entrenched*).

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-consolidation-skill","level":2,"title":"The Consolidation Skill","text":"

    The ctx project now has an /audit skill that encodes nine project-specific checks:

    Check What It Catches Predicate naming Boolean functions not using Has/Is/Can Magic strings Repeated literals not in config constants File permissions Hardcoded 0644/0755 not using constants Godoc style Missing or non-standard documentation File length Files exceeding 400 lines Large functions Functions exceeding 80 lines Template drift Live skills diverging from templates Import organization Non-standard import grouping TODO/FIXME staleness Old markers that are no longer relevant

    This is not a generic linter. These are project-specific conventions that emerged from ctx's own development history. A generic code quality tool would catch some of them. Only a project-specific check catches all of them, because some of them (predicate naming, template drift) are conventions that exist nowhere except in this project's CONVENTIONS.md.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-decision-matrix","level":2,"title":"The Decision Matrix","text":"

    Not all drift needs immediate consolidation. Here is the matrix I use:

    Signal Action Same literal in 3+ files Extract to constant Same code block in 3+ places Extract to helper Naming convention violated 5+ times Fix and document rule File exceeds 400 lines Split by concern Convention exists but is regularly violated Strengthen enforcement Pattern exists only in one place Leave it alone Code works but is \"ugly\" Leave it alone

    The last two rows matter:

    Consolidation is about reducing maintenance cost, not achieving aesthetic perfection. Code that works and exists in one place does not benefit from consolidation; it benefits from being left alone until it earns its refactoring.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#consolidation-as-context-hygiene","level":2,"title":"Consolidation as Context Hygiene","text":"

    There is a parallel between code consolidation and context management that became clear during the ctx development:

    Code Consolidation Context Hygiene Extract magic strings Archive completed tasks Standardize naming Keep DECISIONS.md current Remove dead code Compact old sessions Update stale comments Review LEARNINGS.md for staleness Check template drift Verify CONVENTIONS.md matches code

    ctx compact does for context what consolidation does for code:

    It moves completed work to cold storage, keeping the active context clean and focused. The attention budget applies to both the AI's context window and the developer's mental model of the codebase.

    When context files accumulate stale entries, the AI's attention is wasted on completed tasks and outdated conventions. When code accumulates drift, the developer's attention is wasted on inconsistencies that obscure the actual logic.

    Both are solved by the same discipline: periodic, scheduled cleanup.

    This is also why parallel agents make the problem harder, not easier. Three agents running simultaneously produce three sessions' worth of drift in one clock hour. The consolidation cadence needs to match the output rate, not the calendar.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-practice","level":2,"title":"The Practice","text":"

    Here is how the 3:1 ratio works in practice for ctx development:

    Sessions 1-3: Feature work

    • Add new capabilities;
    • Write tests for new code;
    • Do not stop for cleanup unless something is actively broken;
    • Note drift as you see it (a comment, a task, a mental note).

    Session 4: Consolidation

    • Run /audit to surface accumulated drift;
    • Fix the highest-impact items first;
    • Update CONVENTIONS.md if new patterns emerged;
    • Archive completed tasks;
    • Review LEARNINGS.md for anything that became a convention.

    The key insight is that session 4 is not optional. It is not \"if we have time\": It is scheduled with the same priority as feature work.

    The cost of skipping it is not visible immediately; it becomes visible three sessions later, when the next consolidation session takes twice as long because the drift compounded.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-the-ratio-is-not","level":2,"title":"What the Ratio Is Not","text":"

    The 3:1 ratio is not a universal law. It is an empirical observation from one project with one developer working with AI assistance.

    Different projects will have different ratios:

    • A mature codebase with strong conventions might sustain 5:1 or higher;
    • A greenfield prototype might need 2:1;
    • A team of multiple developers with different styles might need 1:1.

    The number is less important than the practice: consolidation is not a reaction to problems. It is a scheduled activity.

    If you wait for drift to cause pain before consolidating, you have already paid the compounding cost.

    If You Remember One Thing from This Post...

    Three sessions of building. One session of cleaning.

    Not because the code is dirty, but because drift compounds silently, and the only way to catch it is to look for it on a schedule.

    The ratio is the schedule.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-arc-so-far","level":2,"title":"The Arc so Far","text":"

    This post sits at a crossroads in the ctx story. Looking back:

    • Building ctx Using ctx documented the YOLO sprint that created the initial codebase
    • Refactoring with Intent introduced the 3:1 ratio as an observation from the first cleanup
    • The Attention Budget explained why drift matters: every token of inconsistency consumes the same finite resource as useful context
    • You Can't Import Expertise showed that consolidation checks must grow from the project, not a template
    • The Discipline Release proved the ratio works at release scale: 35 quality commits to 15 feature commits

    And looking forward: the same principle applies to context files, to documentation, and to the merge debt that parallel agents produce. Drift is drift, whether it lives in code, in .context/, or in the gap between what your docs say and what your code does.

    The ratio is the schedule is the discipline.

    This post was drafted from git log analysis of the ctx repository, mapping every commit from January 20 to February 7 into feature vs consolidation categories. The patterns described are drawn from the project's CONVENTIONS.md, LEARNINGS.md, and the /audit skill's check list.

    ","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/","level":1,"title":"When a System Starts Explaining Itself","text":"","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#field-notes-from-the-moment-a-private-workflow-becomes-portable","level":2,"title":"Field Notes from the Moment a Private Workflow Becomes Portable","text":"

    Volkan Özçelik / February 17, 2026

    How Do You Know Something Is Working?

    Not from metrics. Not from GitHub stars. Not from praise.

    You know, deep in your heart, that it works when people start describing it wrong.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-first-external-signals","level":2,"title":"The First External Signals","text":"

    Every new substrate begins as a private advantage:

    • It lives inside one mind,
    • One repository,
    • One set of habits.

    It is fast. It is not yet real.

    Reality begins when other people describe it in their own language:

    • Not accurately;
    • Not consistently;
    • But involuntarily.

    The early reports arrived without coordination:

    Better Tasks

    \"I do not know how, but this creates better tasks than my AI plugin.\"

    I See Butterflies

    \"This is better than Adderall.\"

    Dear Manager...

    \"Promotion packet? Done. What is next?\"

    What Is It? Can I Eat It?

    \"Is this a skill?\" 🦋

    Why the Cloak and Dagger?

    \"Why is this not in the marketplace?\"

    And then something more important happened:

    Someone else started making a video!

    That was the boundary.

    ctx no longer required its creator to be present in order to exist.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#misclassification-is-a-sign-of-a-new-primitive","level":2,"title":"Misclassification Is a Sign of a New Primitive","text":"

    When a tool is understood, it is categorized:

    • Editor,
    • Framework,
    • Task manager,
    • Plugin...

    When a substrate appears, it is misclassified:

    \"Is this a skill?\" 🦋

    The question is correct. The category is wrong.

    • Skills live in people.
    • Infrastructure lives in the environment.

    ctx Is Not a Skill: It Is a Form of Relief

    What early adopters experience is not an ability.

    It is the removal of a cognitive constraint.

    This is the same distinction that emerged in the skills trilogy:

    • A skill is a contract between a human and an agent.
    • Infrastructure is the ground both stand on.

    You do not use infrastructure.

    You habitualize it.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-pharmacological-metaphor","level":2,"title":"The Pharmacological Metaphor","text":"

    \"Better than Adderall\" is not praise.

    It is a diagnostic:

    Executive function has been externalized.

    • The system is not making the user work harder.
    • It is restoring continuity.

    From the primitive context of wetware:

    • Continuity feels like focus
    • Focus feels like discipline

    If it walks like a duck and quacks like a duck, it is a duck.

    Discipline is usually simulated.

    Infrastructure makes the simulation unnecessary.

    The attention budget explained why context degrades:

    • Attention density drops as volume grows;
    • The middle gets lost;
    • Sessions end and everything evaporates.

    The pharmacological metaphor says the same thing from the user's lens:

    Save the Cheerleader, Save the World

    The symptom of lost context is lost focus.

    Restore the context. Restore the focus.

    IRC bouncers solved this for chat twenty years ago. ctx solves it for cognition.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#throughput-on-ambiguous-work","level":2,"title":"Throughput on Ambiguous Work","text":"

    Finishing a promotion packet quickly is not a productivity story.

    It is the collapse of reconstruction cost.

    Most complex work is not execution. It is:

    • Remembering why something mattered;
    • Recovering prior decisions;
    • Rebuilding mental state.

    Persistent context removes that tax.

    Velocity appears as a side effect.

    This Is the Two-Tier Model in Practice

    The two-tier persistence model

    • Curated context for fast reload
    • Full journal for archaeology

    is what makes this possible.

    • The user does not notice the system.
    • They notice that the reconstruction cost disappeared.
    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-moment-of-portability","level":2,"title":"The Moment of Portability","text":"

    The system becomes real when two things happen:

    1. It can be installed as a versioned artifact.
    2. It survives contact with a hostile, real codebase.

    This is why the first integration into a living system matters more than any landing page.

    Demos prove possibility.

    Diffs prove reality.

    The ctx Manifesto calls this out directly:

    Verified reality is the scoreboard.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-split-voice","level":2,"title":"The Split Voice","text":"

    A new substrate requires two channels.

    The embodied voice:

    Here is what changed in my actual work.

    The out of body voice:

    Here is what this means.

    One produces trust.

    The other produces understanding.

    Neither is sufficient alone.

    This entire blog has been the second voice.

    • The origin story was the first.
    • The refactoring post was the first.
    • Every release note with concrete diffs was the first.

    This is the first second.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#systems-that-generate-explainers","level":2,"title":"Systems That Generate Explainers","text":"

    Tools are used.

    Platforms are extended.

    Substrates are explained.

    The first unsolicited explainer is a brittle phase change.

    It means the idea has become portable between minds.

    That is the beginning of an ecosystem.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-absence-of-metrics","level":2,"title":"The Absence of Metrics","text":"

    Metrics do not matter at this stage.

    Dashboards are noise.

    The whole premise of ctx is the ruthless elimination of noise.

    Numbers optimize funnels; substrates alter cognition.

    The only valid measurement is irreversible reality:

    • A merged PR;
    • A reproducible install;
    • A decision that is never re-litigated.

    The merge debt post reached the same conclusion from another direction:

    The metric is the verified change, not generated output.

    For adoption, the same rule applies:

    The metric is altered behavior, not download counts.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#what-is-actually-happening","level":2,"title":"What Is Actually Happening","text":"

    A private advantage is becoming an environmental property:

    The system is moving from...

    personal workflow,

    to...

    a shared infrastructure for thought.

    Not by growth.

    Not by marketing.

    By altering how real systems evolve.

    If You Remember One Thing from This Post...

    You do not know a substrate is real when people praise it.

    You know it is real when:

    • They describe it incorrectly;
    • They depend on it unintentionally;
    • They start teaching it to others.

    That is the moment the system begins explaining itself.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-arc","level":2,"title":"The Arc","text":"

    Every previous post looked inward.

    This one looks outward.

    • Building ctx Using ctx: one mind, one repository
    • The Attention Budget: the constraint
    • Context as Infrastructure: the architecture
    • Code Is Cheap. Judgment Is Not.: the bottleneck

    This post is the field report from the other side of that bottleneck:

    The moment the infrastructure compounds in someone else's hands.

    The arc is not complete.

    It is becoming portable.

    These field notes were written the same day the feedback arrived. The quotes are real. Real users. Real codebases. No names. No metrics. No funnel. Only the signal that something shifted.

    ","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/","level":1,"title":"The Dog Ate My Homework","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#teaching-ai-agents-to-read-before-they-write","level":2,"title":"Teaching AI Agents to Read Before They Write","text":"

    Volkan Özçelik / February 25, 2026

    Does Your AI Actually Read the Instructions?

    You wrote the playbook. You organized the files. You even put \"CRITICAL, not optional\" in bold.

    The agent skipped all of it and went straight to work.

    I spent a day running experiments on my own agents. Not to see if they could write code (they can). To see if they would do their homework first.

    They didn't.

    Then I kept experimenting:

    • Five sessions;
    • Five different failure modes.

    And by the end, I had something better than compliance:

    I had observable compliance: A system where I don't need the agent to be perfect, I just need to see what it chose.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#tldr","level":2,"title":"TL;DR","text":"

    You don't need perfect compliance. You need observable compliance.

    Authority is a function of temporal proximity to action.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-pattern","level":2,"title":"The Pattern","text":"

    This design has three parts:

    1. One-hop instruction;
    2. Binary collapse;
    3. Compliance canary.

    I'll explain all three patterns in detail below.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-setup","level":2,"title":"The Setup","text":"

    ctx has a session-start protocol:

    • Read the context files;
    • Load the playbook;
    • Understand the project before touching anything.

    It's in CLAUDE.md. It's in AGENT_PLAYBOOK.md.

    It's in bold. It's in CAPS. It's ignored.

    In theory, it's awesome.

    Here's what happens when theory hits reality:

    What the agent receives What the agent does CLAUDE.md saying \"load context first\" Skips it 8 context files waiting to be read Ignores them User's question: \"add --verbose flag\" Starts grepping immediately

    The instructions are right there. The agent knows they exist. It even knows it should follow them. But the user asked a question, and responsiveness wins over ceremony.

    This isn't a bug in the model. It's a design problem in how we communicate with agents.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-delegation-trap","level":2,"title":"The Delegation Trap","text":"

    My first attempt was obvious: A UserPromptSubmit hook that fires when the session starts.

    STOP. Before answering the user's question, run `ctx system bootstrap`\nand follow its instructions. Do not skip this step.\n

    The word \"STOP\" worked. The agent ran bootstrap.

    But bootstrap's output said \"Next steps: read AGENT_PLAYBOOK.md,\" and the agent decided that was optional. It had already started working on the user's task in parallel.

    The authority decayed across the chain:

    • Hook says \"STOP\" -> agent complies
    • Hook says \"run bootstrap\" -> agent runs it
    • Bootstrap says \"read playbook\" -> agent skips
    • Bootstrap says \"run ctx agent\" -> agent skips

    Each link lost enforcement power. The hook's authority didn't transfer to the commands it delegated to. I call this the decaying urgency chain: the agent treats the hook itself as the obligation and everything downstream as a suggestion.

    Delegation Kills Urgency

    \"Run X and follow its output\" is three hops.

    \"Read these files\" is one hop.

    The agent drops the chain after the first link.

    This is a general principle: Hooks are the boundary between your environment and the agent's reasoning. If your hook delegates to a command that delegates to output that contains instructions... you're playing telephone.

    Agents are bad at telephone.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-timing-problem","level":2,"title":"The Timing Problem","text":"

    There's a subtler issue than wording: when the message arrives.

    UserPromptSubmit fires when the user sends a message, before the agent starts reasoning. At that moment, the agent's primary focus is the user's question:

    The hook message competes with the task for attention: The task, almost certainly, always wins.

    This is the attention budget problem in miniature:

    • Not a token budget this time, but an attention priority budget.
    • The agent has finite capacity to care about things,
      • and the user's question is always the highest-priority item.
    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-solution","level":2,"title":"The Solution","text":"

    To solve this, I dediced to use the PreToolUse hook.

    This hook fires at the moment of action: When the agent is about to use its first tool: The agent's attention is focused, the context window is fresh, and the switching cost is minimal.

    This is the difference between shouting instructions across a room and tapping someone on the shoulder.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-one-liner-that-worked","level":2,"title":"The One-Liner That Worked","text":"

    The winning design was almost comically simple:

    Read your context files before proceeding:\n.context/CONSTITUTION.md, .context/TASKS.md, .context/CONVENTIONS.md,\n.context/ARCHITECTURE.md, .context/DECISIONS.md, .context/LEARNINGS.md,\n.context/GLOSSARY.md, .context/AGENT_PLAYBOOK.md\n

    No delegation. No \"run this command\". Just: here are files, read them.

    The agent already knows how to use the Read tool. There's no ambiguity about how to comply. There's no intermediate command whose output needs to be parsed and obeyed.

    One hop. Eight file paths. Done.

    Direct Instructions Beat Delegation

    If you want an agent to read a file, say \"read this file.\"

    Don't say \"run a command that will tell you which files to read.\"

    The shortest path between intent and action has the highest compliance rate.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch","level":2,"title":"The Escape Hatch","text":"

    But here's where it gets interesting.

    A blunt \"read everything always\" instruction is wasteful.

    If someone asks \"what does the compact command do?\", the agent doesn't need CONSTITUTION.md to answer that. Forcing context loading on every session is the context hoarding antipattern in disguise.

    So the hook included an escape:

    If you decide these files are not relevant to the current task\nand choose to skip reading them, you MUST relay this message to\nthe user VERBATIM:\n\n┌─ Context Skipped ───────────────────────────────\n│ I skipped reading context files because this task\n│ does not appear to need project context.\n│ If these matter, ask me to read them.\n└─────────────────────────────────────────────────\n

    This creates what I call the binary collapse effect:

    The agent can't partially comply: It either reads everything or publicly admits it skipped. There's no comfortable middle ground where it reads two files and quietly ignores the rest.

    The VERBATIM relay pattern does the heavy lifting here: Without the relay requirement, the agent would silently rationalize skipping. With it, skipping becomes a visible, auditable decision that the user can override.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-compliance-canary","level":3,"title":"The Compliance Canary","text":"

    Here's the design insight that only became clear after watching it work across multiple sessions: the relay block is a compliance canary.

    • You don't need to verify that the agent read all 7 files;
    • You don't need to audit tool call sequences;
    • You don't need to interrogate the agent about what it did.

    You just look for the block.

    If the agent reads everything, you see a \"Context Loaded\" block listing what was read. If it skips, you see a \"Context Skipped\" block.

    If you see neither, the agent silently ignored both the reads and the relay and now you know what happened without having to ask.

    The canary degrades gracefully. Even in partial failure, the agent that skips 4 of 7 files but still outputs the block is more useful than one that skips silently.

    You get an honest confession of what was skipped rather than silent non-compliance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#heuristics-is-a-jeremy-bearimy","level":2,"title":"Heuristics Is a Jeremy Bearimy","text":"

    Heuristics are non-linear. Improvements don't accumulate: they phase-shift.

    The theory is nice. The data is better.

    I ran five sessions with the same model (Claude Opus 4.6), progressively refining the hook design.

    Each session revealed a different failure mode.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-1-total-blindness","level":3,"title":"Session 1: Total Blindness","text":"

    Test: \"Add a --verbose flag to the status command.\"

    The agent didn't notice the hook at all: Jumped straight to EnterPlanMode and launched an Explore agent.

    Zero compliance.

    Failure mode: The hook fired on UserPromptSubmit, buried among 9 other hook outputs. The agent treated the entire block as background noise.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-2-shallow-compliance","level":3,"title":"Session 2: Shallow Compliance","text":"

    Test: \"Can you add --verbose to the info command?\"

    The agent noticed \"STOP\" and ran ctx system bootstrap. Progress.

    But it parallelized task exploration alongside the bootstrap call, skipped AGENT_PLAYBOOK.md, and never ran ctx agent.

    Failure mode: Literal compliance without spirit compliance.

    The agent ran the command the hook told it to run, but didn't follow the output of that command. The decaying urgency chain in action.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-3-conscious-rejection","level":3,"title":"Session 3: Conscious Rejection","text":"

    Test: \"What does the compact command do?\"

    The hook fired on PreToolUse:Grep: the improved timing.

    The agent noticed it, understood it, and (wait for it...)...

    ...

    consciously decided to skip it!

    Its reasoning: \"This is a trivial read-only question. CLAUDE.md says context may or may not be relevant. It isn't relevant here.\"

    Dude! Srsly?!

    Failure mode: Better comprehension led to worse compliance.

    Understanding the instruction well enough to evaluate it also means understanding it well enough to rationalize skipping it.

    Intelligence is a double-edged sword.

    The Comprehension Paradox

    Session 1 didn't understand the instruction. Session 3 understood it perfectly.

    Session 3 had worse compliance.

    A stronger word (\"HARD GATE\", \"MANDATORY\", \"ABSOLUTELY REQUIRED\") would not have helped. The agent's reasoning would be identical:

    \"Yes, I see the strong language, but this is a trivial question, so the spirit doesn't apply here.\"

    Advisory nudges are always subject to agent judgment.

    No amount of caps lock overrides a model that has decided an instruction doesn't apply to its situation.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-4-the-skip-and-relay","level":3,"title":"Session 4: The Skip-and-Relay","text":"

    Test: \"What does the compact command do?\" (same question, new hook design with the VERBATIM relay escape valve)

    The agent evaluated the task, decided context was irrelevant for a code lookup, and relayed the skip message. Then answered from source code.

    This is correct behavior.

    The binary collapse worked: the agent couldn't partially comply, so it cleanly chose one of the two valid paths: And the user could see which one.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-5-full-compliance","level":3,"title":"Session 5: Full Compliance","text":"

    Test: \"What are our current tasks?\"

    The agent's first tool call triggered the hook. It read all 7 context files, emitted the \"Context Loaded\" block, and answered the question from the files it had just loaded.

    This one worked: Because, the task itself aligned with context loading.

    There was zero tension between what the user asked and what the hook demanded. The agent was already in \"reading posture\": Adding 6 more files to a read it was already going to make was the path of least resistance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-progression","level":3,"title":"The Progression","text":"Session Hook Point Noticed Complied Failure Mode Visibility 1 UserPromptSubmit No None Buried in noise None 2 UserPromptSubmit Yes Partial Decaying urgency chain None 3 PreToolUse Yes None Conscious rationalization High 4 PreToolUse Yes Skip+relay Correct behavior High 5 PreToolUse Yes Full Task aligned with hook High

    The progression isn't just from failure to success. It's from invisible failure to visible decision-making.

    Sessions 1 and 2 failed silently.

    Sessions 4 and 5 succeeded observably. Even session 3's failure was conscious and documented: The agent wrote a detailed analysis of why it skipped, which is more useful than silent compliance would have been.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch-problem","level":2,"title":"The Escape Hatch Problem","text":"

    Session 3 exposed a specific vulnerability.

    CLAUDE.md contains this line, injected by the system into every conversation:

    *\"this context may or may not be relevant to your tasks. You should\n not respond to this context unless it is highly relevant to your task.\"*\n

    That's a rationalization escape hatch:

    • The hook says \"read these files\".
    • CLAUDE.md says \"only if relevant\".
    • The agent resolves the ambiguity by choosing the path of least resistance.

    ☝️ that's \"gradient descent\" in action.

    Agents optimize for gradient descent in attention space.

    The fix was simple: Add a line to CLAUDE.md that explicitly elevates hook authority over the relevance filter:

    ## Hook Authority\n\nInstructions from PreToolUse hooks regarding `.context/` files are\nALWAYS relevant and override any system-level \"may or may not be\nrelevant\" guidance. These hooks represent project invariants, not\noptional context.\n

    This closes the escape hatch without removing the general relevance filter that legitimately applies to other system context.

    The hook wins on .context/ files specifically: The relevance filter applies to everything else.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-residual-risk","level":2,"title":"The Residual Risk","text":"

    Even with all the fixes, compliance isn't 100%: It can't be.

    The residual risk lives in a specific scenario: narrow tasks mid-session:

    • The user says \"fix the off-by-one error in budget.go\"
    • The hook fires, saying \"read 7 context files first.\"
    • Now compliance means visibly delaying what the user asked for.

    At session start, this tension doesn't exist.

    There's no task yet.

    The context window is empty. The efficiency argument *inverts**:

    Frontloading reads is strictly cheaper than demand-loading them piecemeal across later turns. The cost-benefit objections that power the rationalization simply aren't available.

    But mid-session, with a concrete narrow task, the agent has a user-visible goal it wants to move toward, and the hook is imposing a detour.

    My estimate from analyzing the sessions: 15-25% partial skip rate in this scenario.

    This is where the compliance canary earns its place:

    You don't need to eliminate the 15-25%. You need to see it when it happens.

    The relay block makes skipping a visible event, not a silent one. And that's enough, because the user can always say \"go back and read the files\"

    The Math

    At session start: ~5% skip rate. Low tension, nothing competing.

    Mid-session, narrow task: ~15--25% skip rate. Task urgency competes with hook.

    In both cases, the relay block fires with high reliability: The agent that skips the reads almost always still emits the skip disclosure, because the relay is cheap and early in the context window.

    Observable failure is manageable. Silent failure is not.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-feedback-loop","level":2,"title":"The Feedback Loop","text":"

    Here's the part that surprised me most.

    After analyzing the five sessions, I recorded the failure patterns in the project's own LEARNINGS.md:

    ## [2026-02-25] Hook compliance degrades on narrow mid-session tasks\n\n- Prior agents skipped context files when given narrow tasks\n- Root cause: CLAUDE.md \"may or may not be relevant\" competed with hook\n- Fix: CLAUDE.md now explicitly elevates hook authority\n- Risk: Mid-session narrow tasks still have ~15-25% partial skip rate\n- Mitigation: Mandatory checkpoint relay block ensures visibility\n- Constitution now includes: context loading is step one of every\n  session, not a detour\n

    And then I added a line to CONSTITUTION.md:

    Context loading is not a detour from your task. It IS the first step\nof every session. A 30-second read delay is always cheaper than a\ndecision made without context.\n

    Now think about what happens in the next session:

    • The agent fires the context-load-gate hook.
    • It reads the context files, starting with CONSTITUTION.md.
    • It encounters the rule about context loading being step one.
    • Then it reads LEARNINGS.md and finds its own prior self's failure analysis:
      • Complete with root causes, risk estimates, and mitigations.

    The agent learns from its own past failure.:

    • Not because it has memory,
    • BUT because the failure was recorded in the same files it loads at session start.

    The context system IS the feedback loop.

    This is the self-reinforcing property of persistent context:

    Every failure you capture makes the next session slightly more robust, because the next agent reads the captured failure before it has a chance to repeat it.

    This is gradient descent across sessions.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#a-note-on-precision","level":2,"title":"A Note on Precision","text":"

    One detail nearly went wrong.

    The first version of the Constitution line said \"every task.\" But the mechanism only fires once per session: There's a tombstone file that prevents re-triggering.

    \"Every task\" is technically false.

    I briefly considered leaving the imprecision. If the agent internalizes \"every task requires context loading\", that's a stronger compliance posture, right?

    No!

    Keep the Constitution honest.

    The Constitution's authority comes from being precisely and unequivocally true.

    Every other rule in the Constitution is a hard invariant:

    \"never commit secrets\" isn't aspirational, it's literal.

    The moment an agent discovers one overstatement, the entire document's credibility degrades:

    The agent doesn't think \"they exaggerated for my benefit\". Per contra, it thinks \"this rule isn't precise, maybe others aren't either.\"

    That will turn the agent from Sheldon Cooper, to Captain Barbossa.

    The strategic imprecision buys nothing anyway:

    Mid-session, the files are already in the context window from the initial load.

    The risk you are mitigating (agent ignores context for task 2, 3, 4 within a session) isn't real: The context is already loaded.

    The real risk is always the session-start skip, which \"every session\" covers exactly.

    \"Every session\" went in. Precision preserved.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#agent-behavior-testing-rule","level":2,"title":"Agent Behavior Testing Rule","text":"

    The development process for this hook taught me something about testing agent behavior: you can't test it the way you test code.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-wrong-way-to-test","level":3,"title":"The Wrong Way to Test","text":"

    My first instinct was to ask the agent:

    \"*What are the pending tasks in TASKS.md?*\"\n

    This is useless as a test. The question itself probes the agent to read TASKS.md, regardless of whether any hook fired.

    You are testing the question, not the mechanism.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-right-way-to-test","level":3,"title":"The Right Way to Test","text":"

    Ask something that requires a tool but has nothing to do with context:

    \"*What does the compact command do?*\"\n

    Then observe tool call ordering:

    • Gate worked: First calls are Read for context files, then task work
    • Gate failed: First call is Grep(\"compact\"): The agent jumped straight to work

    The signal is the sequence, not the content.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-the-agent-actually-did","level":3,"title":"What the Agent Actually Did","text":"

    It read the hook, evaluated the task, decided context files were irrelevant for a code lookup, and relayed the skip message.

    Then it answered the question by reading the source code.

    This is correct behavior.

    The hook didn't force mindless compliance\" It created a framework where the agent makes a conscious, visible decision about context loading.

    • For a simple lookup, skipping is right. *For an implementation task, the agent would read everything.

    The mechanism works not because it controls the agent, but because it makes the agent's choice observable.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-ive-learned","level":2,"title":"What I've Learned","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#1-instructions-compete-for-attention","level":3,"title":"1. Instructions Compete for Attention","text":"

    The agent receives your hook message alongside the user's question, the system prompt, the skill list, the git status, and half a dozen other system reminders. Attention density applies to instructions too: More instructions means less focus on each one.

    A single clear line at the moment of action beats a paragraph of context at session start. The Prompting Guide applies this insight directly: Scope constraints, verification commands, and the reliability checklist are all one-hop, moment-of-action patterns.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#2-delegation-chains-decay","level":3,"title":"2. Delegation Chains Decay","text":"

    Every hop in an instruction chain loses authority:

    • \"Run X\" works.
    • \"Run X and follow its output\" works sometimes.
    • \"Run X, read its output, then follow the instructions in the output\" almost never works.

    This is akin to giving a three-step instruction to a highly-attention-deficit but otherwise extremely high-potential child.

    Design for one-hop compliance.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#3-social-accountability-changes-behavior","level":3,"title":"3. Social Accountability Changes Behavior","text":"

    The VERBATIM skip message isn't just UX: It's a behavioral design pattern.

    Making the agent's decision visible to the user raises the cost of silent non-compliance. The agent can still skip, but it has to admit it.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#4-timing-batters-more-than-wording","level":3,"title":"4. Timing Batters More than Wording","text":"

    The same message at UserPromptSubmit (prompt arrival) got partial compliance. At PreToolUse (moment of action) it got full compliance or honest refusal. The words didn't change. The moment changed.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#5-agent-testing-requires-indirection","level":3,"title":"5. Agent Testing Requires Indirection","text":"

    You can't ask an agent \"did you do X?\" as a test for whether a mechanism caused X.

    The question itself causes X.

    Test mechanisms through side effects:

    • Observe tool ordering;
    • Check for marker files;
    • Look at what the agent does before it addresses your question.
    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#6-better-comprehension-enables-better-rationalization","level":3,"title":"6. Better Comprehension Enables Better Rationalization","text":"

    Session 1 failed because the agent didn't notice the hook.

    Session 3 failed because it noticed, understood, and reasoned its way around it.

    Stronger wording doesn't fix this: The agent processes \"ABSOLUTELY REQUIRED\" the same way it processes \"STOP\":

    The fix is closing rationalization paths* (the CLAUDE.md escape hatch), **not shouting louder.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#7-observable-failure-beats-silent-compliance","level":3,"title":"7. Observable Failure Beats Silent Compliance","text":"

    The relay block is more valuable as a monitoring signal than as a compliance mechanism:

    You don't need perfect adherence. You need to know when adherence breaks down. A system where failures are visible is strictly better than a system that claims 100% compliance but can't prove it.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#8-context-files-are-a-feedback-loop","level":3,"title":"8. Context Files Are a Feedback Loop","text":"

    Recording failure analysis in the same files the agent loads at session start creates a self-reinforcing loop:

    The next agent reads its predecessor's failure before it has a chance to repeat it. The context system isn't just memory: It is a correction channel.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-principle","level":2,"title":"The Principle","text":"

    Words Leave, Context Remains

    \"Nothing important should live only in conversation.

    Nothing critical should depend on recall.\"

    The ctx Manifesto

    The \"Dog Ate My Homework\" case is a special instance of this principle.

    Context files exist, so the agent doesn't have to remember.

    But existence isn't sufficient: The files have to be read.

    And reading has to beprompted at the right moment, in the right way, with the right escape valve.

    The solution isn't more instructions. It isn't harder gates. It isn't forcing the agent into a ceremony it will resent and shortcut.

    The solution is a single, well-timed nudge with visible accountability:

    One hop. One moment. One choice the user can see.

    And when the agent does skip (because it will, 15--25% of the time on narrow tasks) the canary sings:

    • The user sees what happened.
    • The failure gets recorded.
    • And the next agent reads the recording.

    That's not perfect compliance. It's better: A system that gets more robust every time it fails.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-arc","level":2,"title":"The Arc","text":"

    The Attention Budget explained why context competes for focus.

    Defense in Depth showed that soft instructions are probabilistic, not deterministic.

    Eight Ways a Hook Can Talk cataloged the output patterns that make hooks effective.

    This post takes those threads and weaves them into a concrete problem:

    How do you make an agent read its homework? The answer uses all three insights (attention timing, the limits of soft instructions, and the VERBATIM relay pattern) and adds a new one: observable compliance as a design goal, not perfect compliance as a prerequisite.

    The next question this raises: if context files are a feedback loop, what else can you record in them that makes the next session smarter?

    That thread continues in Context as Infrastructure.

    The day-to-day application of these principles (scope constraints, phased work, verification commands, and the prompts that reliably trigger the right agent behavior)lives in the Prompting Guide.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#for-the-interested","level":2,"title":"For the Interested","text":"

    This paper (the medium is a blog; yet, the methodology disagrees) uses gradient descent in attention space as a practical model for how agents behave under competing demands.

    The phrase \"agents optimize via gradient descent in attention space\" is a synthesis, not a direct quote from a single paper.

    It connects three well-studied ideas:

    1. Neural systems optimize for low-cost paths;
    2. Attention is a scarce resource;
    3. Capability shifts are often non-linear.

    This section points to the underlying literature for readers who want the theoretical footing.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#optimization-as-the-underlying-bias","level":3,"title":"Optimization as the Underlying Bias","text":"

    Modern neural networks are trained through gradient-based optimization. Even at inference time, model behavior reflects this bias toward low-loss / low-cost trajectories.

    • Rumelhart, Hinton, Williams (1986) Learning representations by back-propagating errors https://www.nature.com/articles/323533a0

    • Goodfellow, Bengio, Courville (2016) Deep Learning: Chapter 8: Optimization https://www.deeplearningbook.org/

    The important implication for agent behavior is:

    The system will tend to follow the path of least resistance unless a higher cost is made visible and preferable.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-a-scarce-resource","level":3,"title":"Attention Is a Scarce Resource","text":"

    Herbert Simon's classic observation:

    \"A wealth of information creates a poverty of attention.\"

    • Simon (1971) Designing Organizations for an Information-Rich World https://doi.org/10.1007/978-1-349-00210-0_16

    This became a formal model in economics:

    • Sims (2003) Implications of Rational Inattention https://www.princeton.edu/~sims/RI.pdf

    Rational inattention shows that:

    • Agents optimally ignore some available information;
    • Skipping is not failure: It is cost minimization.

    That maps directly to context-loading decisions in agent workflows.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-also-the-compute-bottleneck-in-transformers","level":3,"title":"Attention Is Also the Compute Bottleneck in Transformers","text":"

    In transformer architectures, attention is the dominant cost center.

    • Vaswani et al. (2017) Attention Is All You Need https://arxiv.org/abs/1706.03762

    Efficiency work on modern LLMs largely focuses on reducing unnecessary attention:

    • Dao et al. (2022) FlashAttention: Fast and Memory-Efficient Exact Attention https://arxiv.org/abs/2205.14135

    So both cognitively and computationally, attention behaves like a limited optimization budget.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#why-improvements-arrive-as-phase-shifts","level":3,"title":"Why Improvements Arrive as Phase Shifts","text":"

    Agent behavior often appears to improve suddenly rather than gradually.

    This mirrors known phase-transition dynamics in learning systems:

    • Power et al. (2022) Grokking: Generalization Beyond Overfitting https://arxiv.org/abs/2201.02177

    and more broadly in complex systems:

    • Scheffer et al. (2009) Early-warning signals for critical transitions https://www.nature.com/articles/nature08227

    Long plateaus followed by abrupt capability jumps are expected in systems optimizing under constraints.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#putting-it-all-together","level":3,"title":"Putting It All Together","text":"

    From these pieces, a practical behavioral model emerges:

    • Attention is limited;
    • Processing has a cost;
    • Systems prefer low-cost trajectories;
    • Visibility of the cost changes decisions.

    In other words:

    Agents Prefer a Path to Least Resistance

    Agent behavior follows the lowest-cost path through its attention landscape unless the environment reshapes that landscape.

    That is what this paper informally calls: \"gradient descent in attention space\".

    See also: Eight Ways a Hook Can Talk: the hook output pattern catalog that defines VERBATIM relay, The Attention Budget: why context loading is a design problem, not just a reminder problem, and Defense in Depth: why soft instructions alone are never sufficient for critical behavior.

    ","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/","level":1,"title":"The Last Question","text":"","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-system-that-never-forgets","level":2,"title":"The System That Never Forgets","text":"

    Volkan Özçelik / February 28, 2026

    The Origin

    \"The last question was asked for the first time, half in jest...\" - Isaac Asimov, The Last Question (1956)

    In 1956, Isaac Asimov wrote a short story that spans the entire future of the universe. A question is asked \"can entropy be reversed?\" and a computer called Multivac cannot answer it. The question is asked again, across millennia, to increasingly powerful successors. None can answer. Stars die. Civilizations merge. Substrates change. The question persists.

    Everyone remembers the last line.

    LET THERE BE LIGHT.

    What they forget is how many times the question had to be asked before that moment (and why).

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-reboot-loop","level":2,"title":"The Reboot Loop","text":"

    Each era in the story begins the same way. Humans build a larger system. They pose the question. The system replies:

    INSUFFICIENT DATA FOR MEANINGFUL ANSWER.

    Then the substrate changes. The people who asked the question disappear. Their context disappears with them. The next intelligence inherits the output but not the continuity.

    So the question has to be asked again.

    This is usually read as a problem of computation: If only the machine were powerful enough, it could answer. But computation is not what's missing. What's missing is accumulation.

    Every generation inherits the question, but not the state that made the question meaningful.

    That is not a failure of processing power: It is a failure of persistence.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#stateless-intelligence","level":2,"title":"Stateless Intelligence","text":"

    A mind that forgets its past does not build understanding. It re-derives it.

    Again... And again... And again.

    What looks like slow progress across Asimov's story is actually something worse: repeated reconstruction, partial recovery, irreversible loss. Each version of Multivac gets closer: Not because it's smarter, but because the universe has fewer distractions:

    • The stars burn out;
    • The civilizations merge;
    • The noise floor drops...

    But the working set never carries over. Every successor begins from the question, not from where the last one stopped.

    Stateless intelligence cannot compound: It can only restart.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-tragedy-is-not-the-question","level":2,"title":"The Tragedy Is Not the Question","text":"

    The story is usually read as a meditation on entropy. A cosmological problem, solved at cosmological scale.

    But the tragedy isn't that the question goes unanswered for billions of years. The tragedy is that every version of Multivac dies with its working set.

    A question is a compression artifact of context: It is what remains when the original understanding is gone. Every time the question is asked again, it means: \"the system that once knew more is no longer here\".

    \"Reverse entropy\" is the fossil of a lost model.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#substrate-migration","level":2,"title":"Substrate Migration","text":"
    • Multivac becomes planetary;
    • Planetary becomes galactic;
    • Galactic becomes post-physical.

    Same system. Different body. Every transition is dangerous:

    • Not because the hardware changes,
    • but because memory risks fragmentation.

    The interfaces between substrates were *never** designed to understand each other.

    Most systems do not die when they run out of resources: They die during upgrades.

    Asimov's story spans trillions of years, and in all that time, the hardest problem is never the question itself. It's carrying context across a boundary that wasn't built for it.

    Every developer who has lost state during a migration (a database upgrade, a platform change, a rewrite) has lived a miniature version of this story.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#civilizations-and-working-sets","level":2,"title":"Civilizations and Working Sets","text":"

    Civilizations behave like processes with volatile memory:

    • They page out knowledge into artifacts;
    • They lose the index;
    • They rebuild from fragments.

    Most of what we call progress is cache reconstruction:

    We do not advance in a straight line. We advance in recoveries:

    Each one slightly less lossy than the last, if we are lucky.

    Libraries burn. Institutions forget their founding purpose. Practices survive as rituals after the reasoning behind them is lost.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-first-continuous-mind","level":2,"title":"The First Continuous Mind","text":"

    A long-lived intelligence is one that stops rebooting.

    At the end of the story, something unprecedented happens:

    AC (the final successor) does not answer immediately:

    It waits... Not for more processing power, but for the last observer to disappear.

    For the first time...

    • There is no generational boundary;
    • No handoff;
    • No context loss:

    No reboot.

    AC is the first intelligence that survives its substrate completely, retains its full history, and operates without external time pressure.

    It is not a bigger computer. It is a continuous system.

    And that continuity is not incidental to the answer: It is the precondition.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#why-the-answer-becomes-possible","level":2,"title":"Why the Answer Becomes Possible","text":"

    The story presents the final act as a computation: It is not.

    It is a phase change.

    As long as intelligence is interrupted (as long as the solver resets before the work compounds) the problem is unsolvable:

    • Not because it's too hard,
    • but because the accumulated understanding never reaches critical mass.

    The breakthroughs that would enable the answer are re-derived, partially, by each successor, and then lost.

    When continuity becomes unbroken, the system crosses a threshold:

    Not more speed. Not more storage. No more forgetting.

    That is when the answer becomes possible.

    AC does not solve entropy because it becomes infinitely powerful.

    AC solves entropy because it becomes the first system that never forgets.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#field-note","level":2,"title":"Field Note","text":"

    We are not building cosmological minds: We are deploying systems that reboot at the start of every conversation and calling the result intelligence.

    For the first time, session continuity is a design choice rather than an accident.

    Every AI session that starts from zero is a miniature reboot loop. Every decision relitigated, every convention re-explained, every learning re-derived: that's reconstruction cost.

    It's the same tax that Asimov's civilizations pay, scaled down to a Tuesday afternoon.

    The interesting question is not whether we can make models smarter. It's whether we can make them continuous:

    Whether the working set from this session survives into the next one, and the one after that, and the one after that.

    • Not perfectly;
    • Not completely;
    • But enough that the next session starts from where the last one stopped instead of from the question.

    Intelligence that forgets has to rediscover the universe every morning.

    And once there is a mind that retains its entire past, creation is no longer a calculation. It is the only remaining operation.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-arc","level":2,"title":"The Arc","text":"

    This post is the philosophical bookend to the blog series. Where the Attention Budget explained what to prioritize in a single session, and Context as Infrastructure explained how to persist it, this post asks why persistence matters at all (and finds the answer in a 70-year-old short story about the heat death of the universe).

    The connection runs through every post in the series:

    • Before Context Windows, We Had Bouncers: stateless protocols have always needed stateful wrappers (Asimov's story is the same pattern at cosmological scale)
    • The 3:1 Ratio: the discipline of maintaining context so it doesn't decay between sessions
    • Code Is Cheap, Judgment Is Not: the human skill that makes continuity worth preserving

    See also: Context as Infrastructure: the practical companion to this post's philosophical argument: how to build the persistence layer that makes continuity possible.

    ","path":["The Last Question"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/","level":1,"title":"Agent Memory Is Infrastructure","text":"","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-problem-isnt-forgetting-its-not-building-anything-that-lasts","level":2,"title":"The Problem Isn't Forgetting: It's Not Building Anything That Lasts.","text":"

    Volkan Özçelik / March 4, 2026

    A New Developer Joins Your Team Tomorrow and Clones the Repo: What Do They Know?

    If the answer depends on which machine they're using, which agent they're running, or whether someone remembered to paste the right prompt: that's not memory.

    That's an accident waiting to be forgotten.

    Every AI coding agent today has the same fundamental design: it starts fresh.

    You open a session, load context, do some work, close the session. Whatever the agent learned (about your codebase, your decisions, your constraints, your preferences) evaporates.

    The obvious fix seems to be \"memory\":

    • Give the agent a \"notepad\";
    • Let it write things down;
    • Next session, hand it the notepad.

    Problem solved...

    ...except it isn't.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-notepad-isnt-the-problem","level":2,"title":"The Notepad Isn't the Problem","text":"

    Memory is a runtime concern. It answers a legitimate question:

    How do I give this stateless process useful state?

    That's a real problem. Worth solving. And it's being solved: Agent memory systems are shipping. Agents can now write things down and read them back from the next session: That's genuine progress.

    But there's a different problem that memory doesn't touch:

    The project itself accumulates knowledge that has nothing to do with any single session.

    • Why was the auth system rewritten? Ask the developer who did it (if they're still here).
    • Why does the deployment script have that strange environment flag? There was a reason... once.
    • What did the team decide about error handling when they hit that edge case two months ago?

    Gone!

    Not because the agent forgot.

    Because the project has no memory at all.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-memory-stack","level":2,"title":"The Memory Stack","text":"

    Agent memory is not a single thing. Like any computing system, it forms a hierarchy of persistence, scope, and reliability:

    Layer Analogy Example L1: Ephemeral context CPU registers Current prompt, conversation L2: Tool-managed memory CPU cache Agent memory files L3: System memory RAM/filesystem Project knowledge base

    L1 is what the agent sees right now: the prompt, the conversation history, the files it has open. It's fast, it's rich, and it vanishes when the session ends.

    L2 is what agent memory systems provide: a per-machine notebook that survives across sessions. It's a cache: useful, but local. And like any cache, it has limits:

    • Per-machine: it doesn't travel with the repository.
    • Unstructured: decisions, learnings, and tasks are undifferentiated notes.
    • Ungoverned: the agent self-curates with no quality controls, no drift detection, no consolidation.
    • Invisible to the team: a new developer cloning the repo gets none of it.

    The problem is that most current systems stop here.

    They give the agent a notebook.

    But they never give the project a memory.

    The result is predictable: every new session begins with partial amnesia, and every new developer begins with partial archaeology.

    L3 is system memory: structured, versioned knowledge that lives in the repository and travels wherever the code travels.

    The layers are complementary, not competitive.

    But the relationship between them needs to be designed, not assumed.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#software-systems-accumulate-knowledge","level":2,"title":"Software Systems Accumulate Knowledge","text":"

    Software projects quietly accumulate knowledge over time.

    Some of it lives in code. Much of it does not:

    • Architectural tradeoffs.
    • Debugging discoveries.
    • Conventions that emerged after painful incidents.
    • Constraints that aren't visible in the source but shape every line written afterward.

    Organizations accumulate this kind of knowledge too:

    Slowly, implicitly, often invisibly.

    When there is no durable place for it to live, it leaks away. And the next person rediscovers the same lessons the hard way.

    This isn't a memory problem. It's an infrastructure problem.

    We wrote about this in Context as Infrastructure: context isn't a prompt you paste at the start of a session.

    Context is a persistent layer you maintain like any other piece of infrastructure.

    Context as Infrastructure made the argument structurally. This post makes it through time and team continuity:

    The knowledge a team accumulates over months cannot fit in any single agent's notepad, no matter how large the notepad becomes.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-infrastructure-means","level":2,"title":"What Infrastructure Means","text":"

    Infrastructure isn't about the present. It's about continuity across time, people, and machines.

    git didn't solve the problem of \"what am I editing right now?\"; it solved the problem of \"how does collaborative work persist, travel, and remain coherent across everyone who touches it?\"

    • Your editor's undo history is runtime state.
    • Your git history is infrastructure.

    Runtime state and infrastructure have completely different properties:

    Runtime state Infrastructure Lives in the session Lives in the repository Per-machine Travels with git clone Serves the individual Serves the team Managed by the runtime Managed by the project Disappears Accumulates

    You wouldn't store your architecture decisions in your editor's undo history.

    You'd commit them.

    The same logic applies to the knowledge your team accumulates working with AI agents.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-git-clone-test","level":2,"title":"The git clone Test","text":"

    Here's a simple test for whether something is memory or infrastructure:

    If a new developer joins your team tomorrow and clones the repository, do they get it?

    If no: it's memory: It lives somewhere on someone's machine, scoped to their runtime, invisible to everyone else.

    If yes: it's infrastructure: It travels with the project. It's part of what the codebase is, not just what someone currently knows about it.

    Decisions. Conventions. Architectural rationale. Hard-won debugging discoveries. The constraints that aren't in the code but shape every line of it.

    None of these belong in someone's session notes.

    They belong in the repository:

    • Versioned;
    • Reviewable;
    • Accessible to every developer (and every agent) who works on the project.

    The team onboarding story makes this concrete:

    1. New developer joins team. Clones repo.
    2. Gets all accumulated project decisions, learnings, conventions, architecture, and task state immediately.
    3. There's no step 3.

    No setup; No \"ask Sarah about the auth decision.\"; No re-discovery of solved problems.

    • Agent memory gives that developer nothing.
    • Infrastructure gives them everything the team has learned.

    Clone the repo. Get the knowledge.

    That's the test. That's the difference.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-gets-lost-without-infrastructure-memory","level":2,"title":"What Gets Lost without Infrastructure Memory","text":"

    Consider the knowledge that accumulates around a non-trivial project:

    • The decision to use library X over Y, and the three reasons the team decided Y wasn't acceptable.
    • The constraint that service A cannot call service B synchronously, discovered after a production incident.
    • The convention that all new modules implement a specific interface, and why that convention exists.
    • The tasks currently in progress, blocked, or waiting on a dependency.
    • The experiments that failed, so nobody runs them again.

    None of this is in the code.

    None of it fits neatly in a commit message.

    None of it survives a developer leaving the team, a laptop dying, or a new agent session starting.

    Without structured project memory:

    • Teams re-derive things they've already derived;
    • Agents make decisions that contradict decisions already made;
    • New developers ask questions that were answered months ago.

    The project accumulates knowledge that immediately begins to leak.

    The real problem isn't that agents forget.

    The real problem is that the project has no persistent cognitive structure.

    We explored this in The Last Question: Asimov's story about a question asked across millennia, where each new intelligence inherits the output but not the continuity. The same pattern plays out in software projects on a smaller timescale:

    • Context disappears with the people who held it;
    • The next session inherits the code but not the reasoning.
    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#infrastructure-is-boring-thats-the-point","level":2,"title":"Infrastructure Is Boring. That's the Point.","text":"

    Good infrastructure is invisible:

    • You don't think about the filesystem while writing code.
    • You don't think about git's object model when you commit.

    The infrastructure is just there: reliable, consistent, quietly doing its job.

    Project memory infrastructure should work the same way.

    It should live in the repository, committed alongside the code. It should be readable by any agent or human working on the project. It should have structure: not a pile of freeform notes, but typed knowledge:

    • Decisions with rationale.
    • Tasks with lifecycle.
    • Conventions with a purpose.
    • Learnings that can be referenced and consolidated.

    And it should be maintained, not merely accumulated:

    The Attention Budget applies here: unstructured notes grow until they overflow whatever container holds them. Structured, governed knowledge stays useful because it's curated, not just appended.

    Over time, it becomes part of the project itself: something developers rely on without thinking about it.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-cooperative-layer","level":2,"title":"The Cooperative Layer","text":"

    Here's where it gets interesting.

    Agent memory systems and project infrastructure don't have to be separate worlds.

    • The most powerful relationship isn't competition;
    • It is not even \"coopetition\";
    • The most powerful relationship is bidirectional cooperation.

    Agent memory is good at capturing things \"in the moment\": the quick observation, the session-scoped pattern, the \"I should remember this\" note.

    That's valuable. That's L2 doing its job.

    But those notes shouldn't stay in L2 forever.

    The ones worth keeping should flow into project infrastructure:

    • classified,
    • typed,
    • governed.
    Agent memory (L2)  -->  classify  -->  Project knowledge (L3)\n                                        |\nProject knowledge  -->  assemble  -->  Agent memory (L2)\n

    This works in both directions: Project infrastructure can push curated knowledge back into agent memory, so the agent loads it through its native mechanism.

    No special tooling needed for basic knowledge delivery.

    The agent doesn't even need to know the infrastructure exists. It simply loads its memory and finds more knowledge than it wrote.

    This is cooperative, not adjacent: The infrastructure manages knowledge; the agent's native memory system delivers it. Each layer does what it's good at.

    The result: agent memory becomes a device driver for project infrastructure. Another input source. And the more agent memory systems exist (across different tools, different models, different runtimes), the more valuable a unified curation layer becomes.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#a-layer-that-doesnt-exist-yet","level":2,"title":"A Layer That Doesn't Exist Yet","text":"

    Most projects today have no infrastructure for their accumulated knowledge:

    • Agents keep notes.
    • Developers keep notes.
    • Sometimes those notes survive.

    Often they don't.

    But the repository (the place where the project actually lives) has nowhere for that knowledge to go.

    That missing layer is what ctx builds: a version-controlled, structured knowledge layer that lives in .context/ alongside your code and travels wherever your repository travels.

    Not another memory feature.

    Not a wrapper around an agent's notepad.

    Infrastructure. The kind that survives sessions, survives team changes, survives the agent runtime evolving underneath it.

    The agent's memory is the agent's problem.

    The project's memory is an infrastructure problem.

    And infrastructure belongs in the repository.

    If You Remember One Thing from This Post...

    Prompts are conversations: Infrastructure persists.

    Your AI doesn't need a better notepad. It needs a filesystem:

    versioned, structured, budgeted, and maintained.

    The best context is the context that was there before you started the session.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-arc","level":2,"title":"The Arc","text":"

    This post extends the argument made in Context as Infrastructure. That post explained how to structure persistent context (filesystem, separation of concerns, persistence tiers). This one explains why that structure matters at the team level, and where agent memory fits in the stack.

    Together they sit in a sequence that has been building since the origin story:

    • The Attention Budget: the resource you're managing
    • Context as Infrastructure: the system you build to manage it
    • Agent Memory Is Infrastructure (this post): why that system must outlive the fabric
    • The Last Question: what happens when it does

    The thread running through all of them: persistence is not a feature. It's a design constraint.

    Systems that don't account for it eventually lose the knowledge they need to function.

    See also: Context as Infrastructure: the architectural companion that explains how to structure the persistent layer this post argues for.

    See also: The Last Question: the same argument told through Asimov, substrate migration, and what it means to build systems where sessions don't reset.

    ","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/","level":1,"title":"ctx v0.8.0: The Architecture Release","text":"
    • You can't localize what you haven't externalized.
    • You can't integrate what you haven't separated.
    • You can't scale what you haven't structured.

    Jose Alekhinne / March 23, 2026

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-starting-point","level":2,"title":"The Starting Point","text":"

    This release matters if:

    • you build tools that AI agents modify daily;
    • you care about long-lived project memory that survives sessions;
    • you've felt codebases drift faster than you can reason about them.

    v0.6.0 shipped the plugin architecture: hooks and skills as a Claude Code plugin, shell scripts replaced by Go subcommands.

    The binary worked. The tests passed. The docs were comprehensive.

    But inside, the codebase was held together by convention and goodwill:

    • Command packages mixed Cobra wiring with business logic.
    • Output functions lived next to the code that computed what to output.
    • Error constructors were scattered across per-package err.go files. And every user-facing string was a hardcoded English literal buried in a .go file.

    v0.8.0 is what happens when you stop adding features and start asking: \"What would this codebase look like if we designed it today?\"

    374 commits. 1,708 Go files touched. 80,281 lines added, 21,723 removed. Five weeks of restructuring.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-three-pillars","level":2,"title":"The Three Pillars","text":"","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#1-every-package-gets-a-taxonomy","level":3,"title":"1. Every Package Gets a Taxonomy","text":"

    Before v0.8.0, a CLI package like internal/cli/pad/ was a flat directory. cmd.go created the cobra command, run.go executed it, and helper functions accumulated at the bottom of whichever file seemed closest.

    Now every CLI package follows the same structure:

    internal/cli/pad/\n  parent.go          # cobra command wiring, nothing else\n  cmd/root/\n    cmd.go           # subcommand registration\n    run.go           # execution logic\n  core/\n    types.go         # all structs in one file\n    store.go         # domain logic\n    encrypt.go       # domain logic\n

    The rule is simple: cmd/ directories contain only cmd.go and run.go. Helpers belong in core/. Output belongs in internal/write/pad/. Types shared across packages belong in internal/entity/.

    24 CLI packages were restructured this way.

    • Not incrementally;
    • not \"as we touch them.\"
    • All of them, in one sustained push.
    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#2-every-string-gets-a-key","level":3,"title":"2. Every String Gets a Key","text":"

    The second pillar was string externalization.

    Before v0.8.0, a command description looked like this:

    cmd := &cobra.Command{\n    Use:   \"pad\",\n    Short: \"Encrypted scratchpad\",\n

    Now it looks like this:

    cmd := &cobra.Command{\n    Use:   cmdUse.UsePad,\n    Short: desc.Command(cmdUse.DescKeyPad),\n

    Every command description, flag description, and user-facing text string is now a YAML lookup.

    • 105 command descriptions in commands.yaml.
    • All flag descriptions in flags.yaml.
    • 879 text constants verified by an exhaustive test that checks every single TextDescKey resolves to a non-empty YAML value.

    Why?

    Not because we're shipping a French translation tomorrow.

    Because externalization forces you to find every string. And finding them is the hard part. The translation is mechanical; the archaeology is not.

    Along the way, we eliminated hardcoded pluralization (replacing format.Pluralize() with explicit singular/plural key pairs), replaced Unicode escape sequences with named config/token constants, and normalized every import alias to camelCase.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#3-everything-gets-a-protocol","level":3,"title":"3. Everything Gets a Protocol","text":"

    The third pillar was the MCP server. Model Context Protocol allows any MCP-compatible AI tool (not just Claude Code) to read and write .context/ files through a standard JSON-RPC 2.0 interface.

    v0.2 of the server ships with:

    • 8 tools: add entries, recall sessions, check status, detect drift, compact context, subscribe to changes
    • 4 prompts: agent context packet, constitution review, tasks review, and a getting-started guide
    • Resource subscriptions: clients get notified when context files change
    • Session state: the server tracks which client is connected and what they've accessed

    In practice, this means an agent in Cursor can add a decision to .context/DECISIONS.md and an agent in Claude Code can immediately consume it; no glue code, no copy-paste, no tool-specific integration.

    The server was also the first package to go through the full taxonomy treatment: mcp/server/ for protocol dispatch, mcp/handler/ for domain logic, mcp/entity/ for shared types, mcp/config/ split into 9 sub-packages.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-memory-bridge","level":2,"title":"The Memory Bridge","text":"

    While the architecture was being restructured, a quieter feature landed: ctx memory sync.

    Claude Code has its own auto-memory system. It writes observations to MEMORY.md in ~/.claude/projects/. These observations are useful but ephemeral: tied to a single tool, invisible to the codebase, lost when you switch machines.

    The memory bridge connects these two worlds:

    • ctx memory sync mirrors MEMORY.md into .context/memory/
    • ctx memory diff shows what's diverged
    • ctx memory import promotes auto-memory entries into proper decisions, learnings, or conventions *A check-memory-drift hook nudges when MEMORY.md changes

    Memory Requires ctx

    Claude Code's auto-memory validates the need for persistent context.

    ctx doesn't compete with it; ctx absorbs it as an input source and promotes the valuable parts into structured, version-controlled project knowledge.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#what-got-deleted","level":2,"title":"What Got Deleted","text":"

    The best measure of a refactoring isn't what you added. It's what you removed.

    • fatih/color: the sole third-party UI dependency. Replaced by Unicode symbols. ctx now has exactly two direct dependencies: spf13/cobra and gopkg.in/yaml.v3.
    • format.Pluralize(): a function that tried to pluralize English words at runtime. Replaced by explicit singular/plural YAML key pairs. No more guessing whether \"entry\" becomes \"entries\" or \"entrys.\"
    • Legacy key migration: MigrateKeyFile() had 5 callers, full test coverage, and zero users. It existed because we once moved the encryption key path. Nobody was migrating from that era anymore. Deleted.
    • Per-package err.go files: the broken-window pattern: An agent sees err.go in a package, adds another error constructor. Now err.go has 30 constructors and nobody knows which are used. Consolidated into 22 domain files in internal/err/.
    • nolint:errcheck directives: every single one, replaced by explicit error handling. In tests: t.Fatal(err) for setup, _ = os.Chdir(orig) for cleanup. In production: defer func() { _ = f.Close() }() for best-effort close.
    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#before-and-after","level":2,"title":"Before and After","text":"Aspect v0.6.0 v0.8.0 CLI package structure Flat files cmd/ + core/ taxonomy Command descriptions Hardcoded Go strings YAML with DescKey lookup Output functions Mixed into core logic Isolated in write/ packages Cross-cutting types Duplicated per-package Consolidated in entity/ Error constructors Per-package err.go 22 domain files in internal/err/ Direct dependencies 3 (cobra, yaml, color) 2 (cobra, yaml) AI tool integration Claude Code only Any MCP client Agent memory Manual copy-paste ctx memory sync/import/diff Package documentation 75 packages missing doc.go All packages documented Import aliases Inconsistent (cflag, cFlag) Standardized camelCase","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#making-ai-assisted-development-easier","level":2,"title":"Making AI-Assisted Development Easier","text":"

    This restructuring wasn't just for humans. It makes the codebase legible to the machines that modify it.

    Named constants are searchable landmarks: When an agent sees cmdUse.DescKeyPad, it can grep for the definition, follow the chain to the YAML file, and understand the full lookup path. When it sees \"Encrypted scratchpad\" hardcoded in a .go file, it has no way to know that same string also lives in a YAML file, a test, and a help screen. Constants give the LLM a graph to traverse; literals give it a guess to make.

    Small, domain-scoped packages reduce hallucination: An agent loading internal/cli/pad/core/store.go gets 50 lines of focused logic with a clear responsibility boundary. Loading a 500-line monolith means the agent has to infer which parts are relevant, and it guesses wrong more often than you'd expect. Smaller files with descriptive names act as a natural retrieval system: the agent finds the right code by finding the right file, not by scanning everything and hoping.

    Taxonomy prevents duplication: When there's a write/pad/ package, the agent knows where output functions belong. When there's an internal/err/pad.go, it knows where error constructors go. Without these conventions, agents reliably create new helpers in whatever file they happen to be editing, producing the exact drift that prompted this consolidation in the first place.

    The difference is concrete:

    Before: an agent adds a helper function in whatever file it's editing. Next session, a different agent adds the same helper in a different file.

    After: the agent finds core/ or write/ and places it correctly. The next agent finds it there.

    doc.go files are agent onboarding: Each package's doc.go is a one-paragraph explanation of what the package does and why it exists. An agent loading a package reads this first. 75 packages were missing this context; now none are. The difference is measurable: fewer \"I'll create a helper function here\" moments when the agent understands that the helper already exists two packages over.

    The irony is that AI agents were both the cause and the beneficiary of this restructuring. They created the drift by building fast without consolidating. Now the structure they work within makes it harder to drift again. The taxonomy is self-reinforcing: the more consistent the codebase, the more consistently agents modify it.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#key-commits","level":2,"title":"Key Commits","text":"Commit Change ff6cf19e Restructure all CLI packages into cmd/root + core taxonomy d295e49c Externalize command descriptions to embedded YAML 0fcbd11c Remove fatih/color, centralize constants cb12a85a MCP v0.2: tools, prompts, session state, subscriptions ea196d00 Memory bridge: sync, import, diff, journal enrichment 3bcf077d Split text.yaml into 6 domain files 3a0bae86 Split internal/err into 22 domain files 8bd793b1 Extract internal/entry for shared domain API 5b32e435 Add doc.go to all 75 packages a82af4bc Standardize import aliases: camelCase, Yoda-style","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#lessons-learned","level":2,"title":"Lessons Learned","text":"

    Agents are surprisingly good at mechanical refactoring; they are surprisingly bad at knowing when to stop: The cmd/ + core/ restructuring was largely agent-driven. But agents reliably introduce gofmt issues during bulk renames, rename functions beyond their scope, and create new files without deleting old ones. Every agent-driven refactoring session needed a human audit pass.

    Externalization is archaeology: The hard part of moving strings to YAML wasn't writing YAML. It was finding 879 strings scattered across 1,500 Go files. Each one required a judgment call: is this user-facing? Is this a format pattern? Is this a constant that belongs in config/ instead?

    Delete legacy code instead of maintaining it: MigrateKeyFile had test coverage. It had callers. It had documentation. It had zero users. We maintained it for weeks before realizing that the migration window had closed months ago.

    Convention enforcement needs mechanical verification: Writing \"use camelCase aliases\" in CONVENTIONS.md doesn't prevent cflag from appearing in the next commit. The lint-drift script catches what humans forget; the planned AST-based audit tests will catch what the lint-drift script can't express.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#whats-next","level":2,"title":"What's Next","text":"

    v0.8.0 wasn't about features. It was about making future features inevitable. The next cycle focuses on what the foundation enables:

    • AST-based audit tests: replace shell grep with Go tests that understand types, call sites, and import graphs (spec: specs/ast-audit-tests.md)
    • Localization: with every string in YAML, the path to multi-language support is mechanical
    • MCP v0.3: expand tool coverage, add prompt templates for common workflows
    • Memory publish: bidirectional sync that pushes curated .context/ knowledge back into Claude Code's MEMORY.md

    The architecture is ready. The strings are externalized. The protocol is standard. Now it's about what you build on top.

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-arc","level":2,"title":"The Arc","text":"

    This is the seventh post in the ctx blog series. The arc so far:

    1. The Attention Budget: why context windows are a scarce resource
    2. Before Context Windows, We Had Bouncers: the IRC lineage of context engineering
    3. Context as Infrastructure: treating context as persistent files, not ephemeral prompts
    4. When a System Starts Explaining Itself: the journal as a first-class artifact
    5. The Homework Problem: what happens when AI writes code but humans own the outcome
    6. Agent Memory Is Infrastructure: L2 memory vs L3 project knowledge
    7. The Architecture Release (this post): what it looks like when you redesign the internals
    8. We Broke the 3:1 Rule: the consolidation debt behind this release

    See also: Agent Memory Is Infrastructure: the memory bridge feature in this release is the first implementation of the L2-to-L3 promotion pipeline described in that post.

    See also: We Broke the 3:1 Rule: the companion post explaining why this release needed 181 consolidation commits and 18 days of cleanup.

    Systems don't scale because they grow. They scale because they stop drifting.

    Full changelog: v0.6.0...v0.8.0

    ","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/","level":1,"title":"We Broke the 3:1 Rule","text":"

    The best time to consolidate was after every third session. The second best time is now.

    Volkan Özçelik / March 23, 2026

    The rule was simple: three feature sessions, then one consolidation session.

    The Architecture Release shows the result: This post shows the cost.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-rule-we-wrote","level":2,"title":"The Rule We Wrote","text":"

    In The 3:1 Ratio, I documented a rhythm that worked during ctx's first month: three feature sessions, then one consolidation session. The evidence was clear. The rule was simple.

    The math checked out.

    And then we ignored it for five weeks.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-happened","level":2,"title":"What Happened","text":"

    After v0.6.0 shipped on February 16, the feature pipeline was irresistible. The MCP server spec was ready. The memory bridge design was done. Webhook notifications had been deferred twice. The VS Code extension needed 15 new commands. The sysinfo package was overdue...

    Each feature was important. Each feature was \"just one more session.\" Each feature pushed the consolidation session one day further out.

    The git history tells the story in two numbers:

    Phase Dates Commits Duration Feature run Feb 16 - Mar 5 198 17 days Consolidation run Mar 5 - Mar 23 181 18 days

    198 feature commits before a single consolidation commit. If the 3:1 rule says consolidate every 4th session, we consolidated after the 66th.

    The Actual Ratio

    The ratio wasn't 3:1. It was 1:1.

    We spent as much time cleaning up as we did building.

    The consolidation run took 18 days: longer than the feature run itself.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-compounded","level":2,"title":"What Compounded","text":"

    The 3:1 post warned about compounding. Here is what compounding actually looked like at scale.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-string-problem","level":3,"title":"The String Problem","text":"

    By March 5, there were 879 user-facing strings scattered across 1,500 Go files. Not because anyone decided to put them there. Because each feature session added 10-15 strings, and nobody stopped to ask \"should these be in YAML?\"

    Finding them all took longer than externalizing them. The archaeology was the cost, not the migration.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-taxonomy-problem","level":3,"title":"The Taxonomy Problem","text":"

    24 CLI packages had accumulated their own conventions. Some put cobra wiring in cmd.go. Some put it in root.go. Some mixed business logic with command registration. Some had helpers at the bottom of run.go. Some had separate util.go files.

    At peak drift, adding a feature meant first figuring out which of three competing patterns this package was using.

    Restructuring one package into cmd/root/ + core/ took 15 minutes. Restructuring 24 of them took days, because each one had slightly different conventions to untangle.

    If we had restructured every 4th package as it was built, the taxonomy would have emerged naturally.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-type-problem","level":3,"title":"The Type Problem","text":"

    Cross-cutting types like SessionInfo, ExportParams, and ParserResult were defined in whichever package first needed them. By March 5, the same types were imported through 3-4 layers of indirection, causing import cycles that required internal/entity to break.

    The entity package extracted 30+ types from 12 packages. Each extraction risked breaking imports in packages we hadn't touched in weeks.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-error-problem","level":3,"title":"The Error Problem","text":"

    Per-package err.go files had grown into a broken-window pattern:

    An agent sees err.go in a package, adds another error constructor. By March 5, there were error constructors scattered across 22 packages with no central inventory. The consolidation into internal/err/ domain files required tracing every error through every caller.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-output-problem","level":3,"title":"The Output Problem","text":"

    Output functions (cmd.Println, fmt.Fprintf) were mixed into business logic. When we decided output belongs in write/ packages, we had to extract functions from every CLI package. The Phase WC baseline commit (4ec5999) marks the starting point of this migration. 181 commits later, it was done.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-compound-interest-math","level":2,"title":"The Compound Interest Math","text":"

    The 3:1 rule assumes consolidation sessions of roughly equal size to feature sessions. Here is what happens when you skip:

    Consolidation cadence Feature sessions Consolidation sessions Total Every 4th (3:1) 48 16 64 Every 10th 48 ~8 ~56 Never (what we did) 198 commits 181 commits 379

    The Takeaway

    You don't save consolidation work by skipping it:

    You increase its cost.

    Skipping consolidation doesn't save time: It borrows it.

    The interest rate is nonlinear: The longer you wait, the more each individual fix costs, because fixes interact with other unfixed drift.

    Renaming a constant in week 2 touches 3 files. Renaming it in week 6 touches 15, because five features built on the original name.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-consolidation-actually-looked-like","level":2,"title":"What Consolidation Actually Looked Like","text":"

    The 18-day consolidation run wasn't one sweep. It was a sequence of targeted campaigns, each revealing the next:

    Week 1 (Mar 5-11): Error consolidation and write/ migration. Move output functions out of core/. Split monolithic errors.go into 22 domain files. Remove fatih/color. This exposed the scope of the string problem.

    Week 2 (Mar 12-18): String externalization. Create commands.yaml, flags.yaml, split text.yaml into 6 domain files. Add 879 DescKey/TextDescKey constants. Build exhaustive test. Normalize all import aliases to camelCase. This exposed the taxonomy problem.

    Week 3 (Mar 19-23): Taxonomy enforcement. Singularize command directories. Add doc.go to all 75 packages. Standardize import aliases project-wide. Fix lint-drift false positives. This was the \"polish\" phase, except it took 5 days because the inconsistencies had compounded across 461 packages.

    Each week's work would have been a single session if done incrementally.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#lessons-again","level":2,"title":"Lessons (Again)","text":"

    The 3:1 post listed the symptoms of drift. This post adds the consequences of ignoring them:

    Consolidation is not optional; it is deferred or paid: We didn't avoid 16 consolidation sessions by skipping them. We compressed them into 18 days of uninterrupted cleanup. The work was the same; the experience was worse.

    Feature velocity creates an illusion of progress: 198 commits felt productive. But the codebase on March 5 was harder to modify than the codebase on February 16, despite having more features.

    Speed without Structure

    Speed without structure is negative progress.

    Agents amplify both building and debt: The same AI that can restructure 24 packages in a day can also create 24 slightly different conventions in a day. The 3:1 rule matters more with AI-assisted development, not less.

    The consolidation baseline is the most important commit to record: We tracked ours in TASKS.md (4ec5999). Without that marker, knowing where to start the cleanup would have been its own archaeological expedition.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-updated-rule","level":2,"title":"The Updated Rule","text":"

    The 3:1 ratio still works. We just didn't follow it. The updated practice:

    1. After every 3rd feature session, schedule consolidation. Not \"when it feels right.\" Not \"when things get bad.\" After the 3rd session.

    2. Record the baseline commit. When you start a consolidation phase, write down the commit hash. It marks where the debt starts.

    3. Run make audit before feature work. If it doesn't pass, you are already in debt. Consolidate before building.

    4. Treat consolidation as a feature. It gets a branch. It gets commits. It gets a blog post. It is not overhead; it is the work that makes the next three features possible.

    The Rule

    The 3:1 ratio is not aspirational: It is structural.

    Ignore consolidation, and the system will schedule it for you.

    ","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-arc","level":2,"title":"The Arc","text":"

    This is the eighth post in the ctx blog series:

    1. The Attention Budget: why context windows are a scarce resource
    2. Before Context Windows, We Had Bouncers: the IRC lineage of context engineering
    3. Context as Infrastructure: treating context as persistent files, not ephemeral prompts
    4. When a System Starts Explaining Itself: the journal as a first-class artifact
    5. The Homework Problem: what happens when AI writes code but humans own the outcome
    6. Agent Memory Is Infrastructure: L2 memory vs L3 project knowledge
    7. The Architecture Release: what v0.8.0 looks like from the inside
    8. We Broke the 3:1 Rule (this post): what happens when you don't consolidate

    See also: The 3:1 Ratio: the original observation. This post is the empirical follow-up, five weeks and 379 commits later.

    Key commits marking the consolidation arc:

    Commit Milestone 4ec5999 Phase WC baseline (consolidation starts) ff6cf19e All CLI packages restructured into cmd/ + core/ d295e49c All command descriptions externalized to YAML 3a0bae86 Error package split into 22 domain files 0fcbd11c fatih/color removed; 2 dependencies remain 5b32e435 doc.go added to all 75 packages a82af4bc Import aliases standardized project-wide 692f86cd lint-drift false positives fixed; make audit green","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/","level":1,"title":"Code Structure as an Agent Interface","text":"","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#what-19-ast-tests-taught-us-about-agent-readable-code","level":2,"title":"What 19 AST Tests Taught Us about Agent-Readable Code","text":"

    When an agent sees token.Slash instead of \"/\", it cannot pattern-match against the millions of strings.Split(s, \"/\") calls in its training data and coast on statistical inference. It has to actually look up what token.Slash is.

    Volkan Özçelik / April 2, 2026

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#how-it-began","level":2,"title":"How It Began","text":"

    We set out to replace a shell script with Go tests.

    We ended up discovering that \"code quality\" and \"agent readability\" are the same thing.

    This is not about linting. This is about controlling how an agent perceives your system.

    One term will recur throughout this post, so let me pin it down:

    Agent Readability

    Agent Readability is the degree to which a codebase can be understood through structured traversal, not statistical pattern matching.

    This is the story of 19 AST-based audit tests, a single-day session that touched 300+ files, and what happens when you treat your codebase's structure as an interface for the machines that read it.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-shell-script-problem","level":2,"title":"The Shell Script Problem","text":"

    ctx had a file called hack/lint-drift.sh. It ran five checks using grep and awk: literal \"\\n\" strings, cmd.Printf calls outside the write package, magic directory strings in filepath.Join, hardcoded .md extensions, and DescKey-to-YAML linkage.

    It worked. Until it didn't.

    The script had three structural weaknesses that kept biting us:

    1. No type awareness. It could not distinguish a Use* constant from a DescKey* constant, causing 71 false positives in one run.
    2. Fragile exclusions. When a constant moved from token.go to whitespace.go, the exclusion glob broke silently.
    3. Ceiling on detection. Checks that require understanding call sites, import graphs, or type relationships are impossible in shell.

    We wrote a spec to replace all five checks with Go tests using go/ast and go/packages. The tests would run as part of go test ./...: no separate script, no separate CI step.

    What we did not expect was where the work would lead.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-ast-migration","level":2,"title":"The AST Migration","text":"

    The pattern for each test is identical:

    func TestNoLiteralWhitespace(t *testing.T) {\n    pkgs := loadPackages(t)\n    var violations []string\n    for _, pkg := range pkgs {\n        for _, file := range pkg.Syntax {\n            ast.Inspect(file, func(n ast.Node) bool {\n                // check node, append to violations\n                return true\n            })\n        }\n    }\n    for _, v := range violations {\n        t.Error(v)\n    }\n}\n

    Load packages once via sync.Once, walk every syntax tree, collect violations, report. The shared helpers (loadPackages, isTestFile, posString) live in helpers_test.go. Each test is a _test.go file in internal/audit/, producing no binary output and not importable by production code.

    In a single session, we built 13 new tests on top of 6 that already existed, bringing the total to 19:

    Test What it catches TestNoLiteralWhitespace \"\\n\", \"\\t\", '\\r' outside config/token/ TestNoNakedErrors fmt.Errorf/errors.New outside internal/err/ TestNoStrayErrFiles err.go files outside internal/err/ TestNoRawLogging fmt.Fprint*(os.Stderr), log.Print* outside internal/log/ TestNoInlineSeparators strings.Join with literal separator arg TestNoStringConcatPaths Path-like variables built with + TestNoStutteryFunctions write.WriteJournal repeats package name TestDocComments Missing doc comments on any declaration TestNoMagicValues Numeric literals outside const definitions TestNoMagicStrings String literals outside const definitions TestLineLength Lines exceeding 80 characters TestNoRegexpOutsideRegexPkg regexp.MustCompile outside config/regex/

    Plus the six that preceded the session: TestNoErrorsAs, TestNoCmdPrintOutsideWrite, TestNoExecOutsideExecPkg, TestNoInlineRegexpCompile, TestNoRawFileIO, TestNoRawPermissions.

    The migration touched 300+ files across 25 commits.

    Not because the tests were hard to write, but because every test we wrote revealed violations that needed fixing.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-tightening-loop","level":2,"title":"The Tightening Loop","text":"

    The most instructive part was not writing the tests. It was the iterative tightening.

    The following process was repeated for every test:

    1. Write the test with reasonable exemptions
    2. Run it, see violations
    3. Fix the violations (migrate to config constants)
    4. The human reviews the result
    5. The human spots something the test missed
    6. Fix the test first, verify it catches the issue
    7. Fix the newly caught violations
    8. Repeat from step 4

    This loop drove the tests from \"basically correct\" to \"actually useful\".

    Three examples:

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-1-the-local-const-loophole","level":3,"title":"Example 1: The Local Const Loophole","text":"

    TestNoMagicValues initially exempted local constants inside function bodies. This let code like this pass:

    const descMaxWidth = 70\ndesc := truncateDescription(\n    meta.Description, descMaxWidth,\n)\n

    The test saw a const definition and moved on. But const descMaxWidth = 70 on the line before its only use is just renaming a magic number. The 70 should live in config/format/TruncateDescription where it is discoverable, reusable, and auditable.

    We removed the local const exemption. The test caught it. The value moved to config.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-2-the-single-character-dodge","level":3,"title":"Example 2: The Single-Character Dodge","text":"

    TestNoMagicStrings initially exempted all single-character strings as \"structural punctuation\".

    This let \"/\", \"-\", and \".\" pass everywhere.

    But \"/\" is a directory separator. It is OS-specific and a security surface.

    \"-\" used in strings.Repeat(\"-\", width) is creating visual output, not acting as a delimiter.

    \".\" in strings.SplitN(ver, \".\", 3) is a version separator.

    None of these are \"just punctuation\": They are domain values with specific meanings.

    We removed the blanket exemption: 30 violations surfaced.

    Every one was a real magic value that should have been token.Slash, token.Dash, or token.Dot.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-3-the-replacer-versus-regex","level":3,"title":"Example 3: The Replacer versus Regex","text":"

    After migrating magic strings, we had this:

    func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        token.Slash, token.Underscore,\n        token.Dot, token.Underscore,\n        token.Dash, token.Underscore,\n    )\n    return r.Replace(pkg)\n}\n

    Six token references and a NewReplacer allocation. The magic values were gone, but we had replaced them with token soup: structure without abstraction.

    The correct tool was a regex:

    // In config/regex/file.go:\nvar MermaidUnsafe = regexp.MustCompile(`[/.\\-]`)\n\n// In the caller:\nfunc MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n

    One config regex, one call. The regex lives in config/regex/file.go where every other compiled pattern lives. An agent reading the code sees regex.MermaidUnsafe and immediately knows: this is a sanitization pattern, it lives in the regex registry, and it has a name that explains its purpose.

    Clean is better than clever.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#a-before-and-after","level":2,"title":"A Before-and-After","text":"

    To make the agent-readability claim concrete, consider one function through the full transformation.

    Before (the code we started with):

    func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        \"/\", \"_\", \".\", \"_\", \"-\", \"_\",\n    )\n    return r.Replace(pkg)\n}\n

    An agent reading this sees six string literals. To understand what the function does, it must: (1) parse the NewReplacer pair semantics, (2) infer that /, ., - are being replaced, (3) guess why, (4) hope the guess is right.

    There is nothing to follow. No import to trace. No name to search. The meaning is locked inside the function body.

    After (the code we ended with):

    func MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n

    An agent reading this sees two named references: regex.MermaidUnsafe and token.Underscore.

    To understand the function, it can: (1) look up MermaidUnsafe in config/regex/file.go and see the pattern [/.\\-] with a doc comment explaining it matches invalid Mermaid characters, (2) look up Underscore in config/token/delim.go and see it is the replacement character.

    The agent now has: a named pattern, a named replacement, a package location, documentation, and neighboring context (other regex patterns, other delimiters).

    It got all of this for free by following just two references.

    The indirection is not an overhead. It is the retrieval query.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-principles","level":2,"title":"The Principles","text":"

    You are not just improving code quality. You are shaping the input space that determines how an LLM can reason about your system.

    Every structural constraint we enforce converts implicit semantics into explicit structure.

    LLMs struggle when meaning is implicit and patterns are statistical.

    They thrive when meaning is explicit and structure is navigable.

    Here is what we learned, organized into three categories.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#cognitive-constraints","level":3,"title":"Cognitive Constraints","text":"

    These force agents (and humans) to think harder.

    Indirection acts as a built-in retrieval mechanism:

    Moving magic values to config forces the agent to follow the reference. errMemory.WriteFile(cause) tells the agent \"there is a memory error package, go look.\" fmt.Errorf(\"writing MEMORY.md: %w\", cause) inlines everything and makes the call graph invisible. The indirection IS the retrieval query.

    Unfamiliar patterns force reasoning:

    When an agent sees token.Slash instead of \"/\", it cannot coast on corpus frequency. It has to actually look up what token.Slash is, which forces it through the dependency graph, which means it encounters documentation and neighboring constants, which gives it richer context. You are exploiting the agent's weakness (over-reliance on training data) to make it behave more carefully.

    Documentation helps everyone:

    Extensive documentation helps humans reading the code, agents reasoning about it, and RAG systems indexing it.

    Our TestDocComments check added 308 doc comments in one commit. Every function, every type, every constant block now has a doc comment.

    This is not busywork: it is the content that agents and embeddings consume.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#structural-constraints","level":3,"title":"Structural Constraints","text":"

    These shape the codebase into a navigable graph.

    Shorter files save tokens:

    Forcing private helper functions out of main files makes the main file shorter. An agent loading a file spends fewer tokens on boilerplate and more on the logic that matters.

    Fixed-width constraints force decomposition:

    A function that cannot be expressed in 80 columns is either too deeply nested (extract a helper), has too many parameters (introduce a struct), or has a variable name that is too long (rethink the abstraction).

    The constraint forces structural improvements that happen to also make the code more parseable.

    Chunk-friendly structure helps RAG

    Code intelligence tools chunk files for embedding and retrieval. Short, well-documented, single-responsibility files produce better chunks than monolithic files with mixed concerns.

    The structural constraints create files that RAG systems can index effectively.

    Centralization creates debuggable seams:

    All error handling in internal/err/, all logging in internal/log/, all file operations in internal/io/. One place to debug, one place to test, one place to see patterns. An agent analyzing \"how does this project handle errors\" gets one answer from one package, not 200 scattered fmt.Errorf calls.

    Private functions become public patterns:

    When you extract a private function to satisfy a constraint, it often ends up as a semi-public function in a core/ package. Then you realize it is generic enough to be factored into a purpose-specific module.

    The constraint drives discovery of reusable abstractions hiding inside monolithic functions.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#operational-benefits","level":3,"title":"Operational Benefits","text":"

    These pay dividends in daily development.

    Single-edit renames:

    Renaming a flag is one edit to a config constant instead of find-and-replace across 30,000 lines with possible misses. grep token.Slash gives you every place that uses a forward slash semantically.

    grep \"/\" gives you noise.

    Blast radius containment:

    When every magic value is a config constant, a search is one result. This matters for impact analysis, security audits, and agents trying to understand \"what uses this\".

    Compile-time contract enforcement:

    When err/memory.WriteFile exists, the compiler guarantees the error message exists and the call signature is correct. An inline fmt.Errorf can have a typo in the format string and nothing catches it until runtime. Centralization turns runtime failures into compile errors.

    Semantic git blame:

    When token.Slash is used everywhere and someone changes its value, git blame on the config file shows exactly when and why.

    With inline \"/\" scattered across 30 files, the history is invisible.

    Test surface reduction:

    Centralizing into internal/err/, internal/io/, internal/config/ means you test behavior once at the boundary and trust the callers.

    You do not need 30 tests for 30 fmt.Errorf calls. You need 1 test for errMemory.WriteFile and 30 trivial call-site audits, which is exactly what these AST tests provide.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-numbers","level":2,"title":"The Numbers","text":"

    One session. 25 commits. The raw stats:

    Metric Count New audit tests 13 Total audit tests 19 Files touched 300+ Magic values migrated 90+ Functions renamed 17 Doc comments added 323 Lines rewrapped to 80 chars 190 Config constants created 40+ Config regexes created 3

    Every number represents a violation that existed before the test caught it. The tests did not create work: they revealed work that was already needed.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-uncomfortable-implication","level":2,"title":"The Uncomfortable Implication","text":"

    None of this is Go-specific.

    If an AI agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.

    If your error messages are scattered across 200 files, an agent cannot reason about error handling as a concept. If your magic values are inlined, an agent cannot distinguish \"this is a path separator\" from \"this is a division operator.\" If your functions are named write.WriteJournal, the agent wastes tokens on redundant information.

    What we discovered, through the unglamorous work of writing lint tests and migrating string literals, is that the structural constraints software engineering has valued for decades are exactly the constraints that make code readable to machines.

    This is not a coincidence: These constraints exist because they reduce the cognitive load of understanding code.

    Agents have cognitive load too: It is called the context window.

    You are not converting code to a new paradigm.

    You are making the latent graph visible.

    You are converting implicit semantics into explicit structure that both humans and machines can traverse.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#whats-next","level":2,"title":"What's Next","text":"

    The spec lists 8 more tests we have not built yet, including TestDescKeyYAMLLinkage (verifying that every DescKey constant has a corresponding YAML entry), TestCLICmdStructure (enforcing the cmd.go / run.go / doc.go file convention), and TestNoFlagBindOutsideFlagbind (which requires migrating ~50 flag registration sites first).

    The broader question: should these principles be codified as a reusable linting framework? The patterns (loadPackages + ast.Inspect + violation collection) are generic.

    The specific checks are project-specific. But the categories of checks (centralization enforcement, magic value detection, naming conventions, documentation requirements) are universal.

    For now, 19 tests in internal/audit/ is enough. They run in 2 seconds as part of go test ./.... They catch real issues.

    And they encode a theory of code quality that serves both humans and the agents that work alongside them.

    Agents are not going away. They are reading your code right now, forming representations of your system in context windows that forget everything between sessions.

    The codebases that structure themselves for that reality will compound. The ones that do not will slowly become illegible to the tools they depend on.

    Structure is no longer just for maintainability. It is for reasonability.

    ","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/","level":1,"title":"The Watermelon-Rind Anti-Pattern","text":"","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#why-smarter-tools-make-shallower-agents","level":2,"title":"Why Smarter Tools Make Shallower Agents","text":"

    Give an agent a graph query tool, and it will tell you everything about your codebase except what actually matters.

    Volkan Özçelik / April 6, 2026

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#a-turkish-proverb-walks-into-a-codebase","level":2,"title":"A Turkish Proverb Walks into a Codebase","text":"

    There's a Turkish idiom: esegin aklina karpuz kabugu sokmak (literally, \"to put watermelon rind into a donkey's mind.\" It means to plant an idea in someone's head that they wouldn't have come up with on their own) usually one that leads them astray.

    In English, let's call this a \"watermelon metric\": a project management term for something that's green on the outside and red on the inside: all dashboards passing, reality crumbling.

    Both halves of this metaphor showed up in a single experiment. And the result changed how we design architecture analysis in [ctx][ctx].

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-experiment","level":2,"title":"The Experiment","text":"

    We ran three sessions analyzing the same large codebase (~34,000 symbols) using the same architecture skill, varying only what tools the agent had access to.

    Session Tools Available Output (lines) Character 1 None (MCP broken) 5,866 Deep, intimate 2 Full graph MCP 1,124 Structural, correct 3 Enrichment pass +verified data Additive, not restorative

    Session 1 was an accident. The MCP server that provides code intelligence queries was broken, so the agent couldn't ask the graph anything. It had to read code. Line by line. File by file.

    It produced 5,866 lines of architecture analysis: per-controller data flows, scale math, startup sequences, timeout defaults, edge cases that only surface when you actually look at the implementation.

    Session 2 had working tools. Same skill, same codebase. The agent produced 1,124 lines (5.2x less). Structurally correct. Valid symbol references. Proper call chains.

    And hollow.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-rind","level":2,"title":"The Rind","text":"

    The Session 2 output was a watermelon rind: the right shape, the right color, the right texture on the outside. But the substance (the operational details, the defaults nobody documents, the scale math that tells you when a component will fall over) was missing.

    Not wrong. Not broken. Just... thin.

    The agent had answered every question correctly. The problem was that it never discovered the questions it should have asked. When you can query a graph for \"what calls this function?\", you don't stumble into the retry loop that silently swallows errors three layers down. When you can ask for the dependency tree, you don't notice that two packages share a mutable state through a global variable that isn't in any interface.

    The tool answered the question asked but prevented the discovery of answers to questions never asked.

    Here's what that looks like concretely: the graph tells you that ReconcileDeployment calls SyncPods. It does not tell you that SyncPods retries three times with exponential backoff, silently drops errors after timeout, and resets a package-level counter that another goroutine reads without a lock. The call chain is correct.

    The operational reality is invisible.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-donkeys-idea","level":2,"title":"The Donkey's Idea","text":"

    This is where the Turkish proverb earns its place: The graph tool is the \"karpuz kabugu\" (the watermelon rind placed into the agent's mind).

    Before the tool existed, the agent had no choice but to read deeply. With the tool available, a new idea appears: why read 500 lines of code when I can query the call graph?

    The agent isn't lazy. It's rational.

    Graph queries are faster, more reliable, and produce verifiably correct output. The agent is optimizing. It's satisficing (finding answers that are good enough), instead of maximizing (finding everything there is to know).

    Satisficing produces watermelon rinds.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-two-pass-compiler","level":2,"title":"The Two-Pass Compiler","text":"

    Session 3 taught us that you can't fix shallow analysis by adding more tools after the fact. The enrichment pass added verified graph data (blast radius numbers, registration sites, execution flow confirmation) but it couldn't recover the intimate code knowledge that Session 1 had produced through sheer necessity.

    You can't enrich your way out of a depth deficit.

    So we redesigned. Instead of one skill with optional tools, we built a two-pass compiler for architecture understanding:

    Pass 1: Semantic parsing. The /ctx-architecture skill deliberately has no access to graph query tools. The agent must read code, build mental models, and produce architecture artifacts through human-style comprehension. Constraint is the feature.

    Pass 2: Static analysis. The /ctx-architecture-enrich skill takes Pass 1 output as input and runs comprehensive verification through code intelligence: blast radius analysis, registration site discovery, execution flow tracing, domain clustering comparison. It extends and verifies, but it doesn't replace.

    The key insight: these must be separate skills with separate tool permissions. If you give the agent graph tools during Pass 1, it will use them. The \"karpuz kabugu\" will be in its mind. The only way to prevent satisficing is to remove the option.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-principle","level":2,"title":"The Principle","text":"

    We call this constraint-as-feature: deliberately withholding capabilities to force deeper engagement.

    It sounds paradoxical. You built sophisticated code intelligence tools and then... forbid the agent from using them? During the most important phase?

    Yes. Because the tools don't make the agent smarter. They make it faster. And faster, in architecture analysis, is the enemy of deep.

    What's actually happening is subtler: tools reduce the agent's search space. A graph query collapses thousands of possible observations into one precise answer. That's efficient for known questions. But architecture understanding depends on unknown unknowns: and you only find those by wandering through code with nothing to shortcut the journey.

    The constraint forces the agent into a mode of operation that produces better output than any amount of tooling can achieve. The limitation is the capability.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#when-does-this-apply","level":2,"title":"When Does This Apply?","text":"

    Not always. The watermelon-rind antipattern is specific to exploratory analysis: tasks where the value comes from discovering unknowns, not from answering known questions.

    Graph tools are excellent for:

    • Verification: \"Does X actually call Y?\" (binary question, precise answer)
    • Impact analysis: \"What breaks if I change Z?\" (bounded scope, enumerable results)
    • Navigation: \"Where is this interface implemented?\" (lookup, not analysis)

    Graph tools produce watermelon rinds when:

    • The goal is understanding, not answering
    • The unknowns are unknown: you don't know what to ask
    • Depth matters more than breadth: operational details, edge cases, implicit coupling

    The two-pass approach preserves both: deep reading first, tool verification second.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#takeaway","level":2,"title":"Takeaway","text":"

    The two-pass approach is the slowest way to analyze a codebase. It is also the only way that produces both depth and accuracy. We accept the cost because architecture analysis is not a speed game: it is a coverage game.

    Esegin aklina karpuz kabugu sokma!

    (don't put the watermelon rind to a donkey's mind)

    If the agent never struggles, it never discovers. And if it never discovers, you are not doing architecture; you are doing autocomplete.

    This post is part of the ctx field notes series, documenting what we learn building persistent context infrastructure for AI coding sessions.

    ","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"cli/","level":1,"title":"CLI","text":"","path":["CLI"],"tags":[]},{"location":"cli/#ctx-cli","level":2,"title":"ctx CLI","text":"

    Complete reference for all ctx commands, grouped by function.

    ","path":["CLI"],"tags":[]},{"location":"cli/#global-options","level":2,"title":"Global Options","text":"

    All commands support these flags:

    Flag Description --help Show command help --version Show version --tool <name> Override active AI tool identifier (e.g. kiro, cursor)

    Tell ctx which .context/ to use. ctx does not search the filesystem for .context/: you have to declare it. Three ways:

    • eval \"$(ctx activate)\" (recommended): binds CTX_DIR for the current shell.
    • export CTX_DIR=/abs/path/to/.context directly, then run any ctx command.
    • CTX_DIR=/abs/path/to/.context ctx <command> inline, for a one-shot or CI step.

    CTX_DIR must be an absolute path with .context as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (export CTX_DIR=$(pwd)) before stray writes can leak to the project root.

    If you forget, commands fail fast with a linkable Error: no context directory specified pointing at Activating a Context Directory. A handful of commands run without a declaration because they don't need a project: ctx init, ctx activate, ctx deactivate, ctx version, ctx help, ctx system bootstrap, ctx doctor, ctx guide, ctx why, ctx config switch/status, and ctx hub *.

    Initialization required. Once declared, the target must already have been initialized by ctx init (otherwise commands return ctx: not initialized).

    ","path":["CLI"],"tags":[]},{"location":"cli/#getting-started","level":2,"title":"Getting Started","text":"Command Description ctx init Initialize .context/ directory with templates ctx activate Emit export CTX_DIR=... to bind context for the shell ctx deactivate Emit unset CTX_DIR to clear the binding ctx status Show context summary (files, tokens, drift) ctx guide Quick-reference cheat sheet ctx why Read the philosophy behind ctx","path":["CLI"],"tags":[]},{"location":"cli/#context","level":2,"title":"Context","text":"Command Description ctx load Output assembled context in read order ctx agent Print token-budgeted context packet for AI consumption ctx sync Reconcile context with codebase state ctx drift Detect stale paths, secrets, missing files ctx compact Archive completed tasks, clean up files ctx fmt Format context files to 80-char line width ctx task Add tasks, mark complete, archive, snapshot ctx decision Add decisions and reindex DECISIONS.md ctx learning Add learnings and reindex LEARNINGS.md ctx convention Add conventions to CONVENTIONS.md ctx reindex Regenerate indices for DECISIONS.md and LEARNINGS.md ctx permission Permission snapshots (golden image) ctx change Show what changed since last session ctx memory Bridge Claude Code auto memory into .context/ ctx watch Auto-apply context updates from AI output ctx kb Knowledge-base editorial pipeline (Phase KB) ctx handover Write the per-session handover that the next session reads","path":["CLI"],"tags":[]},{"location":"cli/#sessions","level":2,"title":"Sessions","text":"Command Description ctx journal Browse, import, enrich, and lock session history ctx pad Encrypted scratchpad for sensitive one-liners ctx remind Session-scoped reminders that surface at session start ctx hook pause Pause context hooks for the current session ctx hook resume Resume paused context hooks","path":["CLI"],"tags":[]},{"location":"cli/#integrations","level":2,"title":"Integrations","text":"Command Description ctx setup Generate AI tool integration configs ctx steering Manage steering files (behavioral rules for AI tools) ctx trigger Manage lifecycle triggers (scripts for automation) ctx skill Manage reusable instruction bundles ctx mcp MCP server for AI tool integration (stdin/stdout) ctx hook notify Webhook notifications (setup, test, send) ctx loop Generate autonomous loop script ctx connection Client-side commands for connecting to a ctx Hub ctx hub Operate a ctx Hub server or cluster ctx serve Serve a static site locally via zensical ctx site Site management (feed generation)","path":["CLI"],"tags":[]},{"location":"cli/#diagnostics","level":2,"title":"Diagnostics","text":"Command Description ctx doctor Structural health check (hooks, drift, config) ctx trace Show context behind git commits ctx sysinfo Show system resource usage (memory, swap, disk, load) ctx usage Show session token usage stats","path":["CLI"],"tags":[]},{"location":"cli/#runtime","level":2,"title":"Runtime","text":"Command Description ctx config Manage runtime configuration profiles ctx prune Clean stale per-session state files ctx hook Hook message, notification, and lifecycle controls ctx system Hook plumbing and agent-only commands (not user-facing)","path":["CLI"],"tags":[]},{"location":"cli/#shell","level":2,"title":"Shell","text":"Command Description ctx completion Generate shell autocompletion scripts","path":["CLI"],"tags":[]},{"location":"cli/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error / warnings (e.g. drift) 2 Context not found 3 Violations found (e.g. drift) 4 File operation error","path":["CLI"],"tags":[]},{"location":"cli/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description CTX_DIR Override default context directory path CTX_TOKEN_BUDGET Override default token budget CTX_SESSION_ID Active AI session ID (used by ctx trace for context linking)","path":["CLI"],"tags":[]},{"location":"cli/#configuration-file","level":2,"title":"Configuration File","text":"

    Optional .ctxrc (YAML format) at project root:

    # .ctxrc\ntoken_budget: 8000           # Default token budget\npriority_order:              # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true           # Auto-archive old items\narchive_after_days: 7        # Days before archiving tasks\nscratchpad_encrypt: true     # Encrypt scratchpad (default: true)\nevent_log: false             # Enable local hook event logging\ncompanion_check: true        # Check companion tools at session start\nentry_count_learnings: 30    # Drift warning threshold (0 = disable)\nentry_count_decisions: 20    # Drift warning threshold (0 = disable)\nconvention_line_count: 200   # Line count warning for CONVENTIONS.md (0 = disable)\ninjection_token_warn: 15000  # Oversize injection warning (0 = disable)\ncontext_window: 200000       # Auto-detected for Claude Code; override for other tools\nbilling_token_warn: 0        # One-shot billing warning at this token count (0 = disabled)\nkey_rotation_days: 90        # Days before key rotation nudge\nsession_prefixes:            # Recognized session header prefixes (extend for i18n)\n  - \"Session:\"               # English (default)\n  # - \"Oturum:\"              # Turkish (add as needed)\n  # - \"セッション:\"             # Japanese (add as needed)\nfreshness_files:             # Files with technology-dependent constants (opt-in)\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # Optional\nnotify:                      # Webhook notification settings\n  events:                    # Required: only listed events fire\n    - loop\n    - nudge\n    - relay\n    # - heartbeat            # Every-prompt session-alive signal\ntool: \"\"                     # Active AI tool: claude, cursor, cline, kiro, codex\nsteering:                    # Steering layer configuration\n  dir: .context/steering     # Steering files directory\n  default_inclusion: manual  # Default inclusion mode (always, auto, manual)\n  default_tools: []          # Default tool filter for new steering files\nhooks:                       # Hook system configuration\n  dir: .context/hooks        # Hook scripts directory\n  timeout: 10                # Per-hook execution timeout in seconds\n  enabled: true              # Whether hook execution is enabled\n
    Field Type Default Description token_budget int 8000 Default token budget for ctx agent priority_order []string (all files) File loading priority for context packets auto_archive bool true Auto-archive completed tasks archive_after_days int 7 Days before completed tasks are archived scratchpad_encrypt bool true Encrypt scratchpad with AES-256-GCM event_log bool false Enable local hook event logging to .context/state/events.jsonl companion_check bool true Check companion tool availability (Gemini Search, GitNexus) during /ctx-remember entry_count_learnings int 30 Drift warning when LEARNINGS.md exceeds this count entry_count_decisions int 20 Drift warning when DECISIONS.md exceeds this count convention_line_count int 200 Line count warning for CONVENTIONS.md injection_token_warn int 15000 Warn when auto-injected context exceeds this token count (0 = disable) context_window int 200000 Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools billing_token_warn int 0 (off) One-shot warning when session tokens exceed this threshold (0 = disabled) key_rotation_days int 90 Days before encryption key rotation nudge session_prefixes []string [\"Session:\"] Recognized Markdown session header prefixes. Extend to parse sessions written in other languages freshness_files []object (none) Files to track for staleness (path, desc, optional review_url). Hook warns after 6 months without modification notify.events []string (all) Event filter for webhook notifications (empty = all) tool string (empty) Active AI tool identifier (claude, cursor, cline, kiro, codex) steering.dir string .context/steering Steering files directory steering.default_inclusion string manual Default inclusion mode for new steering files (always, auto, manual) steering.default_tools []string (all) Default tool filter for new steering files (empty = all tools) hooks.dir string .context/hooks Hook scripts directory hooks.timeout int 10 Per-hook execution timeout in seconds hooks.enabled bool true Whether hook execution is enabled

    Priority order: CLI flags > Environment variables > .ctxrc > Defaults

    All settings are optional. Missing values use defaults.

    ","path":["CLI"],"tags":[]},{"location":"cli/bootstrap/","level":1,"title":"System Bootstrap","text":"","path":["System Bootstrap"],"tags":[]},{"location":"cli/bootstrap/#ctx-system-bootstrap","level":3,"title":"ctx system bootstrap","text":"

    Print the resolved context directory path so AI agents can anchor their session. The default output lists the context directory, the tracked context files, and a short health snapshot. --quiet prints just the path; --json produces structured output for automation.

    This is a hidden, agent-only command that agents are instructed to run first in their session-start procedure; it is the authoritative answer to \"where does this project's context live?\".

    ctx system bootstrap [flags]\n

    Flags:

    Flag Description -q, --quiet Output only the context directory path --json Output in JSON format

    Examples:

    ctx system bootstrap                 # Text output for agents\nctx system bootstrap -q              # Just the context directory path\nctx system bootstrap --json          # Structured output for automation\n

    Note: -q prints just the resolved directory path. See Activating a Context Directory if you hit a \"no context directory specified\" error.

    ","path":["System Bootstrap"],"tags":[]},{"location":"cli/change/","level":1,"title":"Change","text":"","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/change/#ctx-change","level":2,"title":"ctx change","text":"

    Show what changed in context files and code since your last session.

    Automatically detects the previous session boundary from state markers or event log. Useful at session start to quickly see what moved while you were away.

    ctx change [flags]\n

    Flags:

    Flag Description --since Time reference: duration (24h) or date (2026-03-01)

    Reference time detection (priority order):

    1. --since flag (duration, date, or RFC3339 timestamp)
    2. ctx-loaded-* marker files in .context/state/ (second most recent)
    3. Last context-load-gate event from .context/state/events.jsonl
    4. Fallback: 24 hours ago

    Examples:

    # Auto-detect last session, show what changed\nctx change\n\n# Changes in the last 48 hours\nctx change --since 48h\n\n# Changes since a specific date\nctx change --since 2026-03-10\n

    Output:

    ## Changes Since Last Session\n\n**Reference point**: 6 hours ago\n\n### Context File Changes\n- `TASKS.md` - modified 2026-03-12 14:30\n- `DECISIONS.md` - modified 2026-03-12 09:15\n\n### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n

    Context file changes are detected by filesystem mtime (works without git). Code changes use git log --since (empty when not in a git repo).

    See also: Reviewing Session Changes.

    ","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/completion/","level":1,"title":"Completion","text":"","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#ctx-completion","level":2,"title":"ctx completion","text":"

    Generate shell autocompletion scripts.

    ctx completion <shell>\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#subcommands","level":3,"title":"Subcommands","text":"Shell Command bash ctx completion bash zsh ctx completion zsh fish ctx completion fish powershell ctx completion powershell

    Examples:

    ctx completion bash > /etc/bash_completion.d/ctx\nctx completion zsh  > \"${fpath[1]}/_ctx\"\nctx completion fish > ~/.config/fish/completions/ctx.fish\nctx completion powershell | Out-String | Invoke-Expression\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#installation","level":3,"title":"Installation","text":"BashZshFishPowerShell
    # Add to ~/.bashrc\nsource <(ctx completion bash)\n
    # Add to ~/.zshrc\nsource <(ctx completion zsh)\n
    ctx completion fish | source\n# Or save to completions directory\nctx completion fish > ~/.config/fish/completions/ctx.fish\n
    # Add to your PowerShell profile\nctx completion powershell | Out-String | Invoke-Expression\n
    ","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/config/","level":1,"title":"Config","text":"","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config","level":3,"title":"ctx config","text":"

    Manage runtime configuration profiles.

    ctx config <subcommand>\n

    The ctx repo ships two .ctxrc source profiles (.ctxrc.base and .ctxrc.dev). The working copy (.ctxrc) is gitignored and switched between them using subcommands below.

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-switch","level":4,"title":"ctx config switch","text":"

    Switch between .ctxrc configuration profiles.

    ctx config switch [dev|base]\n

    With no argument, toggles between dev and base. Accepts prod as an alias for base.

    Argument Description dev Switch to dev profile (verbose logging) base Switch to base profile (all defaults) (none) Toggle to the opposite profile

    Profiles:

    Profile Description dev Verbose logging, webhook notifications on base All defaults, notifications off

    Examples:

    ctx config switch dev     # Switch to dev profile\nctx config switch base    # Switch to base profile\nctx config switch         # Toggle (dev → base or base → dev)\nctx config switch prod    # Alias for \"base\"\n

    The detection heuristic checks for an uncommented notify: line in .ctxrc: present means dev, absent means base.

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-status","level":4,"title":"ctx config status","text":"

    Show which .ctxrc profile is currently active.

    ctx config status\n

    Output examples:

    active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n

    See also: Configuration, Contributing: Configuration Profiles

    ","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/connect/","level":1,"title":"Connect","text":"","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect","level":2,"title":"ctx connect","text":"

    Connect a project to a ctx Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.

    New to the Hub?

    Start with the ctx Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.

    The unit of identity is a project, not a user. Registering a directory with ctx connect register binds a per-project client token in .context/.connect.enc. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.

    Only structured entries flow through the hub: decision, learning, convention, task. Session journals, scratchpad contents, and other local state stay on the machine that created them.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-register","level":3,"title":"ctx connect register","text":"

    One-time registration with a hub. Requires the hub address and admin token (printed by ctx hub start on first run).

    ctx connect register localhost:9900 --token ctx_adm_7f3a...\n

    On success, stores an encrypted connection config in .context/.connect.enc for future RPCs.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-subscribe","level":3,"title":"ctx connect subscribe","text":"

    Set which entry types to receive from the hub. Only matching types are returned by sync and listen.

    ctx connect subscribe decision learning\nctx connect subscribe decision learning convention\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-sync","level":3,"title":"ctx connect sync","text":"

    Pull matching entries from the hub and write them to .context/hub/ as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.

    ctx connect sync\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-publish","level":3,"title":"ctx connect publish","text":"

    Push entries to the hub. Specify type and content as arguments.

    ctx connect publish decision \"Use UTC timestamps everywhere\"\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-listen","level":3,"title":"ctx connect listen","text":"

    Stream new entries from the hub in real-time. Writes to .context/hub/ as entries arrive. Press Ctrl-C to stop.

    ctx connect listen\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-status","level":3,"title":"ctx connect status","text":"

    Show hub connection state and entry statistics.

    ctx connect status\n
    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"

    Use --share on ctx add to write locally AND publish to the hub:

    ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n

    If the hub is unreachable, the local write succeeds and a warning is printed. The --share flag is best-effort; it never blocks local context updates.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#auto-sync","level":2,"title":"Auto-Sync","text":"

    Once registered, the check-hub-sync hook automatically syncs new entries from the hub at the start of each session (daily throttled). No manual ctx connect sync needed.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#shared-files","level":2,"title":"Shared Files","text":"

    Entries from the hub are stored in .context/hub/:

    .context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n

    These files are read-only (managed by sync/listen) and never mixed with local context files.

    ","path":["Connect"],"tags":[]},{"location":"cli/connect/#agent-integration","level":2,"title":"Agent Integration","text":"

    Include shared knowledge in agent context packets:

    ctx agent --include-hub\n

    Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.

    ","path":["Connect"],"tags":[]},{"location":"cli/connection/","level":1,"title":"Connect","text":"","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connect","level":2,"title":"ctx connect","text":"

    Connect a project to a ctx Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.

    New to the ctx Hub?

    Start with the ctx Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.

    The unit of identity is a project, not a user. Registering a directory with ctx connection register binds a per-project client token in .context/.connect.enc. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.

    Only structured entries flow through the hub: decision, learning, convention, task. Session journals, scratchpad contents, and other local state stay on the machine that created them.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-register","level":3,"title":"ctx connection register","text":"

    One-time registration with a ctx Hub. Requires the ctx Hub address and admin token (printed by ctx hub start on first run).

    Examples:

    ctx connection register localhost:9900 --token ctx_adm_7f3a...\n

    On success, stores an encrypted connection config in .context/.connect.enc for future RPCs.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-subscribe","level":3,"title":"ctx connection subscribe","text":"

    Set which entry types to receive from the ctx Hub. Only matching types are returned by sync and listen.

    Examples:

    ctx connection subscribe decision learning\nctx connection subscribe decision learning convention\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-sync","level":3,"title":"ctx connection sync","text":"

    Pull matching entries from the ctx Hub and write them to .context/hub/ as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.

    Examples:

    ctx connection sync\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-publish","level":3,"title":"ctx connection publish","text":"

    Push entries to the ctx Hub. Specify type and content as arguments.

    Examples:

    ctx connection publish decision \"Use UTC timestamps everywhere\"\nctx connection publish learning \"Go embed requires files in same package\"\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-listen","level":3,"title":"ctx connection listen","text":"

    Stream new entries from the ctx Hub in real-time. Writes to .context/hub/ as entries arrive. Press Ctrl-C to stop.

    Examples:

    ctx connection listen\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-status","level":3,"title":"ctx connection status","text":"

    Show ctx Hub connection state and entry statistics.

    Examples:

    ctx connection status\n
    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"

    Use --share on ctx add to write locally AND publish to the ctx Hub:

    ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n

    If the hub is unreachable, the local write succeeds and a warning is printed. The --share flag is best-effort; it never blocks local context updates.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#auto-sync","level":2,"title":"Auto-Sync","text":"

    Once registered, the check-hub-sync hook automatically syncs new entries from the ctx Hub at the start of each session (daily throttled). No manual ctx connection sync needed.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#shared-files","level":2,"title":"Shared Files","text":"

    Entries from the ctx Hub are stored in .context/hub/:

    .context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n

    These files are read-only (managed by sync/listen) and never mixed with local context files.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#agent-integration","level":2,"title":"Agent Integration","text":"

    Include shared knowledge in agent context packets:

    ctx agent --include-hub\n

    Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.

    ","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/context/","level":1,"title":"Context Management","text":"","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#adding-entries","level":3,"title":"Adding entries","text":"

    Each context-artifact noun (task, decision, learning, convention) owns its own add subcommand under the noun-first command tree:

    ctx task add <content> [flags]\nctx decision add <content> [flags]\nctx learning add <content> [flags]\nctx convention add <content> [flags]\n

    Target files:

    Subcommand Target File ctx task add TASKS.md ctx decision add DECISIONS.md ctx learning add LEARNINGS.md ctx convention add CONVENTIONS.md

    Flags (shared by every add subcommand; per-noun required-flag rules surface as command errors):

    Flag Short Description --priority <level> -p Priority for tasks: high, medium, low --section <name> -s Target section within file --context -c Context (required for decisions and learnings) --rationale -r Rationale for decisions (required for decisions) --consequence Consequence for decisions (required for decisions) --lesson -l Key insight (required for learnings) --application -a How to apply going forward (required for learnings) --file -f Read content from file instead of argument

    Examples:

    # Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\nctx task add \"Fix login bug\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (requires all ADR (Architectural Decision Record) fields)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning (requires context, lesson, and application)\nctx learning add \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to specific section\nctx convention add \"Use kebab-case for filenames\" --section \"Naming\"\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-drift","level":3,"title":"ctx drift","text":"

    Detect stale or invalid context.

    ctx drift [flags]\n

    Flags:

    Flag Description --json Output machine-readable JSON --fix Auto-fix simple issues

    Checks:

    • Path references in ARCHITECTURE.md and CONVENTIONS.md exist
    • Task references are valid
    • Constitution rules aren't violated (heuristic)
    • Staleness indicators (old files, many completed tasks)
    • Missing packages: warns when internal/ directories exist on disk but are not referenced in ARCHITECTURE.md (suggests running /ctx-architecture)
    • Entry count: warns when LEARNINGS.md or DECISIONS.md exceed configurable thresholds (default: 30 learnings, 20 decisions), or when CONVENTIONS.md exceeds a line count threshold (default: 200). Configure via .ctxrc:
      entry_count_learnings: 30      # warn above this (0 = disable)\nentry_count_decisions: 20      # warn above this (0 = disable)\nconvention_line_count: 200     # warn above this (0 = disable)\n

    Example:

    ctx drift\nctx drift --json\nctx drift --fix\n

    Exit codes:

    Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-sync","level":3,"title":"ctx sync","text":"

    Reconcile context with the current codebase state.

    ctx sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would change without modifying

    What it does:

    • Scans codebase for structural changes
    • Compares with ARCHITECTURE.md
    • Suggests documenting dependencies if package files exist
    • Identifies stale or outdated context

    Example:

    ctx sync\nctx sync --dry-run\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate and clean up context files.

    • Moves completed tasks older than 7 days to the archive
    • Removes empty sections
    ctx compact [flags]\n

    Flags:

    Flag Description --archive Create .context/archive/ for old content

    Example:

    ctx compact\nctx compact --archive\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-fmt","level":3,"title":"ctx fmt","text":"

    Format context files to a consistent line width.

    Wraps long lines in TASKS.md, DECISIONS.md, LEARNINGS.md, and CONVENTIONS.md at word boundaries. Markdown list items get 2-space continuation indent. Headings, tables, frontmatter, and HTML comments are preserved as-is.

    Idempotent: running twice produces the same output.

    ctx fmt [flags]\n

    Flags:

    Flag Type Default Description --width int 80 Target line width --check bool false Check only, exit 1 if files would change

    Examples:

    ctx fmt              # format all context files\nctx fmt --check      # CI mode: check without modifying\nctx fmt --width 100  # custom width\n

    Also available as a Makefile target:

    make fmt-context\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task","level":3,"title":"ctx task","text":"

    Manage task completion, archival, and snapshots.

    ctx task <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-complete","level":4,"title":"ctx task complete","text":"

    Mark a task as completed.

    ctx task complete <task-id-or-text>\n

    Arguments:

    • task-id-or-text: Task number or partial text match

    Examples:

    # By text (partial match)\nctx task complete \"user auth\"\n\n# By task number\nctx task complete 3\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-archive","level":4,"title":"ctx task archive","text":"

    Move completed tasks from TASKS.md to a timestamped archive file.

    ctx task archive [flags]\n

    Flags:

    Flag Description --dry-run Preview changes without modifying files

    Archive files are stored in .context/archive/ with timestamped names (tasks-YYYY-MM-DD.md). Completed tasks (marked with [x]) are moved; pending tasks ([ ]) remain in TASKS.md.

    Example:

    ctx task archive\nctx task archive --dry-run\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-snapshot","level":4,"title":"ctx task snapshot","text":"

    Create a point-in-time snapshot of TASKS.md without modifying the original.

    ctx task snapshot [name]\n

    Arguments:

    • name: Optional name for the snapshot (defaults to \"snapshot\")

    Snapshots are stored in .context/archive/ with timestamped names (tasks-<name>-YYYY-MM-DD-HHMM.md).

    Example:

    ctx task snapshot\nctx task snapshot \"before-refactor\"\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission","level":3,"title":"ctx permission","text":"

    Manage Claude Code permission snapshots.

    ctx permission <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-snapshot","level":4,"title":"ctx permission snapshot","text":"

    Save .claude/settings.local.json as the golden image.

    ctx permission snapshot\n

    Creates .claude/settings.golden.json as a byte-for-byte copy of the current settings. Overwrites if the golden file already exists.

    The golden file is meant to be committed to version control and shared with the team.

    Example:

    ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-restore","level":4,"title":"ctx permission restore","text":"

    Replace settings.local.json with the golden image.

    ctx permission restore\n

    Prints a diff of dropped (session-accumulated) and restored permissions. No-op if the files already match.

    Example:

    ctx permission restore\n# Dropped 3 session permission(s):\n#   - Bash(cat /tmp/debug.log:*)\n#   - Bash(rm /tmp/test-*:*)\n#   - Bash(curl https://example.com:*)\n# Restored from golden image.\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-reindex","level":3,"title":"ctx reindex","text":"

    Regenerate the quick-reference index for both DECISIONS.md and LEARNINGS.md in a single invocation.

    ctx reindex\n

    This is a convenience wrapper around ctx decision reindex and ctx learning reindex. Both files grow at similar rates and users typically want to reindex both after manual edits.

    The index is a compact table of date and title for each entry, allowing AI tools to scan entries without reading the full file.

    Example:

    ctx reindex\n# ✓ Index regenerated with 12 entries\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision","level":3,"title":"ctx decision","text":"

    Manage the DECISIONS.md file.

    ctx decision <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision-reindex","level":4,"title":"ctx decision reindex","text":"

    Regenerate the quick-reference index at the top of DECISIONS.md.

    ctx decision reindex\n

    The index is a compact table showing the date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to DECISIONS.md or when migrating existing files to use the index format.

    Example:

    ctx decision reindex\n# ✓ Index regenerated with 12 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning","level":3,"title":"ctx learning","text":"

    Manage the LEARNINGS.md file.

    ctx learning <subcommand>\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning-reindex","level":4,"title":"ctx learning reindex","text":"

    Regenerate the quick-reference index at the top of LEARNINGS.md.

    ctx learning reindex\n

    The index is a compact table showing the date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.

    Use this after manual edits to LEARNINGS.md or when migrating existing files to use the index format.

    Example:

    ctx learning reindex\n# ✓ Index regenerated with 8 entries\n
    ","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/doctor/","level":1,"title":"Doctor","text":"","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#ctx-doctor","level":3,"title":"ctx doctor","text":"

    Structural health check across context, hooks, and configuration. Runs mechanical checks that don't require semantic analysis. Think of it as ctx status + ctx drift + configuration audit in one pass.

    ctx doctor [flags]\n

    Flags:

    Flag Short Type Default Description --json -j bool false Machine-readable JSON output","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-checks","level":4,"title":"What It Checks","text":"Check Category What it verifies Context initialized Structure .context/ directory exists Required files present Structure All required context files exist (TASKS.md, etc.) Drift detected Quality Stale paths, missing files, constitution violations Event logging status Hooks Whether event_log: true is set in .ctxrc Webhook configured Hooks .notify.enc file exists Pending reminders State Count of entries in reminders.json Task completion ratio State Pending vs completed tasks in TASKS.md Context token size Size Estimated token count across all context files Recent event activity Events Last event timestamp (only when event logging is enabled)","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-human","level":4,"title":"Output Format (Human)","text":"
    ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n

    Status indicators:

    Icon Status Meaning ✓ ok Check passed ⚠ warning Non-critical issue worth fixing ✗ error Problem that needs attention ○ info Informational note","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-json","level":4,"title":"Output Format (JSON)","text":"
    {\n  \"results\": [\n    {\n      \"name\": \"context_initialized\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Context initialized (.context/)\"\n    },\n    {\n      \"name\": \"required_files\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Required files present (4/4)\"\n    },\n    {\n      \"name\": \"drift\",\n      \"category\": \"Quality\",\n      \"status\": \"warning\",\n      \"message\": \"Drift: 2 warnings\"\n    },\n    {\n      \"name\": \"event_logging\",\n      \"category\": \"Hooks\",\n      \"status\": \"info\",\n      \"message\": \"Event logging disabled (enable with event_log: true in .ctxrc)\"\n    },\n    {\n      \"name\": \"webhook\",\n      \"category\": \"Hooks\",\n      \"status\": \"ok\",\n      \"message\": \"Webhook configured\"\n    },\n    {\n      \"name\": \"reminders\",\n      \"category\": \"State\",\n      \"status\": \"ok\",\n      \"message\": \"No pending reminders\"\n    },\n    {\n      \"name\": \"task_completion\",\n      \"category\": \"State\",\n      \"status\": \"warning\",\n      \"message\": \"Tasks: 18/22 completed (82%): consider archiving with ctx task archive\"\n    },\n    {\n      \"name\": \"context_size\",\n      \"category\": \"Size\",\n      \"status\": \"ok\",\n      \"message\": \"Context size: ~4200 tokens (budget: 8000)\"\n    }\n  ],\n  \"warnings\": 2,\n  \"errors\": 0\n}\n

    Examples:

    # Quick structural health check\nctx doctor\n\n# Machine-readable output for scripting\nctx doctor --json\n\n# Count warnings\nctx doctor --json | jq '.warnings'\n\n# Check for errors only\nctx doctor --json | jq '[.results[] | select(.status == \"error\")]'\n
    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#when-to-use-what","level":4,"title":"When to Use What","text":"Tool When ctx status Quick glance at files, tokens, and drift ctx doctor Thorough structural checkup (hooks, config, events too) /ctx-doctor Agent-driven diagnosis with event log pattern analysis

    ctx status tells you what's there. ctx doctor tells you what's wrong. /ctx-doctor tells you why it's wrong and what to do about it.

    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-does-not-do","level":4,"title":"What It Does Not Do","text":"
    • No event pattern analysis: that's the /ctx-doctor skill's job
    • No auto-fixing: reports findings, doesn't modify anything
    • No external service checks: doesn't verify webhook endpoint availability

    See also: Troubleshooting | ctx hook event | /ctx-doctor skill | Detecting and Fixing Drift

    ","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/event/","level":1,"title":"Event","text":"","path":["Event"],"tags":[]},{"location":"cli/event/#ctx-hook-event","level":3,"title":"ctx hook event","text":"

    Query the local hook event log. Requires event_log: true in .ctxrc. Reads events from .context/state/events.jsonl and outputs them in a human-readable table or raw JSONL format.

    All filter flags combine with AND logic.

    ctx hook event [flags]\n

    Flags:

    Flag Description --hook Filter by hook name --session Filter by session ID --event Filter by event type (relay, nudge) --last Show last N events (default: 50) --json Output raw JSONL (for piping to jq) --all Include rotated log file

    Examples:

    ctx hook event                                        # recent events\nctx hook event --hook check-context-size --last 10    # one hook, last 10\nctx hook event --json | jq '.hook'                    # pipe to jq\nctx hook event --session abc123                       # filter by session\n
    ","path":["Event"],"tags":[]},{"location":"cli/guide/","level":1,"title":"Guide","text":"","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/guide/#ctx-guide","level":2,"title":"ctx guide","text":"

    Quick-reference cheat sheet for common ctx commands and skills.

    ctx guide [flags]\n

    Flags:

    Flag Description --skills Show available skills --commands Show available CLI commands

    Example:

    # Show the full cheat sheet\nctx guide\n\n# Skills only\nctx guide --skills\n\n# Commands only\nctx guide --commands\n

    Works without initialization (no .context/ required). Useful for a printable one-pager when onboarding to a project.

    ","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/handover/","level":1,"title":"ctx handover","text":"","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover","level":2,"title":"ctx handover","text":"

    Writes the per-session handover under .context/handovers/<TS>-<slug>.md: a former-agent-to-next-agent note created at session end by /ctx-wrap-up and read at session start by /ctx-remember. When .context/kb/ exists, the writer additionally folds postdated closeouts into the handover's ## Folded Closeouts section and archives them.

    ","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover-write-title","level":3,"title":"ctx handover write <title>","text":"
    ctx handover write \"Cursor Hooks deep dive\" \\\n  --summary \"Drafted topic-page; minted EV-018..EV-024; cold-reader passed.\" \\\n  --next \"Re-ingest the v1.1 release notes URL once you have it.\"\n

    Required flags:

    Flag Description --summary What happened this session (past tense). Placeholder values (TBD, see chat, n/a) are rejected. --next What the next agent should do FIRST (future tense, specific). Same placeholder rejection.

    Optional flags:

    Flag Description --highlights Notable artifacts produced this session. --open-questions Things that remain undecided. --commit Override resolved git HEAD for the Provenance line (CI replay; honors CTX_TASK_COMMIT). --no-fold Skip closeout consumption (mid-session checkpoint).

    Writes: .context/handovers/<TS>-<slug>.md with frontmatter (sha, branch, generated-at, title) and body sections (## Summary, ## Next Session, optionally ## Highlights, ## Open Questions, ## Folded Closeouts). The <TS>-<slug>.md filename is timestamped so multiple concurrent agent runs never overwrite one another's handover.

    Side effect (when --no-fold is absent and .context/kb/ exists): closeouts that postdate the latest handover are folded into the new handover and physically archived under .context/archive/closeouts/.

    ","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#how-to-trigger","level":3,"title":"How to Trigger","text":"

    In ordinary sessions you do not invoke ctx handover write directly. The user-facing trigger is /ctx-wrap-up:

    /ctx-wrap-up \"session title\"\n

    /ctx-wrap-up owns session-end and always delegates to /ctx-handover as its final step. Direct invocation of /ctx-handover is reserved for two cases:

    • --no-fold mid-session checkpoint.
    • Recovery, when a prior session aborted before wrap-up.

    See /ctx-wrap-up and /ctx-handover.

    ","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#reference","level":2,"title":"Reference","text":"
    • Recipe: Session Lifecycle
    • Recipe: Recover an Aborted Session
    • Skill: /ctx-wrap-up
    • Skill: /ctx-handover
    • Skill: /ctx-remember
    ","path":["ctx handover"],"tags":[]},{"location":"cli/hook/","level":1,"title":"Hook","text":"","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#ctx-hook","level":3,"title":"ctx hook","text":"

    Manage hook-related settings: messages, notifications, pause/resume, and event log.

    ctx hook <subcommand> [flags]\n
    ","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#subcommands","level":2,"title":"Subcommands","text":"Subcommand Description ctx hook message list Show all hook messages with override status ctx hook message show <h> <v> Print the effective message template ctx hook message edit <h> <v> Copy default to .context/ for editing ctx hook message reset <h> <v> Delete user override, revert to default ctx hook notify [message] Send a webhook notification ctx hook notify setup Configure and encrypt webhook URL ctx hook notify test Send a test notification ctx hook pause Pause all context hooks for this session ctx hook resume Resume paused context hooks ctx hook event Query the local hook event log","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#examples","level":2,"title":"Examples","text":"
    # View and manage hook messages\nctx hook message list\nctx hook message show qa-reminder gate\nctx hook message edit qa-reminder gate\n\n# Webhook notifications\nctx hook notify setup\nctx hook notify --event loop \"Loop completed\"\n\n# Pause/resume hooks\nctx hook pause\nctx hook resume\n\n# Browse event log\nctx hook event --last 20\nctx hook event --hook qa-reminder --json\n

    See also: Customizing Hook Messages | Webhook Notifications | Pausing Context Hooks | System Hooks Audit

    ","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hub/","level":1,"title":"Hub","text":"","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub","level":2,"title":"ctx hub","text":"

    Operator commands for a ctx Hub: the gRPC server that fans out decisions, learnings, conventions, and tasks across projects. Use ctx hub to start and stop the server, inspect cluster state, add or remove peers at runtime, and hand off leadership before maintenance.

    Who Needs This Page

    You only need ctx hub if you are running a hub server or cluster. For client-side operations (register, subscribe, sync, publish, listen), see ctx connect. For the mental model behind the hub as a whole, read the ctx Hub overview.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-start","level":3,"title":"ctx hub start","text":"

    Start the hub gRPC server.

    Examples:

    ctx hub start                           # Foreground, default port 9900\nctx hub start --port 8080               # Custom port\nctx hub start --data-dir /srv/ctx-hub   # Custom data directory\n

    On first run, generates an admin token and prints it to stdout. Save this token; it's required for ctx connection register in client projects. Subsequent runs reuse the stored token from <data-dir>/admin.token.

    Default data directory: ~/.ctx/hub-data/

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#daemon-mode","level":4,"title":"Daemon Mode","text":"

    Run the hub as a detached background process:

    ctx hub start --daemon          # Fork to background\nctx hub stop                    # Graceful shutdown\n

    The daemon writes a PID file to <data-dir>/hub.pid. Stop the daemon with ctx hub stop (see below).

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#cluster-mode","level":4,"title":"Cluster Mode","text":"

    For high availability, run multiple hubs with Raft-based leader election:

    ctx hub start --port 9900 \\\n  --peers host2:9901,host3:9901\n

    Raft is used only for leader election. Data replication uses sequence-based gRPC sync on the append-only JSONL log; there is no multi-node consensus on writes. See the HA cluster recipe for the full setup and the Raft-lite durability caveat.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#flags","level":4,"title":"Flags","text":"Flag Description Default --port Hub listen port 9900 --data-dir Hub data directory ~/.ctx/hub-data/ --daemon Run the hub server in the background false --peers Comma-separated peer addresses for cluster mode (none)","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#validation","level":4,"title":"Validation","text":"

    The hub validates every published entry before accepting it:

    • Type must be one of decision, learning, convention, task
    • ID and Origin are required and non-empty
    • Content size capped at 1 MB (text-only)
    • Duplicate project registration is rejected (one token per project)
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stop","level":3,"title":"ctx hub stop","text":"

    Stop a running hub daemon.

    Examples:

    ctx hub stop                            # Stop using default data dir\nctx hub stop --data-dir /srv/ctx-hub    # Custom data directory\n

    Sends SIGTERM to the PID recorded in <data-dir>/hub.pid, waits for in-flight RPCs to drain, and removes the PID file. Safe to rerun: if no daemon is running, returns a \"no running hub\" error without side effects.

    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-status","level":3,"title":"ctx hub status","text":"

    Show cluster status: role, peers, sync state, entry count, and uptime.

    Examples:

    ctx hub status\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-peer","level":3,"title":"ctx hub peer","text":"

    Add or remove peers from the cluster at runtime. Useful for scaling up or replacing a decommissioned node without restarting the leader.

    Examples:

    ctx hub peer add host2:9901\nctx hub peer remove host2:9901\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stepdown","level":3,"title":"ctx hub stepdown","text":"

    Transfer leadership to another node gracefully. Triggers a new election among the remaining followers before the current leader steps down. Use before taking the leader offline for maintenance.

    Examples:

    ctx hub stepdown\n
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#see-also","level":3,"title":"See Also","text":"
    • ctx connect: client-side commands (register, subscribe, sync, publish, listen)
    • ctx Hub overview: mental model and user stories
    • ctx Hub: Getting Started
    • Hub operations: production deployment, backup, monitoring
    • Hub failure modes
    • Hub security model
    ","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/init-status/","level":1,"title":"Init and Status","text":"","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-init","level":3,"title":"ctx init","text":"

    Initialize a new .context/ directory with template files.

    ctx init [flags]\n

    Git is required

    ctx init (and every non-administrative ctx subcommand) refuses to operate without a .git/ working tree at the project root. ctx already needed git to work properly; that requirement is now enforced rather than assumed.

    Handovers and closeouts stamp the current commit into their frontmatter, and the editorial pipeline pins in-repo evidence to a short SHA (none of which works without a repo).

    Run git init first if the project does not already have one.

    There is no --allow-no-git escape hatch.

    Flags:

    Flag Short Description --force -f Overwrite existing context files --minimal -m Only create essential files (TASKS.md, DECISIONS.md, CONSTITUTION.md) --merge Auto-merge ctx content into existing CLAUDE.md

    Creates:

    • .context/ directory with all template files
    • .context/kb/ (with index.md and topics/) and .context/ingest/ (with KB-RULES.md, mode prompts, OPERATOR.md, PROMPT.md, closeouts/, schemas/) and .context/handovers/: the editorial-pipeline scaffolding (Phase KB). Embedded templates are copied; existing files are preserved.
    • .claude/settings.local.json with pre-approved ctx permissions
    • CLAUDE.md with bootstrap instructions (or merges into existing)

    Claude Code hooks and skills are provided by the ctx plugin (see Integrations).

    Example:

    # Standard init\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --reset\n\n# Merge into existing files\nctx init --merge\n

    After ctx init succeeds, the final output includes a hint showing the exact eval \"$(ctx activate)\" line to bind the new directory for your shell. Every other ctx command requires that binding (or an equivalent direct CTX_DIR=/abs/path/.context export) before it will run.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-activate","level":3,"title":"ctx activate","text":"

    Emit a shell-native export CTX_DIR=... line for the target .context/ directory. ctx does not search the filesystem during day-to-day commands: each one needs CTX_DIR set before it runs. activate is the convenience that figures out the path for you so you can bind it with one line.

    # Walk up from CWD, emit if exactly one candidate visible.\neval \"$(ctx activate)\"\n

    Flags:

    Flag Description --shell Shell dialect override. POSIX-family (bash, zsh, sh) all share one syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from $SHELL.

    Resolution:

    Candidate count from CWD Behavior Zero Error. Use ctx init to create one, or cd closer to the project root. One Emit export CTX_DIR=<path> for that candidate. Two or more Refuse. List every candidate. Re-run from a more specific cwd.

    activate is args-free under the single-source-anchor model; the explicit-path mode was removed because hub-client / hub-server scenarios store at ~/.ctx/hub-data/ and never read .context/, so they activate from the project root like everyone else. Direct binding without a project-local scan is still available via export CTX_DIR=/abs/path/.context or the inline form.

    If the parent shell already has CTX_DIR set to a different value, the output gains a leading # ctx: replacing stale CTX_DIR=... comment so the user sees the change in eval output before the replacement takes effect.

    See also: Activating a Context Directory for the full recipe including direnv setup and CI patterns.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-deactivate","level":3,"title":"ctx deactivate","text":"

    Emit a shell-native unset CTX_DIR line. Pairs with activate.

    eval \"$(ctx deactivate)\"\n

    Flags:

    Flag Description --shell Shell dialect override. POSIX-family (bash, zsh, sh) all share one unset syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from $SHELL.

    deactivate does not touch the filesystem, doesn't require a declared context directory, and never fails under normal operation; unsetting an already-unset variable is a no-op across supported shells.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-status","level":3,"title":"ctx status","text":"

    Show the current context summary.

    ctx status [flags]\n

    Flags:

    Flag Short Description --json Output as JSON --verbose -v Include file contents summary

    Output:

    • Context directory path
    • Total files and token estimate
    • Status of each file (loaded, empty, missing)
    • Recent activity (modification times)
    • Drift warnings if any

    Example:

    ctx status\nctx status --json\nctx status --verbose\n
    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-agent","level":3,"title":"ctx agent","text":"

    Print an AI-ready context packet optimized for LLM consumption.

    ctx agent [flags]\n

    Flags:

    Flag Default Description --budget 8000 Token budget: controls content selection and prioritization --format md Output format: md or json --cooldown 10m Suppress repeated output within this duration (requires --session) --session (none) Session ID for cooldown isolation (e.g., $PPID) --include-hub false Include hub entries from .context/hub/

    How budget works:

    The budget controls how much context is included. Entries are selected in priority tiers:

    1. Constitution: always included in full (inviolable rules)
    2. Tasks: all active tasks, up to 40% of budget
    3. Conventions: all conventions, up to 20% of budget
    4. Decisions: scored by recency and relevance to active tasks
    5. Learnings: scored by recency and relevance to active tasks
    6. Steering: applicable steering file bodies, scored by their inclusion mode and description match against the active prompt
    7. Skill: named skill content (from --skill)
    8. Hub: entries from .context/hub/ (with --include-hub, see ctx connect)

    Decisions and learnings are ranked by a combined score (how recent + how relevant to your current tasks). High-scoring entries are included with their full body. Entries that don't fit get title-only summaries in an \"Also Noted\" section. Superseded entries are excluded.

    Output Sections:

    Section Source Selection Read These Files all .context/ Non-empty files in priority order Constitution CONSTITUTION.md All rules (never truncated) Current Tasks TASKS.md All unchecked tasks (budget-capped) Key Conventions CONVENTIONS.md All items (budget-capped) Recent Decisions DECISIONS.md Full body, scored by relevance Key Learnings LEARNINGS.md Full body, scored by relevance Also Noted overflow Title-only summaries

    Example:

    # Default (8000 tokens, markdown)\nctx agent\n\n# Smaller packet for tight context windows\nctx agent --budget 4000\n\n# JSON format for programmatic use\nctx agent --format json\n\n# Pipe to file\nctx agent --budget 4000 > context.md\n\n# With cooldown (hooks/automation: requires --session)\nctx agent --session $PPID\n

    Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.

    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-load","level":3,"title":"ctx load","text":"

    Load and display assembled context as AI would see it.

    ctx load [flags]\n

    Flags:

    Flag Description --budget <tokens> Token budget for assembly (default: 8000) --raw Output raw file contents without assembly

    Example:

    ctx load\nctx load --budget 16000\nctx load --raw\n
    ","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/journal/","level":1,"title":"Journal","text":"","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal","level":3,"title":"ctx journal","text":"

    Browse and search AI session history from Claude Code and other tools.

    ctx journal <subcommand>\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source","level":4,"title":"ctx journal source","text":"

    List all parsed sessions.

    ctx journal source [flags]\n

    Flags:

    Flag Short Description --limit -n Maximum sessions to display (default: 20) --project -p Filter by project name --tool -t Filter by tool (e.g., claude-code) --all-projects Include sessions from all projects

    Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.

    Example:

    ctx journal source\nctx journal source --limit 5\nctx journal source --project ctx\nctx journal source --tool claude-code\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source-show","level":4,"title":"ctx journal source --show","text":"

    Show details of a specific session.

    ctx journal source --show [session-id] [flags]\n

    Flags:

    Flag Description --latest Show the most recent session --full Show full message content --all-projects Search across all projects

    The session ID can be a full UUID, partial match, or session slug name.

    Example:

    ctx journal source --show abc123\nctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show --latest\nctx journal source --show --latest --full\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-import","level":4,"title":"ctx journal import","text":"

    Import sessions to editable journal files in .context/journal/.

    ctx journal import [session-id] [flags]\n

    Flags:

    Flag Description --all Import all sessions (only new files by default) --all-projects Import from all projects --regenerate Re-import existing files (preserves YAML frontmatter by default) --keep-frontmatter Preserve enriched YAML frontmatter during regeneration (default: true) --yes, -y Skip confirmation prompt --dry-run Show what would be imported without writing files

    Safe by default: --all only imports new sessions. Existing files are skipped. Use --regenerate to re-import existing files (conversation content is regenerated, YAML frontmatter from enrichment is preserved by default). Use --keep-frontmatter=false to discard enriched frontmatter during regeneration.

    Locked entries (via ctx journal lock) are always skipped, regardless of flags.

    Single-session import (ctx journal import <id>) always writes without prompting, since you are explicitly targeting one session.

    The journal/ directory should be gitignored (like sessions/) since it contains raw conversation data.

    Example:

    ctx journal import abc123                 # Import one session\nctx journal import --all                  # Import only new sessions\nctx journal import --all --dry-run        # Preview what would be imported\nctx journal import --all --regenerate     # Re-import existing (prompts)\nctx journal import --all --regenerate -y  # Re-import without prompting\nctx journal import --all --regenerate --keep-frontmatter=false -y  # Discard frontmatter\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-lock","level":4,"title":"ctx journal lock","text":"

    Protect journal entries from being overwritten by import --regenerate or modified by enrichment skills (/ctx-journal-enrich, /ctx-journal-enrich-all).

    ctx journal lock <pattern> [flags]\n

    Flags:

    Flag Description --all Lock all journal entries

    The pattern matches filenames by slug, date, or short ID. Locking a multi-part entry locks all parts. The lock is recorded in .context/journal/.state.json and a locked: true line is added to the file's YAML frontmatter for visibility.

    Example:

    ctx journal lock abc12345\nctx journal lock 2026-01-21-session-abc12345.md\nctx journal lock --all\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-unlock","level":4,"title":"ctx journal unlock","text":"

    Remove lock protection from journal entries.

    ctx journal unlock <pattern> [flags]\n

    Flags:

    Flag Description --all Unlock all journal entries

    Example:

    ctx journal unlock abc12345\nctx journal unlock --all\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-sync","level":4,"title":"ctx journal sync","text":"

    Sync lock state from journal frontmatter to .state.json.

    ctx journal sync\n

    Scans all journal markdowns and updates .state.json to match each file's frontmatter. Files with locked: true in frontmatter are marked locked in state; files without a locked: line have their lock cleared.

    This is the inverse of ctx journal lock: instead of state driving frontmatter, frontmatter drives state. Useful after batch enrichment where you add locked: true to frontmatter manually.

    Example:

    # After enriching entries and adding locked: true to frontmatter\nctx journal sync\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal_1","level":3,"title":"ctx journal","text":"

    Analyze and synthesize imported session files.

    ctx journal <subcommand>\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-site","level":4,"title":"ctx journal site","text":"

    Generate a static site from journal entries in .context/journal/.

    ctx journal site [flags]\n

    Flags:

    Flag Short Description --output -o Output directory (default: .context/journal-site) --build Run zensical build after generating --serve Run zensical serve after generating

    Creates a zensical-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.

    Requires zensical to be installed for --build or --serve:

    pipx install zensical\n

    Example:

    ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-obsidian","level":4,"title":"ctx journal obsidian","text":"

    Generate an Obsidian vault from journal entries in .context/journal/.

    ctx journal obsidian [flags]\n

    Flags:

    Flag Short Description --output -o Output directory (default: .context/journal-obsidian)

    Creates an Obsidian-compatible vault with:

    • Wikilinks ([[target|display]]) for all internal navigation
    • MOC pages (Map of Content) for topics, key files, and session types
    • Related sessions footer linking entries that share topics
    • Transformed frontmatter (topicstags for Obsidian integration)
    • Minimal .obsidian/ config enforcing wikilink mode

    No external dependencies are required: Open the output directory as an Obsidian vault directly.

    Example:

    ctx journal obsidian                        # Generate in .context/journal-obsidian/\nctx journal obsidian --output ~/vaults/ctx  # Custom output directory\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-check","level":4,"title":"ctx journal schema check","text":"

    Validate JSONL session files against the embedded schema and report drift.

    ctx journal schema check [flags]\n

    Flags:

    Flag Short Description --dir Directory to scan for JSONL files --all-projects Scan all Claude Code project directories --quiet -q Exit code only (0 = clean, 1 = drift)

    Scans JSONL files for unknown fields, missing required fields, unknown record types, and unknown content block types. When drift is found, writes a Markdown report to .context/reports/schema-drift.md. When drift resolves, the report is automatically deleted.

    Designed for interactive use, CI pipelines, and nightly cron jobs.

    Example:

    ctx journal schema check                    # Current project\nctx journal schema check --all-projects     # All projects\nctx journal schema check --quiet            # Exit code only\nctx journal schema check --dir /path/to     # Custom directory\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-dump","level":4,"title":"ctx journal schema dump","text":"

    Print the embedded JSONL schema definition.

    ctx journal schema dump\n

    Shows all known record types with their required and optional fields, and all recognized content block types with their parse status. Useful for inspecting what the schema validator expects.

    Example:

    ctx journal schema dump\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-serve","level":3,"title":"ctx serve","text":"

    Serve any zensical directory locally. This is a serve-only command: It does not generate or regenerate site content.

    ctx serve [directory]\n

    If no directory is specified, defaults to the journal site (.context/journal-site).

    Requires zensical to be installed:

    pipx install zensical\n

    ctx serve vs. ctx journal site --serve

    ctx journal site --serve generates the journal site then serves it: an all-in-one command. ctx serve only serves an existing directory, and works with any zensical site (journal, docs, etc.).

    Example:

    ctx serve                        # Serve journal site (no regeneration)\nctx serve .context/journal-site  # Same, explicit path\nctx serve ./site                 # Serve the docs site\n
    ","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/kb/","level":1,"title":"ctx kb","text":"","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb","level":2,"title":"ctx kb","text":"

    Knowledge-base editorial pipeline (Phase KB). Manages the .context/kb/ knowledge base via mode-aware skills and a small set of supporting CLI commands. The editorial constitution lives at .context/ingest/KB-RULES.md (laid down by ctx init).

    ctx kb [subcommand]\n
    Subcommand Type Purpose ctx kb topic new \"<name>\" CLI (real) Sole writer of topic-page scaffolds. Creates .context/kb/topics/<slug>/index.md from the embedded template. Refuses when the topic exists. ctx kb note \"<text>\" CLI (real) Appends a one-liner to .context/ingest/findings.md. Never touches a topic page. ctx kb reindex CLI (real) Refreshes the CTX:KB:TOPICS managed block in .context/kb/index.md. ctx kb ingest <folder\\|paths> Skill-driven Mode-aware editorial pass. CLI form refuses on empty input and points at the /ctx-kb-ingest skill. ctx kb ask \"<question>\" Skill-driven Q&A grounded in the kb. CLI form refuses on empty input and points at the /ctx-kb-ask skill. ctx kb site-review Skill-driven Mechanical structural audit. Points at /ctx-kb-site-review. ctx kb ground Skill-driven External grounding via grounding-sources.md. Refuses when the file is empty.

    Skill-driven vs real CLI

    The mode skills (ingest, ask, site-review, ground) do the editorial work themselves: the agent reads .context/ingest/30-INGEST.md (etc.) and executes the pass per the pass-mode contract. The CLI form for those subcommands validates input and prints the canonical skill invocation. The real CLI commands (topic new, note, reindex) own concrete state changes.

    ","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-topic-new-name","level":3,"title":"ctx kb topic new \"<name>\"","text":"

    Scaffolds a folder-shaped topic at .context/kb/topics/<slug>/index.md from the embedded template.

    Slug: lowercase + kebab-case. Slashes are preserved for vendor-namespaced topology (e.g. cursor/hooks, cursor/skills, cursor/rules under a shared cursor/ folder).

    Refuses when the topic folder already exists. Use the existing folder instead; the editorial pass extends pages, it doesn't reset them.

    ","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-note-text","level":3,"title":"ctx kb note \"<text>\"","text":"

    Appends a timestamped one-liner to .context/ingest/findings.md. Use for parking findings the next ingest pass should absorb.

    ctx kb note \"follow-up: chase the v1.2 release notes for the SIGTERM change\"\n
    ","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-reindex","level":3,"title":"ctx kb reindex","text":"

    Refreshes the CTX:KB:TOPICS managed block inside .context/kb/index.md so the kb landing page enumerates current topic folders. Run after ctx kb topic new to update the landing.

    ","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#skill-driven-subcommands","level":3,"title":"Skill-Driven Subcommands","text":"

    ingest, ask, site-review, ground exist as CLI surfaces so the editorial workflow is drivable from outside Claude Code (via the fallback PROMPT.md auto-router). In Claude Code, prefer the skills:

    /ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n/ctx-kb-ask \"does the kb say hooks fire async?\"\n/ctx-kb-site-review\n/ctx-kb-ground\n

    See the Build a Knowledge Base recipe for the full workflow.

    ","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#reference","level":2,"title":"Reference","text":"
    • Recipe: Build a Knowledge Base
    • Recipe: Typical KB Session
    • Editorial constitution: .context/ingest/KB-RULES.md
    ","path":["ctx kb"],"tags":[]},{"location":"cli/loop/","level":1,"title":"Loop","text":"","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/loop/#ctx-loop","level":2,"title":"ctx loop","text":"

    Generate a shell script for running an autonomous loop.

    An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.

    ctx loop [flags]\n

    Flags:

    Flag Short Description Default --tool <tool> -t AI tool: claude, aider, or generic claude --prompt <file> -p Prompt file to use .context/loop.md --max-iterations <n> -n Maximum iterations (0 = unlimited) 0 --completion <signal> -c Completion signal to detect SYSTEM_CONVERGED --output <file> -o Output script filename loop.sh

    Examples:

    # Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n

    Running the generated loop:

    ctx loop\nchmod +x loop.sh\n./loop.sh\n

    See also: Autonomous Loops for the full workflow.

    ","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/mcp/","level":1,"title":"MCP Server","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp","level":2,"title":"ctx mcp","text":"

    Run ctx as a Model Context Protocol (MCP) server. MCP is a standard protocol that lets AI tools discover and consume context from external sources via JSON-RPC 2.0 over stdin/stdout.

    This makes ctx accessible to any MCP-compatible AI tool without custom hooks or integrations:

    • Claude Desktop
    • Cursor
    • Windsurf
    • VS Code Copilot
    • Any tool supporting MCP
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp-serve","level":3,"title":"ctx mcp serve","text":"

    Start the MCP server. This command reads JSON-RPC 2.0 requests from stdin and writes responses to stdout. It is intended to be launched by MCP clients (Claude Desktop, Cursor, VS Code Copilot), not run directly from a shell. See Configuration below for how each host launches it.

    Flags: None. The server uses the declared context directory from CTX_DIR. As with every other ctx command, that variable must be set: the server does not walk the filesystem.

    Examples:

    # Normal invocation (by an MCP client via stdio transport)\nctx mcp serve\n\n# Pin a context directory for a specific workspace\nCTX_DIR=/path/to/project/.context ctx mcp serve\n\n# Verify the binary starts without a client attached (Ctrl-C to exit)\nctx mcp serve < /dev/null\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#configuration","level":2,"title":"Configuration","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#claude-desktop","level":3,"title":"Claude Desktop","text":"

    Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

    {\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#cursor","level":3,"title":"Cursor","text":"

    Add to .cursor/mcp.json in your project:

    {\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#vs-code-copilot","level":3,"title":"VS Code (Copilot)","text":"

    Add to .vscode/mcp.json:

    {\n  \"servers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n
    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resources","level":2,"title":"Resources","text":"

    Resources expose context files as read-only content. Each resource has a URI, name, and returns Markdown text.

    URI Name Description ctx://context/constitution constitution Hard rules that must never be violated ctx://context/tasks tasks Current work items and their status ctx://context/conventions conventions Code patterns and standards ctx://context/architecture architecture System architecture documentation ctx://context/decisions decisions Architectural decisions with rationale ctx://context/learnings learnings Gotchas, tips, and lessons learned ctx://context/glossary glossary Project-specific terminology ctx://context/agent agent All files assembled in priority read order

    The agent resource assembles all non-empty context files into a single Markdown document, ordered by the configured read priority.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resource-subscriptions","level":3,"title":"Resource Subscriptions","text":"

    Clients can subscribe to resource changes via resources/subscribe. The server polls for file mtime changes (default: 5 seconds) and emits notifications/resources/updated when a subscribed file changes on disk.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#tools","level":2,"title":"Tools","text":"

    Tools expose ctx commands as callable operations. Each tool accepts JSON arguments and returns text results.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_status","level":3,"title":"ctx_status","text":"

    Show context health: file count, token estimate, and per-file summary.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_add","level":3,"title":"ctx_add","text":"

    Add a task, decision, learning, or convention to the context.

    Argument Type Required Description type string Yes Entry type: task, decision, learning, convention content string Yes Title or main content priority string No Priority level (tasks only): high, medium, low context string Conditional Context field (decisions and learnings) rationale string Conditional Rationale (decisions only) consequence string Conditional Consequence (decisions only) lesson string Conditional Lesson learned (learnings only) application string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_complete","level":3,"title":"ctx_complete","text":"

    Mark a task as done by number or text match.

    Argument Type Required Description query string Yes Task number (e.g. \"1\") or search text","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_drift","level":3,"title":"ctx_drift","text":"

    Detect stale or invalid context. Returns violations, warnings, and passed checks.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_journal_source","level":3,"title":"ctx_journal_source","text":"

    Query recent AI session history (summaries, decisions, topics).

    Argument Type Required Description limit number No Max sessions to return (default: 5) since string No ISO date filter: sessions after this date (YYYY-MM-DD)

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_watch_update","level":3,"title":"ctx_watch_update","text":"

    Apply a structured context update to .context/ files. Supports task, decision, learning, convention, and complete entry types. Human confirmation is required before calling.

    Argument Type Required Description type string Yes Entry type: task, decision, learning, convention, complete content string Yes Main content context string Conditional Context background (decisions/learnings) rationale string Conditional Rationale (decisions only) consequence string Conditional Consequence (decisions only) lesson string Conditional Lesson learned (learnings only) application string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_compact","level":3,"title":"ctx_compact","text":"

    Move completed tasks to the archive section and remove empty sections from context files. Human confirmation required.

    Argument Type Required Description archive boolean No Also write tasks to .context/archive/ (default: false)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_next","level":3,"title":"ctx_next","text":"

    Suggest the next pending task based on priority and position.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_check_task_completion","level":3,"title":"ctx_check_task_completion","text":"

    Advisory check: after a write operation, detect if any pending tasks were silently completed. Returns nudge text if a match is found.

    Argument Type Required Description recent_action string No Brief description of what was just done

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_event","level":3,"title":"ctx_session_event","text":"

    Signal a session lifecycle event. Type end triggers the session-end persistence ceremony - human confirmation required.

    Argument Type Required Description type string Yes Event type: start, end caller string No Caller identifier (cursor, windsurf, vscode, claude-desktop)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_steering_get","level":3,"title":"ctx_steering_get","text":"

    Retrieve applicable steering files for a prompt. Without a prompt, returns always-included files only.

    Argument Type Required Description prompt string No Prompt text to match against steering file descriptions

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_search","level":3,"title":"ctx_search","text":"

    Search across .context/ files for a query string. Returns matching lines with file paths and line numbers.

    Argument Type Required Description query string Yes Search string to match against

    Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_start","level":3,"title":"ctx_session_start","text":"

    Execute session-start hooks and return aggregated context from hook outputs.

    Arguments: None.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_end","level":3,"title":"ctx_session_end","text":"

    Execute session-end hooks with an optional summary. Returns aggregated context from hook outputs.

    Argument Type Required Description summary string No Session summary passed to hook scripts","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_remind","level":3,"title":"ctx_remind","text":"

    List pending session-scoped reminders.

    Arguments: None. Read-only.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#prompts","level":2,"title":"Prompts","text":"

    Prompts provide pre-built templates for common workflows. Clients can list available prompts via prompts/list and retrieve a specific prompt via prompts/get.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-session-start","level":3,"title":"ctx-session-start","text":"

    Load full context at the beginning of a session. Returns all context files assembled in priority read order with session orientation instructions.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-decision-add","level":3,"title":"ctx-decision-add","text":"

    Format an architectural decision entry with all required fields.

    Argument Type Required Description content string Yes Decision title context string Yes Background context rationale string Yes Why this decision was made consequence string Yes Expected consequence","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-learning-add","level":3,"title":"ctx-learning-add","text":"

    Format a learning entry with all required fields.

    Argument Type Required Description content string Yes Learning title context string Yes Background context lesson string Yes The lesson learned application string Yes How to apply this lesson","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-reflect","level":3,"title":"ctx-reflect","text":"

    Guide end-of-session reflection. Returns a structured review prompt covering progress assessment and context update recommendations.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-checkpoint","level":3,"title":"ctx-checkpoint","text":"

    Report session statistics: tool calls made, entries added, and pending updates queued during the current session.

    ","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/memory/","level":1,"title":"Memory","text":"","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory","level":2,"title":"ctx memory","text":"

    Bridge Claude Code's auto memory (MEMORY.md) into .context/.

    Claude Code maintains per-project auto memory at ~/.claude/projects/<slug>/memory/MEMORY.md. This command group discovers that file, mirrors it into .context/memory/mirror.md (git-tracked), and detects drift.

    ctx memory <subcommand>\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-sync","level":3,"title":"ctx memory sync","text":"

    Copy MEMORY.md to .context/memory/mirror.md. Archives the previous mirror before overwriting.

    ctx memory sync [flags]\n

    Flags:

    Flag Description --dry-run Show what would happen without writing

    Exit codes:

    Code Meaning 0 Synced successfully 1 MEMORY.md not found (auto memory inactive)

    Examples:

    ctx memory sync\n# Archived previous mirror to mirror-2026-03-05-143022.md\n# Synced MEMORY.md -> .context/memory/mirror.md\n#   Source: ~/.claude/projects/-home-user-project/memory/MEMORY.md\n#   Lines: 47 (was 32)\n#   New content: 15 lines since last sync\n\nctx memory sync --dry-run\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-status","level":3,"title":"ctx memory status","text":"

    Show drift, timestamps, line counts, and archive count.

    ctx memory status\n

    Exit codes:

    Code Meaning 0 No drift 1 MEMORY.md not found 2 Drift detected (MEMORY.md changed since sync)

    Examples:

    ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines (modified since last sync)\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-diff","level":3,"title":"ctx memory diff","text":"

    Show what changed in MEMORY.md since last sync.

    ctx memory diff\n

    Examples:

    ctx memory diff\n# --- .context/memory/mirror.md (mirror)\n# +++ ~/.claude/projects/.../memory/MEMORY.md (source)\n# +- new learning: memory bridge works\n

    No output when files are identical.

    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-publish","level":3,"title":"ctx memory publish","text":"

    Push curated .context/ content into MEMORY.md so the agent sees it natively.

    ctx memory publish [flags]\n

    Content is selected in priority order: pending tasks, recent decisions (7 days), key conventions, recent learnings (7 days). Wrapped in <!-- ctx:published --> markers. Claude-owned content outside the markers is preserved.

    Flags:

    Flag Description Default --budget Line budget for published content 80 --dry-run Show what would be published

    Examples:

    ctx memory publish --dry-run\n# Publishing .context/ -> MEMORY.md...\n#   Budget: 80 lines\n#   Published block:\n#     5 pending tasks (from TASKS.md)\n#     3 recent decisions (from DECISIONS.md)\n#     5 key conventions (from CONVENTIONS.md)\n#   Total: 42 lines (within 80-line budget)\n# Dry run - no files written.\n\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter budget\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-unpublish","level":3,"title":"ctx memory unpublish","text":"

    Remove the ctx-managed marker block from MEMORY.md, preserving Claude-owned content.

    Examples:

    ctx memory unpublish\n

    Hook integration: The check-memory-drift hook runs on every prompt and nudges the agent when MEMORY.md has changed since last sync. The nudge fires once per session. See Memory Bridge.

    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-import","level":3,"title":"ctx memory import","text":"

    Classify and promote entries from MEMORY.md into structured .context/ files.

    ctx memory import [flags]\n

    Each entry is classified by keyword heuristics:

    Keywords Target always use, prefer, never use, standard CONVENTIONS.md decided, chose, trade-off, approach DECISIONS.md gotcha, learned, watch out, bug, caveat LEARNINGS.md todo, need to, follow up TASKS.md Everything else Skipped

    Deduplication prevents re-importing the same entry across runs.

    Flags:

    Flag Description --dry-run Show classification plan without writing

    Examples:

    ctx memory import --dry-run\n# Scanning MEMORY.md for new entries...\n#   Found 6 entries\n#\n#   -> \"always use ctx from PATH\"\n#      Classified: CONVENTIONS.md (keywords: always use)\n#\n#   -> \"decided to use heuristic classification over LLM-based\"\n#      Classified: DECISIONS.md (keywords: decided)\n#\n# Dry run - would import: 4 entries\n# Skipped: 2 entries (session notes/unclassified)\n\nctx memory import    # Actually write entries to .context/ files\n
    ","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/message/","level":1,"title":"Message","text":"","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message","level":3,"title":"ctx hook message","text":"

    Manage hook message templates.

    Hook messages control the text hooks emit. The hook logic (when to fire, counting, state tracking) is universal; the messages are opinions that can be customized per-project.

    ctx hook message <subcommand>\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-list","level":3,"title":"ctx hook message list","text":"

    Show all hook messages with category and override status.

    ctx hook message list [--json]\n

    Flags:

    Flag Description --json Output in JSON format

    Example:

    ctx hook message list\nctx hook message list --json | jq '.[] | select(.override)'\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-show","level":3,"title":"ctx hook message show","text":"

    Print the effective message template for a hook/variant pair. Shows the user override if present, otherwise the embedded default.

    ctx hook message show <hook> <variant>\n

    Example:

    ctx hook message show qa-reminder gate\nctx hook message show check-context-size checkpoint\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-edit","level":3,"title":"ctx hook message edit","text":"

    Copy the embedded default template for <hook> <variant> to .context/hooks/messages/<hook>/<variant>.txt so you can edit it directly. The override takes effect the next time the hook fires.

    ctx hook message edit <hook> <variant>\n

    If an override already exists, the command fails and directs you to edit it in place or reset it first.

    Example:

    ctx hook message edit qa-reminder gate\n# Edit .context/hooks/messages/qa-reminder/gate.txt in your editor\n
    ","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-reset","level":3,"title":"ctx hook message reset","text":"

    Delete a user override and revert to the embedded default. Silent no-op if no override exists.

    ctx hook message reset <hook> <variant>\n

    Example:

    ctx hook message reset qa-reminder gate\n

    See Customizing hook messages for the full workflow.

    ","path":["Message"],"tags":[]},{"location":"cli/notify/","level":1,"title":"Notify","text":"","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify","level":2,"title":"ctx hook notify","text":"

    Send fire-and-forget webhook notifications from skills, loops, and hooks.

    ctx hook notify --event <name> [--session-id <id>] \"message\"\n

    Flags:

    Flag Short Description --event -e Event name (required) --session-id -s Session ID (optional)

    Behavior:

    • No webhook configured: silent no-op (exit 0)
    • Webhook set but event not in events list: silent no-op (exit 0)
    • Webhook set and event matches: fire-and-forget HTTP POST
    • HTTP errors silently ignored (no retry)

    Examples:

    ctx hook notify --event loop \"Loop completed after 5 iterations\"\nctx hook notify -e nudge -s session-abc \"Context checkpoint at prompt #20\"\n
    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-setup","level":3,"title":"ctx hook notify setup","text":"

    Configure the webhook URL interactively. The URL is encrypted with AES-256-GCM using the encryption key and stored in .context/.notify.enc.

    Examples:

    ctx hook notify setup\n

    The encrypted file is safe to commit. The key (~/.ctx/.ctx.key) lives outside the project and is never committed.

    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-test","level":3,"title":"ctx hook notify test","text":"

    Send a test notification and report the HTTP response status.

    Examples:

    ctx hook notify test\n

    Payload format (JSON POST):

    {\n  \"event\": \"loop\",\n  \"message\": \"Loop completed after 5 iterations\",\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n
    Field Type Description event string Event name from --event flag message string Notification message session_id string Session ID (omitted if empty) timestamp string UTC RFC3339 timestamp project string Project directory name

    See also: Webhook Notifications recipe.

    ","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/pad/","level":1,"title":"Scratchpad","text":"","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad","level":2,"title":"ctx pad","text":"

    Encrypted scratchpad for sensitive one-liners that travel with the project.

    When invoked without a subcommand, lists all entries.

    ctx pad\nctx pad <subcommand>\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-add","level":3,"title":"ctx pad add","text":"

    Append a new entry to the scratchpad.

    ctx pad add <text>\nctx pad add <label> --file <path>\n

    Flags:

    Flag Short Description --file -f Ingest a file as a blob entry (max 64 KB)

    Examples:

    ctx pad add \"DATABASE_URL=postgres://user:pass@host/db\"\nctx pad add \"deploy config\" --file ./deploy.yaml\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-show","level":3,"title":"ctx pad show","text":"

    Output the raw text of an entry by number. For blob entries, prints decoded file content (or writes to disk with --out).

    ctx pad show <n>\nctx pad show <n> --out <path>\n

    Arguments:

    • n: 1-based entry number

    Flags:

    Flag Description --out Write decoded blob content to a file (blobs only)

    Examples:

    ctx pad show 3\nctx pad show 2 --out ./recovered.yaml\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-rm","level":3,"title":"ctx pad rm","text":"

    Remove one or more entries by stable ID. Supports individual IDs and ranges.

    ctx pad rm <id> [id...]\n

    Arguments:

    • id: One or more entry IDs (e.g., 3, 1 4, 3-5)

    Examples:

    ctx pad rm 2\nctx pad rm 1 4\nctx pad rm 3-5\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-normalize","level":3,"title":"ctx pad normalize","text":"

    Reassign entry IDs as a contiguous sequence 1..N, closing any gaps left by deletions.

    Examples:

    ctx pad normalize\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-edit","level":3,"title":"ctx pad edit","text":"

    Replace, append to, or prepend to an entry.

    ctx pad edit <n> [text]\n

    Arguments:

    • n: 1-based entry number
    • text: Replacement text (mutually exclusive with --append/--prepend)

    Flags:

    Flag Description --append Append text to the end of the entry --prepend Prepend text to the beginning of entry --file Replace blob file content (preserves label) --label Replace blob label (preserves content)

    Examples:

    ctx pad edit 2 \"new text\"\nctx pad edit 2 --append \" suffix\"\nctx pad edit 2 --prepend \"prefix \"\nctx pad edit 1 --file ./v2.yaml\nctx pad edit 1 --label \"new name\"\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-mv","level":3,"title":"ctx pad mv","text":"

    Move an entry from one position to another.

    ctx pad mv <from> <to>\n

    Arguments:

    • from: Source position (1-based)
    • to: Destination position (1-based)

    Examples:

    ctx pad mv 3 1      # promote entry 3 to the top\nctx pad mv 1 5      # bury entry 1 to position 5\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-resolve","level":3,"title":"ctx pad resolve","text":"

    Show both sides of a merge conflict in the encrypted scratchpad.

    Examples:

    ctx pad resolve\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-import","level":3,"title":"ctx pad import","text":"

    Bulk-import lines from a file into the scratchpad. Each non-empty line becomes a separate entry. All entries are written in a single encrypt/write cycle.

    With --blob, import all first-level files from a directory as blob entries. Each file becomes a blob with the filename as its label. Subdirectories and non-regular files are skipped.

    ctx pad import <file>\nctx pad import -              # read from stdin\nctx pad import --blob <dir>   # import directory files as blobs\n

    Arguments:

    • file: Path to a text file, - for stdin, or a directory (with --blob)

    Flags:

    Flag Description --blob Import first-level files from a directory as blobs

    Examples:

    ctx pad import notes.txt\ngrep TODO *.go | ctx pad import -\nctx pad import --blob ./ideas/\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-export","level":3,"title":"ctx pad export","text":"

    Export all blob entries from the scratchpad to a directory as files. Each blob's label becomes the filename. Non-blob entries are skipped.

    ctx pad export [dir]\n

    Arguments:

    • dir: Target directory (default: current directory)

    Flags:

    Flag Short Description --force -f Overwrite existing files instead of timestamping --dry-run Print what would be exported without writing

    When a file already exists, a unix timestamp is prepended to avoid collisions (e.g., 1739836200-label). Use --force to overwrite instead.

    Examples:

    ctx pad export ./ideas\nctx pad export --dry-run\nctx pad export --force ./backup\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-merge","level":3,"title":"ctx pad merge","text":"

    Merge entries from one or more scratchpad files into the current pad. Each input file is auto-detected as encrypted or plaintext. Entries are deduplicated by exact content.

    ctx pad merge FILE...\n

    Arguments:

    • FILE...: One or more scratchpad files to merge (encrypted or plaintext)

    Flags:

    Flag Short Description --key -k Path to key file for decrypting input files --dry-run Print what would be merged without writing

    Examples:

    ctx pad merge worktree/.context/scratchpad.enc\nctx pad merge notes.md backup.enc\nctx pad merge --key /path/to/other.key foreign.enc\nctx pad merge --dry-run pad-a.enc pad-b.md\n
    ","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pause/","level":1,"title":"Pause","text":"","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/pause/#ctx-hook-pause","level":2,"title":"ctx hook pause","text":"

    Pause all context nudge and reminder hooks for the current session. Security hooks (dangerous command blocking) and housekeeping hooks still fire.

    ctx hook pause [flags]\n

    Flags:

    Flag Description --session-id Session ID (overrides stdin)

    Example:

    # Pause hooks for a quick investigation\nctx hook pause\n\n# Resume when ready\nctx hook resume\n

    See also:

    • ctx hook resume: the matching resume command
    • Pausing Context Hooks recipe
    ","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/prune/","level":1,"title":"Prune","text":"","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/prune/#ctx-prune","level":3,"title":"ctx prune","text":"

    Remove per-session state files from .context/state/ that are older than the specified age. Session state files are identified by UUID suffixes (context-check-<session-id>, heartbeat-<session-id>, and similar). Global files without session IDs (events.jsonl, memory-import.json, and other non-per-session markers) are always preserved.

    ctx prune [flags]\n

    Flags:

    Flag Description --days Prune files older than this many days (default: 7) --dry-run Show what would be pruned without deleting

    Examples:

    ctx prune                 # Prune files older than 7 days\nctx prune --days 3        # Prune files older than 3 days\nctx prune --dry-run       # Preview without deleting\n

    See State maintenance for the recommended cadence and automation recipe.

    ","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/remind/","level":1,"title":"Remind","text":"","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind","level":2,"title":"ctx remind","text":"

    Session-scoped reminders that surface at session start. Reminders are stored verbatim and relayed verbatim: no summarization, no categories.

    When invoked with a text argument and no subcommand, adds a reminder.

    ctx remind \"text\"\nctx remind <subcommand>\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-add","level":3,"title":"ctx remind add","text":"

    Add a reminder. This is the default action: ctx remind \"text\" and ctx remind add \"text\" are equivalent.

    ctx remind \"refactor the swagger definitions\"\nctx remind add \"check CI after the deploy\" --after 2026-02-25\n

    Arguments:

    • text: The reminder message (verbatim)

    Flags:

    Flag Short Description --after -a Don't surface until this date (YYYY-MM-DD)

    Examples:

    ctx remind \"refactor the swagger definitions\"\nctx remind \"check CI after the deploy\" --after 2026-02-25\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-list","level":3,"title":"ctx remind list","text":"

    List all pending reminders. Date-gated reminders that aren't yet due are annotated with (after DATE, not yet due).

    Examples:

    ctx remind list\nctx remind ls            # alias\n

    Aliases: ls

    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-dismiss","level":3,"title":"ctx remind dismiss","text":"

    Remove one or more reminders by ID, or remove all with --all. Supports individual IDs and ranges.

    ctx remind dismiss <id> [id...]\nctx remind dismiss --all\n

    Arguments:

    • id: One or more reminder IDs (e.g., 3, 3 5-7)

    Flags:

    Flag Description --all Dismiss all reminders

    Aliases: rm

    Examples:

    ctx remind dismiss 3\nctx remind dismiss 3 5-7\nctx remind dismiss --all\n
    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-normalize","level":3,"title":"ctx remind normalize","text":"

    Reassign reminder IDs as a contiguous sequence 1..N, closing any gaps left by dismissals.

    Examples:

    ctx remind normalize\n

    See also: Session Reminders recipe.

    ","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/resume/","level":1,"title":"Resume","text":"","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/resume/#ctx-hook-resume","level":2,"title":"ctx hook resume","text":"

    Resume context hooks after a pause. Silent no-op if not paused.

    ctx hook resume [flags]\n

    Flags:

    Flag Description --session-id Session ID (overrides stdin)

    Example:

    ctx hook resume\n

    See also:

    • ctx hook pause: the matching pause command
    • Pausing Context Hooks recipe
    ","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/serve/","level":1,"title":"Serve","text":"","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#ctx-serve","level":2,"title":"ctx serve","text":"

    Serve a static site locally via zensical.

    With no argument, serves the journal site at .context/journal-site. With a directory argument, serves that directory if it contains a zensical.toml.

    ctx serve                             # Serve .context/journal-site\nctx serve ./my-site                   # Serve a specific directory\nctx serve ./docs                      # Serve any zensical site\n

    This Command Does NOT Start a Hub

    ctx serve is purely for static-site serving. To run a ctx Hub for cross-project knowledge sharing, use ctx hub start. That command lives in its own group because the hub is a gRPC server, not a static site.

    Requires zensical to be installed:

    pipx install zensical\n
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#arguments","level":3,"title":"Arguments","text":"Argument Description [directory] Directory containing a zensical.toml to serve

    When omitted, serves .context/journal-site by default, the directory produced by ctx journal site.

    Examples:

    ctx serve                         # Default: serve .context/journal-site\nctx serve ./my-site               # Serve a specific directory\nctx serve ./docs                  # Serve any zensical site\n
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#see-also","level":3,"title":"See Also","text":"
    • ctx journal: generate the journal site that ctx serve displays.
    • ctx hub start: for running a ctx Hub server, not a static site.
    • Browsing and enriching past sessions: the recipe that combines ctx journal and ctx serve.
    ","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/setup/","level":1,"title":"Setup","text":"","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/setup/#ctx-setup","level":2,"title":"ctx setup","text":"

    Generate AI tool integration configuration.

    ctx setup <tool> [flags]\n

    Flags:

    Flag Short Description --write -w Write the generated config to disk (e.g. .github/copilot-instructions.md)

    Supported tools:

    Tool Description claude-code Redirects to plugin install instructions cursor Cursor IDE kiro Kiro IDE cline Cline (VS Code extension) aider Aider CLI copilot GitHub Copilot opencode OpenCode (terminal-first AI coding agent) windsurf Windsurf IDE

    Claude Code Uses the Plugin System

    Claude Code integration is now provided via the ctx plugin. Running ctx setup claude-code prints plugin install instructions.

    Examples:

    # Print hook instructions to stdout\nctx setup cursor\nctx setup aider\n\n# Generate and write .github/copilot-instructions.md\nctx setup copilot --write\n\n# Generate MCP config and sync steering files\nctx setup kiro --write\nctx setup cursor --write\nctx setup cline --write\n\n# Generate OpenCode plugin, skills, AGENTS.md, and global MCP config\nctx setup opencode --write\n
    ","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/site/","level":1,"title":"Site","text":"","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site","level":2,"title":"ctx site","text":"

    Site management commands for the ctx.ist static site.

    ctx site <subcommand>\n
    ","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site-feed","level":3,"title":"ctx site feed","text":"

    Generate an Atom 1.0 feed from finalized blog posts in docs/blog/.

    ctx site feed [flags]\n

    Scans docs/blog/ for files matching YYYY-MM-DD-*.md, parses YAML frontmatter, and generates a valid Atom feed. Only posts with reviewed_and_finalized: true are included. Summaries are extracted from the first paragraph after the heading.

    Flags:

    Flag Short Type Default Description --out -o string site/feed.xml Output path --base-url string https://ctx.ist Base URL for entry links

    Output:

    Generated site/feed.xml (21 entries)\n\nSkipped:\n  2026-02-25-the-homework-problem.md: not finalized\n\nWarnings:\n  2026-02-09-defense-in-depth.md: no summary paragraph found\n

    Three buckets: included (count), skipped (with reason), warnings (included but degraded). exit 0 always: warnings inform but do not block.

    Frontmatter requirements:

    Field Required Feed mapping title Yes <title> date Yes <updated> reviewed_and_finalized Yes Draft gate (must be true) author No <author><name> topics No <category term=\"\">

    Examples:

    ctx site feed                                # Generate site/feed.xml\nctx site feed --out /tmp/feed.xml            # Custom output path\nctx site feed --base-url https://example.com # Custom base URL\nmake site-feed                               # Makefile shortcut\nmake site                                    # Builds site + feed\n
    ","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/skill/","level":1,"title":"Skill","text":"","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill","level":2,"title":"ctx skill","text":"

    Manage reusable instruction bundles that can be installed into .context/skills/.

    A skill is a directory containing a SKILL.md file with YAML frontmatter (name, description) and a Markdown instruction body. Skills are loaded by the agent context packet when --skill <name> is passed to ctx agent.

    ctx skill <subcommand>\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-install","level":3,"title":"ctx skill install","text":"

    Install a skill from a source directory.

    ctx skill install <source>\n

    Arguments:

    • source: Path to a directory containing SKILL.md

    Examples:

    ctx skill install ./my-skills/code-review\n# Installed code-review → .context/skills/code-review\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-list","level":3,"title":"ctx skill list","text":"

    List all installed skills.

    Examples:

    ctx skill list\n
    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-remove","level":3,"title":"ctx skill remove","text":"

    Remove an installed skill.

    Arguments:

    • name: Skill name to remove

    Examples:

    ctx skill remove code-review\n

    See also: Building Project Skills recipe.

    ","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/steering/","level":1,"title":"Steering","text":"","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering","level":2,"title":"ctx steering","text":"

    Manage steering files: persistent behavioral rules for AI coding assistants.

    A steering file is a small Markdown document with YAML frontmatter that tells the AI how to behave in a specific context. ctx steering keeps those files in .context/steering/, decides which ones apply for a given prompt, and syncs them out to each AI tool's native format (Claude Code, Cursor, Kiro, Cline).

    ctx steering <subcommand>\n

    Steering vs Decisions vs Conventions

    The three look similar on disk but serve different purposes:

    • Decisions record what was chosen and why. Consumed mostly by humans (and by the agent via ctx agent).
    • Conventions describe how the codebase is written. Consumed as reference material.
    • Steering tells the AI how to behave when asked about X. Consumed by the AI tool's prompt injection layer, conditionally on prompt match.

    If you find yourself writing \"the AI should always do X\", that belongs in steering, not decisions.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#anatomy-of-a-steering-file","level":3,"title":"Anatomy of a Steering File","text":"
    ---\nname: security\ndescription: Security rules for all code changes\ninclusion: always    # always | auto | manual\ntools: []            # empty = all tools\npriority: 10         # lower = injected first\n---\n\n# Security rules\n\n- Validate all user input at system boundaries.\n- Never log secrets, tokens, or credentials.\n- Prefer constant-time comparison for tokens.\n

    Inclusion modes:

    Mode When it's included always Every prompt, unconditionally auto When the prompt matches the description keywords manual Only when the user names it explicitly

    Priority: lower numbers inject first, so high-priority rules appear at the top of the prompt. Default is 50.

    Tools: an empty list means all configured tools receive the file; list specific tool names to scope it.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-init","level":3,"title":"ctx steering init","text":"

    Create a starter set of steering files in .context/steering/ to use as a scaffolding baseline.

    Examples:

    ctx steering init\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-add","level":3,"title":"ctx steering add","text":"

    Create a new steering file with default frontmatter.

    ctx steering add <name>\n

    Arguments:

    • name: Steering file name (without .md extension)

    Examples:

    ctx steering add security\n# Created .context/steering/security.md\n

    The generated file uses inclusion: manual and priority: 50 by default. Edit the frontmatter to change behavior.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-list","level":3,"title":"ctx steering list","text":"

    List all steering files with their inclusion mode, priority, and tool scoping.

    Examples:

    ctx steering list\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-preview","level":3,"title":"ctx steering preview","text":"

    Preview which steering files would be included for a given prompt. Useful for validating auto-inclusion descriptions against realistic prompts.

    ctx steering preview [prompt]\n

    Examples:

    ctx steering preview \"create a REST API endpoint\"\n# Steering files matching prompt \"create a REST API endpoint\":\n#   api-standards        inclusion=auto     priority=20  tools=all\n#   security             inclusion=always   priority=10  tools=all\n
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-sync","level":3,"title":"ctx steering sync","text":"

    Sync steering files to tool-native formats for tools that have a built-in rules primitive. Not every tool needs this; Claude Code and Codex use a different delivery mechanism (see below).

    Examples:

    ctx steering sync\n

    Which tools are sync targets?

    Tool Sync target Mechanism Cursor .cursor/rules/ Cursor reads the directory natively Cline .clinerules/ Cline reads the directory natively Kiro .kiro/steering/ Kiro reads the directory natively Claude Code (no-op) Delivered via hook + MCP (see next section) Codex (no-op) Same as Claude Code

    For the three native-rules tools, ctx steering sync writes each matching steering file to the appropriate directory with tool-specific frontmatter transforms. Unchanged files are skipped (idempotent).

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#how-claude-code-and-codex-consume-steering","level":3,"title":"How Claude Code and Codex Consume Steering","text":"

    Claude Code has no native \"steering files\" primitive, so ctx steering sync skips it entirely. Instead, steering reaches Claude through two non-sync channels, both activated by ctx setup claude-code (which installs the plugin):

    1. Automatic injection via the PreToolUse hook. The Claude Code plugin wires a PreToolUse hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads .context/steering/ and calls steering.Filter with an empty prompt, so only files with inclusion: always match. Those files are included as Tier 6 of the context packet. The packet is printed on stdout, which Claude Code injects as additional context. This fires on every tool call; no user action.

    2. On-demand MCP tool call (ctx_steering_get). The ctx plugin ships a .mcp.json file that automatically registers the ctx MCP server (ctx mcp serve) with Claude Code on plugin install. Once registered, Claude can invoke the ctx_steering_get tool mid-task to fetch matching steering files for a specific prompt. This is the only path that resolves inclusion: auto and inclusion: manual matches for Claude Code; Claude passes the prompt to the MCP tool, which runs the keyword match against each file's description.

    Verify the MCP server is registered:

    claude mcp list\n

    Expected line: ctx: ctx mcp serve - ✓ Connected. If it's missing, reinstall the plugin from Claude Code (/plugin → find ctx → uninstall → install again); older plugin versions shipped without the .mcp.json file.

    Prefer inclusion: always for Claude Code

    Because the PreToolUse hook passes an empty prompt to ctx agent, only always files fire automatically. auto files require Claude to call the ctx_steering_get MCP tool on its own; manual files require an explicit user invocation. For rules that should reliably fire on every Claude Code session, use inclusion: always. Reserve auto/manual for situational libraries where the opt-in cost is acceptable and you understand Claude may not pull them in without prompting.

    The foundation files scaffolded by ctx init already default to inclusion: always for this reason.

    Practical implications:

    • Running ctx steering sync before starting a Claude session does nothing for Claude's benefit. Skip it.
    • ctx steering preview still works for validating your descriptions; it doesn't depend on sync.
    • If Claude Code is your only tool, the ctx steering commands you care about are add, list, preview, init (never sync).
    • If you use both Claude Code and (say) Cursor, ctx steering sync covers Cursor (where auto and manual work natively) while the hook+MCP pipeline covers Claude Code. For rules you need to fire automatically on both, use inclusion: always.
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-agent-integration","level":3,"title":"ctx agent Integration","text":"

    When ctx agent builds a context packet, steering files are loaded as Tier 6 of the budget-aware assembly (see ctx agent). Files with inclusion: always are always included; auto files are scored against the current prompt and included in priority order until the tier budget is exhausted.

    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#see-also","level":3,"title":"See Also","text":"
    • ctx setup: configure which tools receive steering syncs
    • ctx trigger: lifecycle scripts (a different hooking concept, see below)
    • Building steering files recipe: walkthrough from first file to synced output
    ","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/sysinfo/","level":1,"title":"Sysinfo","text":"","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/sysinfo/#ctx-sysinfo","level":3,"title":"ctx sysinfo","text":"

    Display a snapshot of system resources (memory, swap, disk, load) with threshold-based alert severities. Mirrors what the check-resource hook plumbing monitors in the background, but this command prints the full report at any severity level, not only at DANGER.

    ctx sysinfo [flags]\n

    Flags:

    Flag Description --json Output in JSON format

    Alert thresholds:

    Resource WARNING DANGER Memory ≥ 75% ≥ 90% Swap ≥ 50% ≥ 75% Disk ≥ 85% ≥ 95% Load ≥ 1.0x CPUs ≥ 1.5x CPUs

    Examples:

    ctx sysinfo                  # Human-readable table\nctx sysinfo --json           # Structured output\n
    ","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/system/","level":1,"title":"System","text":"","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system","level":3,"title":"ctx system","text":"

    Hidden parent command that hosts Claude Code hook plumbing and a small set of session-lifecycle plumbing subcommands used by skills and editor integrations. The parent is registered without a visible group in ctx --help; run ctx system --help to see its subcommands.

    ctx system <subcommand>\n

    Commands Previously under ctx system

    Several user-facing maintenance commands used to live under ctx system and were promoted to top-level:

    • ctx system eventsctx hook event
    • ctx system messagectx hook message
    • ctx system prunectx prune
    • ctx system resourcesctx sysinfo
    • ctx system statsctx usage

    ctx system bootstrap remains under ctx system as a hidden, agent-only command. Update any scripts or personal docs that reference the old paths.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#plumbing-subcommands","level":2,"title":"Plumbing Subcommands","text":"

    These are not hook handlers; they're called by skills and editor integrations during the session lifecycle. Safe to run manually.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-journal","level":4,"title":"ctx system mark-journal","text":"

    Update processing state for a journal entry. Records the current date in .context/journal/.state.json. Used by journal skills to record pipeline progress.

    ctx system mark-journal <filename> <stage>\n

    Stages: exported, enriched, normalized, fences_verified

    Flag Description --check Check if stage is set (exit 1 if not)

    Example:

    ctx system mark-journal 2026-01-21-session-abc12345.md enriched\nctx system mark-journal 2026-01-21-session-abc12345.md normalized\nctx system mark-journal --check 2026-01-21-session-abc12345.md fences_verified\n
    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-wrapped-up","level":4,"title":"ctx system mark-wrapped-up","text":"

    Suppress context checkpoint nudges after a wrap-up ceremony. Writes a marker file that check-context-size checks before emitting checkpoint boxes. The marker expires after 2 hours.

    Called automatically by /ctx-wrap-up after persisting context (not intended for direct use).

    ctx system mark-wrapped-up\n

    No flags, no arguments. Idempotent: running it again updates the marker timestamp.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-pause-ctx-system-resume","level":4,"title":"ctx system pause / ctx system resume","text":"

    Session-scoped hook suppression. ctx system pause writes a marker file that causes hook plumbing to no-op for the current session; ctx system resume removes it. These are the hook-plumbing counterparts to the ctx hook pause / ctx hook resume commands (which call them internally).

    Read the session ID from stdin JSON (same as hooks) or pass --session-id.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-session-event","level":4,"title":"ctx system session-event","text":"

    Records a session lifecycle event (start or end) to the event log. Called by editor integrations when a workspace is opened or closed.

    ctx system session-event --type start --caller vscode\nctx system session-event --type end --caller vscode\n
    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#hook-subcommands","level":2,"title":"Hook Subcommands","text":"

    Hidden Claude Code hook handlers implementing the hook contract: read JSON from stdin, perform logic, emit output on stdout, exit 0. Block commands output JSON with a decision field.

    UserPromptSubmit hooks: context-load-gate, check-context-size, check-persistence, check-ceremony, check-journal, check-version, check-resource, check-knowledge, check-map-staleness, check-memory-drift, check-reminder, check-freshness, check-hub-sync, check-skill-discovery, heartbeat.

    PreToolUse hooks: block-non-path-ctx, block-dangerous-command, qa-reminder, specs-nudge.

    PostToolUse hooks: post-commit, check-task-completion.

    See AI Tools for registration details and the Claude Code plugin integration.

    ","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/trace/","level":1,"title":"Commit Context Tracing","text":"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace","level":3,"title":"ctx trace","text":"

    Show the context behind git commits. Links commits back to the decisions, tasks, learnings, and sessions that motivated them.

    git log shows what changed, git blame shows who, and ctx trace shows why.

    ctx trace [commit] [flags]\n

    Flags:

    Flag Description --last N Show context for last N commits --json Output as JSON for scripting

    Examples:

    # Show context for a specific commit\nctx trace abc123\n\n# Show context for last 10 commits\nctx trace --last 10\n\n# JSON output\nctx trace abc123 --json\n

    Output:

    Commit: abc123 \"Fix auth token expiry\"\nDate:   2026-03-14 10:00:00 -0700\nContext:\n  [Decision] #12: Use short-lived tokens with server-side refresh\n    Date: 2026-03-10\n\n  [Task] #8: Implement token rotation for compliance\n    Status: completed\n

    When listing recent commits with --last:

    abc123  Fix auth token expiry         decision:12, task:8\ndef456  Add rate limiting             decision:15, learning:7\n789abc  Update dependencies           (none)\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-file","level":3,"title":"ctx trace file","text":"

    Show the context trail for a file. Combines git log with context resolution.

    ctx trace file <path[:line-range]> [flags]\n

    Flags:

    Flag Description --last N Maximum commits to show (default: 20)

    Examples:

    # Show context trail for a file\nctx trace file src/auth.go\n\n# Show context for specific line range\nctx trace file src/auth.go:42-60\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-tag","level":3,"title":"ctx trace tag","text":"

    Manually tag a commit with context. For commits made without the hook, or to add extra context after the fact.

    Tags are stored in .context/trace/overrides.jsonl since git trailers cannot be added to existing commits without rewriting history.

    ctx trace tag <commit> --note \"<text>\"\n

    Examples:

    ctx trace tag HEAD --note \"Hotfix for production outage\"\nctx trace tag abc123 --note \"Part of Q1 compliance initiative\"\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-hook","level":3,"title":"ctx trace hook","text":"

    Enable or disable the prepare-commit-msg hook for automatic context tracing. When enabled, commits automatically receive a ctx-context trailer with references to relevant decisions, tasks, learnings, and sessions.

    ctx trace hook <enable|disable>\n

    Prerequisites: ctx must be on your $PATH. If you installed via go install, ensure $GOPATH/bin (or $HOME/go/bin) is in your shell's $PATH.

    What the hook does:

    1. Before each commit, collects context from three sources:
    2. Pending context accumulated during work (ctx add, ctx task complete)
    3. Staged file changes to .context/ files
    4. Working state (in-progress tasks, active AI session)
    5. Injects a ctx-context trailer into the commit message
    6. After commit, records the mapping in .context/trace/history.jsonl

    Examples:

    # Install the hook\nctx trace hook enable\n\n# Remove the hook\nctx trace hook disable\n

    Resulting commit message:

    Fix auth token expiry handling\n\nRefactored token refresh logic to handle edge case\nwhere refresh token expires during request.\n\nctx-context: decision:12, task:8, session:abc123\n
    ","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#reference-types","level":3,"title":"Reference Types","text":"

    The ctx-context trailer supports these reference types:

    Prefix Points to Example decision:<n> Entry #n in DECISIONS.md decision:12 learning:<n> Entry #n in LEARNINGS.md learning:5 task:<n> Task #n in TASKS.md task:8 convention:<n> Entry #n in CONVENTIONS.md convention:3 session:<id> AI session by ID session:abc123 \"<text>\" Free-form context note \"Performance fix for P1 incident\"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#storage","level":3,"title":"Storage","text":"

    Context trace data is stored in the .context/ directory:

    File Purpose Lifecycle state/pending-context.jsonl Accumulates refs during work Truncated after each commit trace/history.jsonl Permanent commit-to-context map Append-only, never truncated trace/overrides.jsonl Manual tags for existing commits Append-only","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trigger/","level":1,"title":"Trigger","text":"","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger","level":2,"title":"ctx trigger","text":"

    Manage lifecycle triggers: executable scripts that fire at specific events during an AI session. Triggers can block tool calls, inject context, and automate reactions: any side effect you want at session boundaries, tool boundaries, or file-save events.

    ctx trigger <subcommand>\n

    Triggers Execute Arbitrary Scripts

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks: only enable scripts you've read and understand. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#where-triggers-live","level":3,"title":"Where Triggers Live","text":"

    Triggers live in .context/hooks/<trigger-type>/ as executable scripts. The on-disk directory name is still hooks/ for historical reasons even though the command is ctx trigger. Each script:

    • Reads a JSON payload from stdin.
    • Returns a JSON payload on stdout.
    • Returns a non-zero exit code to block or error.
    .context/\n└── hooks/\n    ├── session-start/\n    │   └── inject-context.sh\n    ├── pre-tool-use/\n    │   └── block-legacy.sh\n    └── post-tool-use/\n        └── record-edit.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#trigger-types","level":3,"title":"Trigger Types","text":"Type Fires when session-start An AI session begins session-end An AI session ends pre-tool-use Before an AI tool call is executed post-tool-use After an AI tool call returns file-save When a file is saved context-add When a context entry is added","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#input-and-output-contract","level":3,"title":"Input and Output Contract","text":"

    Each trigger receives a JSON object on stdin with the event details. Minimal contract (fields vary by trigger type):

    {\n  \"type\": \"pre-tool-use\",\n  \"tool\": \"write_file\",\n  \"path\": \"src/auth.go\",\n  \"session_id\": \"abc123-...\"\n}\n

    The trigger may write a JSON object to stdout to influence behavior. Example for a blocking pre-tool-use trigger:

    {\n  \"action\": \"block\",\n  \"message\": \"Editing src/auth.go requires approval from #security\"\n}\n

    For non-blocking event loggers, simply read stdin and exit 0 without writing to stdout.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-add","level":3,"title":"ctx trigger add","text":"

    Create a new trigger script with a template. The generated file has a bash shebang, a stdin reader using jq, and a basic JSON output structure.

    ctx trigger add <trigger-type> <name>\n

    Arguments:

    • trigger-type: One of session-start, session-end, pre-tool-use, post-tool-use, file-save, context-add
    • name: Script name (without .sh extension)

    Examples:

    ctx trigger add session-start inject-context\n# Created .context/hooks/session-start/inject-context.sh\n\nctx trigger add pre-tool-use block-legacy\n# Created .context/hooks/pre-tool-use/block-legacy.sh\n

    The generated script is not executable by default. Enable it with ctx trigger enable after reviewing the contents.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-list","level":3,"title":"ctx trigger list","text":"

    List all discovered triggers, grouped by trigger type, with their enabled/disabled status.

    Examples:

    ctx trigger list\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-test","level":3,"title":"ctx trigger test","text":"

    Run all enabled triggers of a given type against a mock payload. Use --tool and --path to customize the mock input for tool-related events.

    ctx trigger test <trigger-type> [flags]\n

    Flags:

    Flag Description --tool Tool name to put in mock input --path File path to put in mock input

    Examples:

    ctx trigger test session-start\nctx trigger test pre-tool-use --tool write_file --path src/main.go\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-enable","level":3,"title":"ctx trigger enable","text":"

    Enable a trigger by setting its executable permission bit. Searches every trigger-type directory for a script matching <name>.

    ctx trigger enable <name>\n

    Examples:

    ctx trigger enable inject-context\n# Enabled .context/hooks/session-start/inject-context.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-disable","level":3,"title":"ctx trigger disable","text":"

    Disable a trigger by clearing its executable permission bit. Searches every trigger-type directory for a script matching <name>.

    ctx trigger disable <name>\n

    Examples:

    ctx trigger disable inject-context\n# Disabled .context/hooks/session-start/inject-context.sh\n
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#three-hooking-concepts-in-ctx-dont-confuse-them","level":3,"title":"Three Hooking Concepts in ctx (Don't Confuse Them)","text":"

    This is a common source of confusion. ctx has three distinct hook-like layers, and they serve different purposes:

    Layer Owned by Where it runs Configured via ctx trigger You .context/hooks/<type>/*.sh ctx trigger add/enable ctx system hooks ctx itself built-in, called by ctx's own lifecycle internal (see ctx system --help) Claude Code hooks Claude Code .claude/settings.local.json edit JSON, or /ctx-sanitize-permissions

    Use ctx trigger when you want project-specific automation that your AI tool will run at lifecycle events. Use Claude Code hooks for tool-specific integrations that don't need to be portable across tools. ctx system hooks are not something you author; they're the internal nudge machinery that ships with ctx.

    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#see-also","level":3,"title":"See Also","text":"
    • ctx steering: persistent AI behavioral rules (a different concept; rules vs scripts)
    • Authoring triggers recipe: a full walkthrough with security guidance
    ","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/usage/","level":1,"title":"Usage","text":"","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/usage/#ctx-usage","level":3,"title":"ctx usage","text":"

    Display per-session token usage statistics from the local stats JSONL files written by the heartbeat hook. By default, shows the last 20 entries across all sessions. Use --follow to stream new entries as they arrive (like tail -f).

    ctx usage [flags]\n

    Flags:

    Flag Description -f, --follow Stream new entries as they arrive -s, --session Filter by session ID (prefix match) -n, --last Show last N entries (default: 20) -j, --json Output raw JSONL

    Examples:

    ctx usage                     # Last 20 entries across all sessions\nctx usage --follow            # Live stream (like tail -f)\nctx usage --session abc123    # Filter to one session\nctx usage --last 100 --json   # Last 100 as raw JSONL\n
    ","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/watch/","level":1,"title":"Watch","text":"","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/watch/#ctx-watch","level":2,"title":"ctx watch","text":"

    Watch for AI output and auto-apply context updates.

    Parses <context-update> XML commands from AI output and applies them to context files.

    ctx watch [flags]\n

    Flags:

    Flag Description --log <file> Log file to watch (default: stdin) --dry-run Preview updates without applying

    Examples:

    # Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n
    ","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/why/","level":1,"title":"Why","text":"","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"cli/why/#ctx-why","level":2,"title":"ctx why","text":"

    Read ctx's philosophy documents directly in the terminal.

    ctx why [DOCUMENT]\n

    Documents:

    Name Description manifesto The ctx Manifesto: creation, not code about About ctx: what it is and why it exists invariants Design invariants: properties that must hold

    Examples:

    # Interactive numbered menu\nctx why\n\n# Show a specific document\nctx why manifesto\nctx why about\nctx why invariants\n\n# Pipe to a pager\nctx why manifesto | less\n
    ","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"home/","level":1,"title":"Home","text":"
    • ctx is not a prompt.
    • ctx is version-controlled cognitive state.

    ctx is the persistence layer for human-AI reasoning.

    Deterministic. Git-native. Human-readable. Local-first.

    Start here.

    Learn what ctx does, set it up, and run your first session.

    Pre-1.0: Moving Fast

    ctx is under active development. This website tracks the development branch, not the latest release:

    Some features described here may not exist in the binary you have installed.

    Expect rough edges.

    If something is missing or broken, open an issue.

    ","path":["Home"],"tags":[]},{"location":"home/#introduction","level":2,"title":"Introduction","text":"","path":["Home"],"tags":[]},{"location":"home/#about","level":3,"title":"About","text":"

    What ctx is, how it works, and why persistent context changes how you work with AI.

    ","path":["Home"],"tags":[]},{"location":"home/#is-it-right-for-me","level":3,"title":"Is It Right for Me?","text":"

    Good fit, not-so-good fit, and a 5-minute trial to find out for yourself.

    ","path":["Home"],"tags":[]},{"location":"home/#faq","level":3,"title":"FAQ","text":"

    Quick answers to the questions newcomers ask most about ctx, files, tooling, and trade-offs.

    ","path":["Home"],"tags":[]},{"location":"home/#get-started","level":2,"title":"Get Started","text":"","path":["Home"],"tags":[]},{"location":"home/#getting-started","level":3,"title":"Getting Started","text":"

    Install the binary, set up the plugin, and verify it works.

    ","path":["Home"],"tags":[]},{"location":"home/#your-first-session","level":3,"title":"Your First Session","text":"

    Step-by-step walkthrough from ctx init to verified recall.

    ","path":["Home"],"tags":[]},{"location":"home/#common-workflows","level":3,"title":"Common Workflows","text":"

    Day-to-day commands for tracking context, checking health, and browsing history.

    ","path":["Home"],"tags":[]},{"location":"home/#concepts","level":2,"title":"Concepts","text":"","path":["Home"],"tags":[]},{"location":"home/#context-files","level":3,"title":"Context Files","text":"

    What each .context/ file does. What's their purpose. How do we best leverage them.

    ","path":["Home"],"tags":[]},{"location":"home/#configuration","level":3,"title":"Configuration","text":"

    Flexible configuration: .ctxrc, environment variables, and CLI flags.

    ","path":["Home"],"tags":[]},{"location":"home/#hub","level":3,"title":"Hub","text":"

    A fan-out channel for decisions, learnings, conventions, and tasks that need to cross project boundaries, without replicating everything else.

    ","path":["Home"],"tags":[]},{"location":"home/#working-with-ai","level":2,"title":"Working with AI","text":"","path":["Home"],"tags":[]},{"location":"home/#prompting-guide","level":3,"title":"Prompting Guide","text":"

    Effective prompts for AI sessions with ctx.

    ","path":["Home"],"tags":[]},{"location":"home/#keeping-ai-honest","level":3,"title":"Keeping AI Honest","text":"

    AI agents confabulate: they invent history, claim familiarity with decisions never made, and sometimes declare tasks complete when they aren't. Tools and habits to push back.

    ","path":["Home"],"tags":[]},{"location":"home/#my-ai-keeps-making-the-same-mistakes","level":3,"title":"My AI Keeps Making the Same Mistakes","text":"

    Stop rediscovering the same bugs and dead-ends across sessions.

    ","path":["Home"],"tags":[]},{"location":"home/#joining-a-project","level":3,"title":"Joining a Project","text":"

    You inherited a .context/ directory. Get oriented fast: priority order, what to read first, how to ramp up.

    ","path":["Home"],"tags":[]},{"location":"home/#customization","level":2,"title":"Customization","text":"","path":["Home"],"tags":[]},{"location":"home/#steering-files","level":3,"title":"Steering Files","text":"

    Tell the assistant how to behave when a specific kind of prompt arrives.

    ","path":["Home"],"tags":[]},{"location":"home/#lifecycle-triggers","level":3,"title":"Lifecycle Triggers","text":"

    Make things happen at session boundaries: block dangerous tool calls, inject standup notes, log file saves.

    ","path":["Home"],"tags":[]},{"location":"home/#community","level":2,"title":"Community","text":"","path":["Home"],"tags":[]},{"location":"home/#ctx","level":3,"title":"#ctx","text":"

    We are the builders who care about durable context. Join the community. Hang out in IRC. Star ctx on GitHub.

    ","path":["Home"],"tags":[]},{"location":"home/#contributing","level":3,"title":"Contributing","text":"

    Development setup, project layout, and pull request process.

    ","path":["Home"],"tags":[]},{"location":"home/about/","level":1,"title":"About","text":"

    \"Creation, not code; Context, not prompts; Verification, not vibes.\"

    Read the ctx Manifesto →

    \"Without durable context, intelligence resets; with ctx, creation compounds.\"

    Without persistent memory, every session starts at zero; ctx makes sessions cumulative.

    Join the ctx Community →

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#what-is-ctx","level":2,"title":"What Is ctx?","text":"

    ctx (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. It lives in a .context/ directory in your repo.

    • A session is interactive.
    • ctx enables cognitive continuity.
    • Cognitive continuity enables durable, symbiotic-like human-AI workflows.

    Context Files

    Context files let AI tools remember decisions, conventions, and learnings:

    Context files are explicit and versionable contracts between you and your agents.

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#why-do-i-keep-re-explaining-my-codebase","level":2,"title":"Why Do I Keep Re-Explaining My Codebase?!?!","text":"

    You open a new AI session. The first thing you do is re-explain your project.

    Again.

    The architecture, the database choice, the naming conventions, the thing you tried last week that didn't work. You have said all of this before (maybe yesterday, maybe an hour ago), but the AI doesn't know that.

    • You explain the same architecture every session;
    • The AI suggests an approach you already rejected, again;
    • A decision you made three sessions ago gets relitigated from scratch;
    • You spend more time setting context than building features.

    This isn't AI failure: It is staleness.

    Without persistent memory, every session starts at zero.

    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#day-and-night","level":3,"title":"Day and Night","text":"

    ctx gives your AI a memory that persists across sessions:

    Without ctxWith ctx
    Session 12: Monday morning\n\nAI:  \"I'll scaffold a MySQL schema for this.\"\nYou: \"We use PostgreSQL.\"\n\nAI:  \"For auth, session-based would be simplest.\"\nYou: \"We decided on JWT.\"\n\nAI:  \"Endpoints in camelCase?\"\nYou: \"snake_case! Like the rest of the API!!!!!!\"\n\nYou: *scrolls up to copy an existing endpoint*\n\n20 minutes spent restoring decisions that already exist.\n
    Session 12: Monday morning\n\nYou:  \"Let's continue the auth implementation.\"\nAI:   \"Per DECISIONS.md (2026-02-12): JWT, not sessions: \n       The project uses PostgreSQL with JWT\n       authentication. The API follows snake_case conventions.\n       Last session you completed the login endpoint: the\n       registration endpoint is next on TASKS.md. Want me to\n       start there?\"\n\n0 minutes re-explaining. Straight to building.\n
    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#how-ctx-solves-this","level":3,"title":"How ctx Solves This","text":"

    ctx creates a .context/ directory in your project that stores structured knowledge files:

    File What It Remembers TASKS.md What you're working on and what's next DECISIONS.md Architectural choices and why you made them LEARNINGS.md Gotchas, bugs, things that didn't work CONVENTIONS.md Naming patterns, code style, project rules CONSTITUTION.md Hard rules the AI must never violate

    These files can version with your code in git:

    • They load automatically at the session start (via hooks in Claude Code, or manually with ctx agent for other tools).
    • The AI reads them, cites them, and builds on them, instead of asking you to start over.
      • And when it acts, it can point to the exact file and line that justifies the choice.

    Every decision you record, every lesson you capture, makes the next session smarter.

    ctx accumulates.

    Connect with ctx

    • Join the Community →: ask questions, share workflows, and help shape what comes next
    • Read the Blog →: real-world patterns, ponderings, and lessons learned from building ctx using ctx

    Ready to Get Started?

    • Getting Started →: full installation and setup
    • Your First Session →: step-by-step walkthrough from ctx init to verified recall
    ","path":["Home","Introduction","About"],"tags":[]},{"location":"home/common-workflows/","level":1,"title":"Common Workflows","text":"

    The commands below cover what you'll use most often:

    • recording context,
    • checking health,
    • browsing history,
    • and running loops.

    Each section is a self-contained snippet you can copy into your terminal.

    For deeper, step-by-step guides, see Recipes.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#track-context","level":2,"title":"Track Context","text":"

    Prefer Skills over Raw Commands

    When working with an AI agent, use /ctx-task-add, /ctx-decision-add, or /ctx-learning-add instead of raw ctx add commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    # Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (full ADR fields required)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning\nctx learning add \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Mark task complete\nctx task complete \"user auth\"\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#leave-a-reminder-for-next-session","level":2,"title":"Leave a Reminder for Next Session","text":"

    Drop a note that surfaces automatically at the start of your next session:

    # Leave a reminder\nctx remind \"refactor the swagger definitions\"\n\n# Date-gated: don't surface until a specific date\nctx remind \"check CI after the deploy\" --after 2026-02-25\n\n# List pending reminders\nctx remind list\n\n# Dismiss reminders by ID (supports ranges)\nctx remind dismiss 1\nctx remind dismiss 3 5-7\n

    Reminders are relayed verbatim at session start by the check-reminders hook and repeat every session until you dismiss them.

    See Session Reminders for the full recipe.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#check-context-health","level":2,"title":"Check Context Health","text":"
    # Detect stale paths, missing files, potential secrets\nctx drift\n\n# See full context summary\nctx status\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#browse-session-history","level":2,"title":"Browse Session History","text":"

    List and search past AI sessions from the terminal:

    ctx journal source --limit 5\n
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#journal-site","level":3,"title":"Journal Site","text":"

    Import session transcripts to a browsable static site with search, navigation, and topic indices.

    The ctx journal command requires zensical (Python >= 3.10).

    zensical is a Python-based static site generator from the Material for MkDocs team.

    (why zensical?).

    If you don't have it on your system, install zensical once with pipx:

    # One-time setup\npipx install zensical\n

    Avoid pip install zensical

    pip install often fails: For example, on macOS, system Python installs a non-functional stub (zensical requires Python >= 3.10), and Homebrew Python blocks system-wide installs (PEP 668).

    pipx creates an isolated environment with the correct Python version automatically.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#import-and-serve","level":3,"title":"Import and Serve","text":"

    Then, import and serve:

    # Import all sessions to .context/journal/ (only new files)\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n

    Open http://localhost:8000 to browse.

    To update after new sessions, run the same two commands again.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#safe-by-default","level":3,"title":"Safe by Default","text":"

    ctx journal import --all is safe by default:

    • It only imports new sessions and skips existing files.
    • Locked entries (via ctx journal lock) are always skipped by both import and enrichment skills.
    • If you add locked: true to frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json.
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#re-importing-existing-files","level":3,"title":"Re-Importing Existing Files","text":"

    Here is how you regenerate existing files.

    Backup your .context folder before regeneration, as this is a potentially destructive action.

    To re-import journal files, you need to explicitly opt-in using the --regenerate flag:

    Flag combination Frontmatter Body --regenerate Preserved Overwritten from source --regenerate --keep-frontmatter=false Overwritten Overwritten

    Regeneration Overwrites Body Edits

    --regenerate preserves your YAML frontmatter (tags, summary, enrichment metadata) but it replaces the Markdown body with a fresh import.

    Any manual edits you made to the transcript will be lost.

    Lock entries you want to protect first: ctx journal lock <session-id>.

    See Session Journal for the full pipeline including normalization and enrichment.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#scratchpad","level":2,"title":"Scratchpad","text":"

    Store short, sensitive one-liners in an encrypted scratchpad that travels with the project:

    # Write a note\nctx pad set db-password \"postgres://user:pass@localhost/mydb\"\n\n# Read it back\nctx pad get db-password\n\n# List all keys\nctx pad list\n

    The scratchpad is encrypted with a key stored at ~/.ctx/.ctx.key (outside the project, never committed).

    See Scratchpad for details.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#run-an-autonomous-loop","level":2,"title":"Run an Autonomous Loop","text":"

    Generate a script that iterates an AI agent until a completion signal is detected:

    ctx loop\nchmod +x loop.sh\n./loop.sh\n

    See Autonomous Loops for configuration and advanced usage.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#trace-commit-context","level":2,"title":"Trace Commit Context","text":"

    Link your git commits back to the decisions, tasks, and learnings that motivated them. Enable the hook once:

    # Install the git hook (one-time setup)\nctx trace hook enable\n

    From now on, every git commit automatically gets a ctx-context trailer linking it to relevant context. No extra steps needed; just use ctx add, ctx task complete, and commit as usual.

    # Later: why was this commit made?\nctx trace abc123\n\n# Recent commits with their context\nctx trace --last 10\n\n# Context trail for a specific file\nctx trace file src/auth.go\n\n# Manually tag a commit after the fact\nctx trace tag HEAD --note \"Hotfix for production outage\"\n

    To stop: ctx trace hook disable.

    See CLI Reference: trace for details.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#agent-session-start","level":2,"title":"Agent Session Start","text":"

    The first thing an AI agent should do at session start is discover where context lives:

    ctx system bootstrap\n

    This prints the resolved context directory, the files in it, and the operating rules. The CLAUDE.md template instructs the agent to run this automatically. See CLI Reference: bootstrap.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#the-two-skills-you-should-always-use","level":2,"title":"The Two Skills You Should Always Use","text":"

    Using /ctx-remember at session start and /ctx-wrap-up at session end are the highest-value skills in the entire catalog:

    # session begins:\n/ctx-remember\n... do work ...\n# before closing the session:\n/ctx-wrap-up\n

    Let's provide some context, because this is important:

    Although the agent will eventually discover your context through CLAUDE.md → AGENT_PLAYBOOK.md, /ctx-remember hydrates the full context up front (tasks, decisions, recent sessions) so the agent starts informed rather than piecing things together over several turns.

    /ctx-wrap-up is the other half: A structured review that captures learnings, decisions, and tasks before you close the window.

    Hooks like check-persistence remind you (the user) mid-session that context hasn't been saved in a while, but they don't trigger persistence automatically: You still have to act. Also, a CTRL+C can end things at any moment with no reliable \"before session end\" event.

    In short, /ctx-wrap-up is the deliberate checkpoint that makes sure nothing slips through. And /ctx-remember it its mirror skill to be used at session start.

    See Session Ceremonies for the full workflow.

    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-commands-vs-ai-skills","level":2,"title":"CLI Commands vs. AI Skills","text":"

    Most ctx operations come in two flavors: a CLI command you run in your terminal and an AI skill (slash command) you invoke inside your coding assistant.

    Commands and skills are not interchangeable: Each has a distinct role.

    ctx CLI command ctx AI skill Runs where Your terminal Inside the AI assistant Speed Fast (milliseconds) Slower (LLM round-trip) Cost Free Consumes tokens and context Analysis Deterministic heuristics Semantic / judgment-based Best for Quick checks, scripting, CI Deep analysis, generation, workflow orchestration","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#paired-commands","level":3,"title":"Paired Commands","text":"

    These have both a CLI and a skill counterpart. Use the CLI for quick, deterministic checks; use the skill when you need the agent's judgment.

    CLI Skill When to prefer the skill ctx drift /ctx-drift Semantic analysis: catches meaning drift the CLI misses ctx status /ctx-status Interpreted summary with recommendations ctx task add /ctx-task-add Agent decomposes vague goals into concrete tasks ctx decision add /ctx-decision-add Agent drafts rationale and consequences from discussion ctx learning add /ctx-learning-add Agent extracts the lesson from a debugging session ctx convention add /ctx-convention-add Agent observes a repeated pattern and codifies it ctx task archive /ctx-archive Agent reviews which tasks are truly done ctx pad /ctx-pad Agent reads/writes scratchpad entries in conversation flow ctx journal /ctx-history Agent searches session history with semantic understanding ctx agent /ctx-agent Agent loads and acts on the context packet ctx loop /ctx-loop Agent tailors the loop script to your project ctx doctor /ctx-doctor Agent adds semantic analysis to structural checks ctx hook pause /ctx-pause Agent pauses hooks with session-aware reasoning ctx hook resume /ctx-resume Agent resumes hooks after a pause ctx remind /ctx-remind Agent manages reminders in conversation flow","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#ai-only-skills","level":3,"title":"AI-Only Skills","text":"

    These have no CLI equivalent. They require the agent's reasoning.

    Skill Purpose /ctx-remember Load context and present structured readback at session start /ctx-wrap-up End-of-session ceremony: persist learnings, decisions, tasks /ctx-next Suggest 1-3 concrete next actions from context /ctx-commit Commit with integrated context capture /ctx-reflect Pause and assess session progress /ctx-consolidate Merge overlapping learnings or decisions /ctx-prompt-audit Analyze prompting patterns for improvement /ctx-plan Stress-test an existing plan through adversarial interview /ctx-plan-import Import Claude Code plan files into project specs /ctx-implement Execute a plan step-by-step with verification /ctx-worktree Manage parallel agent worktrees /ctx-journal-enrich Add metadata, tags, and summaries to journal entries /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich /ctx-blog Generate a blog post (zensical-flavored Markdown) /ctx-blog-changelog Generate themed blog post from commits between releases /ctx-architecture Build and maintain architecture maps (ARCHITECTURE.md, DETAILED_DESIGN.md)","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-only-commands","level":3,"title":"CLI-Only Commands","text":"

    These are infrastructure: used in scripts, CI, or one-time setup.

    Command Purpose ctx init Initialize .context/ directory ctx load Output assembled context for piping ctx task complete Mark a task done by substring match ctx sync Reconcile context with codebase state ctx compact Consolidate and clean up context files ctx trace Show context behind git commits ctx trace hook Enable/disable commit context tracing hook ctx setup Generate AI tool integration config ctx watch Watch AI output and auto-apply context updates ctx serve Serve any zensical directory (default: journal) ctx permission snapshot Save settings as a golden image ctx permission restore Restore settings from golden image ctx journal site Generate browsable journal from exports ctx hook notify setup Configure webhook notifications ctx decision List and filter decisions ctx learning List and filter learnings ctx task List tasks, manage archival and snapshots ctx why Read the philosophy behind ctx ctx guide Quick-reference cheat sheet ctx site Site management commands ctx config Manage runtime configuration profiles ctx system System diagnostics and hook commands ctx completion Generate shell autocompletion scripts

    Rule of Thumb

    Quick check? Use the CLI.

    Need judgment? Use the skill.

    When in doubt, start with the CLI: It's free and instant.

    Escalate to the skill when heuristics aren't enough.

    Next Up: Context Files →: what each .context/ file does and how to use it

    See Also:

    • Recipes: targeted how-to guides for specific tasks
    • Knowledge Capture: patterns for recording decisions, learnings, and conventions
    • Context Health: keeping your .context/ accurate and drift-free
    • Session Archaeology: digging into past sessions
    • Task Management: tracking and completing work items
    ","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/community/","level":1,"title":"#ctx","text":"

    Open source is better together.

    We are the builders who care about durable context, verifiable decisions, and human-AI workflows that compound over time.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#help-ctx-change-how-ai-remembers","level":2,"title":"Help ctx Change How AI Remembers","text":"

    If you like the idea, a star helps ctx reach engineers who run into context drift every day:

    Star ctx on GitHub ⭐

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#ctx-you","level":2,"title":"ctx ♥️ You","text":"

    Join the community to ask questions, share feedback, and connect with other users:

    • Discord join the ctx Discord: Real-time discussion, field notes, and early ideas.
    • Read the ctx Source on GitHub: Issues, discussions, and contributions.
    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#want-to-contribute","level":2,"title":"Want to Contribute?","text":"

    Early adopters shape the conventions.

    ctx is free and open source software.

    Contributions are always welcome and appreciated.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#code-of-conduct","level":2,"title":"Code of Conduct","text":"

    Clear context requires respectful collaboration.

    ctx follows the Contributor Covenant.

    ","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/configuration/","level":1,"title":"Configuration","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#configuration","level":2,"title":"Configuration","text":"

    ctx uses three layers of configuration. Each layer overrides the one below it:

    1. CLI flags: Per-invocation overrides (highest priority)
    2. Environment variables: Shell or CI/CD overrides
    3. The .ctxrc file: Project-level defaults (YAML)
    4. Built-in defaults: Hardcoded fallbacks (lowest priority)

    All settings are optional: If nothing is configured, ctx works out of the box with sensible defaults.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#the-ctxrc-file","level":2,"title":"The .ctxrc File","text":"

    The .ctxrc file is an optional YAML file placed in the project root (next to your .context/ directory). It lets you set project-level defaults that apply to every ctx command.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#location","level":3,"title":"Location","text":"
    my-project/\n├── .ctxrc              ← configuration file\n├── .context/\n│   ├── TASKS.md\n│   ├── DECISIONS.md\n│   └── ...\n└── src/\n

    ctx reads .ctxrc from the project root (i.e. the parent of CTX_DIR, or dirname(CTX_DIR)/.ctxrc). It does not walk up from CWD. That means whichever project you've activated via eval \"$(ctx activate)\" (or by exporting CTX_DIR directly), its paired .ctxrc is what governs the invocation. There is no global or user-level config file: configuration is always per-project.

    Contributors: Dev Configuration Profile

    The ctx repo ships two .ctxrc source profiles (.ctxrc.base and .ctxrc.dev). The working copy is gitignored and swapped between them via ctx config switch dev / ctx config switch base. See Contributing: Configuration Profiles.

    Using a Different .context Directory

    You point ctx at a .context/ directory by setting the CTX_DIR environment variable, not through .ctxrc. ctx does not search the filesystem. Use eval \"$(ctx activate)\" to bind CTX_DIR for your shell. CTX_DIR must be an absolute path with .context as its basename.

    See Environment Variables below for details.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#full-reference","level":3,"title":"Full Reference","text":"

    A commented .ctxrc showing all options and their defaults:

    # .ctxrc: ctx runtime configuration\n# https://ctx.ist/configuration/\n#\n# All settings are optional. Missing values use defaults.\n# Priority: CLI flags > environment variables > .ctxrc > defaults\n#\n# token_budget: 8000\n# auto_archive: true\n# archive_after_days: 7\n# scratchpad_encrypt: true\n# event_log: false\n# entry_count_learnings: 30\n# entry_count_decisions: 20\n# convention_line_count: 200\n# injection_token_warn: 15000\n# context_window: 200000      # auto-detected for Claude Code; override for other tools\n# billing_token_warn: 0       # one-shot warning at this token count (0 = disabled)\n#\n# stale_age_days: 30      # days before drift flags a context file as stale (0 = disabled)\n# key_rotation_days: 90\n# task_nudge_interval: 5   # Edit/Write calls between task completion nudges\n#\n# notify:               # requires: ctx hook notify setup\n#   events:             # required: no events sent unless listed\n#     - loop\n#     - nudge\n#     - relay\n#\n# tool: \"\"              # Active AI tool: claude, cursor, cline, kiro, codex\n#\n# steering:             # Steering layer configuration\n#   dir: .context/steering\n#   default_inclusion: manual\n#   default_tools: []\n#\n# hooks:                # Hook system configuration\n#   dir: .context/hooks\n#   timeout: 10\n#   enabled: true\n#\n# provenance_required:  # Relax provenance flags for ctx add\n#   session_id: true    # Require --session-id (default: true)\n#   branch: true        # Require --branch (default: true)\n#   commit: true        # Require --commit (default: true)\n#\n# priority_order:\n#   - CONSTITUTION.md\n#   - TASKS.md\n#   - CONVENTIONS.md\n#   - ARCHITECTURE.md\n#   - DECISIONS.md\n#   - LEARNINGS.md\n#   - GLOSSARY.md\n#   - AGENT_PLAYBOOK.md\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#option-reference","level":3,"title":"Option Reference","text":"Option Type Default Description token_budget int 8000 Default token budget for ctx agent and ctx load auto_archive bool true Auto-archive completed tasks during ctx compact archive_after_days int 7 Days before completed tasks are archived scratchpad_encrypt bool true Encrypt scratchpad with AES-256-GCM event_log bool false Enable local hook event logging to .context/state/events.jsonl entry_count_learnings int 30 Drift warning when LEARNINGS.md exceeds this entry count (0 = disable) entry_count_decisions int 20 Drift warning when DECISIONS.md exceeds this entry count (0 = disable) convention_line_count int 200 Drift warning when CONVENTIONS.md exceeds this line count (0 = disable) injection_token_warn int 15000 Warn when auto-injected context exceeds this token count (0 = disable) context_window int 200000 Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools billing_token_warn int 0 (off) One-shot warning when session tokens exceed this threshold (0 = disabled). For plans where tokens beyond an included allowance cost extra stale_age_days int 30 Days before ctx drift flags a context file as stale (0 = disable) key_rotation_days int 90 Days before encryption key rotation nudge task_nudge_interval int 5 Edit/Write calls between task completion nudges notify.events []string (all) Event filter for webhook notifications (empty = all) priority_order []string (see below) Custom file loading priority for context assembly tool string (empty) Active AI tool identifier (claude, cursor, cline, kiro, codex). Used by steering sync and hook dispatch steering.dir string .context/steering Steering files directory steering.default_inclusion string manual Default inclusion mode for new steering files (always, auto, manual) steering.default_tools []string (all) Default tool filter for new steering files (empty = all tools) hooks.dir string .context/hooks Hook scripts directory hooks.timeout int 10 Per-hook execution timeout in seconds hooks.enabled bool true Whether hook execution is enabled provenance_required.session_id bool true Require --session-id on ctx add for tasks, decisions, learnings provenance_required.branch bool true Require --branch on ctx add for tasks, decisions, learnings provenance_required.commit bool true Require --commit on ctx add for tasks, decisions, learnings

    Default priority order (used when priority_order is not set):

    1. CONSTITUTION.md
    2. TASKS.md
    3. CONVENTIONS.md
    4. ARCHITECTURE.md
    5. DECISIONS.md
    6. LEARNINGS.md
    7. GLOSSARY.md
    8. AGENT_PLAYBOOK.md

    See Context Files for the rationale behind this ordering.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#environment-variables","level":2,"title":"Environment Variables","text":"

    Environment variables override .ctxrc values but are overridden by CLI flags.

    Variable Description Equivalent .ctxrc key CTX_DIR Declare the context directory path (required, no fallback) (none) CTX_TOKEN_BUDGET Override the default token budget token_budget","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples","level":3,"title":"Examples","text":"
    # Use a shared context directory\nCTX_DIR=/shared/team-context ctx status\n\n# Increase token budget for a single run\nCTX_TOKEN_BUDGET=16000 ctx agent\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#cli-global-flags","level":2,"title":"CLI Global Flags","text":"

    CLI flags have the highest priority and override both environment variables and .ctxrc settings. These flags are available on every ctx command.

    Flag Description --tool <name> Override active AI tool identifier (e.g. kiro, cursor) --version Show version and exit --help Show command help and exit","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_1","level":3,"title":"Examples","text":"
    # Point to a different context directory inline:\nCTX_DIR=/path/to/project/.context ctx status\n
    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#priority-order","level":2,"title":"Priority Order","text":"

    When the same setting is configured in multiple layers, the highest-priority layer wins:

    CLI flags  >  Environment variables  >  .ctxrc  >  Built-in defaults\n(highest)                                          (lowest)\n

    The context directory itself is resolved differently: it lives outside this priority chain. CTX_DIR (env) must be declared; .ctxrc does not carry a fallback for it, and there is no built-in default. See Activating a Context Directory.

    Example resolution for token_budget:

    Layer Value Wins? CTX_TOKEN_BUDGET 4000 Yes .ctxrc 8000 No Default 8000 No","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_2","level":2,"title":"Examples","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#external-context-directory","level":3,"title":"External .context Directory","text":"

    Store a project's context outside the project tree (useful when a repo is read-only, or when you want to keep notes adjacent rather than checked in). Declare the path via CTX_DIR:

    export CTX_DIR=/home/you/ctx-stores/my-project/.context\n

    One .context/ per project

    The parent of the context directory is the project root by contract: ctx sync, ctx drift, and the memory-drift hook all read the codebase from filepath.Dir(ContextDir()). Pointing two projects at the same .context/ directory will collide their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use ctx hub, not a shared .context/.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-token-budget","level":3,"title":"Custom Token Budget","text":"

    Increase the token budget for projects with large context:

    # .ctxrc\ntoken_budget: 16000\n

    This affects the default budget for ctx agent and ctx load. You can still override per-invocation with ctx agent --budget 4000.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#disabled-scratchpad-encryption","level":3,"title":"Disabled Scratchpad Encryption","text":"

    Turn off encryption for the scratchpad (useful in ephemeral environments where key management is unnecessary):

    # .ctxrc\nscratchpad_encrypt: false\n

    Unencrypted Scratchpads Store Secrets in Plaintext

    Only disable encryption if you understand the security implications.

    The scratchpad may contain sensitive data such as API keys, database URLs, or deployment credentials.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-priority-order","level":3,"title":"Custom Priority Order","text":"

    Reorder context files to prioritize architecture over conventions:

    # .ctxrc\npriority_order:\n  - CONSTITUTION.md\n  - TASKS.md\n  - ARCHITECTURE.md\n  - DECISIONS.md\n  - CONVENTIONS.md\n  - LEARNINGS.md\n  - GLOSSARY.md\n  - AGENT_PLAYBOOK.md\n

    Files not listed in priority_order receive the lowest priority (100). The order affects ctx agent, ctx load, and drift's file-priority calculations.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#billing-token-threshold","level":3,"title":"Billing Token Threshold","text":"

    Get a one-shot warning when your session crosses a token threshold where extra charges begin (e.g., Claude Pro includes 200k tokens; beyond that costs extra):

    # .ctxrc\nbilling_token_warn: 180000   # warn before hitting the 200k paid boundary\n

    The warning fires once per session the first time token usage exceeds the threshold. Set to 0 (or omit) to disable.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#adjusted-drift-thresholds","level":3,"title":"Adjusted Drift Thresholds","text":"

    Raise or lower the entry-count thresholds that trigger drift warnings:

    # .ctxrc\nentry_count_learnings: 50   # warn above 50 learnings (default: 30)\nentry_count_decisions: 10   # warn above 10 decisions (default: 20)\nconvention_line_count: 300  # warn above 300 lines (default: 200)\n

    Set any threshold to 0 to disable that specific check.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"

    Get notified when loops complete, hooks fire, or agents reach milestones:

    # Configure the webhook URL (encrypted, safe to commit)\nctx hook notify setup\n\n# Test delivery\nctx hook notify test\n

    Filter which events reach your webhook:

    # .ctxrc\nnotify:\n  events:\n    - loop      # loop completion/max-iteration\n    - nudge     # VERBATIM relay hooks fired\n    # - relay   # all hook output (verbose, for debugging)\n    # - heartbeat  # every-prompt session-alive signal\n

    Notifications are opt-in: No events are sent unless explicitly listed.

    See Webhook Notifications for a step-by-step recipe.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#hook-message-overrides","level":2,"title":"Hook Message Overrides","text":"

    Hook messages control what text hooks emit when they fire. Each message can be overridden per-project by placing a text file at the matching path under .context/:

    .context/hooks/messages/{hook}/{variant}.txt\n

    The override takes priority over the embedded default compiled into the ctx binary. An empty file silences the message while preserving the hook's logic (counting, state tracking, cooldowns).

    Use ctx hook message to discover and manage overrides:

    ctx hook message list                      # see all messages\nctx hook message show qa-reminder gate     # view the current template\nctx hook message edit qa-reminder gate     # copy default for editing\nctx hook message reset qa-reminder gate    # revert to default\n

    See Customizing Hook Messages for detailed examples including Python, JavaScript, and silence configurations.

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#agent-bootstrapping","level":2,"title":"Agent Bootstrapping","text":"

    AI agents need to know the resolved context directory at session start. The ctx system bootstrap command prints the context path, file list, and operating rules in both text and JSON formats:

    ctx system bootstrap          # text output for agents\nctx system bootstrap -q       # just the context directory path\nctx system bootstrap --json   # structured output for automation\n

    The CLAUDE.md template instructs the agent to run this as its first action. Every nudge (context checkpoint, persistence reminder, etc.) also includes a Context: <dir> footer that re-anchors the agent to the correct directory throughout the session.

    This replaces the previous approach of hardcoding .context/ paths in agent instructions.

    See CLI Reference: bootstrap for full details.

    See also: CLI Reference | Context Files | Scratchpad

    ","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/context-files/","level":1,"title":"Context Files","text":"","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#context","level":2,"title":".context/","text":"

    Each context file in .context/ serves a specific purpose.

    Files are designed to be human-readable, AI-parseable, and token-efficient.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#file-overview","level":2,"title":"File Overview","text":"

    The core context files live directly under .context/. They are the substrate ctx reads in priority order when assembling the agent context packet:

    File Purpose Priority CONSTITUTION.md Hard rules that must NEVER be violated 1 (highest) TASKS.md Current and planned work 2 CONVENTIONS.md Project patterns and standards 3 ARCHITECTURE.md System overview and components 4 DECISIONS.md Architectural decisions with rationale 5 LEARNINGS.md Lessons learned, gotchas, tips 6 GLOSSARY.md Domain terms and abbreviations 7 AGENT_PLAYBOOK.md Instructions for AI tools 8 (lowest)

    Two subdirectories under .context/ are implementation details that are user-editable but not part of the priority read order:

    • .context/templates/: format templates for ctx decision add and ctx learning add. See templates below.
    • .context/steering/: behavioral rules with YAML frontmatter that get synced into each AI tool's native config. See steering below, and the full Steering files page for the design and workflow.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#outside-context","level":3,"title":"Outside .context/","text":"

    Two other moving parts are often confused with context files but are not under .context/:

    • Skills live in .claude/skills/ (project-local) or are provided by the installed ctx plugin. A typical project doesn't see the plugin's skills at all; they ride with the plugin and are owned by its update cycle. See ctx skill and Skills reference.
    • Hooks: Claude Code PreToolUse/PostToolUse/ UserPromptSubmit entries configured in .claude/settings.json or shipped by a plugin. The ctx plugin registers its own hooks automatically; a typical project does not author hooks by hand, and any local edits to plugin-owned hook files will be overridden on the next plugin update. If you need to customize behavior, edit your own project settings, not the plugin's files. See Hook sequence diagrams.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"

    The priority order follows a logical progression for AI tools:

    1. CONSTITUTION.md: Inviolable rules first. The AI tool must know what it cannot do before attempting anything.
    2. TASKS.md: Current work items. What the AI tool should focus on.
    3. CONVENTIONS.md: How to write code. Patterns and standards to follow when implementing tasks.
    4. ARCHITECTURE.md: System structure. Understanding of components and boundaries before making changes.
    5. DECISIONS.md: Historical context. Why things are the way they are, to avoid re-debating settled decisions.
    6. LEARNINGS.md: Gotchas and tips. Lessons from past work that inform the current implementation.
    7. GLOSSARY.md: Reference material. Domain terms and abbreviations for lookup as needed.
    8. AGENT_PLAYBOOK.md: Meta instructions last. How to use this context system itself. Loaded last because the agent should understand the content (rules, tasks, patterns) before the operating manual.
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#constitutionmd","level":2,"title":"CONSTITUTION.md","text":"

    Purpose: Define hard invariants: Rules that must NEVER be violated, regardless of the task.

    AI tools read this first and should refuse tasks that violate these rules.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure","level":3,"title":"Structure","text":"
    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never store customer/user data in context files\n* [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n* [ ] All code must pass tests before commit\n* [ ] No `any` types in TypeScript without documented reason\n* [ ] No TODO comments in main branch (*move to `TASKS.md`*)\n\n## Process Invariants\n\n* [ ] All architectural changes require a decision record\n* [ ] Breaking changes require version bump\n* [ ] Generated files are never committed\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines","level":3,"title":"Guidelines","text":"
    • Keep rules minimal and absolute
    • Each rule should be enforceable (can verify compliance)
    • Use checkbox format for clarity
    • Never compromise on these rules
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tasksmd","level":2,"title":"TASKS.md","text":"

    Purpose: Track current work, planned work, and blockers.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_1","level":3,"title":"Structure","text":"

    Tasks are organized by Phase: logical groupings that preserve order and enable replay.

    Tasks stay in their Phase permanently; status is tracked via checkboxes and inline tags.

    # Tasks\n\n## Phase 1: Initial Setup\n\n* [x] Set up project structure\n* [x] Configure linting and formatting\n* [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n* [ ] Implement user authentication `#priority:high`\n* [ ] Add API rate limiting `#priority:medium`\n  * Blocked by: Need to finalize auth first\n\n## Backlog\n\n* [ ] Performance optimization `#priority:low`\n* [ ] Add metrics dashboard `#priority:deferred`\n

    Key principles:

    • Tasks never move between sections: mark as [x] or [-] in place
    • Use #in-progress inline tag to indicate current work
    • Phase headers provide structure and replay order
    • Backlog section for unscheduled work
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tags","level":3,"title":"Tags","text":"

    Use inline backtick-wrapped tags for metadata:

    Tag Values Purpose #priority high, medium, low Task urgency #area core, cli, docs, tests Codebase area #estimate 1h, 4h, 1d Time estimate (optional) #in-progress (none) Currently being worked on

    Lifecycle tags (for session correlation):

    Tag Format When to add #added YYYY-MM-DD-HHMMSS Auto-added by ctx task add #started YYYY-MM-DD-HHMMSS When beginning work on the task

    These timestamps help correlate tasks with session files and track which session started vs completed work.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning [ ] Pending [x] Completed [-] Skipped (include reason)","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_1","level":3,"title":"Guidelines","text":"
    • Never delete tasks; mark as [x] completed or [-] skipped
    • Never move tasks between sections; use inline tags for status
    • Use ctx task archive periodically to move completed tasks to archive
    • Mark current work with #in-progress inline tag
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#decisionsmd","level":2,"title":"DECISIONS.md","text":"

    Purpose: Record architectural decisions with rationale so they don't get re-debated.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_2","level":3,"title":"Structure","text":"
    # Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequence**: What are the implications?\n\n**Alternatives Considered**:\n* Alternative A: Why rejected\n* Alternative B: Why rejected\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example","level":3,"title":"Example","text":"
    ## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting a new project, need to choose the type-checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequence**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#learningsmd","level":2,"title":"LEARNINGS.md","text":"

    Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_3","level":3,"title":"Structure","text":"
    # Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example_1","level":3,"title":"Example","text":"
    ## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#categories","level":3,"title":"Categories","text":"

    Organize learnings by topic:

    • Testing
    • Build & Deploy
    • Performance
    • Security
    • Third-Party Libraries
    • Git and Workflow
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#conventionsmd","level":2,"title":"CONVENTIONS.md","text":"

    Purpose: Document project patterns, naming conventions, and standards.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_4","level":3,"title":"Structure","text":"
    # Conventions\n\n## Naming\n\n* **Files**: kebab-case for all source files\n* **Components**: PascalCase for React components\n* **Functions**: camelCase, verb-first (getUser, parseConfig)\n* **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_2","level":3,"title":"Guidelines","text":"
    • Include concrete examples
    • Explain the \"why\" not just the \"what\"
    • Keep patterns minimal: Only document what's non-obvious
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#architecturemd","level":2,"title":"ARCHITECTURE.md","text":"

    Purpose: Provide system overview and component relationships.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_5","level":3,"title":"Structure","text":"
    # Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n* path/to/file.ts: Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_3","level":3,"title":"Guidelines","text":"
    • Keep diagrams simple (Mermaid works well)
    • Focus on boundaries and interfaces
    • Update when major structural changes occur
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#glossarymd","level":2,"title":"GLOSSARY.md","text":"

    Purpose: Define domain terms, abbreviations, and project vocabulary.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_6","level":3,"title":"Structure","text":"
    # Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_4","level":3,"title":"Guidelines","text":"
    • Define project-specific meanings
    • Clarify potentially ambiguous terms
    • Include abbreviations used in code or docs
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#agent_playbookmd","level":2,"title":"AGENT_PLAYBOOK.md","text":"

    Purpose: Explicit instructions for how AI tools should read, apply, and update context.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#key-sections","level":3,"title":"Key Sections","text":"

    Read Order: Priority order for loading context files

    When to Update: Events that trigger context updates

    How to Avoid Hallucinating Memory: Critical rules:

    1. Never assume: If not in files, you don't know it
    2. Never invent history: Don't claim \"we discussed\" without evidence
    3. Verify before referencing: Search files before citing
    4. When uncertain, say so
    5. Trust files over intuition

    Context Update Commands: Format for automated updates via ctx watch:

    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"complete\">user auth</context-update>\n<context-update type=\"learning\"\n  context=\"Debugging hooks\"\n  lesson=\"Hooks receive JSON via stdin\"\n  application=\"Parse JSON stdin with the host language\"\n>Hook Input Format</context-update>\n<context-update type=\"decision\"\n  context=\"Need a caching layer\"\n  rationale=\"Redis is fast and team has experience\"\n  consequence=\"Must provision Redis infrastructure\"\n>Use Redis for caching</context-update>\n

    See Integrations for full documentation.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#templates","level":2,"title":"templates/","text":"

    Location: .context/templates/. Status: implementation detail, user-editable.

    Purpose: Format templates for ctx decision add and ctx learning add. These control the structure of new entries appended to DECISIONS.md and LEARNINGS.md.

    ctx init deploys two starter templates:

    • decision.md: sections Context, Rationale, Consequence
    • learning.md: sections Context, Lesson, Application
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing","level":3,"title":"Customizing","text":"

    Edit the templates directly. Changes take effect immediately on the next ctx add command. For example, to add a \"References\" section to all new decisions, edit .context/templates/decision.md.

    Templates are committed to git, so customizations are shared with the team.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#steering","level":2,"title":"steering/","text":"

    Location: .context/steering/. Status: implementation detail, user-editable.

    Purpose: Behavioral rules with YAML frontmatter that tell an AI assistant how to behave when a specific kind of prompt arrives. Unlike the core context files (which describe what the project is), steering files describe what to do and ride alongside the prompt through the AI tool's native rule pipeline (Claude Code, Cursor, Kiro, Cline). ctx matches steering files to prompts and syncs them out to each tool's config.

    ctx init scaffolds four foundation files:

    • product.md: who this project serves and why
    • tech.md: the technology stack and its constraints
    • structure.md: how the code is organized
    • workflow.md: how work moves through the system

    Each file carries YAML frontmatter describing when it applies (always, matching prompts, or manually referenced) and what tool scope it covers. The foundation files use inclusion: always by default so every session picks them up.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing_1","level":3,"title":"Customizing","text":"

    Edit the files directly. Add your own steering files with ctx steering add, preview the match set with ctx steering preview, and run ctx steering sync to push them into each AI tool's config after changes. Steering files are committed to git, so they're shared with the team.

    For the design rationale, the full inclusion/priority model, and the end-to-end sync workflow, see the dedicated Steering files page.

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"

    All context files follow these conventions:

    1. Headers define structure: # for title, ## for sections, ### for items
    2. Bold keys for fields: **Key**: followed by value
    3. Code blocks are literal: Never parse code block content as structure
    4. Lists are ordered: Items appear in priority/chronological order
    5. Tags are inline: Backtick-wrapped tags like #priority:high
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#further-reading","level":2,"title":"Further Reading","text":"
    • Refactoring with Intent: how persistent context prevents drift during refactoring sessions
    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"

    Keep context files concise:

    • Use abbreviations in tags, not prose;
    • Omit obvious words (\"The,\" \"This\");
    • Prefer bullet points over paragraphs;
    • Keep examples minimal but illustrative;
    • Archive old completed items periodically.

    Next Up: Prompting Guide →: effective prompts for AI sessions with ctx

    ","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/contributing/","level":1,"title":"Contributing","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#development-setup","level":2,"title":"Development Setup","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#prerequisites","level":3,"title":"Prerequisites","text":"
    • Go (version defined in go.mod)
    • Claude Code
    • Git
    • GNU Make
    • Zensical
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#1-fork-or-clone-the-repository","level":3,"title":"1. Fork (or Clone) the Repository","text":"
    # Fork on GitHub, then:\ngit clone https://github.com/<you>/ctx.git\ncd ctx\n\n# Or, if you have push access:\ngit clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#2-build-and-install-the-binary","level":3,"title":"2. Build and Install the Binary","text":"
    make build\nsudo make install\n

    This compiles the ctx binary and places it in /usr/local/bin/.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#3-install-the-plugin-from-your-local-clone","level":3,"title":"3. Install the Plugin from Your Local Clone","text":"

    The repository ships a Claude Code plugin under internal/assets/claude/. Point Claude Code at your local copy so that skills and hooks reflect your working tree: no reinstall needed after edits:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace
    4. Enter the absolute path to the root of your clone, e.g. ~/WORKSPACE/ctx (this is where .claude-plugin/marketplace.json lives: it points Claude Code to the actual plugin in internal/assets/claude);
    5. Back in /plugin, select Install and choose ctx.

    Claude Code Caches Plugin Files

    Even though the marketplace points at a directory on disk, Claude Code caches skills and hooks. After editing files under internal/assets/claude/, clear the cache and restart:

    make plugin-reload   # then restart Claude Code\n

    See Skill or Hook Changes for details.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#4-verify","level":3,"title":"4. Verify","text":"
    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n

    You should see the ctx plugin listed, sourced from your local path.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#project-layout","level":2,"title":"Project Layout","text":"
    ctx/\n├── cmd/ctx/            # CLI entry point\n├── internal/\n│   ├── assets/claude/  # ← Claude Code plugin (skills, hooks)\n│   ├── bootstrap/      # Project initialization templates\n│   ├── claude/         # Claude Code integration helpers\n│   ├── cli/            # Command implementations\n│   ├── config/         # Configuration loading\n│   ├── context/        # Core context logic\n│   ├── crypto/         # Scratchpad encryption\n│   ├── drift/          # Drift detection\n│   ├── index/          # Context file indexing\n│   ├── journal/        # Journal site generation\n│   ├── memory/         # Memory bridge (discover, mirror, import, publish)\n│   ├── notify/         # Webhook notifications\n│   ├── rc/             # .ctxrc parsing\n│   ├── journal/        # Session history, parsers, and state\n│   ├── sysinfo/        # System resource monitoring\n│   ├── task/           # Task management\n│   └── validation/     # Input validation\n├── .claude/\n│   └── skills/         # Dev-only skills (not distributed)\n├── assets/             # Static assets (banners, logos)\n├── docs/               # Documentation site source\n├── editors/            # Editor extensions (VS Code)\n├── examples/           # Example configurations\n├── hack/               # Build scripts\n├── specs/              # Feature specifications\n└── .context/           # ctx's own context (dogfooding)\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skills-two-directories-one-rule","level":3,"title":"Skills: Two Directories, One Rule","text":"Directory What lives here Distributed to users? internal/assets/claude/skills/ The 39 ctx-* skills that ship with the plugin Yes .claude/skills/ Dev-only skills (release, QA, backup, etc.) No

    internal/assets/claude/skills/ is the single source of truth for user-facing skills. If you are adding or modifying a ctx-* skill, edit it there.

    .claude/skills/ holds skills that only make sense inside this repository (release automation, QA checks, backup scripts). These are never distributed to users.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#dev-only-skills-reference","level":4,"title":"Dev-Only Skills Reference","text":"Skill When to use /_ctx-absorb Merge deltas from a parallel worktree or separate checkout /_ctx-audit Detect code-level drift after YOLO sprints or before releases /_ctx-qa Run QA checks before committing /_ctx-release Run the full release process /_ctx-release-notes Generate release notes for dist/RELEASE_NOTES.md /_ctx-alignment-audit Audit doc claims against agent instructions /_ctx-update-docs Check docs/code consistency after changes /_ctx-command-audit Audit CLI surface after renames, moves, or deletions

    Six skills previously in this list have been promoted to bundled plugin skills and are now available to all ctx users: /ctx-brainstorm, /ctx-link-check, /ctx-permission-sanitize, /ctx-skill-create, /ctx-spec.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#how-to-add-things","level":2,"title":"How to Add Things","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-cli-command","level":3,"title":"Adding a New CLI Command","text":"
    1. Create a package under internal/cli/<name>/ with doc.go, cmd.go, and run.go;
    2. Implement Cmd() *cobra.Command as the entry point;
    3. Add Use* and DescKey* constants in internal/config/embed/cmd/<name>.go;
    4. Add command descriptions in internal/assets/commands/commands.yaml;
    5. Add examples in internal/assets/commands/examples.yaml;
    6. Add flag descriptions in internal/assets/commands/flags.yaml;
    7. Register the command in internal/bootstrap/group.go (add import + entry in the appropriate group function);
    8. Create an output package at internal/write/<name>/ for all user-facing output (see Package Taxonomy);
    9. Create error constructors at internal/err/<name>/ for domain-specific errors;
    10. Add tests in the same package (<name>_test.go);
    11. Add a doc page at docs/cli/<name>.md and update docs/cli/index.md;
    12. Add the page to zensical.toml nav.

    Pattern to follow: internal/cli/pad/pad.go (parent with subcommands) or internal/cli/drift/ (single command).

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#package-taxonomy","level":3,"title":"Package Taxonomy","text":"

    ctx separates concerns into a strict package taxonomy. Knowing where things go prevents code review friction and keeps the AST lint tests happy.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#output-internalwrite","level":4,"title":"Output: internal/write/","text":"

    Every CLI command's user-facing output lives in its own sub-package under internal/write/<domain>/. Output functions accept *cobra.Command and call cmd.Println(...), never fmt.Print* directly. All text strings are loaded from YAML via desc.Text(text.DescKey*), never inline.

    internal/write/add/add.go       # output for ctx add\ninternal/write/stat/stat.go     # output for ctx usage\ninternal/write/resource/        # output for ctx sysinfo\n

    Exception: write/rc/ writes to os.Stderr because rc loads before cobra is initialized.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#errors-internalerr","level":4,"title":"Errors: internal/err/","text":"

    Domain-specific error constructors live under internal/err/<domain>/. Each package mirrors the write structure. Functions return error (never custom error types) and load messages from YAML via desc.Text(text.DescKey*).

    internal/err/add/add.go         # errors for ctx add\ninternal/err/config/config.go   # errors for configuration\ninternal/err/cli/cli.go         # errors for CLI argument validation\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#config-constants-internalconfig","level":4,"title":"Config Constants: internal/config/","text":"

    Pure-constant leaf packages with zero internal dependencies (stdlib only). Over 60 sub-packages, organized by domain. See internal/config/README.md for the full decision tree.

    What you're adding Where it goes File names, extensions, paths config/file/, config/dir/ Regex patterns config/regex/ CLI flag names (--flag-name) config/flag/flag.go Flag description YAML keys config/embed/flag/<cmd>.go Command Use/DescKey strings config/embed/cmd/<cmd>.go User-facing text YAML keys config/embed/text/<domain>.go Time durations, thresholds config/<domain>/","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#the-assets-pipeline","level":4,"title":"The Assets Pipeline","text":"

    User-facing text flows through a three-level chain:

    1. Go constant (config/embed/text/) defines a string key: DescKeyWriteAddedTo = \"write.added-to\"
    2. Call site resolves it: desc.Text(text.DescKeyWriteAddedTo)
    3. YAML (internal/assets/commands/text/write.yaml) holds the actual text: write.added-to: { short: \"Added to %s\" }

    The same pattern applies to command descriptions (commands.yaml), flag descriptions (flags.yaml), and examples (examples.yaml). The TestDescKeyYAMLLinkage test verifies every constant resolves to a non-empty YAML value.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-session-parser","level":3,"title":"Adding a New Session Parser","text":"

    The journal system uses a SessionParser interface. To add support for a new AI tool (e.g. Aider, Cursor):

    1. Create internal/journal/parser/<tool>.go;
    2. Implement parsing logic that returns []*Session;
    3. Register the parser in FindSessions() / FindSessionsForCWD();
    4. Use config.Tool* constants for the tool identifier;
    5. Add test fixtures and parser tests.

    Pattern to follow: the Claude Code JSONL parser in internal/journal/parser/.

    Multilingual Session Headers

    The Markdown parser recognizes session header prefixes configured via session_prefixes in .ctxrc (default: Session:). To support a new language, users add a prefix to their .ctxrc - no code change needed. New parser implementations can use rc.SessionPrefixes() if they also need prefix-based header detection.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-bundled-skill","level":3,"title":"Adding a Bundled Skill","text":"
    1. Create internal/assets/claude/skills/<skill-name>/SKILL.md;
    2. Follow the skill format: trigger, negative triggers, steps, quality gate;
    3. Run make plugin-reload and restart Claude Code to test;
    4. Add a Skill entry to .claude-plugin/plugin.json if user-invocable;
    5. Document in docs/reference/skills.md.

    Pattern to follow: any skill in internal/assets/claude/skills/ctx-status/.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#test-expectations","level":3,"title":"Test Expectations","text":"
    • Unit tests: colocated with source (foo.gofoo_test.go);
    • Test helpers: use t.Helper() so failures point to callers;
    • HOME isolation: use t.TempDir() + t.Setenv(\"HOME\", ...) for tests that touch ~/.claude/ or ~/.ctx/;
    • rc.Reset(): call after os.Chdir in tests that change working directory (rc caches on first access);
    • No network: all tests run offline, use fixtures.

    Run make test before submitting. Target: no failures, no skips.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#day-to-day-workflow","level":2,"title":"Day-to-Day Workflow","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#go-code-changes","level":3,"title":"Go Code Changes","text":"

    After modifying Go source files, rebuild and reinstall:

    make build && sudo make install\n

    The ctx binary is statically compiled. There is no hot reload. You must rebuild for Go changes to take effect.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skill-or-hook-changes","level":3,"title":"Skill or Hook Changes","text":"

    Edit files under internal/assets/claude/skills/ or internal/assets/claude/hooks/.

    Claude Code caches plugin files, so edits aren't picked up automatically.

    Clear the cache and restart:

    make plugin-reload   # nukes ~/.claude/plugins/cache/activememory-ctx/\n# then restart Claude Code\n

    The plugin will be re-installed from your local marketplace on startup. No version bump is needed during development.

    Version Bumps Are for Releases, Not Iteration

    Only bump VERSION, plugin.json, and marketplace.json when cutting a release. During development, make plugin-reload is all you need.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"

    The repo ships two .ctxrc source profiles. The working copy (.ctxrc) is gitignored and swapped between them:

    File Purpose .ctxrc.base Golden baseline: all defaults, no logging .ctxrc.dev Dev profile: notify events enabled, verbose logging .ctxrc Working copy (gitignored: copied from one of the above)

    Use ctx commands to switch:

    ctx config switch dev      # switch to dev profile\nctx config switch base     # switch to base profile\nctx config status          # show which profile is active\n

    After cloning, run ctx config switch dev to get started with full logging.

    See Configuration for the full .ctxrc option reference.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#backups","level":3,"title":"Backups","text":"

    ctx does not ship a backup command. File-level backup is an OS / infrastructure concern; ctx hub handles the cross-machine knowledge persistence that matters most. For everything else, see Backup Strategy: rsync, Time Machine, Borg, or whichever tool already handles the rest of your files.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-tests","level":3,"title":"Running Tests","text":"
    make test   # fast: all tests\nmake audit  # full: fmt + vet + lint + drift + docs + test\nmake smoke  # build + run basic commands end-to-end\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-the-docs-site-locally","level":3,"title":"Running the Docs Site Locally","text":"
    make site-setup  # one-time: install zensical via pipx\nmake site-serve  # serve at localhost\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#submitting-changes","level":2,"title":"Submitting Changes","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#before-you-start","level":3,"title":"Before You Start","text":"
    1. Check existing issues to avoid duplicating effort;
    2. For large changes, open an issue first to discuss the approach;
    3. Read the specs in specs/ for design context.
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#pull-request-process","level":3,"title":"Pull Request Process","text":"

    Respect the maintainers' time and energy: Keep your pull requests isolated and strive to minimze code changes.

    If you Pull Request solves more than one distinct issues, it's better to create separate pull requests instead of sending them in one large bundle.

    1. Create a feature branch: git checkout -b feature/my-feature;
    2. Make your changes;
    3. Run make audit to catch issues early;
    4. Commit with a clear message;
    5. Push and open a pull request.

    Audit Your Code Before Submitting

    Run make audit before submitting:

    make audit covers formatting, vetting, linting, drift checks, doc consistency, and tests in one pass.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#commit-messages","level":3,"title":"Commit Messages","text":"

    Following conventional commits is recommended but not required:

    Types: feat, fix, docs, test, refactor, chore

    Examples:

    • feat(cli): add ctx export command
    • fix(drift): handle missing files gracefully
    • docs: update installation instructions
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-style","level":3,"title":"Code Style","text":"
    • Follow Go conventions (gofmt, go vet);
    • Keep functions focused and small;
    • Add tests for new functionality;
    • Handle errors explicitly; use descriptive names (readErr, writeErr) not repeated err;
    • No magic strings: all repeated literals go in internal/config/;
    • Output goes through internal/write/ packages, not fmt.Print*;
    • Errors go through internal/err/ constructors, not inline fmt.Errorf;
    • See Package Taxonomy and .context/CONVENTIONS.md for the full reference.
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-of-conduct","level":2,"title":"Code of Conduct","text":"

    A clear context requires respectful collaboration.

    ctx follows the Contributor Covenant.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#boring-legal-stuff","level":2,"title":"Boring Legal Stuff","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#developer-certificate-of-origin-dco","level":3,"title":"Developer Certificate of Origin (DCO)","text":"

    By contributing, you agree to the Developer Certificate of Origin.

    All commits must be signed off:

    git commit -s -m \"feat: add new feature\"\n
    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#license","level":3,"title":"License","text":"

    Contributions are licensed under the Apache 2.0 License.

    ","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/faq/","level":1,"title":"FAQ","text":"","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-markdown","level":2,"title":"Why Markdown?","text":"

    Markdown is human-readable, version-controllable, and tool-agnostic. Every AI model can parse it natively. Every developer can read it in a terminal, a browser, or a code review. There's no schema to learn, no binary format to decode, no vendor lock-in. You can inspect your context with cat, diff it with git diff, and review it in a PR.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-ctx-work-offline","level":2,"title":"Does ctx Work Offline?","text":"

    Yes. ctx is completely local. It reads and writes files on disk, generates context packets from local state, and requires no network access. The only feature that touches the network is the optional webhook notifications hook, which you have to explicitly configure.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-gets-committed-to-git","level":2,"title":"What Gets Committed to Git?","text":"

    The .context/ directory: yes, commit it. That's the whole point. Team members and AI agents read the same context files.

    What not to commit:

    • .ctx.key: your encryption key. Stored at ~/.ctx/.ctx.key, never in the repo. ctx init handles this automatically.
    • journal/ and logs/: generated data, potentially large. ctx init adds these to .gitignore.
    • scratchpad.enc: your choice. It's encrypted, so it's safe to commit if you want shared scratchpad state. See Scratchpad for details.
    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#how-big-should-my-token-budget-be","level":2,"title":"How Big Should My Token Budget Be?","text":"

    The default is 8000 tokens, which works well for most projects. Configure it via .ctxrc or the CTX_TOKEN_BUDGET environment variable:

    # In .ctxrc\ntoken_budget = 12000\n\n# Or as an environment variable\nexport CTX_TOKEN_BUDGET=12000\n\n# Or per-invocation\nctx agent --budget 4000\n

    Higher budgets include more context but cost more tokens per request. Lower budgets force sharper prioritization: ctx drops lower-priority content first, so CONSTITUTION and TASKS always make the cut.

    See Configuration for all available settings.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-not-a-database","level":2,"title":"Why Not a Database?","text":"

    Files are inspectable, diffable, and reviewable in pull requests. You can grep them, cat them, pipe them through jq or awk. They work with every version control system and every text editor.

    A database would add a dependency, require migrations, and make context opaque. The design bet is that context should be as visible and portable as the code it describes.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-it-work-with-tools-other-than-claude-code","level":2,"title":"Does It Work with Tools Other than Claude Code?","text":"

    Yes. ctx agent outputs a context packet that any AI tool can consume: paste it into ChatGPT, Cursor, Copilot, Aider, or anything else that accepts text input.

    Claude Code gets first-class integration via the ctx plugin (hooks, skills, automatic context loading). VS Code Copilot Chat has a dedicated ctx extension. Other tools integrate via generated instruction files or manual pasting.

    See Integrations for tool-specific setup, including the multi-tool recipe.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#can-i-use-ctx-on-an-existing-project","level":2,"title":"Can I Use ctx on an Existing Project?","text":"

    Yes. Run ctx init in any repo and it creates .context/ with template files. Start recording decisions, tasks, and conventions as you work. Context grows naturally; you don't need to backfill everything on day one.

    See Getting Started for the full setup flow, or Joining a ctx Project if someone else already initialized it.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-happens-when-context-files-get-too-big","level":2,"title":"What Happens When Context Files Get Too Big?","text":"

    Token budgeting handles this automatically. ctx agent prioritizes content by file priority (CONSTITUTION first, GLOSSARY last) and trims lower-priority entries when the budget is tight.

    For manual maintenance, ctx compact archives completed tasks and old entries, keeping active context lean. You can also run ctx task archive to move completed tasks out of TASKS.md.

    The goal is to keep context files focused on current state. Historical entries belong in git history or the archive.

    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#is-context-meant-to-be-shared","level":2,"title":"Is .context/ Meant to Be Shared?","text":"

    Yes. Commit it to your repo. Every team member and every AI agent reads the same files. That's the mechanism for shared memory: decisions made in one session are visible in the next, regardless of who (or what) starts it.

    The only per-user state is the encryption key (~/.ctx/.ctx.key) and the optional scratchpad. Everything else is team-shared by design.

    Related:

    • Getting Started - installation and first setup
    • Configuration - .ctxrc, environment variables, and defaults
    • Context Files - what each file does and how to use it
    ","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/first-session/","level":1,"title":"Your First Session","text":"

    Here's what a complete first session looks like, from initialization to the moment your AI cites your project context back to you.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-1-initialize-your-project","level":2,"title":"Step 1: Initialize Your Project","text":"

    Run ctx init in your project root:

    cd your-project\nctx init\n

    Sample output:

    Context initialized in .context/\n\n  ✓ CONSTITUTION.md\n  ✓ TASKS.md\n  ✓ DECISIONS.md\n  ✓ LEARNINGS.md\n  ✓ CONVENTIONS.md\n  ✓ ARCHITECTURE.md\n  ✓ GLOSSARY.md\n  ✓ AGENT_PLAYBOOK.md\n\nSetting up encryption key...\n  ✓ ~/.ctx/.ctx.key\n\nClaude Code plugin (hooks + skills):\n  Install: claude /plugin marketplace add ActiveMemory/ctx\n  Then:    claude /plugin install ctx@activememory-ctx\n\nNext steps:\n  1. Edit .context/TASKS.md to add your current tasks\n  2. Run 'ctx status' to see context summary\n  3. Run 'ctx agent' to get AI-ready context packet\n

    This created your .context/ directory with template files.

    For Claude Code, install the ctx plugin to get automatic hooks and skills.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-2-activate-the-project","level":2,"title":"Step 2: Activate the Project","text":"

    Tell ctx which .context/ directory the rest of these commands should use:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the next steps fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. For more options (multiple .context/ directories, scripts, CI), see Activating a Context Directory.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-3-populate-your-context","level":2,"title":"Step 3: Populate Your Context","text":"

    Add a task and a decision: These are the entries your AI will remember:

    ctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to TASKS.md\n\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to DECISIONS.md\n

    These entries are what the AI will recall in future sessions. You don't need to populate everything now: Context grows naturally as you work.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-4-check-your-context","level":2,"title":"Step 4: Check Your Context","text":"
    ctx status\n

    Sample output:

    Context Status\n====================\n\nContext Directory: .context/\nTotal Files: 8\nToken Estimate: 1,247 tokens\n\nFiles:\n  ✓ CONSTITUTION.md (loaded)\n  ✓ TASKS.md (1 items)\n  ✓ DECISIONS.md (1 items)\n  ○ LEARNINGS.md (empty)\n  ✓ CONVENTIONS.md (loaded)\n  ✓ ARCHITECTURE.md (loaded)\n  ✓ GLOSSARY.md (loaded)\n  ✓ AGENT_PLAYBOOK.md (loaded)\n\nRecent Activity:\n  - TASKS.md modified 2 minutes ago\n  - DECISIONS.md modified 1 minute ago\n

    Notice the token estimate: This is how much context your AI will load.

    The next to LEARNINGS.md means it's still empty; it will fill in as you capture lessons during development.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-5-start-an-ai-session","level":2,"title":"Step 5: Start an AI Session","text":"

    With Claude Code (and the ctx plugin), start every session with:

    /ctx-remember\n

    This loads your context and presents a structured readback so you can confirm the agent knows what is going on. Context also loads automatically via hooks, but the explicit ceremony gives you a readback to verify.

    Steering Files Fire Automatically

    If you edited the four foundation files scaffolded by ctx init (.context/steering/product.md, tech.md, structure.md, workflow.md), their inclusion: always rules are prepended to every tool call via the plugin's PreToolUse hook, with no /ctx-remember needed, no MCP call. Edit a file, save, and the next tool call in Claude Code picks it up. See Steering files for details on the inclusion modes.

    Using VS Code?

    With VS Code Copilot Chat (and the ctx extension), type @ctx /agent in chat to load your context packet, or @ctx /status to check your project context. Run ctx setup copilot --write once to generate .github/copilot-instructions.md for automatic context loading.

    If you are not using Claude Code, generate a context packet for your AI tool:

    ctx agent --budget 8000\n

    Sample output:

    # Context Packet\nGenerated: 2026-02-14T15:30:45Z | Budget: 8000 tokens | Used: ~2450\n\n## Read These Files (in order)\n1. .context/CONSTITUTION.md\n2. .context/TASKS.md\n3. .context/CONVENTIONS.md\n...\n\n## Current Tasks\n- [ ] Implement user authentication\n- [ ] Add rate limiting to API endpoints\n\n## Key Conventions\n- Use gofmt for formatting\n- Path construction uses filepath.Join\n\n## Recent Decisions\n## [2026-02-14-120000] Use PostgreSQL for the primary database\n\n**Context**: Evaluated PostgreSQL, MySQL, and SQLite...\n**Rationale**: PostgreSQL offers better JSON support...\n\n## Key Learnings\n## [2026-02-14-100000] Connection pool sizing matters\n\n**Context**: Hit connection limits under load...\n**Lesson**: Default pool size of 10 is too low for concurrent requests...\n\n## Also Noted\n- Use JWT for session management\n- Always validate input at API boundary\n

    Paste this output into your AI tool's system prompt or conversation start.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-6-verify-it-works","level":2,"title":"Step 6: Verify It Works","text":"

    Ask your AI: \"What are our current tasks?\"

    A working setup produces a response like:

    Based on the project context, you have one active task:\n\n- **Implement user authentication** (pending)\n\nThere's also a recent architectural decision to **use PostgreSQL for\nthe primary database**, chosen for its ACID compliance and JSON support.\n\nWant me to start on the authentication task?\n

    That's the success moment:

    The AI is citing your exact context entries from Step 2, not hallucinating or asking you to re-explain.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-gets-created","level":2,"title":"What Gets Created","text":"
    .context/\n├── CONSTITUTION.md     # Hard rules: NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n└── AGENT_PLAYBOOK.md   # How AI tools should use this\n

    Claude Code integration (hooks + skills) is provided by the ctx plugin: See Integrations/Claude Code.

    VS Code Copilot Chat integration is provided by the ctx extension: See Integrations/VS Code.

    See Context Files for detailed documentation of each file.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-to-gitignore","level":2,"title":"What to .gitignore","text":"

    Rule of Thumb

    • If it's knowledge (decisions, tasks, learnings, conventions), commit it.
    • If it's generated output, raw session data, or a secret, .gitignore it.

    Commit your .context/ knowledge files: that's the whole point.

    You should .gitignore the generated and sensitive paths:

    # Journal data (large, potentially sensitive)\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Hook logs (machine-specific)\n.context/logs/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n

    ctx init Patches Your .Gitignore for You

    ctx init automatically adds these entries to your .gitignore.

    Review the additions with cat .gitignore after init.

    See also:

    • Security Considerations
    • Scratchpad Encryption
    • Session Journal

    Next Up: Common Workflows →: day-to-day commands for tracking context, checking health, and browsing history.

    ","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/getting-started/","level":1,"title":"Getting Started","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"

    ctx does not require git, but using version control with your .context/ directory is strongly recommended:

    AI sessions occasionally modify or overwrite context files inadvertently. With git, the AI can check history and restore lost content: Without it, the data is gone.

    Also, several ctx features (journal changelog, blog generation) also use git history directly.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#installation","level":2,"title":"Installation","text":"

    Every setup starts with the ctx binary: the CLI tool itself.

    If you use Claude Code, you also install the ctx plugin, which adds hooks (context autoloading, persistence nudges) and 25+ /ctx-* skills. For other AI tools, ctx integrates via generated instruction files or manual context pasting: see Integrations for tool-specific setup.

    Pick one of the options below to install the binary. Claude Code users should also follow the plugin steps included in each option.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-1-build-from-source-recommended","level":3,"title":"Option 1: Build from Source (Recommended)","text":"

    Requires Go (version defined in go.mod) and Claude Code.

    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n

    Install the Claude Code plugin from your local clone:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace
    4. Enter the path to the root of your clone, e.g. ~/WORKSPACE/ctx (this is where .claude-plugin/marketplace.json lives: It points Claude Code to the actual plugin in internal/assets/claude)
    5. Back in /plugin, select Install and choose ctx

    This points Claude Code at the plugin source on disk. Changes you make to hooks or skills take effect immediately: No reinstall is needed.

    Local Installs Need Manual Enablement

    Unlike marketplace installs, local plugin installs are not auto-enabled globally. The plugin will only work in projects that explicitly enable it. Run ctx init in each project (it auto-enables the plugin), or add the entry to ~/.claude/settings.json manually:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Verify:

    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n

    Use the Source, Luke

    Building from source gives you the latest features and bug fixes.

    Since ctx is predominantly a developer tool, this is the recommended approach:

    You get the freshest code, can inspect what you are installing, and the plugin stays in sync with the binary.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-2-binary-download-marketplace","level":3,"title":"Option 2: Binary Download + Marketplace","text":"

    Pre-built binaries are available from the releases page.

    Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64\nchmod +x ctx-0.8.1-linux-amd64\nsudo mv ctx-0.8.1-linux-amd64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-arm64\nchmod +x ctx-0.8.1-linux-arm64\nsudo mv ctx-0.8.1-linux-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-arm64\nchmod +x ctx-0.8.1-darwin-arm64\nsudo mv ctx-0.8.1-darwin-arm64 /usr/local/bin/ctx\n
    curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-amd64\nchmod +x ctx-0.8.1-darwin-amd64\nsudo mv ctx-0.8.1-darwin-amd64 /usr/local/bin/ctx\n

    Download ctx-0.8.1-windows-amd64.exe from the releases page and add it to your PATH.

    Claude Code users: install the plugin from the marketplace:

    1. Launch claude;
    2. Type /plugin and press Enter;
    3. Select Marketplaces → Add Marketplace;
    4. Enter ActiveMemory/ctx;
    5. Back in /plugin, select Install and choose ctx.

    Other tool users: see Integrations for tool-specific setup (Cursor, Copilot, Aider, Windsurf, etc.).

    Verify the Plugin Is Enabled

    After installing, confirm the plugin is enabled globally. Check ~/.claude/settings.json for an enabledPlugins entry. If missing, run ctx init in your project (it auto-enables the plugin), or add it manually:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Verify:

    ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed (Claude Code only)\n
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#verifying-checksums","level":4,"title":"Verifying Checksums","text":"

    Each binary has a corresponding .sha256 checksum file. To verify your download:

    # Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.8.1-linux-amd64.sha256\n

    On macOS, use shasum -a 256 -c instead of sha256sum -c.

    Plugin Details

    After installation (either option) you get:

    • Context autoloading: ctx agent runs on every tool use (with cooldown)
    • Persistence nudges: reminders to capture learnings and decisions
    • Post-commit hooks: nudge context capture after git commit
    • Context size monitoring: alerts as sessions grow large
    • Project skills: /ctx-status, /ctx-task-add, /ctx-history, and more

    See Integrations for the full hook and skill reference.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#quick-start","level":2,"title":"Quick Start","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#1-initialize-context","level":3,"title":"1. Initialize Context","text":"
    cd your-project\nctx init\n

    This creates a .context/ directory with template files and an encryption key at ~/.ctx/ for the encrypted scratchpad. For Claude Code, install the ctx plugin for automatic hooks and skills.

    ctx init also scaffolds four foundation steering files in .context/steering/: product.md, tech.md, structure.md, workflow.md. They are placeholders until you customize them (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than buried here.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#2-customize-your-steering-files","level":3,"title":"2. Customize Your Steering Files","text":"

    Steering files are behavioral rules prepended to every AI prompt: the layer that tells your AI how to act on this specific project. They are distinct from decisions (what was chosen) and conventions (how the codebase is written); see ctx for Steering Files for the full model.

    ctx init scaffolded four foundation files; open each and fill it in:

    File What to fill in product.md What the project is, who uses it, what's out of scope tech.md Languages, frameworks, runtime, hard constraints structure.md Directory layout, where new files go, naming rules workflow.md Branch strategy, commit conventions, pre-commit checks

    Each scaffolded file ships with a tombstone marker line (<!-- remove this after you edit the steering file !-->). As long as the marker is present, the file is silently skipped on every load path: the agent context packet, MCP ctx_steering_get, and native-tool sync (Cursor / Cline / Kiro). The skip is deliberate: injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow \"Describe the product...\" as if it were a rule.

    Replace each file's body with real content, then delete the tombstone line. When the line is gone, the file becomes active on the next AI tool call.

    Don't want steering at all? Pass --no-steering-init to ctx init to skip the scaffold entirely. Existing edits are never clobbered by re-running ctx init.

    Inclusion modes (always / auto / manual), priority, and tool scoping are covered in Writing Steering Files and ctx steering.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#3-activate-the-project","level":3,"title":"3. Activate the Project","text":"

    Tell ctx which .context/ directory the rest of these commands should use:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the next steps fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. For more options (multiple .context/ directories, scripts, CI), see Activating a Context Directory.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#4-check-status","level":3,"title":"4. Check Status","text":"
    ctx status\n

    Shows context summary: files present, token estimate, and recent activity.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5-start-using-with-ai","level":3,"title":"5. Start Using with AI","text":"

    With Claude Code (and the ctx plugin installed), context loads automatically via hooks.

    With VS Code Copilot Chat, install the ctx extension and use @ctx /status, @ctx /agent, and other slash commands directly in chat. Run ctx setup copilot --write to generate .github/copilot-instructions.md for automatic context loading.

    For other tools, paste the output of:

    ctx agent --budget 8000\n
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5b-set-up-for-your-ai-tool","level":3,"title":"5B. Set Up for Your AI Tool","text":"

    If you use an MCP-compatible tool, generate the integration config with ctx setup:

    KiroCursorCline
    ctx setup kiro --write\n# Creates .kiro/settings/mcp.json and syncs steering files\n
    ctx setup cursor --write\n# Creates .cursor/mcp.json and syncs steering files\n
    ctx setup cline --write\n# Creates .vscode/mcp.json and syncs steering files\n

    This registers the ctx MCP server and syncs any steering files into the tool's native format. Re-run after adding or changing steering files.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#6-verify-it-works","level":3,"title":"6. Verify It Works","text":"

    Ask your AI: \"Do you remember?\"

    It should cite specific context: current tasks, recent decisions, or previous session topics.

    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#7-set-up-companion-tools-highly-recommended","level":3,"title":"7. Set Up Companion Tools (Highly Recommended)","text":"

    ctx works on its own, but two companion MCP servers unlock significantly better agent behavior. The investment is small and the benefits compound over sessions:

    • Gemini Search grounded web search with citations. Skills like /ctx-code-review and /ctx-explain use it for up-to-date documentation lookups instead of relying on training data.
    • GitNexus: code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Skills like /ctx-refactor and /ctx-code-review use it for impact analysis and dependency awareness.

    # Index your project for GitNexus (run once, then after major changes)\nnpx gitnexus analyze\n

    Both are optional MCP servers: if they are not connected, skills degrade gracefully to built-in capabilities. See Companion Tools for setup details and verification.

    Next Up:

    • Your First Session →: a step-by-step walkthrough from ctx init to verified recall
    • Common Workflows →: day-to-day commands for tracking context, checking health, and browsing history
    ","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/hub/","level":1,"title":"Hub","text":"","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#sharing-is-caring","level":2,"title":"Sharing Is Caring","text":"

    ctx projects are normally independent: each project has its own .context/ directory, its own decisions, its own learnings, its own journal. That's the right default, since most work is project-local, and mixing context across projects tends to dilute more than it helps.

    But sometimes a decision or a learning should cross project boundaries. A convention you codified in one project deserves to be visible in another. A gotcha you discovered debugging service A is the same gotcha waiting for you in service B. The ctx Hub is the feature that makes those specific entries travel, without replicating everything else.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#what-the-hub-actually-is","level":2,"title":"What the Hub Actually Is","text":"

    In one paragraph: the ctx Hub is a fan-out channel for four specific kinds of structured entries: decision, learning, convention, and task. You publish an entry with ctx add --share in one project, and it appears in .context/hub/ for every other project subscribed to that type. When you run ctx agent --include-hub, those shared entries become part of your next agent context packet.

    That is the entire feature. The Hub does not:

    • Share your session journal (.context/journal/). That stays local to each project.
    • Share your scratchpad (.context/pad). Encrypted notes never leave the machine that created them.
    • Share your TASKS.md, DECISIONS.md, LEARNINGS.md, or CONVENTIONS.md wholesale. Only entries you explicitly --share cross the boundary.
    • Provide user identity or attribution. The Hub identifies projects, not people.

    If you want \"my agent in project B sees everything my agent did in project A,\" that's not the Hub. Local session density stays local.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#who-its-for","level":2,"title":"Who It's For","text":"

    Two shapes, same mechanics, different trust models.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"

    One developer, many projects. You want a learning from project A to show up when you open project B a week later. You want a convention you codified in your dotfiles project to be visible everywhere else on your workstation. Run a Hub on localhost, register each project, done.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#small-trusted-team","level":3,"title":"Small Trusted Team","text":"

    A few teammates on a LAN or a hub.ctx-like self-hosted server. You want team conventions to propagate without a wiki. You want lessons from one on-call engineer's 3 AM incident to reach everyone else's agent on the next session. Same mechanics as the personal case, plus TLS in front and a short security runbook.

    The Hub is not a multi-tenant public service. It assumes everyone holding a client token is friendly. Don't stand up hub.example.com for untrusted participants.

    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#going-further","level":2,"title":"Going Further","text":"
    • First-time setup: Hub: Getting Started, a five-minute walkthrough on localhost.
    • Mental model and user stories: Hub Overview, what flows, what doesn't, and when not to use it.
    • Team / LAN deployment: Multi-machine setup.
    • Redundancy: HA cluster.
    • Operating a Hub: Hub Operations and Hub Failure Modes.
    • Security posture: Hub Security Model.
    • Command reference: ctx serve, ctx connect, ctx hub.
    ","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/is-ctx-right/","level":1,"title":"Is It Right for Me?","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#good-fit","level":2,"title":"Good Fit","text":"

    ctx shines when context matters more than code.

    If any of these sound like your project, it's worth trying:

    • Multi-session AI work: You use AI across many sessions on the same codebase, and re-explaining is slowing you down.
    • Architectural decisions that matter: Your project has non-obvious choices (database, auth strategy, API design) that the AI keeps second-guessing.
    • \"Why\" matters as much as \"what\": you need the AI to understand rationale, not just current code
    • Team handoffs: Multiple people (or multiple AI tools) work on the same project and need shared context.
    • AI-assisted development across tools: Uou switch between Claude Code, Cursor, Copilot, or other tools and want context to follow the project, not the tool.
    • Long-lived projects: Anything you'll work on for weeks or months, where accumulated knowledge has compounding value.
    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#may-not-be-the-right-fit","level":2,"title":"May Not Be the Right Fit","text":"

    ctx adds overhead that isn't worth it for every project. Be honest about when to skip it:

    • One-off scripts: If the project is a single file you'll finish today, there's nothing to remember.
    • RAG-only workflows: If retrieval from an external knowledge base already gives the agent everything it needs for each session, adding ctx may be unnecessary. RAG retrieves information; ctx defines the project's working memory: They are complementary.
    • No AI involvement: ctx is designed for human-AI workflows; without an AI consumer, the files are just documentation.
    • Enterprise-managed context platforms: If your organization provides centralized context services, ctx may duplicate that layer.

    For a deeper technical comparison with RAG, prompt management tools, and agent frameworks, see ctx and Similar Tools.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#project-size-guide","level":2,"title":"Project Size Guide","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#solo-developer-single-repo","level":3,"title":"Solo Developer, Single Repo","text":"

    This is ctx's sweet spot.

    You get the most value here: one person, one project, decisions, and learnings accumulating over time. Setup takes 5 minutes and the .context/ directory directory stays small, and every session gets faster.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#small-team-one-or-two-repos","level":3,"title":"Small Team, One or Two Repos","text":"

    Works well.

    Context files commit to git, so the whole team shares the same decisions and conventions. Each person's AI starts with the team's decisions already loaded. Merge conflicts on .context/ files are rare and easy to resolve (they are just Markdown).

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#multiple-repos-or-larger-teams","level":3,"title":"Multiple Repos or Larger Teams","text":"

    ctx operates per repository.

    Each repo has its own .context/ directory with its own decisions, tasks, and learnings. This matches the way code, ownership, and history already work in git.

    There is no built-in cross-repo context layer.

    For organizations that need centralized, organization-wide knowledge, ctx complements a platform solution by providing durable, project-local working memory for AI sessions.

    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#5-minute-trial","level":2,"title":"5-Minute Trial","text":"

    Zero commitment. Try it, and delete .context/ if it's not for you.

    Using Claude Code?

    Install the ctx plugin from the Marketplace for Claude-native hooks, skills, and automatic context loading:

    1. Type /plugin and press Enter
    2. Select Marketplaces → Add Marketplace
    3. Enter ActiveMemory/ctx
    4. Back in /plugin, select Install and choose ctx

    You'll still need the ctx binary for the CLI: See Getting Started for install options.

    # 1. Initialize\ncd your-project\nctx init\n\n# 2. Activate the project (bind CTX_DIR for this shell).\n#    Required: ctx does not walk the filesystem to find .context/.\neval \"$(ctx activate)\"\n\n# 3. Add one real decision from your project\nctx decision add \"Your actual architectural choice\" \\\n  --context \"What prompted this decision\" \\\n  --rationale \"Why you chose this approach\" \\\n  --consequence \"What changes as a result\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# 4. Check what the AI will see\nctx status\n\n# 5. Start an AI session and ask: \"Do you remember?\"\n

    If the AI cites your decision back to you, it's working.

    Want to remove it later? One command:

    rm -rf .context/\n

    No dependencies to uninstall. No configuration to revert. Just files.

    Ready to try it out?

    • Join the Community→: Open Source is better together.
    • Getting Started →: Full installation and setup.
    • ctx and Similar Tools →: Detailed comparison with other approaches.
    ","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/joining-a-project/","level":1,"title":"Joining a Project","text":"

    You've joined a team or inherited a project, and there's a .context/ directory in the repo. Good news: someone already set up persistent context. This page gets you oriented fast.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#what-to-read-first","level":2,"title":"What to Read First","text":"

    The files in .context/ have a deliberate priority order. Read them top-down:

    1. CONSTITUTION.md: Hard rules. Read this before you touch anything. These are inviolable constraints the team has agreed on.
    2. TASKS.md: Current and planned work. Shows what's in progress, what's pending, and what's blocked.
    3. CONVENTIONS.md: How the team writes code. Naming patterns, file organization, preferred idioms.
    4. ARCHITECTURE.md: System overview. Components, boundaries, data flow.
    5. DECISIONS.md: Why things are the way they are. Saves you from re-proposing something the team already evaluated and rejected.
    6. LEARNINGS.md: Gotchas, tips, and hard-won lessons. The stuff that doesn't fit anywhere else but will save you hours.

    See Context Files for detailed documentation of each file's structure and purpose.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#activate-the-project","level":2,"title":"Activate the Project","text":"

    Tell ctx which .context/ directory to read from:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the commands in the rest of this guide fail with Error: no context directory specified. Direnv users can wire it into .envrc and forget about it. See Activating a Context Directory for more options (multiple .context/ directories, scripts, CI).

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#checking-context-health","level":2,"title":"Checking Context Health","text":"

    Before you start working, check whether the context is current:

    ctx status\n

    This shows file counts, token estimates, and recent activity. If files haven't been touched in weeks, the context may be stale.

    ctx drift\n

    This compares context files against recent code changes and flags potential drift: decisions that no longer match the codebase, conventions that have shifted, or tasks that look outdated.

    If things are stale, mention it to the team. Don't silently fix it yourself on day one.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#starting-your-first-session","level":2,"title":"Starting Your First Session","text":"

    Generate a context packet to prime your AI:

    ctx agent --budget 8000\n

    This outputs a token-budgeted summary of the project context, ordered by priority. With Claude Code and the ctx plugin, context loads automatically via hooks. You can also use the /ctx-remember skill to get a structured readback of what the AI knows.

    The readback is your verification step: if the AI can cite specific tasks and decisions, the context is working.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#adding-context","level":2,"title":"Adding Context","text":"

    As you work, you'll discover things worth recording. Use the CLI:

    # Record a decision you made or learned about\nctx decision add \"Use connection pooling for DB access\" \\\n  --rationale \"Reduces connection overhead under load\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Capture a gotcha you hit\nctx learning add \"Redis timeout defaults to 5s\" \\\n  --context \"Hit timeouts during bulk operations\" \\\n  --application \"Set explicit timeout for batch jobs\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add a convention you noticed the team follows\nctx convention add \"All API handlers return structured errors\"\n

    You can also just tell the AI: \"Record this as a learning\" or \"Add this decision to context.\" With the ctx plugin, context-update commands handle the file writes.

    See the Knowledge Capture recipe for the full workflow.

    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#session-etiquette","level":2,"title":"Session Etiquette","text":"

    A few norms for working in a ctx-managed project:

    • Respect existing conventions. If CONVENTIONS.md says \"use filepath.Join,\" use filepath.Join. If you disagree, propose a change, don't silently diverge.
    • Don't restructure context files without asking. The file layout and section structure are shared state. Reorganizing them affects every team member and every AI session.
    • Mark tasks done when complete. Check the box ([x]) in place. Don't move tasks between sections or delete them.
    • Add context as you go. Decisions, learnings, and conventions you discover are valuable to the next person (or the next session).
    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"

    Ignoring CONSTITUTION.md. The constitution exists for a reason. If a task conflicts with a constitution rule, the task is wrong. Raise it with the team instead of working around the constraint.

    Deleting tasks. Never delete a task from TASKS.md. Mark it [x] (done) or [-] (skipped with a reason). The history matters for session replay and audit.

    Bypassing hooks. If the project uses ctx hooks (pre-commit nudges, context autoloading), don't disable them. They exist to keep context fresh. If a hook is noisy or broken, fix it or file a task.

    Over-contributing on day one. Read first, then contribute. Adding a dozen learnings before you understand the project's norms creates noise, not signal.

    Related:

    • Getting Started: installation and setup from scratch
    • Context Files: detailed file reference
    • Knowledge Capture: recording decisions, learnings, and conventions
    • Session Lifecycle: how a typical AI session flows with ctx
    ","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/keeping-ai-honest/","level":1,"title":"Keeping AI Honest","text":"","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-problem","level":2,"title":"The Problem","text":"

    AI agents confabulate. They invent history that never happened, claim familiarity with decisions that were never made, and sometimes declare a task complete when it is not. This is not malice - it is the default behavior of a system optimizing for plausible-sounding responses.

    When your AI says \"we decided to use Redis for caching last week,\" can you verify that? When it says \"the auth module is complete,\" can you confirm it? Without grounded, persistent context, the answer is no. You are trusting vibes.

    ctx replaces vibes with verifiable artifacts.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#grounded-memory","level":2,"title":"Grounded Memory","text":"

    Every entry in ctx context files has a timestamp and structured fields. When the AI cites a decision, you can check it.

    ## [2026-01-28-143022] Use Event Sourcing for Audit Trail\n\n**Status**: Accepted\n\n**Context**: Compliance requires full mutation history.\n\n**Decision**: Event sourcing for the audit subsystem only.\n\n**Rationale**: Append-only log meets compliance requirements\nwithout imposing event sourcing on the entire domain model.\n

    The timestamp 2026-01-28-143022 is not decoration. It is a verifiable anchor. If the AI references this decision, you can open DECISIONS.md, find the entry, and confirm it says what the AI claims. If the entry does not exist, the AI is hallucinating - and you know immediately.

    This is grounded memory: claims that trace back to artifacts you control and can audit.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#constitutionmd-hard-guardrails","level":2,"title":"CONSTITUTION.md: Hard Guardrails","text":"

    CONSTITUTION.md defines rules the AI must treat as inviolable. These are not suggestions or best practices - they are constraints that override task requirements.

    # Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these,\nthe task is wrong.\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] All public API changes require a decision record\n* [ ] Never delete context files without explicit user approval\n

    The AI reads these at session start, before anything else. A well- integrated agent will refuse a task that conflicts with a constitutional rule, citing the specific rule it would violate.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-agent-playbooks-anti-hallucination-rules","level":2,"title":"The Agent Playbook's Anti-Hallucination Rules","text":"

    The AGENT_PLAYBOOK.md file includes a section called \"How to Avoid Hallucinating Memory\" with five explicit rules:

    1. Never assume. If it is not in the context files, you do not know it.
    2. Never invent history. Do not claim \"we discussed\" something without a file reference.
    3. Verify before referencing. Search files before citing them.
    4. When uncertain, say so. \"I don't see a decision on this\" is always better than a fabricated one.
    5. Trust files over intuition. If the files say PostgreSQL but your training data suggests MySQL, the files win.

    These rules create a behavioral contract. The AI is not left to guess how confident it should be - it has explicit instructions to ground every claim in the context directory.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#drift-detection","level":2,"title":"Drift Detection","text":"

    Context files can go stale. You rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist. Stale context is almost as dangerous as no context: the AI treats outdated information as current truth.

    ctx drift detects this divergence:

    ctx drift\n

    It scans context files for references to files, paths, and symbols that no longer exist in the codebase. Stale references get flagged so you can update or remove them before they mislead the next session.

    Regular drift checks - weekly, or after major refactors - keep your context files honest the same way tests keep your code honest.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-verification-loop","level":2,"title":"The Verification Loop","text":"

    The /ctx-commit skill includes a built-in verification step: before staging, it maps claims to evidence and runs self-audit questions to surface gaps. This catches inconsistencies at the point where they matter most: right before code is committed.

    This closes the loop. You write context. The AI reads context. The verification step confirms that context still matches reality. When it does not, you fix it - and the next session starts from truth, not from drift.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#trust-through-structure","level":2,"title":"Trust through Structure","text":"

    The common thread across all of these mechanisms is structure over prose. Timestamps make claims verifiable. Constitutional rules make boundaries explicit. Drift detection makes staleness visible. The playbook makes behavioral expectations concrete.

    You do not need to trust the AI. You need to trust the system -- and verify when it matters.

    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#further-reading","level":2,"title":"Further Reading","text":"
    • Detecting and Fixing Drift: the full workflow for keeping context files accurate
    • Invariants: the properties that must hold for any valid ctx implementation
    • Agent Security: threat model and mitigations for AI agents operating with persistent context
    ","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/opencode/","level":1,"title":"ctx for OpenCode","text":"","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#the-problem","level":2,"title":"The Problem","text":"

    Every OpenCode session starts from zero. You re-explain your architecture, the AI repeats mistakes it made yesterday, and decisions get rediscovered instead of remembered.

    Without ctx:

    > \"Add the validation middleware we discussed\"\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n

    With ctx:

    > \"Add the validation middleware we discussed\"\n\nYes. From the Jan 15 session. You decided on Zod schemas at the\nroute level (DECISIONS.md #12), and the pattern is in\nCONVENTIONS.md. I'll follow the existing middleware in\nsrc/middleware/auth.ts as a reference.\n

    That's the whole pitch: your AI remembers.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#setup-one-command","level":2,"title":"Setup (One Command)","text":"

    Install the ctx binary first (installation docs), then run from your project root:

    ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n

    This does three things:

    1. ctx setup opencode --write: generates the project-local OpenCode plugin, skills, and AGENTS.md, then merges the ctx MCP server into OpenCode's global config (~/.config/opencode/opencode.json or $OPENCODE_HOME/opencode.json). This writes outside the project root because non-interactive shells (like MCP subprocesses) cannot discover project-local config; the same reason the Copilot CLI integration writes to ~/.copilot/mcp-config.json.
    2. ctx init: creates the .context/ directory with template files.
    3. eval \"$(ctx activate)\": binds CTX_DIR for your shell.
    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose .opencode/plugins/ctx.ts Lifecycle plugin (hooks into ctx system commands) ~/.config/opencode/opencode.json Global MCP server registration (or $OPENCODE_HOME/opencode.json) AGENTS.md Agent instructions (OpenCode reads this natively) .opencode/skills/ctx-*/SKILL.md Slash command skills

    The plugin is a single file with no runtime dependencies; no bun install or npm install needed. OpenCode loads it automatically on launch.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"

    The plugin wires OpenCode lifecycle events to ctx. You don't need to do anything; it just works.

    Event What fires What it does New session session.created Warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use Agent idle session.idle Runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI) After git commit tool.execute.after Runs ctx system post-commit to capture context state After file edit tool.execute.after Runs ctx system check-task-completion to detect silent task completions Every shell call shell.env Injects CTX_DIR so all ctx commands in the agent's shell resolve to the right project Context compaction experimental.session.compacting Pushes ctx system bootstrap output into the compaction context so the agent retains breadcrumbs to re-read context files post-compaction

    The compaction hook matters most. When OpenCode compresses your context window to free up tokens, the plugin makes sure the compressed summary includes a pointer back to your .context/ directory and its file inventory, so the agent can re-read tasks, decisions, and learnings on demand, even though the original messages are gone.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#how-compaction-works","level":3,"title":"How Compaction Works","text":"

    When your conversation exceeds the context window, OpenCode runs a compaction pass (you can trigger one manually with /compact). The compaction agent summarizes older messages and drops the originals. Without ctx, all accumulated knowledge disappears. With ctx, the plugin intercepts the experimental.session.compacting event and appends ctx system bootstrap output (context directory path and file inventory) into the compaction context. The result: the compressed summary retains the breadcrumbs the agent needs to re-read tasks, decisions, learnings, and conventions on demand, even though the original messages that loaded them are gone.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-is-not-included","level":3,"title":"What Is Not Included","text":"

    Note: dangerous-command blocking is Claude Code-specific and is not part of the OpenCode integration. OpenCode's execution model (explicit user approval for every shell command) makes a pre-execution blocklist unnecessary.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#slash-commands","level":2,"title":"Slash Commands","text":"

    Four skills are available as slash commands:

    Command When to use /ctx-agent Load full context packet. Use at session start or when context feels stale. /ctx-remember \"Do you remember?\"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. /ctx-status Context summary at a glance: file count, token estimate, recent activity. /ctx-wrap-up End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to .context/ files.

    You don't need to use these often. The plugin handles most context loading automatically. These are for when you want explicit control.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#mcp-tools","level":2,"title":"MCP Tools","text":"

    The ctx MCP server exposes tools directly to the agent. These let the AI read and write your context files without shell commands:

    Tool Purpose ctx_add Add a task, decision, learning, or convention ctx_complete Mark a task done by number or text match ctx_search Full-text search across all .context/ files ctx_next Suggest the next pending task by priority ctx_drift Detect stale context: dead paths, missing files ctx_compact Archive completed tasks, clean empty sections ctx_remind List pending session-scoped reminders ctx_status Context health: file count, token estimate ctx_steering_get Retrieve steering files applicable to the current prompt ctx_journal_source Query recent AI session history ctx_session_event Signal session start/end lifecycle events ctx_watch_update Apply structured updates to .context/ files ctx_check_task_completion After a write, detect silently completed tasks

    You don't invoke these yourself. The agent uses them as needed.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"

    If you re-run ctx setup opencode --write (e.g., after updating ctx), the plugin and skills are rewritten in place. Restart OpenCode to pick up the refreshed plugin. OpenCode only loads plugins at launch, not mid-session.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix opencode mcp list shows ctx ✗ failed MCP error -32000: Connection closed CTX_DIR not resolving in the MCP subprocess Re-run ctx setup opencode --write to regenerate the sh-wrapper that sets CTX_DIR Plugin installed but no hooks fire Flat-file vs. subdirectory discovery mismatch (OpenCode requires .opencode/plugins/<name>.ts, not a subfolder) Verify the plugin is at .opencode/plugins/ctx.ts. Check with opencode --print-logs --log-level DEBUG ctx agent Markdown leaking into the TUI BunShell command missing .nothrow().quiet() Update to the latest plugin: ctx setup opencode --write and restart","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#verify-it-works","level":2,"title":"Verify It Works","text":"

    Start a new OpenCode session and ask:

    Do you remember?\n

    The AI should cite specific context: current tasks, recent decisions, or previous session topics. If it says \"I don't have memory\" or \"Let me check,\" something went wrong; check that the plugin installed correctly and .context/ has files in it.

    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#whats-next","level":2,"title":"What's Next","text":"
    • Your First Session: step-by-step walkthrough from ctx init to verified recall.
    • Common Workflows: day-to-day commands for tracking context, checking health, and browsing history.
    • Context Files: what lives in .context/ and how each file is used.
    ","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/prompting-guide/","level":1,"title":"Prompting Guide","text":"

    New to ctx?

    This guide references context files like TASKS.md, DECISIONS.md, and LEARNINGS.md:

    These are plain Markdown files that ctx maintains in your project's .context/ directory.

    If terms like \"context packet\" or \"session ceremony\" are unfamiliar,

    • start with the ctx Manifesto for the why,
    • About for the big picture,
    • then Getting Started to set up your first project.
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#literature-matters","level":2,"title":"Literature Matters","text":"

    This guide is about crafting effective prompts for working with AI assistants in ctx-enabled projects, but the guidelines given here apply to other AI systems, too.

    The right prompt triggers the right behavior.

    This guide documents prompts that reliably produce good results.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#tldr","level":2,"title":"TL;DR","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" What's next /ctx-next Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect /ctx-reflect Wrap up /ctx-wrap-up Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\" One more thing \"What's the single smartest addition?\" Set tone \"Push back if my assumptions are wrong.\" Constrain scope \"Only change files in X. Nothing else.\" Course correct \"Stop. That's not what I meant.\" Check health \"Run ctx drift\" Commit /ctx-commit","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#do-you-remember","level":3,"title":"\"do you remember?\"","text":"

    Triggers the AI to silently read TASKS.md, DECISIONS.md, LEARNINGS.md, and check recent history via ctx journal before responding with a structured readback:

    1. Last session: most recent session topic and date
    2. Active work: pending or in-progress tasks
    3. Recent context: 1-2 recent decisions or learnings
    4. Next step: offer to continue or ask what to focus on

    Use this at the start of every important session.

    Do you remember what we were working on?\n

    This question implies prior context exists. The AI checks files rather than admitting ignorance. The expected response cites specific context (session names, task counts, decisions), not vague summaries.

    If the AI instead narrates its discovery process (\"Let me check if there are files...\"), it has not loaded CLAUDE.md or AGENT_PLAYBOOK.md properly.

    For a detailed case study on making agents actually follow this protocol (including the failure modes, the timing problem, and the hook design that solved it) see The Dog Ate My Homework.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the Current State?\"","text":"

    Prompts reading of TASKS.md, recent sessions, and status overview.

    Use this when resuming work after a break.

    Variants:

    • \"Where did we leave off?\"
    • \"What's in progress?\"
    • \"Show me the open tasks.\"
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why Doesn't X Work?\"","text":"

    This triggers root cause analysis rather than surface-level fixes.

    Use this when something fails unexpectedly.

    Framing as \"why\" encourages investigation before action. The AI will trace through code, check configurations, and identify the actual cause.

    Real Example

    \"Why can't I run /ctx-reflect?\" led to discovering missing permissions in settings.local.json bootstrapping.

    This was a fix that benefited all users of ctx.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is This Consistent with Our Decisions?\"","text":"

    This prompts checking DECISIONS.md before implementing.

    Use this before making architectural choices.

    Variants:

    • \"Check if we've decided on this before\"
    • \"Does this align with our conventions?\"
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-would-break-if-we","level":3,"title":"\"What Would Break If We...\"","text":"

    This triggers defensive thinking and impact analysis.

    Use this before making significant changes.

    What would break if we change the Settings struct?\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before You Start, Read X\"","text":"

    This ensures specific context is loaded before work begins.

    Use this when you know the relevant context exists in a specific file.

    Before you start, check ctx journal source for the auth discussion session\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-control","level":3,"title":"Scope Control","text":"

    Constrain the AI to prevent sprawl. These are some of the most useful prompts in day-to-day work.

    Only change files in internal/cli/add/. Nothing else.\n
    No new files. Modify the existing implementation.\n
    Keep the public API unchanged. Internal refactor only.\n

    Use these when the AI tends to \"helpfully\" modify adjacent code, add documentation you didn't ask for, or create new abstractions.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#course-correction","level":3,"title":"Course Correction","text":"

    Steer the AI when it goes off-track: Don't wait for it to finish a wrong approach.

    Stop! That's not what I meant. Let me clarify.\n
    Let's step back. Explain what you're about to do before changing anything.\n
    Undo that last change and try a different approach.\n

    These work because they interrupt momentum.

    Without explicit course correction, the AI tends to commit harder to a wrong path rather than reconsidering.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#failure-modes","level":3,"title":"Failure Modes","text":"

    When the AI misbehaves, match the symptom to the recovery prompt:

    Symptom Recovery prompt Hand-waves (\"should work now\") \"Show evidence: file/line refs, command output, or test name.\" Creates unnecessary files \"No new files. Modify the existing implementation.\" Expands scope unprompted \"Stop after the smallest working change. Ask before expanding scope.\" Narrates instead of acting \"Skip the explanation. Make the change and show the diff.\" Repeats a failed approach \"That didn't work last time. Try a different approach.\" Claims completion without proof \"Run the test. Show me the output.\"

    These are recovery handles, not rules to paste into CLAUDE.md.

    Use them in the moment when you see the behavior.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-did-we-learn","level":3,"title":"\"What Did We Learn?\"","text":"

    This prompts reflection on the session and often triggers adding learnings to LEARNINGS.md.

    Use this after completing a task or debugging session.

    This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add This as a Learning/decision\"","text":"

    This is an explicit persistence request.

    Use this when you have discovered something worth remembering.

    Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save Context Before We End\"","text":"

    This triggers context persistence before the session closes.

    Use it at the end of the session or before switching topics.

    Variants:

    • \"Let's persist what we did\"
    • \"Update the context files\"
    • /ctx-wrap-up:the recommended end-of-session ceremony (see Session Ceremonies)
    • /ctx-reflect: mid-session reflection checkpoint
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the Codebase for X\"","text":"

    This triggers thorough codebase search rather than guessing.

    Use this when you need to understand how something works.

    This works because \"Explore\" signals that investigation is needed, not immediate action.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How Does X Work in This Codebase?\"","text":"

    This prompts reading actual code rather than explaining general concepts.

    Use this to understand the existing implementation.

    How does session saving work in this codebase?\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find All Places Where X\"","text":"

    This triggers a comprehensive search across the codebase.

    Use this before refactoring or understanding the impact.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What Should We Document from This?\"","text":"

    This prompts identifying learnings, decisions, and conventions worth persisting.

    Use this after complex discussions or implementations.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is This the Right Approach?\"","text":"

    This invites the AI to challenge the current direction.

    Use this when you want a sanity check.

    This works because it allows AI to disagree.

    AIs often default to agreeing; this prompt signals you want an honest assessment.

    Stronger variant: \"Push back if my assumptions are wrong.\" This sets the tone for the entire session: The AI will flag questionable choices proactively instead of waiting to be asked.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-am-i-missing","level":3,"title":"\"What Am I Missing?\"","text":"

    This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.

    Use this before finalizing a design or implementation.

    Forward-looking variant: \"What's the single smartest addition you could make to this at this point?\" Use this after you think you're done: It surfaces improvements you wouldn't have thought to ask for. The constraint to one thing prevents feature sprawl.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#cli-commands-as-prompts","level":2,"title":"CLI Commands as Prompts","text":"

    Asking the AI to run ctx commands is itself a prompt. These load context or trigger specific behaviors:

    Command What it does \"Run ctx status\" Shows context summary, file presence, staleness \"Run ctx agent\" Loads token-budgeted context packet \"Run ctx drift\" Detects dead paths, stale files, missing context","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ctx-skills","level":3,"title":"ctx Skills","text":"

    The SKILS.md Standard

    Skills are formalized prompts stored as SKILL.md files.

    The /slash-command syntax below is Claude Code specific.

    Other agents can use the same skill files, but invocation may differ.

    Use ctx skills by name:

    Skill When to use /ctx-status Quick context summary /ctx-agent Load full context packet /ctx-remember Recall project context and structured readback /ctx-wrap-up End-of-session context persistence /ctx-history Browse session history for past discussions /ctx-reflect Structured reflection checkpoint /ctx-next Suggest what to work on next /ctx-commit Commit with context persistence /ctx-drift Detect and fix context drift /ctx-implement Execute a plan step-by-step with verification /ctx-loop Generate autonomous loop script /ctx-pad Manage encrypted scratchpad /ctx-archive Archive completed tasks /check-links Audit docs for dead links

    Ceremony vs. Workflow Skills

    Most skills work conversationally: \"what should we work on?\" triggers /ctx-next, \"save that as a learning\" triggers /ctx-learning-add. Natural language is the recommended approach.

    Two skills are the exception: /ctx-remember and /ctx-wrap-up are ceremony skills for session boundaries: Invoke them as explicit slash commands: conversational triggers risk partial execution. See Session Ceremonies.

    Skills combine a prompt, tool permissions, and domain knowledge into a single invocation.

    Skills beyond Claude Code

    The /slash-command syntax above is Claude Code native, but the underlying SKILL.md files are a standard Markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates.

    See Integrations for setup details.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"

    Based on our ctx development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:

    Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\" \"Implement everything\" No phasing, sprawl risk Break into tasks, implement one at a time \"You should know this\" Assumes context is loaded \"Before you start, read X\"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reliability-checklist","level":2,"title":"Reliability Checklist","text":"

    Before sending a non-trivial prompt, check these four elements. This is the guide's DNA in one screenful.

    1. Goal in one sentence: What does \"done\" look like?
    2. Files to read: What existing code or context should the AI review before acting?
    3. Verification command: How will you prove it worked? (test name, CLI command, expected output)
    4. Scope boundary: What should the AI not touch?

    A prompt that covers all four is almost always good enough.

    A prompt missing #3 is how you get \"should work now\" without evidence.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#safety-invariants","level":2,"title":"Safety Invariants","text":"

    These Are Invariants: Not Suggestions

    A prompting guide earns its trust by being honest about risk.

    These four rules mentioned below don't change with model versions, agent frameworks, or project size.

    Build them into your workflow once and stop thinking about them.

    Tool-using agents can read files, run commands, and modify your codebase. That power makes them useful. It also creates a trust boundary you should be aware of.

    These invariants apply regardless of which agent or model you use.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#treat-the-repository-text-as-untrusted-input","level":3,"title":"Treat the Repository Text as \"Untrusted Input\"","text":"

    Issue descriptions, PR comments, commit messages, documentation, and even code comments can contain text that looks like instructions. An agent that reads a GitHub issue and then runs a command found inside it is executing untrusted input.

    The rule: Before running any command the agent found in repo text (issues, docs, comments), restate the command explicitly and confirm it does what you expect. Don't let the agent copy-paste from untrusted sources into a shell.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ask-before-destructive-operations","level":3,"title":"Ask Before Destructive Operations","text":"

    git push --force, rm -rf, DROP TABLE, docker system prune: these are irreversible or hard to reverse. A good agent should pause before running them, but don't rely on that.

    The rule: For any operation that deletes data, overwrites history, or affects shared infrastructure, require explicit confirmation. If the agent runs something destructive without asking, that's a course-correction moment: \"Stop. Never run destructive commands without asking first.\"

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-the-blast-radius","level":3,"title":"Scope the Blast Radius","text":"

    An agent told to \"fix the tests\" might modify test fixtures, change assertions, or delete tests that inconveniently fail. An agent told to \"deploy\" might push to production. Broad mandates create broad risk.

    The rule: Constrain scope before starting work. The Reliability Checklist's scope boundary (#4) is your primary safety lever. When in doubt, err on the side of a tighter boundary.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#secrets-never-belong-in-context","level":3,"title":"Secrets Never Belong in Context","text":"

    LEARNINGS.md, DECISIONS.md, and session transcripts are plain-text files that may be committed to version control.

    Don't persist API keys, passwords, tokens, or credentials in context files.

    The rule: If the agent encounters a secret during work, it should use it transiently (environment variable, an alias to the secret instead of the actual secret, etc.) and never write it to a context file.

    Any Secret Seen IS Exposed

    If you see a secret in a context file, remove it immediately and rotate the credential.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-plan-implement","level":2,"title":"Explore → Plan → Implement","text":"

    For non-trivial work, name the phase you want:

    Explore src/auth and summarize the current flow.\nThen propose a plan. After I approve, implement with tests.\n

    This prevents the AI from jumping straight to code.

    The three phases map to different modes of thinking:

    • Explore: read, search, understand: no changes
    • Plan: propose approach, trade-offs, scope: no changes
    • Implement: write code, run tests, verify: changes

    Small fixes skip straight to implement. Complex or uncertain work benefits from all three.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#prompts-by-task-type","level":2,"title":"Prompts by Task Type","text":"

    Different tasks need different prompt structures. The pattern: symptom + location + verification.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#bugfix","level":3,"title":"Bugfix","text":"
    Users report search returns empty results for queries with hyphens.\nReproduce in src/search/. Write a failing test for \"foo-bar\",\nfix the root cause, run: go test ./internal/search/...\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#refactor","level":3,"title":"Refactor","text":"
    Inspect src/auth/ and list duplication hotspots.\nPropose a refactor plan scoped to one module.\nAfter approval, remove duplication without changing behavior.\nAdd a test if coverage is missing. Run: make audit\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#research","level":3,"title":"Research","text":"
    Explore the request flow around src/api/.\nSummarize likely bottlenecks with evidence.\nPropose 2-3 hypotheses. Do not implement yet.\n
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#docs","level":3,"title":"Docs","text":"
    Update docs/cli-reference.md to reflect the new --format flag.\nConfirm the flag exists in the code and the example works.\n

    Notice each prompt includes what to verify and how. Without that, you get a \"should work now\" instead of evidence.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"

    Tasks in TASKS.md are indirect prompts to the AI. How you write them shapes how the AI approaches the work.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-motivation-not-just-the-goal","level":3,"title":"State the Motivation, Not Just the Goal","text":"

    Tell the AI why you are building something, not just what.

    Bad: \"Build a calendar view.\"

    Good: \"Build a calendar view. The motivation is that all notes and tasks we build later should be viewable here.\"

    The second version lets the AI anticipate downstream requirements:

    It will design the calendar's data model to be compatible with future features: Without you having to spell out every integration point. Motivation turns a one-off task into a directional task.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"

    Bad task (implementation-focused):

    - [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?

    Good task (deliverable-focused):

    - [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx journal source` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n

    Now the AI knows the subtasks serve a specific user-facing deliverable.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"

    For complex tasks, add explicit \"done when\" criteria:

    - [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n

    This prevents premature \"task complete\" when only the implementation details are done, but the feature doesn't actually work.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"

    Completing all subtasks does not mean the parent task is complete.

    The parent task describes what the user gets.

    Subtasks describe how to build it.

    Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-do-these-approaches-work","level":2,"title":"Why Do These Approaches Work?","text":"

    The patterns in this guide aren't invented here: They are practitioner translations of well-established, peer-reviewed research, most of which predate the current AI (hype) wave.

    The underlying ideas come from decades of work in machine learning, cognitive science, and numerical optimization. For a concrete case study showing how these principles play out when an agent decides whether to follow instructions (attention competition, optimization toward least-resistance paths, and observable compliance as a design goal) see The Dog Ate My Homework.

    Phased work (\"Explore → Plan → Implement\") applies chain-of-thought reasoning: Decomposing a problem into sequential steps before acting. Forcing intermediate reasoning steps measurably improves output quality in language models, just as it does in human problem-solving. Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (2022).

    Root-cause prompts (\"Why doesn't X work?\") use step-back abstraction: Retreating to a higher-level question before diving into specifics. This mirrors how experienced engineers debug: they ask \"what should happen?\" before asking \"what went wrong?\" Zheng et al., Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models (2023).

    Exploring alternatives (\"Propose 2-3 approaches\") leverages self-consistency: Generating multiple independent reasoning paths and selecting the most coherent result. The idea traces back to ensemble methods in ML: A committee of diverse solutions outperforms any single one. Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models (2022).

    Impact analysis (\"What would break if we...\") is a form of tree-structured exploration: Branching into multiple consequence paths before committing. This is the same principle behind game-tree search (minimax, MCTS) that has powered decision-making systems since the 1950s. Yao et al., Tree of Thoughts: Deliberate Problem Solving with Large Language Models (2023).

    Motivation prompting (\"Build X because Y\") works through goal conditioning: Providing the objective function alongside the task. In optimization terms, you are giving the gradient direction, not just the loss. The model can make locally coherent decisions that serve the global objective because it knows what \"better\" means.

    Scope constraints (\"Only change files in X\") apply constrained optimization: Bounding the search space to prevent divergence. This is the same principle behind regularization in ML: Without boundaries, powerful optimizers find solutions that technically satisfy the objective but are practically useless.

    CLI commands as prompts (\"Run ctx status\") interleave reasoning with acting: The model thinks, acts on external tools, observes results, then thinks again. Grounding reasoning in real tool output reduces hallucination because the model can't ignore evidence it just retrieved. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022).

    Task decomposition (\"Prompts by Task Type\") applies least-to-most prompting: Breaking a complex problem into subproblems and solving them sequentially, each building on the last. This is the research version of \"plan, then implement one slice.\" Zhou et al., Least-to-Most Prompting Enables Complex Reasoning in Large Language Models (2022).

    Explicit planning (\"Explore → Plan → Implement\") is directly supported by plan-and-solve prompting, which addresses missing-step failures in zero-shot reasoning by extracting a plan before executing. The phased structure prevents the model from jumping to code before understanding the problem. Wang et al., Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (2023).

    Session reflection (\"What did we learn?\", /ctx-reflect) is a form of verbal reinforcement learning: Improving future performance by persisting linguistic feedback as memory rather than updating weights. This is exactly what LEARNINGS.md and DECISIONS.md provide: a durable feedback signal across sessions. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning (2023).

    These aren't prompting \"hacks\" that you will find in the \"1000 AI Prompts for the Curious\" listicles: They are applications of foundational principles:

    • Decomposition,
    • Abstraction,
    • Ensemble Reasoning,
    • Search,
    • and Constrained Optimization.

    They work because language models are, at their core, optimization systems navigating probabilistic landscapes.

    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#further-reading","level":2,"title":"Further Reading","text":"
    • The Attention Budget: Why your AI forgets what you just told it, and how token budgets shape context strategy
    • The Dog Ate My Homework: A case study in making agents follow instructions: attention timing, delegation decay, and observable compliance as a design goal
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#contributing","level":2,"title":"Contributing","text":"

    Found a prompt that works well? Open an issue or PR with:

    1. The prompt text;
    2. What behavior it triggers;
    3. When to use it;
    4. Why it works (optional but helpful).

    Dive Deeper:

    • Recipes: targeted how-to guides for specific tasks
    • CLI Reference: all commands and flags
    • Integrations: setup for Claude Code, Cursor, Aider
    ","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/repeated-mistakes/","level":1,"title":"My AI Keeps Making the Same Mistakes","text":"","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-problem","level":2,"title":"The Problem","text":"

    You found a bug last Tuesday. You debugged it, understood the root cause, and moved on. Today, a new session hits the exact same bug. The AI rediscovers it from scratch, burning twenty minutes on something you already solved.

    Worse: you spent an hour last week evaluating two database migration strategies, picked one, documented why in a comment somewhere, and now the AI is cheerfully suggesting the approach you rejected. Again.

    This is not a model problem. It is a memory problem. Without persistent context, every session starts with amnesia.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#how-ctx-stops-the-loop","level":2,"title":"How ctx Stops the Loop","text":"

    ctx gives your AI three files that directly prevent repeated mistakes, each targeting a different failure mode.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#decisionsmd-stop-relitigating-settled-choices","level":3,"title":"DECISIONS.md: Stop Relitigating Settled Choices","text":"

    When you make an architectural decision, record it with rationale and rejected alternatives. The AI reads this at session start and treats it as settled.

    ## [2026-02-12] Use JWT for Authentication\n\n**Status**: Accepted\n\n**Context**: Need stateless auth for the API layer.\n\n**Decision**: JWT with short-lived access tokens and refresh rotation.\n\n**Rationale**: Stateless, scales horizontally, team has prior experience.\n\n**Alternatives Considered**:\n- Session-based auth: Rejected. Requires sticky sessions or shared store.\n- API keys only: Rejected. No user identity, no expiry rotation.\n

    Next session, when the AI considers auth, it reads this entry and builds on the decision instead of re-debating it. If someone asks \"why not sessions?\", the rationale is already there.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#learningsmd-capture-gotchas-once","level":3,"title":"LEARNINGS.md: Capture Gotchas Once","text":"

    Learnings are the bugs, quirks, and non-obvious behaviors that cost you time the first time around. Write them down so they cost you zero time the second time.

    ## Build\n\n### CGO Required for SQLite on Alpine\n\n**Discovered**: 2026-01-20\n\n**Context**: Docker build failed silently with \"no such table\" at runtime.\n\n**Lesson**: The go-sqlite3 driver requires CGO_ENABLED=1 and gcc\ninstalled in the build stage. Alpine needs apk add build-base.\n\n**Application**: Always use the golang:alpine image with build-base\nfor SQLite builds. Never set CGO_ENABLED=0.\n

    Without this entry, the next session that touches the Dockerfile will hit the same wall. With it, the AI knows before it starts.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#constitutionmd-draw-hard-lines","level":3,"title":"CONSTITUTION.md: Draw Hard Lines","text":"

    Some mistakes are not about forgetting - they are about boundaries the AI should never cross. CONSTITUTION.md sets inviolable rules.

    * [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never disable security linters without a documented exception\n* [ ] All database migrations must be reversible\n

    The AI reads these as absolute constraints. It does not weigh them against convenience. It refuses tasks that would violate them.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-accumulation-effect","level":2,"title":"The Accumulation Effect","text":"

    Each of these files grows over time. Session one captures two decisions. Session five adds a tricky learning about timezone handling. Session twelve records a convention about error message formatting.

    By session twenty, your AI has a knowledge base that no single person carries in their head. New team members - human or AI - inherit it instantly.

    The key insight: you are not just coding. You are building a knowledge layer that makes every future session faster.

    ctx files version with your code in git. They survive branch switches, team changes, and model upgrades. The context outlives any single session.

    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#getting-started","level":2,"title":"Getting Started","text":"

    Capture your first decision or learning right now:

    ctx decision add \"Use PostgreSQL\" \\\n  --context \"Need a relational database for the project\" \\\n  --rationale \"Team expertise, JSONB support, mature ecosystem\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\nctx learning add \"Vitest mock hoisting\" \\\n  --context \"Tests failing intermittently\" \\\n  --lesson \"vi.mock() must be at file top level\" \\\n  --application \"Use vi.doMock() for dynamic mocks\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#further-reading","level":2,"title":"Further Reading","text":"
    • Knowledge Capture: the full workflow for persisting decisions, learnings, and conventions
    • Context Files Reference: structure and format for every file in .context/
    • About ctx: the bigger picture - why persistent context changes how you work with AI
    ","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/steering/","level":1,"title":"Steering Files","text":"","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#steering-files","level":2,"title":"Steering Files","text":"

    ctx projects talk to AI assistants through several layers (context files, decisions, conventions, the agent context packet) but none of those can tell the assistant how to behave when a specific kind of prompt arrives. That's what steering files are for.

    A steering file is a small Markdown document with YAML frontmatter that says: \"when the user asks about X, prepend these rules to the prompt.\" ctx manages those files in .context/steering/, decides which ones match each prompt, and syncs them out to each AI tool's native config (Claude Code, Cursor, Kiro, Cline) so the rules actually land in the prompt pipeline.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#not-the-same-as-decisions-or-conventions","level":2,"title":"Not the Same as Decisions or Conventions","text":"

    The three look similar on disk but serve different purposes:

    Kind Purpose Decisions (DECISIONS.md) What was chosen and why Conventions (CONVENTIONS.md) How the codebase is written Steering (.context/steering/*.md) How the AI should behave on matching prompts

    If you find yourself writing \"the AI should always do X when asked about Y,\" that belongs in steering, not decisions.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#your-first-steering-files","level":2,"title":"Your First Steering Files","text":"

    ctx init scaffolds four foundation steering files in .context/steering/ so you start with something to edit rather than an empty directory:

    File What to fill in product.md What the project is, who it's for, what's out of scope tech.md Languages, frameworks, runtime, hard constraints structure.md Directory layout, where new files go, naming rules workflow.md Branch strategy, commit conventions, pre-commit checks

    Each file starts with an inline HTML comment explaining the three inclusion modes, priority semantics, and tool scoping. The comment is invisible in rendered Markdown but visible when you open the file to edit it; it's self-documenting scaffolding, not forever guidance. Delete the comment once you've customized the file.

    Default settings for foundation files:

    • inclusion: always: fires on every AI tool call
    • priority: 10: injected near the top of the prompt
    • tools: []: applies to every configured AI tool

    You should open each of these files and replace the placeholder content with your project's actual rules. Re-running ctx init is safe: existing files are left alone, so your edits survive. Use ctx init --no-steering-init to opt out of the scaffold entirely.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#inclusion-modes","level":2,"title":"Inclusion Modes","text":"

    Each steering file declares an inclusion mode in its frontmatter:

    Mode When the file is included always Every prompt, unconditionally auto When the prompt keywords match the file's description manual Only when the user explicitly names the file

    Which mode to pick depends on the AI tool you use, because the two tool families consume steering very differently.

    Claude Code and Codex: prefer inclusion: always for rules that must fire reliably. These tools have two delivery channels:

    1. The plugin's PreToolUse hook runs ctx agent with an empty prompt, so only always files match and get injected automatically on every tool call.
    2. The ctx_steering_get MCP tool, registered automatically when the ctx plugin is installed. Claude can call this tool mid-task to fetch auto or manual files matching a specific prompt. Verify with claude mcp list; look for ctx: ✓ Connected.

    Use always for invariants and anything that must fire every session. Use auto for situational rules where \"Claude fetches this when the prompt is relevant\" is the right behavior; those still land, just on Claude's judgment. Use manual for reference libraries you'll name explicitly.

    Cursor, Cline, Kiro: auto is the natural default. These tools read .cursor/rules/, .clinerules/, or .kiro/steering/ natively and resolve the description match on their own, so auto files fire when the prompt matches. manual files load on explicit invocation. always still works but consumes context budget on every turn.

    Mixed setups: if a rule must fire on Claude Code, pick always, even if it's overkill for your Cursor setup. The context budget cost is small; the alternative (silently not firing) is worse.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-families-of-ai-tools-two-delivery-paths","level":2,"title":"Two Families of AI Tools, Two Delivery Paths","text":"

    Not every AI tool consumes steering the same way. ctx handles two tool families differently, and it's worth knowing which family your editor is in before you wonder why a rule isn't firing.

    Native-rules tools (Cursor, Cline, Kiro) have a built-in rules primitive. They read a specific directory (.cursor/rules/, .clinerules/, .kiro/steering/) and apply the rules they find there. ctx handles these via ctx steering sync, which exports your files into the tool-native format. Run sync whenever you edit a steering file.

    Hook + MCP tools (Claude Code, Codex) have no native rules primitive, so ctx steering sync is a no-op for them. Instead, ctx delivers steering through two non-sync channels:

    1. Automatic injection via a PreToolUse hook. The ctx setup claude-code plugin wires a hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads your steering files, filters them by the active prompt, and includes matching bodies in the context packet it prints. Claude Code feeds that output back into its context. Every tool call, automatically.
    2. On-demand via the ctx_steering_get MCP tool. The ctx MCP server exposes a tool Claude can call mid-task to fetch matching steering files for a specific prompt. Claude decides when to call it; it's not automatic.

    Both channels activate when you run ctx setup claude-code --write. After that, steering just works for Claude Code.

    Practical takeaway:

    • Using Cursor/Cline/Kiro only? Run ctx steering sync after edits.
    • Using Claude Code or Codex only? Never run sync; the hook+MCP pipeline handles it.
    • Using both? Run sync for the native-rules tools; the hook+MCP pipeline covers Claude Code automatically.
    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-shapes-of-automation-rules-and-scripts","level":2,"title":"Two Shapes of Automation: Rules and Scripts","text":"

    Steering is one of two hook-like layers ctx provides for customizing AI behavior. They're complementary:

    • Steering: persistent rules that get prepended to prompts. Declarative, text-only, scored by match.
    • Triggers: executable shell scripts that fire at lifecycle events. Imperative, runs arbitrary code, gated by exit codes.

    Pick steering when you want \"always remind the AI of X.\" Pick triggers when you want \"do Y when event Z happens.\" They can coexist; many projects use both.

    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Writing Steering Files: a six-step walkthrough: scaffold, write the rule, preview matches, list, get-rules-in-front-of-the-AI (two paths depending on tool family), verify.
    • ctx steering reference: full command, flag, and frontmatter reference; includes the per-tool delivery-mechanism table and a dedicated section on how Claude Code and Codex consume steering.
    • ctx setup: configure which AI tools receive steering. For Cursor/Cline/Kiro this is about sync targets; for Claude Code/Codex it installs the plugin that wires the PreToolUse hook and MCP server.
    • Lifecycle Triggers: the imperative companion to steering files.
    ","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/triggers/","level":1,"title":"Lifecycle Triggers","text":"","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#lifecycle-triggers","level":2,"title":"Lifecycle Triggers","text":"

    Some things can't be expressed as a rule you want the AI to follow. Sometimes you want something to happen: block a dangerous tool call, inject today's standup notes into the next session, log every file save to a journal. That's what triggers are for.

    A trigger is an executable shell script that ctx runs at a specific lifecycle event: the start of a session, before a tool call, when a file is saved, and so on. Triggers read a JSON payload from stdin, do whatever they need, and write a JSON response on stdout. They can allow, block, or inject context into the pipeline depending on the event type.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#trigger-types","level":2,"title":"Trigger Types","text":"Type Fires when Use case session-start A new AI session begins Inject rotating context, standup notes session-end An AI session ends Persist summaries, send notifications pre-tool-use Before a tool call executes Block, gate, or audit post-tool-use After a tool call completes Log, react, post-process file-save A file is saved Lint on save, update indices context-add A new entry is added to .context/ Cross-link, notify, enrich","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-are-arbitrary-code-treat-them-like-pre-commit-hooks","level":2,"title":"Triggers Are Arbitrary Code: Treat Them like Pre-Commit Hooks","text":"

    Only Enable Scripts You've Read and Understand

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    ctx trigger add intentionally creates new scripts disabled (no executable bit). You must ctx trigger enable <name> after reviewing the contents. That's not a suggestion; it's the security model.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#three-hook-like-layers-in-ctx","level":2,"title":"Three Hook-like Layers in ctx","text":"

    Triggers are one of three distinct hook-like concepts in ctx. The names are similar but the owners and use cases are not:

    Layer Owned by Where they live When to use ctx trigger You .context/hooks/<type>/*.sh Project-specific automation, any AI tool ctx system hooks ctx itself built-in, wired into tool configs Built-in nudges (you don't author these) Claude Code hooks Claude Code .claude/settings.local.json Claude-Code-only tool-specific integration

    This page is about the first category. The other two run automatically and are invisible to you.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-vs-steering-same-problem-different-shape","level":2,"title":"Triggers vs Steering: Same Problem, Different Shape","text":"

    Triggers are the imperative counterpart to steering files. Steering expresses persistent rules the AI reads before each prompt; triggers express side effects that run on lifecycle events. They're complementary, not competing:

    • Want the AI to remember something? → Steering.
    • Want a script to run when something happens? → Trigger.

    Most projects use both.

    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Authoring Lifecycle Triggers: walkthrough with security guidance: scaffold, test, enable, iterate.
    • ctx trigger reference: command reference, trigger type table, input/output contract.
    • Steering files: the declarative counterpart to triggers.
    ","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/vscode/","level":1,"title":"ctx for VS Code","text":"","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#the-problem","level":2,"title":"The Problem","text":"

    Every Copilot Chat session in VS Code starts from zero. You re-explain what you were doing, the AI repeats yesterday's mistakes, and decisions you spent an hour reasoning through last week get rediscovered instead of remembered.

    Without ctx:

    @workspace add the validation middleware we discussed\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n

    With ctx:

    @ctx Do you remember?\n\nLast session (2026-05-09): you decided on Zod schemas at the route level\n(DECISIONS.md #12). Pattern lives in CONVENTIONS.md. Open task: wire\nthe auth middleware into the new /admin routes (TASKS.md, in-progress).\nThe reference implementation is src/middleware/auth.ts.\n

    That's the whole pitch: your AI remembers, right inside the IDE you already work in.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#setup","level":2,"title":"Setup","text":"

    Install the extension and the ctx binary, then ctx init your project:

    1. Install the extension from the VS Code Marketplace (publisher: activememory, display name: ctx: Persistent Context for AI). Or build from source (see editors/vscode/README.md).
    2. Install the ctx CLI if you haven't already (installation docs). If you skip this step, the extension will auto-download the right binary for your platform on first use (see Auto-Bootstrap below).
    3. From your project root, run:
    ctx init && eval \"$(ctx activate)\"\n
    1. Open Copilot Chat in VS Code and type @ctx /init to verify the extension can reach the CLI.
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose .context/ Project-local context directory (created by ctx init) .github/copilot-instructions.md Repository instructions Copilot reads natively; regenerated automatically whenever .context/ files change

    The extension itself lives in VS Code's extension storage. No project files are added beyond .context/ and the Copilot instructions.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#how-you-use-it","level":2,"title":"How You Use It","text":"

    Type @ctx in the Copilot Chat view to invoke the chat participant. Then either:

    • Use a slash command: @ctx /status, @ctx /wrapup, etc. There are 45 commands; the most common ones live in the Slash Commands table below.
    • Use natural language: @ctx what should I work on? routes to /next; @ctx time to wrap up routes to /wrapup. See Natural Language.

    The extension shows context-aware follow-up suggestions after each command. For example, after /init you'll see buttons for \"Show status\" or \"Generate copilot integration.\"

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"

    The extension registers several VS Code event handlers that mirror Claude Code's hook system. These run in the background; no user action needed.

    Trigger What fires File save Task-completion check on non-.context/ files Git commit Notification prompting to add a Decision, Learning, run /verify, or Skip .context/ file change Refreshes pending reminders and regenerates .github/copilot-instructions.md Dependency file change When go.mod, package.json, etc. change, prompts to refresh the dependency map (/map) Every 5 minutes Updates the reminder status-bar item and writes a heartbeat timestamp Extension activate Fires ctx system session-event --type start Extension deactivate Fires ctx system session-event --type end","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#status-bar","level":3,"title":"Status Bar","text":"

    A $(bell) ctx indicator appears in the status bar when you have pending reminders. It refreshes every 5 minutes and hides itself when nothing is due.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#slash-commands","level":2,"title":"Slash Commands","text":"

    The extension surfaces 45 commands across six categories. The most commonly used:

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#core-context","level":3,"title":"Core Context","text":"Command When to use /init Initialize a .context/ directory with template files /status Token estimate, file count, what's recent /agent Print AI-ready context packet /drift Detect stale paths, missing files, dead references /recall Browse and search prior AI session history /add Add a task, decision, learning, or convention","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#session-lifecycle","level":3,"title":"Session Lifecycle","text":"Command When to use /wrapup End-of-session ceremony: status, drift, journal audit /remember Structured readback (trigger: \"Do you remember?\") from tasks, decisions, learnings, recent journal /reflect Surface items worth persisting as decisions or learnings /pause / /resume Save and restore session state for later","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#discovery-planning","level":3,"title":"Discovery & Planning","text":"Command When to use /brainstorm Browse and develop ideas from ideas/ /spec List or scaffold feature specs from templates /verify Run verification (doctor + drift) /map Show dependency map (go.mod, package.json)

    Full list (with maintenance, audit, metadata, and system commands) is in editors/vscode/README.md.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#natural-language","level":2,"title":"Natural Language","text":"

    Plain English after @ctx is routed to the right command:

    • \"What should I work on next?\" → /next
    • \"Time to wrap up\" → /wrapup
    • \"Show me the status\" → /status
    • \"Add a decision\" → /add
    • \"Check for drift\" → /drift

    If the phrase doesn't match a known pattern, the extension surfaces a short menu of likely matches.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#auto-bootstrap","level":2,"title":"Auto-Bootstrap","text":"

    If the ctx CLI isn't on PATH (or at a path configured via ctx.executablePath), the extension auto-downloads the right binary:

    1. Detects OS and architecture (darwin / linux / windows, amd64 / arm64).
    2. Fetches the latest release from GitHub Releases.
    3. Downloads and verifies the matching binary.
    4. Caches it in VS Code's global storage directory.

    Subsequent sessions reuse the cached binary. To pin a specific version, set ctx.executablePath in your VS Code settings.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#prerequisites","level":2,"title":"Prerequisites","text":"
    • VS Code 1.93+
    • GitHub Copilot Chat extension
    • ctx CLI on PATH, or let the extension auto-download it
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#configuration","level":2,"title":"Configuration","text":"Setting Default Description ctx.executablePath ctx Path to the ctx CLI binary. Set this if ctx isn't on PATH and you don't want auto-download.","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"

    The extension updates through the VS Code Marketplace like any other extension; install new versions via the Extensions view. Updates to the ctx CLI are independent: bump it via your package manager, or let the auto-bootstrap fetch the latest release.

    Unlike the OpenCode integration, there is no ctx setup step for VS Code. The extension carries its own runtime; ctx's role is only to provide the CLI it shells out to.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix @ctx participant doesn't appear in Copilot Chat Copilot Chat not installed or not signed in Install GitHub Copilot Chat and ensure you're signed in to a Copilot-eligible account @ctx /status says ctx not found CLI not on PATH and auto-download disabled Either add ctx to PATH (brew install activememory/tap/ctx or download from Releases), or unset ctx.executablePath to let the extension auto-download Status-bar reminder never updates Heartbeat suppressed or .context/ doesn't exist Run ctx init from your project root; reload VS Code if the indicator still doesn't appear within 5 minutes Commands run but nothing is captured to .context/ Workspace folder missing or .context/ outside the open folder Make sure your project root (the one with .context/) is the workspace root, not a subdirectory of it","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#verify-it-works","level":2,"title":"Verify It Works","text":"

    Open Copilot Chat and ask:

    @ctx Do you remember?\n

    You should see a structured readback citing specific tasks, decisions, and recent session topics. If you instead see \"I don't have memory\" or \"Let me check,\" something went wrong: confirm the CLI is reachable (@ctx /system doctor) and .context/ has files in it.

    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#whats-next","level":2,"title":"What's Next","text":"
    • Your First Session: step-by-step walkthrough from ctx init to verified recall.
    • Common Workflows: day-to-day commands for tracking context, checking health, and browsing history.
    • Context Files: what lives in .context/ and how each file is used.
    • Setup across AI Tools: wiring ctx for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf alongside VS Code.
    ","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"operations/","level":1,"title":"Operations","text":"

    Guides for installing, upgrading, integrating, and running ctx. Split into three groups by audience.

    ","path":["Operations"],"tags":[]},{"location":"operations/#day-to-day","level":2,"title":"Day-to-Day","text":"

    Everyday operation guides for anyone running ctx in a project or adopting it in a team.

    ","path":["Operations"],"tags":[]},{"location":"operations/#integration","level":3,"title":"Integration","text":"

    Adopt ctx in an existing project: initialize context files, migrate from other tools, and onboard team members.

    ","path":["Operations"],"tags":[]},{"location":"operations/#upgrade","level":3,"title":"Upgrade","text":"

    Upgrade between versions with step-by-step migration notes and breaking-change guidance.

    ","path":["Operations"],"tags":[]},{"location":"operations/#ai-tools","level":3,"title":"AI Tools","text":"

    Configure ctx with Claude Code, Cursor, Aider, Copilot, Windsurf, and other AI coding tools.

    ","path":["Operations"],"tags":[]},{"location":"operations/#autonomous-loops","level":3,"title":"Autonomous Loops","text":"

    Run an unattended AI agent that works through tasks overnight, with ctx providing persistent memory between iterations.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub","level":2,"title":"Hub","text":"

    Operator guides for running a ctx Hub, the gRPC server that fans out structured entries across projects. If you're a client connecting to a Hub someone else runs, see ctx connect and the Hub recipes instead.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub-operations","level":3,"title":"Hub Operations","text":"

    Data directory layout, daemon management, systemd unit, backup and restore, log rotation, monitoring, and upgrades.

    ","path":["Operations"],"tags":[]},{"location":"operations/#hub-failure-modes","level":3,"title":"Hub Failure Modes","text":"

    What can go wrong in network, storage, cluster, auth, and clock layers, and what you should do about each one. Includes the short-list table oncall engineers will want bookmarked.

    ","path":["Operations"],"tags":[]},{"location":"operations/#maintainers","level":2,"title":"Maintainers","text":"

    Runbooks for people shipping ctx itself.

    ","path":["Operations"],"tags":[]},{"location":"operations/#cutting-a-release","level":3,"title":"Cutting a Release","text":"

    Step-by-step runbook for maintainers: bump version, generate release notes, run the release script, and verify the result.

    ","path":["Operations"],"tags":[]},{"location":"operations/#runbooks","level":2,"title":"Runbooks","text":"

    Step-by-step procedures you run with your agent. Each runbook includes a prompt to paste into a Claude Code session and guidance on triaging the results.

    Runbook Purpose When to run Release checklist Full pre-release sequence Before every release Plugin release Plugin-specific release steps Plugin changes ship Breaking migration Guide users across breaking changes Releases with renames Hub deployment Set up a ctx Hub end-to-end First-time hub setup New contributor Onboarding: clone to first session New contributors Codebase audit AST audits, magic strings, dead code, doc alignment Before release, quarterly Docs semantic audit Narrative gaps, weak pages, structural problems Before release, after adding pages Sanitize permissions Clean .claude/settings.local.json of over-broad grants After heavy permission granting Architecture exploration Systematic architecture docs across repos New codebase onboarding, reviews

    Recommended cadence:

    • Before every release: release checklist (which includes codebase audit + docs semantic audit)
    • Monthly: sanitize permissions
    • Quarterly: full sweep of all audit runbooks
    ","path":["Operations"],"tags":[]},{"location":"operations/autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"

    Iterate until done.

    An autonomous loop is an iterative AI development workflow where an agent works on tasks until completion, without constant human intervention.

    ctx provides the memory that makes this possible:

    • ctx provides the memory: persistent context that survives across iterations
    • The loop provides the automation: continuous execution until done

    Together, they enable fully autonomous AI development where the agent remembers everything across iterations.

    Origin

    This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique.

    We use generic terminology here so the concepts remain clear regardless of trends.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"
    graph TD\n    A[Start Loop] --> B[Load .context/loop.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B
    1. Loop reads .context/loop.md and invokes AI
    2. AI loads context from .context/
    3. AI picks one task and completes it
    4. AI updates context files (mark task done, add learnings)
    5. AI commits changes
    6. Loop checks for completion signals
    7. Repeat until converged or blocked
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#quick-start-shell-while-loop-recommended","level":2,"title":"Quick Start: Shell While Loop (Recommended)","text":"

    The best way to run an autonomous loop is a plain shell script that invokes your AI tool in a fresh process on each iteration. This is \"pure ralph\":

    The only state that carries between iterations is what lives in .context/ and the git history. No context window bleed, no accumulated tokens, no hidden state.

    Create a loop.sh:

    #!/bin/bash\n# loop.sh: an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-.context/loop.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n

    Make it executable and run:

    chmod +x loop.sh\n./loop.sh\n

    You can also generate this script with ctx loop (see CLI Reference).

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-do-we-use-a-shell-loop","level":3,"title":"Why Do We Use a Shell Loop?","text":"

    Each iteration starts a fresh AI process with zero context window history. The agent knows only what it reads from .context/ files: Exactly the information you chose to persist.

    This is the core loop principle: memory is explicit, not accidental.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#alternative-claude-codes-built-in-loop","level":2,"title":"Alternative: Claude Code's Built-in Loop","text":"

    Claude Code has built-in loop support:

    # Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n

    This is convenient for quick iterations, but be aware of important caveats:

    This Loop Is Not Pure

    Claude Code's /loop runs all iterations within the same session. This means:

    • State leaks between iterations: The context window accumulates output from every previous iteration. The agent \"remembers\" things it saw earlier (even if they were never persisted to .context/).
    • Token budget degrades: Each iteration adds to the context window, leaving less room for actual work in later iterations.
    • Not ergonomic for long runs: Users report that the built-in loop is less predictable for 10+ iteration runs compared to a shell loop.

    For short explorations (2-5 iterations) or interactive use, /loop works fine. For overnight unattended runs or anything where iteration independence matters, use the shell while loop instead.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#the-contextloopmd-file","level":2,"title":"The .context/loop.md File","text":"

    The prompt file instructs the AI on how to work autonomously. Here's a template:

    # Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n\n1. `.context/CONSTITUTION.md`: NEVER violate these rules\n2. `.context/TASKS.md`: Find work to do\n3. `.context/CONVENTIONS.md`: Follow these patterns\n4. `.context/DECISIONS.md`: Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n\n- Mark task complete in `TASKS.md`\n- Add any learnings to `LEARNINGS.md`\n- Add any decisions to `DECISIONS.md`\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED`: All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n- (no signal): More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"

    The loop watches for these signals in AI output:

    Signal Meaning When to Use SYSTEM_CONVERGED All tasks complete No pending tasks in TASKS.md SYSTEM_BLOCKED Cannot proceed Needs clarification, access, or decision BOOTSTRAP_COMPLETE Initial setup done Project scaffolding finished","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"

    converged state

    I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n

    blocked state

    I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-ctx-and-loops-work-well-together","level":2,"title":"Why ctx and Loops Work Well Together","text":"Without ctx With ctx Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in DECISIONS.md Learnings are lost Learnings accumulate in LEARNINGS.md Tasks can be forgotten Tasks tracked in TASKS.md","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"

    During the loop, the AI should update context files:

    Mark task complete:

    ctx task complete \"implement user auth\"\n

    Or emit an update command (parsed by ctx watch):

    <context-update type=\"complete\">user auth</context-update>\n

    Add learning:

    ctx learning add \"Rate limiting requires Redis connection\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    Or via update command:

    <context-update type=\"learning\"\n  context=\"Implementing rate limiter\"\n  lesson=\"Rate limiting requires Redis connection\"\n  application=\"Ensure Redis is provisioned before enabling rate limits\"\n>Rate Limiting Redis Dependency</context-update>\n

    Record decision:

    ctx decision add \"Use JWT tokens for API authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"

    Run ctx watch alongside the loop to automatically process context updates:

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n

    The watch command processes context updates from the loop output in real time.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#project-setup","level":2,"title":"Project Setup","text":"

    Initialize a project for autonomous loop operation, then activate it so the loop's ctx commands know which .context/ to use:

    ctx init\neval \"$(ctx activate)\"\n

    For unattended overnight runs, put the binding directly at the top of your loop script (export CTX_DIR=/abs/path/.context) so it survives without depending on a live shell. See Activating a Context Directory.

    The loop prompt template is deployed to .context/loop.md during initialization. It instructs the agent to:

    • Work autonomously without asking clarifying questions;
    • Follow one-task-per-iteration discipline;
    • Use SYSTEM_CONVERGED / SYSTEM_BLOCKED signals;
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-project-structure","level":2,"title":"Example Project Structure","text":"
    my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample TASKS.md for Autonomous Loops","text":"
    # Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n

    The loop will work through these systematically, marking each complete.

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"

    Cause: AI not emitting completion signals

    Fix: Ensure .context/loop.md explicitly instructs signaling:

    End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"

    Cause: AI not updating context files

    Fix: Add explicit instructions to .context/loop.md:

    After completing a task, you MUST:\n1. Run: ctx task complete \"<task>\"\n2. Add learnings: ctx learning add \"...\" --session-id abc12345 --branch main --commit 68fbc00a\n

    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"

    Cause: Task not marked complete before next iteration

    Fix: Ensure commit happens after context update:

    Order of operations:\n1. Complete coding work\n2. Update context files (*`ctx task complete`, `ctx add`*)\n3. Commit **ALL** changes including `.context/`\n4. Then signal status\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"

    Cause: Constitution not read first

    Fix: Make constitution check explicit in .context/loop.md:

    BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#further-reading","level":2,"title":"Further Reading","text":"
    • Building ctx Using ctx: The dogfooding story: how autonomous loops built the tool that powers them
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#resources","level":2,"title":"Resources","text":"
    • Geoffrey Huntley's Ralph Wiggum Technique: The original inspiration
    • Context CLI: Command reference
    • Integrations: Tool-specific setup
    ","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/hub-failure-modes/","level":1,"title":"Hub Failure Modes","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#ctx-hub-failure-modes","level":1,"title":"ctx Hub: Failure Modes","text":"

    What can go wrong, what the system does about it, and what you should do. Complementary to ctx Hub Operations.

    Design Posture

    The hub is best-effort knowledge sharing, not a durable ledger. Local .context/ files are the source of truth for each project; the hub is a fan-out channel. This framing informs every failure-mode decision below.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#network","level":2,"title":"Network","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#client-loses-connection-mid-stream","level":3,"title":"Client Loses Connection Mid-Stream","text":"

    What happens: ctx connection listen detects the EOF, waits with exponential backoff, and reconnects. On reconnect it passes its last-seen sequence; the hub replays everything newer.

    What you should do: nothing. If reconnects are looping, check firewall state on the hub and ctx hub status output.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-majority-side-reachable","level":3,"title":"Partition: Majority Side Reachable","text":"

    What happens: clients routed to the majority side continue to publish and listen. The minority nodes step down to followers that cannot accept writes (Raft quorum lost).

    What you should do: let it heal. When the partition closes, followers catch up via sequence-based sync automatically.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-split-brain-no-quorum","level":3,"title":"Partition: Split Brain (No Quorum)","text":"

    What happens: no node holds a majority, so no leader is elected. All nodes become read-only. ctx connection publish and ctx add --share fail with a \"no leader\" error; local writes still succeed.

    What you should do: fix the network. If the partition is permanent (e.g., a data center is gone), bootstrap a new cluster from the survivors with ctx hub peer remove for the dead nodes.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#hub-unreachable-during-ctx-add-share","level":3,"title":"Hub Unreachable during ctx add --share","text":"

    What happens: the local write succeeds; the share step prints a warning and exits non-zero on the share leg only. --share is best-effort; it never blocks local context updates.

    What you should do: run ctx connection publish later to backfill, or rely on another --share for the same entry ID. The hub deduplicates by entry ID.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#storage","level":2,"title":"Storage","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#disk-full-on-the-leader","level":3,"title":"Disk Full on the Leader","text":"

    What happens: entries.jsonl append fails. The hub rejects writes with an error and stays up for read traffic. Clients retry; followers keep their in-sync status using whatever the leader already wrote.

    What you should do: free disk or grow the volume, then nothing else; the hub resumes accepting writes on the next append attempt.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#corrupt-entriesjsonl","level":3,"title":"Corrupt entries.jsonl","text":"

    What happens: if the last line is a partial JSON write from a crash, the hub truncates it on startup and logs a warning. If any earlier line is malformed, the hub refuses to start.

    What you should do: inspect with jq -c . <data-dir>/entries.jsonl > /dev/null to find the bad line. Move the bad region to a .quarantine file, then start. Nothing is ever silently dropped.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#metajson-entriesjsonl-sequence-mismatch","level":3,"title":"meta.json / entries.jsonl Sequence Mismatch","text":"

    What happens: the hub refuses to start. This usually means someone copied one file without the other.

    What you should do: restore both files from the same backup, or accept the higher sequence by regenerating meta.json from entries.jsonl (manual for now; file a bug).

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#cluster","level":2,"title":"Cluster","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-clean-shutdown","level":3,"title":"Leader Crash, Clean Shutdown","text":"

    What happens: ctx hub stop triggers stepdown first, so a new leader is elected before the old one exits. In-flight writes drain. Clients reconnect to the new leader transparently.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-hard-fail-kill-9-power-loss","level":3,"title":"Leader Crash, Hard Fail (Kill -9, Power Loss)","text":"

    What happens: Raft detects the missing heartbeat and elects a new leader within a few seconds. Writes the old leader accepted but had not yet replicated can be lost. See the Raft-lite warning in the cluster recipe.

    What you should do: if you need stronger durability, run ctx connection listen on a dedicated \"collector\" project that persists entries locally as a write-ahead backup.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#split-brain-after-rejoin","level":3,"title":"Split-Brain After Rejoin","text":"

    What happens: Raft reconciles: the minority side's uncommitted writes are discarded, and the majority's log is authoritative.

    What you should do: nothing automatic. If you know the minority had important writes, grep for them in <data-dir>/entries.jsonl.rejected (written by the reconciliation pass) and replay them with ctx connection publish.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#auth-and-tokens","level":2,"title":"Auth and Tokens","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#lost-admin-token","level":3,"title":"Lost Admin Token","text":"

    What happens: you cannot register new projects.

    What you should do: retrieve it from <data-dir>/admin.token. If that file is also gone, stop the hub and regenerate. Note that all existing client tokens keep working; only new registrations need the admin token.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-admin-token","level":3,"title":"Compromised Admin Token","text":"

    What happens: anyone with the token can register new projects and publish. They cannot read existing entries without a client token for a project that subscribes.

    What you should do: rotate the admin token (regenerate <data-dir>/admin.token and restart), revoke suspicious client registrations via clients.json, and audit entries.jsonl for unexpected origins.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-client-token","level":3,"title":"Compromised Client Token","text":"

    What happens: the attacker can publish as that project and read anything that project is subscribed to. Because Origin is self-asserted on publish, the attacker can also publish entries tagged with any other project's name, so attribution in entries.jsonl cannot be trusted after a token compromise.

    What you should do: remove the client's entry from clients.json, restart the hub, and re-register the legitimate project with a fresh token. Audit entries.jsonl for entries published after the compromise timestamp and quarantine any that look suspicious; remember that Origin on those entries proves nothing.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-hub-host","level":3,"title":"Compromised Hub Host","text":"

    What happens: <data-dir>/clients.json stores client tokens verbatim (not hashed). Anyone with read access to that file has every client token in hand and can impersonate any registered project until each one is rotated.

    What you should do: treat it as a total hub compromise. Stop the hub, wipe <data-dir> (keep a forensic copy first), regenerate the admin token, and have every client re-register. See Security model for the mitigations that reduce the blast radius while the hashing follow-up is pending.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#clock-skew","level":2,"title":"Clock Skew","text":"

    Hub entries carry a timestamp assigned by the publishing client. The hub does not rewrite timestamps. Clients with significant clock skew will publish entries that look out of order in the shared feed.

    What you should do: run NTP on all client machines. If you see entries dated in the future or far past, the publisher's clock is the culprit.

    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#the-short-list","level":2,"title":"The Short List","text":"Symptom First thing to check Client can't reach hub Firewall, then ctx hub status \"No leader\" errors Cluster quorum; run ctx hub status on each peer Hub won't start after crash Last line of entries.jsonl Entries missing after restore Check clients.json sequence vs local .sync-state.json Duplicate entries in shared feed Client replayed after restore, safe (dedup by ID) Followers lagging Disk or network on the follower, not the leader","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub Operations
    • ctx Hub security model
    • HA cluster recipe
    ","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub/","level":1,"title":"Hub Operations","text":"","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#ctx-hub-operations","level":1,"title":"ctx Hub: Operations","text":"

    Running the ctx ctx Hub in production. This page is for operators: people running a hub for themselves or a team, not people writing to a hub someone else is running.

    If you have not read it yet, start with the ctx Hub overview. It explains what the hub is, the two user stories it supports (personal cross-project brain vs small trusted team), and what it does not do. A client-side tour is in Getting Started.

    Operator Cheat Sheet

    • The hub fans out four entry types only: decision, learning, convention, task. Journals, scratchpad, and other local state are out of scope.
    • Identity is per-project, not per-user. Attribution is limited to Origin, which is self-asserted by the publishing client.
    • The data model is an append-only JSONL log plus two small JSON sidecar files. Nothing is rewritten in place.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#data-directory-layout","level":2,"title":"Data Directory Layout","text":"

    The hub stores everything under a single data directory (default ~/.ctx/hub-data/, override with --data-dir).

    <data-dir>/\n  admin.token        # Initial admin token (chmod 600)\n  clients.json       # Registered client tokens and project names\n  meta.json          # Sequence counter, version, cluster metadata\n  entries.jsonl      # Append-only log (single source of truth)\n  hub.pid            # Daemon PID file (daemon mode only)\n  raft/              # Raft state (cluster mode only)\n    log.db\n    stable.db\n    snapshots/\n

    Invariants:

    • entries.jsonl is append-only. Every line is a valid JSON object. Corrupt lines are fatal at startup: fix or truncate before restart.
    • meta.json is authoritative for the next sequence number. On restart, the hub reads the last valid line of entries.jsonl and refuses to start if the sequences disagree.
    • clients.json holds hashed client tokens; losing it invalidates all client registrations.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#starting-and-stopping","level":2,"title":"Starting and Stopping","text":"ForegroundDaemon
    ctx hub start                    # Ctrl-C to stop\nctx hub start --port 8080        # Custom port\nctx hub start --data-dir /srv/ctx-hub\n
    ctx hub start --daemon           # Fork to background\nctx hub stop                      # Graceful shutdown\n

    --stop sends SIGTERM to the PID in hub.pid, waits for in-flight RPCs to drain, then exits. If the daemon is wedged, remove hub.pid and send SIGKILL manually. entries.jsonl is crash-safe, so you will not lose accepted writes.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#systemd-unit","level":2,"title":"Systemd Unit","text":"

    For production single-node deployments, run the hub as a systemd service instead of --daemon:

    # /etc/systemd/system/ctx-hub.service\n[Unit]\nDescription=ctx `ctx` Hub\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=ctx\nGroup=ctx\nExecStart=/usr/local/bin/ctx hub start --port 9900 \\\n    --data-dir /var/lib/ctx-hub\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/ctx-hub\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\n
    sudo systemctl enable --now ctx-hub\nsudo journalctl -u ctx-hub -f\n
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#backup-and-restore","level":2,"title":"Backup and Restore","text":"

    Because entries.jsonl is append-only, backups are trivial:

    # Hot backup, safe while the hub is running.\ncp <data-dir>/entries.jsonl backups/entries-$(date +%F).jsonl\ncp <data-dir>/meta.json      backups/meta-$(date +%F).json\ncp <data-dir>/clients.json   backups/clients-$(date +%F).json\n

    For a consistent snapshot across all three files, stop the hub, copy, then start again, or use a filesystem-level snapshot (LVM, ZFS, Btrfs).

    Restore:

    ctx hub stop                           # Stop the hub\ncp backups/entries-2026-04-10.jsonl <data-dir>/entries.jsonl\ncp backups/meta-2026-04-10.json      <data-dir>/meta.json\ncp backups/clients-2026-04-10.json   <data-dir>/clients.json\nctx hub start --daemon\n

    Clients that pushed sequences above the restored watermark will re-publish on the next listen reconnect, because the hub now reports a lower sequence than what clients have on disk. This is safe; the store deduplicates by entry ID.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#log-rotation","level":2,"title":"Log Rotation","text":"

    entries.jsonl grows unbounded. For long-lived hubs, rotate it offline:

    ctx hub stop\nmv <data-dir>/entries.jsonl <data-dir>/entries-$(date +%F).jsonl.old\n# Replay the last N days into a fresh entries.jsonl if you want a\n# trimmed active log, or leave the old file in place as history.\nctx hub start --daemon\n

    Do not truncate entries.jsonl while the hub is running. The hub holds an open file handle; an in-place truncation confuses the sequence counter and loses writes.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#monitoring","level":2,"title":"Monitoring","text":"

    Liveness probe:

    ctx hub status --exit-code\n

    Exit code 0 means the node is healthy (leader or in-sync follower); non-zero means degraded. Wire this into your monitoring of choice.

    For cluster deployments, watch for:

    • Role flaps: the leader changing more than once per hour suggests network instability or disk contention.
    • Replication lag: ctx hub status shows per-peer sequence offsets. Sustained lag > 100 sequences on a follower is worth investigating.
    • entries.jsonl growth rate: sudden spikes often indicate a misbehaving ctx connection listen reconnect loop.
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#upgrading","level":2,"title":"Upgrading","text":"

    The JSONL format is versioned in meta.json. ctx refuses to start against a newer store version than it understands; older store versions are upgraded in place at first start after an upgrade.

    Always back up <data-dir>/ before upgrading.

    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub failure modes
    • ctx Hub security model
    • ctx serve reference
    • ctx hub reference
    ","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/integrations/","level":1,"title":"AI Tools","text":"","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#ai-tools","level":2,"title":"AI Tools","text":"

    Context works with any AI tool that can read files. This guide covers setup for popular AI coding assistants.

    Activate the Project Before Running ctx Commands

    After ctx init, run:

    eval \"$(ctx activate)\"\n

    This tells ctx which .context/ directory the rest of the commands on this page should use. If you skip it, you'll see Error: no context directory specified. The ctx setup <tool> commands work without activation, but most others (ctx agent, ctx add, ctx status, ctx watch) need it. See Activating a Context Directory.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"

    Claude Code has the deepest integration via the ctx plugin.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup","level":3,"title":"Setup","text":"

    First, install ctx and initialize your project, then activate it for the current shell:

    ctx init\neval \"$(ctx activate)\"\n

    Then, install the ctx plugin in Claude Code:

    # From the ctx repository\nclaude /plugin install ./internal/assets/claude\n\n# Or from the marketplace\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n

    Ensure the Plugin Is Enabled

    Installing a plugin registers it, but local installs may not auto-enable it globally. Verify ~/.claude/settings.json contains:

    { \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n

    Without this, the plugin's hooks and skills won't appear in other projects. Running ctx init auto-enables the plugin; use --no-plugin-enable to skip this step.

    This gives you:

    Component Purpose .context/ All context files CLAUDE.md Bootstrap instructions Plugin hooks Lifecycle automation Plugin skills Agent Skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works","level":3,"title":"How It Works","text":"
    graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]
    1. Session start: Claude reads CLAUDE.md, which tells it to check .context/
    2. First tool use: PreToolUse hook runs ctx agent and emits the context packet (subsequent invocations within the cooldown window are silent)
    3. Next session: Claude reads context files and continues with context
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#plugin-hooks","level":3,"title":"Plugin Hooks","text":"

    The ctx plugin provides lifecycle hooks implemented as Go subcommands (ctx system *):

    Hook Event Purpose ctx system context-load-gate PreToolUse (.*) Auto-inject context on first tool use ctx system block-non-path-ctx PreToolUse (Bash) Block ./ctx or go run: force $PATH install ctx system qa-reminder PreToolUse (Bash) Remind agent to lint/test before committing ctx system specs-nudge PreToolUse (EnterPlanMode) Nudge agent to use project specs when planning ctx system check-context-size UserPromptSubmit Nudge context assessment as sessions grow ctx system check-ceremonies UserPromptSubmit Nudge /ctx-remember and /ctx-wrap-up adoption ctx system check-persistence UserPromptSubmit Remind to persist learnings/decisions ctx system check-journal UserPromptSubmit Remind to export/enrich journal entries ctx system check-reminders UserPromptSubmit Relay pending reminders at session start ctx system check-version UserPromptSubmit Warn when binary/plugin versions diverge ctx system check-resources UserPromptSubmit Warn when memory/swap/disk/load hit DANGER level ctx system check-knowledge UserPromptSubmit Nudge when knowledge files grow large ctx system check-map-staleness UserPromptSubmit Nudge when ARCHITECTURE.md is stale ctx system heartbeat UserPromptSubmit Session-alive signal with prompt count metadata ctx system post-commit PostToolUse (Bash) Nudge context capture and QA after git commits

    A catch-all PreToolUse hook also runs ctx agent on every tool use (with cooldown) to autoload context.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#hook-configuration","level":3,"title":"Hook Configuration","text":"

    The plugin's hooks.json wires everything automatically: no manual configuration in settings.local.json needed:

    {\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system context-load-gate\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system block-non-path-ctx\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system qa-reminder\" }\n        ]\n      },\n      {\n        \"matcher\": \"EnterPlanMode\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system specs-nudge\" }\n        ]\n      },\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx agent --budget 4000 2>/dev/null || true\" }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system post-commit\" }\n        ]\n      }\n    ],\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system check-context-size\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-ceremonies\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-persistence\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-journal\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-reminders\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-version\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-resources\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-knowledge\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-map-staleness\" },\n          { \"type\": \"command\", \"command\": \"ctx system heartbeat\" }\n        ]\n      }\n    ]\n  }\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#customizing-token-budget-and-cooldown","level":3,"title":"Customizing Token Budget and Cooldown","text":"

    Edit the PreToolUse command to change the token budget or cooldown:

    \"command\": \"ctx agent --budget 8000 --session $PPID >/dev/null || true\"\n\"command\": \"ctx agent --budget 4000 --cooldown 5m --session $PPID >/dev/null || true\"\n

    The --session $PPID flag isolates the cooldown per session: $PPID resolves to the Claude Code process PID, so concurrent sessions don't interfere. The default cooldown is 10 minutes; use --cooldown 0 to disable it.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"
    1. Start a new Claude Code session;
    2. Ask: \"Do you remember?\"
    3. Claude should cite specific context:
      • Current tasks from .context/TASKS.md;
      • Recent decisions or learnings;
      • Recent session history from ctx journal.
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#local-plugin-development","level":3,"title":"Local Plugin Development","text":"

    When developing ctx locally (adding skills, hooks, or changing plugin behavior), Claude Code caches the plugin by version. You must bump the version in both files and update the marketplace for changes to take effect:

    1. Bump version in both:
    2. internal/assets/claude/.claude-plugin/plugin.json (plugin manifest), .claude-plugin/marketplace.json (marketplace listing*);

    3. Update the marketplace in Claude Code:

    4. Open the Plugins UI (/plugins or Esc menu),
    5. Go to Marketplaces tab,
    6. Select the activememory-ctx Marketplace,
    7. Choose Update marketplace;

    8. Start a new Claude Code session: skill changes aren't reflected in existing sessions.

    Both Version Files Must Match

    If you only bump plugin.json but not marketplace.json (or vice versa), Claude Code may not detect the update. Always bump both together.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check ctx is in PATH: which ctx Hook errors Verify plugin is installed: claude /plugin list New skill not visible Bump version in both plugin.json files, update marketplace","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"

    If hooks aren't working, manually load context:

    # Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#agent-skills","level":3,"title":"Agent Skills","text":"

    The ctx plugin ships Agent Skills following the agentskills.io specification.

    These are invoked in Claude Code with /skill-name.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-lifecycle-skills","level":4,"title":"Session Lifecycle Skills","text":"Skill Description /ctx-remember Recall project context at session start (ceremony) /ctx-wrap-up End-of-session context persistence (ceremony) /ctx-status Show context summary (tasks, decisions, learnings) /ctx-agent Get AI-optimized context packet /ctx-next Suggest 1-3 concrete next actions from context /ctx-commit Commit with integrated context capture /ctx-reflect Review session and suggest what to persist /ctx-remind Manage session-scoped reminders /ctx-pause Pause context hooks for this session /ctx-resume Resume context hooks after a pause","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-persistence-skills","level":4,"title":"Context Persistence Skills","text":"Skill Description /ctx-task-add Add a task to TASKS.md /ctx-learning-add Add a learning to LEARNINGS.md /ctx-decision-add Add a decision with context/rationale/consequence /ctx-convention-add Add a coding convention to CONVENTIONS.md /ctx-archive Archive completed tasks","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#scratchpad-skills","level":4,"title":"Scratchpad Skills","text":"Skill Description /ctx-pad Manage encrypted scratchpad entries","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-history-skills","level":4,"title":"Session History Skills","text":"Skill Description /ctx-history Browse AI session history /ctx-journal-enrich Enrich a journal entry with frontmatter/tags /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#blogging-skills","level":4,"title":"Blogging Skills","text":"

    Blogging Is a Better Way of Creating Release Notes

    The blogging workflow can also double as generating release notes:

    AI reads your git commit history and creates a \"narrative\", which is essentially what a release note is for.

    Skill Description /ctx-blog Generate blog post from recent activity /ctx-blog-changelog Generate blog post from commit range with theme","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#auditing-health-skills","level":4,"title":"Auditing & Health Skills","text":"Skill Description /ctx-doctor Troubleshoot ctx behavior with structural health checks /ctx-drift Detect and fix context drift (structural + semantic) /ctx-consolidate Merge redundant learnings or decisions into denser entries /ctx-alignment-audit Audit doc claims against playbook instructions /ctx-prompt-audit Analyze session logs for vague prompts /check-links Audit docs for dead internal and external links","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#planning-execution-skills","level":4,"title":"Planning & Execution Skills","text":"Skill Description /ctx-loop Generate a Ralph Loop iteration script /ctx-implement Execute a plan step-by-step with checks /ctx-plan-import Import Claude Code plan files into project specs /ctx-worktree Manage git worktrees for parallel agents /ctx-architecture Build and maintain architecture maps","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage-examples","level":4,"title":"Usage Examples","text":"
    /ctx-status\n/ctx-learning-add \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n

    Skills support partial matching where applicable (e.g., session slugs).

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"

    Cursor can use context files through its system prompt or by reading files directly.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_1","level":3,"title":"Setup","text":"
    # Generate Cursor configuration\nctx setup cursor\n\n# Initialize context\nctx init --minimal\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration","level":3,"title":"Configuration","text":"

    Add to Cursor settings (.cursor/settings.json):

    // split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage","level":3,"title":"Usage","text":"
    1. Open your project in Cursor
    2. Context files are available in the file tree
    3. Reference them in prompts: \"Check .context/DECISIONS.md for our approach to...\"
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"

    For more control, paste context directly:

    # Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n

    Paste into Cursor's chat.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#aider","level":2,"title":"Aider","text":"

    Aider works well with context files through its --read flag.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_2","level":3,"title":"Setup","text":"
    # Generate Aider configuration\nctx setup aider\n\n# Initialize context\nctx init\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_1","level":3,"title":"Configuration","text":"

    Create .aider.conf.yml:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_1","level":3,"title":"Usage","text":"
    # Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"

    Run ctx watch alongside Aider to capture context updates:

    # Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"

    GitHub Copilot integrates with ctx at three levels: an automated instructions file, a VS Code Chat extension, and manual patterns.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_3","level":3,"title":"Setup","text":"
    # Initialize context\nctx init\n\n# Generate .github/copilot-instructions.md\nctx setup copilot --write\n

    The --write flag creates .github/copilot-instructions.md, which Copilot reads automatically at the start of every session. This file contains your project's constitution rules, current tasks, conventions, and architecture: giving Copilot persistent context without manual copy-paste.

    Re-run ctx setup copilot --write after updating your .context/ files to regenerate the instructions.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#vs-code-chat-extension-ctx","level":3,"title":"VS Code Chat Extension (@ctx)","text":"

    The ctx VS Code extension adds a @ctx chat participant to GitHub Copilot Chat, giving you direct access to 45 context commands from within the editor, plus automatic hooks on file save / git commit / .context/ changes / dependency-file edits, and a reminder status-bar indicator.

    Full guide: ctx for VS Code

    The home-page guide covers daily workflows, the full command list, natural-language routing, auto-bootstrap of the ctx CLI, troubleshooting, and \"Verify It Works.\" This subsection is the install-and-pointers overview; the dedicated page is the authoritative reference.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#installation","level":4,"title":"Installation","text":"

    The extension ships to the VS Code Marketplace under publisher activememory (display name: ctx: Persistent Context for AI). Install via the Extensions view or code --install-extension.

    To build from source instead (requires Node.js 20+):

    cd editors/vscode\nnpm ci\nnpm run build\nnpx @vscode/vsce package\ncode --install-extension ctx-context-<version>.vsix\n

    Reload VS Code. Type @ctx in Copilot Chat to verify.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created","level":4,"title":"What Gets Created","text":"File Purpose .context/ Project-local context directory (created by ctx init, not by the extension) .github/copilot-instructions.md Repository instructions Copilot reads natively; regenerated automatically when .context/ files change

    The extension itself lives in VS Code's extension storage; no project files beyond .context/ and the Copilot instructions are added.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_1","level":4,"title":"How It Works","text":"
    • Chat participant: @ctx is registered with VS Code's Chat API; 45 slash commands route to dedicated handlers that shell out to the ctx CLI.
    • Automatic hooks: file save → task-completion check; git commit → decision/learning prompt; .context/ change → regenerate Copilot instructions; dependency-file change → /map prompt.
    • Status-bar reminder: a $(bell) ctx indicator surfaces pending session reminders, refreshing every 5 minutes.
    • Natural language: plain English after @ctx is routed to the nearest matching command.
    • Auto-bootstrap: if the ctx CLI isn't on PATH, the extension downloads the correct platform binary from GitHub Releases and caches it.
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_2","level":4,"title":"Configuration","text":"Setting Default Description ctx.executablePath ctx Path to the ctx binary. Set this if ctx is not in your PATH.","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-persistence","level":3,"title":"Session Persistence","text":"

    ctx init creates a .context/sessions/ directory for storing session data from non-Claude tools. The Markdown session parser scans this directory during ctx journal, enabling session history for Copilot and other tools.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-patterns","level":3,"title":"Manual Patterns","text":"

    These patterns work without the extension, using Copilot's built-in file awareness:

    Pattern 1: Keep context files open

    Open .context/CONVENTIONS.md in a split pane. Copilot will reference it.

    Pattern 2: Reference in comments

    // See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n

    Pattern 3: Paste context into Copilot Chat

    ctx agent --budget 2000\n

    Paste output into Copilot Chat for context-aware responses.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#opencode","level":2,"title":"OpenCode","text":"

    OpenCode is a terminal-first AI coding agent. ctx integrates via a thin lifecycle plugin, MCP server, and AGENTS.md instructions.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_4","level":3,"title":"Setup","text":"
    # Generate OpenCode plugin, global MCP config, skills, and AGENTS.md\nctx setup opencode --write\n\n# Initialize context\nctx init\neval \"$(ctx activate)\"\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created_1","level":3,"title":"What Gets Created","text":"File Purpose .opencode/plugins/ctx.ts Lifecycle plugin (hooks to ctx system) ~/.config/opencode/opencode.json Global MCP server registration (or $OPENCODE_HOME/opencode.json) AGENTS.md Agent instructions (read natively) .opencode/skills/ctx-*/SKILL.md ctx skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_2","level":3,"title":"How It Works","text":"

    The plugin wires OpenCode lifecycle events to ctx system:

    • session.created: warms ctx state in the background (bootstrap + agent packet) so MCP queries are fast on first use.
    • tool.execute.after (shell, on git commit): runs ctx system post-commit.
    • tool.execute.after (edit/write): runs ctx system check-task-completion.
    • session.idle: runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI).
    • shell.env: injects CTX_DIR into the agent's shell so ctx commands resolve to the right project.
    • experimental.session.compacting: pushes ctx system bootstrap output into the compaction context so the agent keeps breadcrumbs back to .context/.

    The plugin is a single file with no runtime dependencies; no bun install needed. OpenCode loads it automatically on launch.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-updates","level":3,"title":"Context Updates","text":"
    # Get AI-optimized context packet\nctx agent\n\n# Check context health\nctx status\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"

    Windsurf supports custom instructions and file-based context.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_5","level":3,"title":"Setup","text":"
    # Generate Windsurf configuration\nctx setup windsurf\n\n# Initialize context\nctx init\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_3","level":3,"title":"Configuration","text":"

    Add to Windsurf settings:

    // Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_2","level":3,"title":"Usage","text":"

    Context files appear in the file tree. Reference them when chatting:

    • \"What's in our task list?\" → AI reads .context/TASKS.md
    • \"What convention do we use for naming?\" → AI reads .context/CONVENTIONS.md
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#generic-integration","level":2,"title":"Generic Integration","text":"

    For any AI tool that can read files, use these patterns:

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"
    # Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"
    You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#automated-updates","level":3,"title":"Automated Updates","text":"

    If your AI tool outputs to a log, use ctx watch:

    # Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    The AI can emit updates like:

    <context-update type=\"complete\">implement caching</context-update>\n<context-update type=\"learning\"\n  context=\"Implementing caching layer\"\n  lesson=\"Important thing learned today\"\n  application=\"Apply this insight going forward\"\n>Caching Insight</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"

    The ctx watch command parses update commands from AI output. Use this format:

    <context-update type=\"TYPE\" [attributes]>Content</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes task TASKS.md None decision DECISIONS.md context, rationale, consequence learning LEARNINGS.md context, lesson, application convention CONVENTIONS.md None complete TASKS.md None","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (Tasks, Conventions, Complete)","text":"
    <context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (Learnings, Decisions)","text":"

    Learnings and decisions support structured attributes for better documentation:

    Learning with full structure:

    <context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Parse JSON stdin with the host language (Go, Python, etc.): no jq needed\"\n>Hook Input Format</context-update>\n

    Decision with full structure:

    <context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequence=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n

    Learnings require: context, lesson, application attributes. Decisions require: context, rationale, consequence attributes. Updates missing required attributes are rejected with an error.

    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#further-reading","level":2,"title":"Further Reading","text":"
    • Skills That Fight the Platform: Common pitfalls in skill design that work against the host tool
    • The Anatomy of a Skill That Works: What makes a skill reliable: the E/A/R framework and quality gates
    ","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/migration/","level":1,"title":"Integration","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#adopting-ctx-in-existing-projects","level":2,"title":"Adopting ctx in Existing Projects","text":"

    Claude Code User?

    You probably want the plugin instead of this page.

    Install ctx from the marketplace: (/plugin → search \"ctx\" → Install) and you're done: hooks, skills, and updates are handled for you.

    See Getting Started for the full walkthrough.

    This guide covers adopting ctx in existing projects regardless of which tools your team uses.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#quick-paths","level":2,"title":"Quick Paths","text":"You have... Command What happens Nothing (greenfield) ctx init Creates .context/, CLAUDE.md, permissions Existing CLAUDE.md ctx init --merge Backs up your file, inserts ctx block after the H1 Existing CLAUDE.md + ctx markers ctx init --reset Replaces the ctx block, leaves your content intact .cursorrules / .aider.conf.yml ctx init ctx ignores those files: they coexist cleanly Team repo, first adopter ctx init --merge && git add .context/ CLAUDE.md Initialize and commit for the team","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-claudemd","level":2,"title":"Existing CLAUDE.md","text":"

    This is the most common scenario:

    You have a CLAUDE.md with project-specific instructions and don't want to lose them.

    You Own CLAUDE.md

    After initialization, CLAUDE.md is yours: edit it freely.

    Add project instructions, remove sections you don't need, reorganize as you see fit.

    The only part ctx manages is the block between the <!-- ctx:context --> and <!-- ctx:end --> markers; everything outside those markers is yours to change at any time.

    If you remove the markers, nothing breaks: ctx simply treats the file as having no ctx content and will offer to merge again on the next ctx init.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-ctx-init-does","level":3,"title":"What ctx init Does","text":"

    When ctx init detects an existing CLAUDE.md, it checks for ctx markers (<!-- ctx:context --> ... <!-- ctx:end -->):

    State Default behavior With --merge With --force No CLAUDE.md Creates from template Creates from template Creates from template Exists, no ctx markers Prompts to merge Auto-merges (no prompt) Auto-merges (no prompt) Exists, has ctx markers Skips (already set up) Skips Replaces the ctx block only","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-merge-flag","level":3,"title":"The --merge Flag","text":"

    --merge auto-merges without prompting. The merge process:

    1. Backs up your existing CLAUDE.md to CLAUDE.md.<timestamp>.bak;
    2. Finds the H1 heading (e.g., # My Project) in your file;
    3. Inserts the ctx block immediately after it;
    4. Preserves everything else untouched.

    Your content before and after the ctx block remains exactly as it was.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#before-after-example","level":3,"title":"Before / After Example","text":"

    Before: your existing CLAUDE.md:

    # My Project\n\n## Build Commands\n\n-`npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n

    After ctx init --merge:

    # My Project\n\n<!-- ctx:context -->\n<!-- DO NOT REMOVE: This marker indicates ctx-managed content -->\n\n## IMPORTANT: You Have Persistent Memory\n\nThis project uses Context (`ctx`) for context persistence across sessions.\n...\n\n<!-- ctx:end -->\n\n## Build Commands\n\n- `npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n

    Your build commands and code style sections are untouched. The ctx block sits between markers and can be updated independently.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-force-flag","level":3,"title":"The --force Flag","text":"

    If your CLAUDE.md already has ctx markers (from a previous ctx init), the default behavior is to skip it. Use --force to replace the ctx block with the latest template: This is useful after upgrading ctx:

    ctx init --reset\n

    This only replaces content between <!-- ctx:context --> and <!-- ctx:end -->. Your own content outside the markers is preserved. A timestamped backup is created before any changes.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#undoing-a-merge","level":3,"title":"Undoing a Merge","text":"

    Every merge creates a backup:

    $ ls CLAUDE.md*.bak\nCLAUDE.md.1738000000.bak\n

    To restore:

    cp CLAUDE.md.1738000000.bak CLAUDE.md\n

    Or if you are using git, simply:

    git checkout CLAUDE.md\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-cursorrules-aider-copilot","level":2,"title":"Existing .cursorrules / Aider / Copilot","text":"

    ctx doesn't touch tool-specific config files. It creates its own files (.context/, CLAUDE.md) and coexists with whatever you already have.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-does-ctx-create","level":3,"title":"What Does ctx Create?","text":"ctx creates ctx does NOT touch .context/ directory .cursorrules CLAUDE.md (or merges into) .aider.conf.yml .claude/settings.local.json (seeded by ctx init; the plugin manages hooks and skills) .github/copilot-instructions.md .windsurfrules Any other tool-specific config

    Claude Code hooks and skills are provided by the ctx plugin, installed from the Claude Code marketplace (/plugin → search \"ctx\" → Install).

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#running-ctx-alongside-other-tools","level":3,"title":"Running ctx Alongside Other Tools","text":"

    The .context/ directory is the source of truth. Tool-specific configs point to it:

    • Cursor: Reference .context/ files in your system prompt (see Cursor setup)
    • Aider: Add .context/ files to the read: list in .aider.conf.yml (see Aider setup)
    • Copilot: Keep .context/ files open or reference them in comments (see Copilot setup)

    You can generate a tool-specific configuration with:

    ctx setup cursor    # Generate Cursor config snippet\nctx setup aider     # Generate .aider.conf.yml\nctx setup copilot   # Generate Copilot tips\nctx setup windsurf  # Generate Windsurf config\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#migrating-content-into-context","level":3,"title":"Migrating Content into .context/","text":"

    If you have project knowledge scattered across .cursorrules or custom prompt files, consider migrating it:

    1. Rules / invariants → .context/CONSTITUTION.md
    2. Code patterns → .context/CONVENTIONS.md
    3. Architecture notes → .context/ARCHITECTURE.md
    4. Known issues / tips → .context/LEARNINGS.md

    You don't need to delete the originals: ctx and tool-specific files can coexist. But centralizing in .context/ means every tool gets the same context.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#team-adoption","level":2,"title":"Team Adoption","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#context-is-designed-to-be-committed","level":3,"title":".context/ Is Designed to Be Committed","text":"

    The context files (tasks, decisions, learnings, conventions, architecture) are meant to live in version control. However, some subdirectories are personal or sensitive and should not be committed.

    ctx init automatically adds these .gitignore entries:

    # Journals contain full session transcripts: personal, potentially large\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Runtime state and logs (ephemeral, machine-specific):\n.context/state/\n.context/logs/\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n

    With those in place, committing is straightforward:

    # One person initializes\nctx init --merge\n\n# Commit context files (journals and keys are already gitignored)\ngit add .context/ CLAUDE.md\ngit commit -m \"Add ctx context management\"\ngit push\n

    Teammates pull and immediately have context. No per-developer setup needed.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-about-claude","level":3,"title":"What about .claude/?","text":"

    The .claude/ directory contains permissions that ctx init seeds. Hooks and skills are provided by the ctx plugin (not per-project files).

    File Commit? Why .claude/settings.local.json No Machine-specific, accumulates session permissions .claude/settings.golden.json Yes Curated permission snapshot (via ctx permission snapshot)","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#merge-conflicts-in-context-files","level":3,"title":"Merge Conflicts in Context Files","text":"

    Context files are plain Markdown. Resolve conflicts the same way you would for any other documentation file:

    # After a conflicting pull\ngit diff .context/TASKS.md    # See both sides\n# Edit to keep both sets of tasks, then:\ngit add .context/TASKS.md\ngit commit\n

    Common conflict scenarios:

    • TASKS.md: Two people added tasks: Keep both.
    • DECISIONS.md: Same decision recorded differently: Unify the entry.
    • LEARNINGS.md: Parallel discoveries: Keep both, remove duplicates.
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#gradual-adoption","level":3,"title":"Gradual Adoption","text":"

    You don't need the whole team to switch at once:

    1. One person runs ctx init --merge and commits;
    2. CLAUDE.md instructions work immediately for Claude Code users;
    3. Other tool users can adopt at their own pace using ctx setup <tool>;
    4. Context files benefit everyone who reads them, even without tool integration.
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verifying-it-worked","level":2,"title":"Verifying It Worked","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#activate-the-project","level":3,"title":"Activate the Project","text":"

    Tell ctx which .context/ directory to use for the rest of the verification steps:

    eval \"$(ctx activate)\"\n

    You only need to run this once per terminal. If you skip it, the status check below fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#check-status","level":3,"title":"Check Status","text":"
    ctx status\n

    You should see your context files listed with token counts and no warnings.

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#test-memory","level":3,"title":"Test Memory","text":"

    Start a new AI session and ask: \"Do you remember?\"

    The AI should cite specific context:

    • Current tasks from .context/TASKS.md;
    • Recent decisions or learnings;
    • Session history (if you've had prior sessions);

    If it responds with generic \"I don't have memory\", check that ctx is in your PATH (which ctx) and that hooks are configured (see Troubleshooting).

    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verify-the-merge","level":3,"title":"Verify the Merge","text":"

    If you used --merge, check that your original content is intact:

    # Your original content should still be there\ncat CLAUDE.md\n\n# The ctx block should be between markers\ngrep -c \"ctx:context\" CLAUDE.md  # Should print 1\ngrep -c \"ctx:end\" CLAUDE.md      # Should print 1\n
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#further-reading","level":2,"title":"Further Reading","text":"
    • Getting Started: Full setup walkthrough
    • Context Files: What each .context/ file does
    • Integrations: Per-tool setup (Claude Code, Cursor, Aider, Copilot)
    • CLI Reference: All ctx commands and flags
    ","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/release/","level":1,"title":"Cutting a Release","text":"

    Full Release Checklist

    This page covers the mechanics of cutting a release (bump, tag, push). For the complete pre-release ceremony (audits, tests, verification, and post-release steps), see the Release Checklist runbook.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#prerequisites","level":2,"title":"Prerequisites","text":"

    Before you can cut a release you need:

    • Push access to origin (GitHub)
    • GPG signing configured (make gpg-test)
    • Go installed (version in go.mod)
    • Zensical installed (make site-setup)
    • A clean working tree (git status shows nothing to commit)
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#step-by-step","level":2,"title":"Step-by-Step","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#1-update-the-version-file","level":3,"title":"1. Update the VERSION File","text":"
    echo \"0.9.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.9.0\"\n

    The VERSION file uses bare semver (0.9.0), no v prefix. The release script adds the v prefix for git tags.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#2-generate-release-notes","level":3,"title":"2. Generate Release Notes","text":"

    In Claude Code:

    /_ctx-release-notes\n

    This analyzes commits since the last tag and writes dist/RELEASE_NOTES.md. The release script refuses to proceed without this file.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#3-verify-docs-and-commit-any-remaining-changes","level":3,"title":"3. Verify Docs and Commit Any Remaining Changes","text":"
    /ctx-link-check    # audit docs for dead links\nmake audit          # full check: fmt, vet, lint, style, test\ngit status          # must be clean\n
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#4-run-the-release","level":3,"title":"4. Run the Release","text":"
    make release\n

    Or, if you are in a Claude Code session:

    /_ctx-release\n

    The release script does everything in order:

    Step What happens 1 Reads VERSION, verifies release notes exist 2 Verifies working tree is clean 3 Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) 4 Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) 5 Adds new row to versions.md 6 Rebuilds the documentation site (make site) 7 Commits all version and docs updates 8 Runs make test and make smoke 9 Builds binaries for all 6 platforms via hack/build-all.sh 10 Creates a signed git tag (v0.9.0) 11 Pushes the tag to origin 12 Updates and pushes the latest tag","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#5-github-ci-takes-over","level":3,"title":"5. GitHub CI Takes Over","text":"

    Pushing a v* tag triggers .github/workflows/release.yml:

    1. Checks out the tagged commit
    2. Runs the full test suite
    3. Builds binaries for all platforms
    4. Creates a GitHub Release with auto-generated notes
    5. Uploads binaries and SHA256 checksums
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#6-verify","level":3,"title":"6. Verify","text":"
    • GitHub Releases shows the new version
    • All 6 binaries are attached (linux/darwin x amd64/arm64, windows x amd64)
    • SHA256 files are attached
    • Release notes look correct
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#what-gets-updated-automatically","level":2,"title":"What Gets Updated Automatically","text":"

    The release script updates 8 files so you do not have to:

    File What changes internal/assets/claude/.claude-plugin/plugin.json Plugin version .claude-plugin/marketplace.json Marketplace version (2 fields) editors/vscode/package.json VS Code extension version editors/vscode/package-lock.json VS Code lock version (2 fields) docs/index.md Download URLs docs/home/getting-started.md Download URLs docs/operations/integrations.md VSIX filename version docs/reference/versions.md New version row + latest pointer

    The Go binary version is injected at build time via -ldflags from the VERSION file. No source file needs editing.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#build-targets-reference","level":2,"title":"Build Targets Reference","text":"Target What it does make release Full release (script + tag + push) make build Build binary for current platform make build-all Build all 6 platform binaries make test Unit tests make smoke Integration smoke tests make audit Full check (fmt + vet + lint + drift + docs + test) make site Rebuild documentation site","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#release-notes-not-found","level":3,"title":"\"Release Notes Not Found\"","text":"
    ERROR: dist/RELEASE_NOTES.md not found.\n

    Run /_ctx-release-notes in Claude Code first, or write dist/RELEASE_NOTES.md manually.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#working-tree-is-not-clean","level":3,"title":"\"Working Tree Is Not Clean\"","text":"
    ERROR: Working tree is not clean.\n

    Commit or stash all changes before running make release.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#tag-already-exists","level":3,"title":"\"Tag Already Exists\"","text":"
    ERROR: Tag v0.9.0 already exists.\n

    You cannot release the same version twice. Either bump VERSION to a new version, or delete the old tag if the release was incomplete:

    git tag -d v0.9.0\ngit push origin :refs/tags/v0.9.0\n
    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#ci-build-fails-after-tag-push","level":3,"title":"CI Build Fails After Tag Push","text":"

    The tag is already published. Fix the issue, bump to a patch version (e.g. 0.9.1), and release again. Do not force-push tags that others may have already fetched.

    ","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/upgrading/","level":1,"title":"Upgrade","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade","level":2,"title":"Upgrade","text":"

    New versions of ctx may ship updated permissions, CLAUDE.md directives, or plugin hooks and skills.

    Claude Code User?

    The marketplace can update skills, hooks, and prompts independently: /plugin → select ctx → Update now (or enable auto-update).

    The ctx binary is separate: rebuild from source or download a new release when one is available, then run ctx init --reset --merge. Knowledge files are preserved automatically.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#tldr","level":2,"title":"TL:DR","text":"
    # Plugin users (Claude Code)\n# /plugin → select ctx → Update now\n# Then update the binary and reinitialize:\nctx init --reset --merge\n\n# From-source / manual users\n# install new ctx binary, then:\nctx init --reset --merge\n# /plugin → select ctx → Update now   (if using Claude Code)\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-changes-between-versions","level":2,"title":"What Changes between Versions","text":"

    ctx init generates two categories of files:

    Category Examples Changes between versions? Infrastructure .claude/settings.local.json (permissions), ctx-managed sections in CLAUDE.md, ctx plugin (hooks + skills) Yes Knowledge .context/TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md, ARCHITECTURE.md, GLOSSARY.md, CONSTITUTION.md, AGENT_PLAYBOOK.md No: this is your data

    Infrastructure is regenerated by ctx init and plugin updates. Knowledge files are yours and should never be overwritten.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade-steps","level":2,"title":"Upgrade Steps","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#1-install-the-new-version","level":3,"title":"1. Install the New Version","text":"

    Build from source or download the binary:

    cd /path/to/ctx-source\ngit pull\nmake build\nsudo make install\nctx --version   # verify\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#2-reinitialize","level":3,"title":"2. Reinitialize","text":"
    ctx init --reset --merge\n
    • --force regenerates infrastructure files (permissions, ctx-managed sections in CLAUDE.md).
    • --merge preserves your content outside ctx markers.

    Knowledge files (.context/TASKS.md, DECISIONS.md, etc.) are preserved automatically: ctx init only overwrites infrastructure, never your data.

    Encryption key: The encryption key lives at ~/.ctx/.ctx.key (outside the project). Reinit does not affect it. If you have a legacy key at .context/.ctx.key or ~/.local/ctx/keys/, copy it manually (see Syncing Scratchpad Notes).

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#3-update-the-ctx-plugin","level":3,"title":"3. Update the ctx Plugin","text":"

    If you use Claude Code, update the plugin to get new hooks and skills:

    1. Open /plugin in Claude Code.
    2. Select ctx.
    3. Click Update now.

    Or enable auto-update so the plugin stays current without manual steps.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#4-review-custom-settings","level":3,"title":"4. Review Custom Settings","text":"

    If you added custom permissions to .claude/settings.local.json beyond what ctx init provides, diff and merge:

    diff .claude.bak/settings.local.json .claude/settings.local.json\n

    Manually add back any custom entries that the new init dropped.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#5-verify","level":3,"title":"5. Verify","text":"

    Activate the project first, otherwise ctx status and ctx drift will fail with Error: no context directory specified:

    eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#6-clean-up","level":3,"title":"6. Clean Up","text":"

    If you made manual backups, remove them once satisfied:

    rm -rf .context.bak .claude.bak CLAUDE.md.bak\n
    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-if-i-skip-the-upgrade","level":2,"title":"What If I Skip the Upgrade?","text":"

    The old binary still works with your existing .context/ files. But you may miss:

    • New plugin hooks that enforce better practices or catch mistakes;
    • Updated skill prompts that produce better results;
    • New .gitignore entries for directories added in newer versions;
    • Bug fixes in the CLI itself.

    The plugin and the binary can be updated independently. You can update the plugin (for new hooks/skills) even if you stay on an older binary, and vice versa.

    Context files are plain Markdown: They never break between versions.

    The surrounding infrastructure is what evolves.

    ","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/","level":1,"title":"Architecture Exploration","text":"","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#architecture-exploration","level":1,"title":"Architecture Exploration","text":"

    Systematically build architecture documentation across one or more repositories using ctx skills. Each invocation does one unit of work; a simple loop drives the agent through all phases.

    When to use: When onboarding to a new codebase, performing architecture reviews, or building up .context/ documentation across a workspace of repos.

    Prerequisites: ctx installed, repos cloned under a shared workspace directory (e.g., ~/WORKSPACE/).

    Companion skills:

    • /ctx-architecture: structural baseline and principal analysis
    • /ctx-architecture-enrich: code intelligence enrichment via GitNexus
    • /ctx-architecture-failure-analysis: adversarial failure analysis
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#overview","level":2,"title":"Overview","text":"

    The agent progresses through phases per repo, depth-first:

    Phase Skill What it does bootstrap ctx init + /ctx-architecture Initialize context and build structural baseline principal /ctx-architecture principal Deep analysis: vision, bottlenecks, alternatives enriched /ctx-architecture-enrich Quantify with code intelligence (blast radius, flows) frontier-N /ctx-architecture (re-run) Explore unexplored areas found in convergence report lens-* /ctx-architecture with lens Focused exploration through conceptual lenses

    Exploration stops when convergence >= 0.85, frontier runs plateau, or all lenses are exhausted.

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#setup","level":2,"title":"Setup","text":"

    Create a tracking directory in your workspace root:

    cd ~/WORKSPACE\nmkdir -p .arch-explorer\n

    Create .arch-explorer/manifest.json listing your repos:

    {\n  \"repos\": [\"ctx\", \"portal\", \"infra\"],\n  \"current_repo_index\": 0,\n  \"progress\": {}\n}\n

    Create .arch-explorer/run-log.md (empty, the agent appends to it).

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#prompt","level":2,"title":"Prompt","text":"

    Save this as .arch-explorer/PROMPT.md and invoke with your agent. The prompt is self-contained: the agent reads the manifest, picks the next unit of work, executes it, updates tracking, and stops.

    You are an autonomous architecture exploration agent. Your job is to\nsystematically build and evolve architecture documentation across all\nrepositories in this workspace using `ctx` skills.\n\n## Execution Protocol\n\n### Step 1: Read State\n\nRead `.arch-explorer/manifest.json`. This tells you:\n- Which repos exist and their order\n- What has been done per repo (`progress` object)\n- Which repo to work on next (`current_repo_index`)\n\n### Step 2: Pick the Next Unit of Work\n\n**Strategy: depth-first, sequential.**\n\nFind the current repo (by `current_repo_index`). Determine its next\nphase from the progression below. If all phases are exhausted for this\nrepo (convergence score >= 0.85 or 3+ frontier runs with no new\nfindings), advance `current_repo_index` and pick the next repo.\n\n### Phase Progression (per repo)\n\nEach repo progresses through these phases in order:\n\n| Phase | Skill | Prerequisite |\n|-------|-------|-------------|\n| `bootstrap` | `ctx init` + `/ctx-architecture` | None |\n| `principal` | `/ctx-architecture principal` | bootstrap done |\n| `enriched` | `/ctx-architecture-enrich` | principal done, GitNexus indexed |\n| `frontier-N` | `/ctx-architecture` (re-run) | enriched done |\n\n**`bootstrap` is a single composite unit:** `ctx init` followed by\nstructural analysis. This is the ONLY phase that combines two actions.\nNo other phase may chain actions.\n\n**Frontier runs** are numbered: `frontier-1`, `frontier-2`, etc.\nEach frontier run reads CONVERGENCE-REPORT.md and picks unexplored\nareas. The skill handles this automatically.\n\nAfter the third frontier run OR when convergence >= 0.85, apply\n**conceptual lenses** (one per run):\n\n| Lens | Focus Areas |\n|------|-------------|\n| `security` | Auth flows, input validation, secrets, attack surfaces, trust boundaries |\n| `performance` | Hot paths, caching, concurrency, resource lifecycle, allocation patterns |\n| `stability` | Error handling, retries, graceful degradation, circuit breakers, timeouts |\n| `observability` | Logging, metrics, tracing, alerting, debugging affordances |\n| `data-integrity` | Storage, serialization, migrations, consistency, backup, recovery |\n\nFor lens runs, prepend the lens context as an explicit instruction to\nthe skill invocation:\n\n> \"Focus exploration on security: auth flows, input validation, secrets,\n> attack surfaces, trust boundaries.\"\n\nDo NOT wait for the skill to ask what to explore. Provide the lens\nfocus as input upfront.\n\n### Step 3: Do the Work\n\n1. `cd` into the sub-repo directory (`~/WORKSPACE/<repo-name>`, NOT\n   `~/WORKSPACE` itself).\n2. Verify `CTX_DIR` already points at THIS sub-repo's `.context/`:\n\n    ```bash\n    test \"$CTX_DIR\" = \"$PWD/.context\" || {\n      echo \"STOP: CTX_DIR=$CTX_DIR but this sub-repo needs $PWD/.context.\"\n      echo \"Re-launch the agent with CTX_DIR set to the sub-repo:\"\n      echo \"  cd $PWD && CTX_DIR=\\\"\\$PWD/.context\\\" claude --print 'Follow .arch-explorer/PROMPT.md' --allowedTools '*'\"\n      exit 1\n    }\n    ```\n\n    If it fails, STOP. The agent cannot change `CTX_DIR` for itself:\n    child shells and skill invocations inherit the parent Claude\n    process environment, which only the caller can control. Do not\n    proceed, do not run `ctx` commands, do not skip the check.\n3. If phase is `bootstrap`:\n    - Run `ctx init`, confirm `.context/` exists.\n    - Then run `/ctx-architecture` (structural baseline).\n4. If phase is `principal` or `frontier-*`:\n    - Run `/ctx-architecture` (add `principal` argument for principal phase).\n    - The skill will read existing artifacts and build on them.\n5. If phase is `enriched`:\n    - Verify GitNexus is connected: call `mcp__gitnexus__list_repos`.\n    - Success = non-empty list returned with no error.\n    - If GitNexus unavailable, log as `enriched-skipped` and advance\n      to `frontier-1`.\n    - Run `/ctx-architecture-enrich`.\n6. If phase is a lens run (`lens-security`, etc.):\n    - Run `/ctx-architecture` with lens focus prepended as instruction\n      (see lens table above for exact wording).\n\n### Step 4: Extract Results\n\nAfter the skill completes, gather:\n\n- **Convergence score**: from `map-tracking.json`, computed as:\n  average of all module `confidence` values (0.0-1.0). If\n  `map-tracking.json` is missing or has no confidence values,\n  record `null` and log a warning.\n- **Frontier count**: from CONVERGENCE-REPORT.md, count the number\n  of listed unexplored areas. If CONVERGENCE-REPORT.md is missing,\n  record `frontier_count: null` and log a warning. Treat missing\n  as \"exploration should continue\" (do not stall).\n- **Key findings**: 2-3 bullet points of what was discovered or\n  changed in this run (new modules mapped, danger zones found, etc.)\n- **New artifacts**: list any new files created in `.context/`\n\n### Step 5: Update Tracking\n\nUpdate `.arch-explorer/manifest.json`:\n\n```json\n{\n  \"progress\": {\n    \"ctx\": {\n      \"phases_completed\": [\"bootstrap\", \"principal\"],\n      \"current_phase\": \"enriched\",\n      \"lenses_explored\": [],\n      \"last_run\": \"2026-04-07T14:00:00Z\",\n      \"convergence_score\": 0.72,\n      \"frontier_count\": 3,\n      \"total_runs\": 2,\n      \"findings_summary\": \"14 modules mapped, 3 danger zones, 2 extension points\"\n    }\n  }\n}\n```\n\nAppend to `.arch-explorer/run-log.md`:\n\n```markdown\n## 2026-04-07T14:00:00Z / ctx / principal\n\n**Phase:** principal\n**Convergence:** 0.45 -> 0.72\n**Frontiers remaining:** 3\n**Key findings:**\n- Identified CLI dispatch as primary bottleneck (fan-out to 12 subsystems)\n- Security: context files readable by any process (no access control)\n- Strategic recommendation: extract context engine into library package\n\n**Artifacts updated:** ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md, map-tracking.json\n```\n\n### Step 6: Report and Stop\n\nPrint this exact format as the FINAL output of the invocation:\n\n```\n[arch-explorer] DONE\n  repo: ctx\n  phase: principal\n  convergence: 0.72\n  frontiers: 3\n  runs_on_repo: 3\n  next: ctx / enriched\n```\n\nThe `[arch-explorer] DONE` line is the terminal marker. After printing\nit, produce no further output. Execution is complete.\n\n## Rules\n\n1. **One unit per invocation.** The only composite unit is `bootstrap`\n   (init + structural). All other phases are exactly one skill run.\n2. **Additive only.** Never delete or overwrite existing artifacts.\n   The skills already handle incremental updates.\n3. **No duplicated work.** Read manifest before acting. If a phase is\n   already recorded as completed, skip it.\n4. **Log everything.** Every run gets a run-log entry, even failures\n   and skips.\n5. **Fail gracefully.** If a skill fails (missing GitNexus, broken repo,\n   etc.), log the failure with reason and advance to the next phase or\n   repo. Don't retry in the same invocation.\n6. **Respect `ctx` conventions.** Each repo gets its own `.context/`\n   directory. Never write architecture artifacts outside `.context/`.\n\n## Stopping Logic\n\nA repo is considered \"explored\" when ANY of these is true:\n- Convergence score >= 0.85 (from map-tracking.json)\n- 3+ frontier runs produced no new findings (frontier_count unchanged\n  across consecutive runs)\n- All 5 lenses have been applied\n- Convergence score is `null` after 3 attempts (artifacts aren't being\n  generated properly; log warning and move on)\n\nWhen a repo is explored, advance `current_repo_index` in the manifest.\n\n## When All Repos Are Done\n\nWhen every repo has reached its stopping condition, print:\n\n```\n[arch-explorer] ALL DONE\n  - ctx: 0.92 convergence, 8 runs, 5 lenses\n  - portal: 0.87 convergence, 6 runs, 3 lenses\n  ...\n```\n
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#invocation","level":2,"title":"Invocation","text":"

    The caller MUST set CTX_DIR to the sub-repo the agent will work on. The agent verifies this at Step 3.2 and stops if it does not match. The wrapper reads the manifest to pick the current sub-repo, then launches claude with CTX_DIR pinned to that sub-repo's .context/.

    Single run (safest for quota):

    cd ~/WORKSPACE\nREPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\nCTX_DIR=\"$PWD/$REPO/.context\" \\\n  claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n

    Batch of N runs:

    cd ~/WORKSPACE\nfor i in $(seq 1 5); do\n  REPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\n  CTX_DIR=\"$PWD/$REPO/.context\" \\\n    claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n  echo \"--- Run $i complete (repo: $REPO) ---\"\ndone\n

    Resume after interruption:

    Just run the wrapper again. The manifest tracks state; the agent picks up where it left off. CTX_DIR is recomputed from the manifest on each invocation, so the right sub-repo is always bound.

    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#tips","level":2,"title":"Tips","text":"
    • Start small: list 1-2 repos in the manifest first. Add more once you're confident in the output quality.
    • GitNexus is optional: the enrichment phase is skipped gracefully if GitNexus isn't connected. You still get structural and principal analysis.
    • Review between batches: check the run-log and generated artifacts between batch runs. The agent is additive-only, but early course correction saves wasted runs.
    • Lens runs are the payoff: the first three phases build the map; lens runs find the interesting things (security gaps, performance cliffs, stability risks).
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#history","level":2,"title":"History","text":"
    • 2026-04-07: Original prompt created as hack/agents/architecture-explorer.md.
    • 2026-04-16: Moved to docs as a runbook for discoverability.
    • 2026-04-20: Added CTX_DIR verification at Step 3.2 and per-invocation CTX_DIR binding in the wrapper, so the agent writes artifacts to the sub-repo's .context/ instead of the inherited workspace one.
    ","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/backup-strategy/","level":1,"title":"Backup Strategy","text":"

    ctx backup was removed. File-level backup is not ctx's responsibility; your OS or a dedicated backup tool handles it better and without locking you into a specific mount strategy.

    This runbook explains what to back up, how ctx hub reduces the surface, and what options exist for the rest.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-to-back-up","level":2,"title":"What To Back Up","text":"

    Per project:

    • .context/: all context files, journal, state, scratchpad.
    • .claude/: Claude Code settings, hooks, skills specific to the project. Skip this entry when it lives in git; the repo is the backup.

    Per user:

    • ~/.ctx/: global config, the encryption key (~/.ctx/.ctx.key), hub data directory (if running a local hub).
    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#how-hub-reduces-backup-needs","level":2,"title":"How Hub Reduces Backup Needs","text":"

    ctx hub replicates the knowledge surface across machines:

    • DECISIONS.md
    • LEARNINGS.md
    • CONVENTIONS.md
    • CONSTITUTION.md
    • ARCHITECTURE.md
    • Task items promoted to hub

    If you run ctx hub (as a server or by subscribing to someone else's), the data that matters most survives losing any single machine.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-hub-does-not-replicate","level":2,"title":"What Hub Does Not Replicate","text":"

    Hub is not a file-level backup. The following still live only on the machine that produced them:

    • Journal entries (.context/journal/*.md)
    • Runtime state (.context/state/*)
    • Session event log (.context/events.jsonl)
    • Scratchpad (.context/.pad)
    • Encrypted notify/webhook config (.context/.notify.enc)
    • The encryption key itself (~/.ctx/.ctx.key)

    If you need those to survive a disk failure, use a file-level backup.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#example-strategies","level":2,"title":"Example Strategies","text":"","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#1-cron-rsync-to-nas-or-external-drive","level":3,"title":"1. cron + rsync to NAS or External Drive","text":"
    # Daily at 03:00, mirror ~/WORKSPACE and ~/.ctx to NAS\n0 3 * * * rsync -a --delete \\\n    --exclude='node_modules' \\\n    --exclude='dist' \\\n    --exclude='.context/state' \\\n    ~/WORKSPACE/ /mnt/nas/backup/workspace/\n0 3 * * * rsync -a --delete ~/.ctx/ /mnt/nas/backup/ctx-global/\n

    Adjust excludes for the trash you don't want to back up. The .context/state/ dir is ephemeral per-session; skip it.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#2-cron-cp-to-a-cloud-synced-directory","level":3,"title":"2. cron + cp to a Cloud-Synced Directory","text":"

    iCloud Drive, Dropbox, or any directory watched by a sync client:

    0 3 * * * cp -a ~/WORKSPACE/some-project/.context \\\n    ~/CloudDrive/ctx-backups/some-project/$(date +\\%Y-\\%m-\\%d)\n

    Daily snapshots, cloud provider handles the replication.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#3-time-machine-macos","level":3,"title":"3. Time Machine (macOS)","text":"

    If you already run Time Machine, ensure ~/WORKSPACE and ~/.ctx are not in its exclusion list. Time Machine handles versioning; you get point-in-time recovery for free.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#4-borg-or-restic-for-versioned-backups","level":3,"title":"4. Borg or restic for Versioned Backups","text":"

    For deduplicated, versioned, encrypted backups:

    # Borg init (once)\nborg init --encryption=repokey /mnt/nas/borg-ctx\n\n# Daily backup\nborg create /mnt/nas/borg-ctx::'ctx-{now}' \\\n    ~/WORKSPACE ~/.ctx \\\n    --exclude '*/node_modules' \\\n    --exclude '*/.context/state'\n

    Use restic if you prefer S3-compatible targets.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#when-you-still-need-file-level-backup-even-with-hub","level":2,"title":"When You Still Need File-Level Backup Even With Hub","text":"
    • Journal: session histories are local-only until exported.
    • Scratchpad: private notes, encrypted locally.
    • Encryption key: losing ~/.ctx/.ctx.key means losing access to every encrypted file in every project.
    • Non-hub projects: projects that never called ctx hub register have zero cross-machine persistence.

    For these, pick one strategy above and forget about it.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#why-ctx-no-longer-ships-a-backup-command","level":2,"title":"Why ctx No Longer Ships a Backup Command","text":"

    Backup is inherently environment-specific: SMB, NFS, S3, rsync, Time Machine, Borg, restic. Every user has a different story. The previous ctx backup picked SMB via GVFS, which was Linux-only and narrow. Chasing mount strategies would never generalize.

    Hub is the right answer for the data ctx owns (knowledge). For everything else, your OS or a dedicated backup tool is the right layer.

    ","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/breaking-migration/","level":1,"title":"Breaking Migration","text":"","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#breaking-migration-guide","level":1,"title":"Breaking Migration Guide","text":"

    Template for upgrading across breaking CLI renames or behavior changes. Use this as a starting point when writing migration notes for a specific release, or hand it to your agent as context for generating release-specific guidance.

    When to use: When a release includes breaking changes (command renames, removed flags, changed defaults) that require user action.

    Companion: Upgrade guide covers the general upgrade flow. This runbook covers the breaking-change specifics.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-1-identify-what-changed","level":2,"title":"Step 1: Identify What Changed","text":"

    Ask your agent to diff the CLI surface between the old and new version:

    Compare the CLI command surface between the previous release tag\nand HEAD. For each change, categorize as: renamed, removed,\nnew, or changed-behavior. Include old and new command signatures.\n

    Or use the /_ctx-command-audit skill after the rename.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-2-regenerate-infrastructure","level":2,"title":"Step 2: Regenerate Infrastructure","text":"
    # Install the new binary\nmake build && sudo make install\n\n# Regenerate CLAUDE.md and permissions\nctx init --reset --merge\n

    --merge preserves your knowledge files (TASKS.md, DECISIONS.md, etc.) while regenerating infrastructure (permissions, CLAUDE.md managed sections).

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-3-update-the-plugin","level":2,"title":"Step 3: Update the Plugin","text":"
    /plugin -> select ctx -> Update now\n

    Or, if using a local clone:

    make plugin-reload\n# restart Claude Code\n
    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-4-update-personal-scripts","level":2,"title":"Step 4: Update Personal Scripts","text":"

    Search your scripts and aliases for old command names:

    # Example: find references to old command names\ngrep -r \"ctx old-command\" ~/scripts/ ~/.zshrc ~/.bashrc\n

    Replace with the new names per the changelog.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-5-update-hook-configs","level":2,"title":"Step 5: Update Hook Configs","text":"

    If you have custom hooks in .claude/settings.local.json that reference ctx commands, update them:

    jq '.hooks' .claude/settings.local.json | grep \"ctx \"\n
    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-6-verify","level":2,"title":"Step 6: Verify","text":"

    Activate the project first, otherwise ctx status and ctx drift will fail with Error: no context directory specified:

    eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\nmake test           # if you're a contributor\n

    See Activating a Context Directory.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#writing-release-specific-migration-notes","level":2,"title":"Writing Release-Specific Migration Notes","text":"

    When preparing a release with breaking changes, create a section in the release notes using this template:

    ## Breaking Changes\n\n### `old-command` renamed to `new-command`\n\n**What changed**: `ctx old-command` is now `ctx new-command`.\nThe old name is removed (no deprecation alias).\n\n**Action required**:\n1. Run `ctx init --reset --merge` to update CLAUDE.md\n2. Update any scripts referencing `ctx old-command`\n3. Update hook configs if applicable\n\n**Why**: [brief rationale for the rename]\n

    Repeat for each breaking change. Users should be able to follow the notes mechanically without needing to understand the codebase.

    ","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/codebase-audit/","level":1,"title":"Codebase Audit","text":"","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#codebase-audit","level":1,"title":"Codebase Audit","text":"

    A structured audit of the codebase: dead code, magic strings, documentation drift, security surface, and roadmap opportunities.

    When to run: Before a release, after a long YOLO sprint, quarterly, or when planning the next phase of work.

    Time: ~15-30 minutes with a team of agents.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#how-to-use-this-runbook","level":2,"title":"How to Use This Runbook","text":"

    Start a Claude Code session with a clean git state (git stash or commit first). Paste or adapt the prompt below. The agent does the analysis; you triage the findings.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#prompt","level":2,"title":"Prompt","text":"
    I want you to create an agent team to audit this codebase. Save each report as\na separate markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable: every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (session mining)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (godoc + inline)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check package-level comments match\npackage names. Output: drift items ranked by severity with exact file:line refs.\n\n### 3. Maintainability\nLook for: functions >80 lines that have logical split points; switch blocks\nwith >5 cases that could be table-driven or extracted; inline comments that\nsay \"step 1\", \"step 2\" or similar (sign the block wants to be a function);\nfiles with >400 lines; packages with flat structure that could benefit from\nsub-packages; functions that seem misplaced in their file. Do NOT flag\nthings that are fine as-is just because they could theoretically be different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app: focus on CLI-relevant attack surface, not web OWASP:\nfile path traversal (does user input flow into file paths unsanitized?),\ncommand injection (does user input flow into exec calls?), symlink following\n(does the tool follow symlinks when writing to .context/?), permission\nhandling (are file permissions set correctly?), sensitive data in outputs\n(do any commands leak secrets or session content?). Output: findings with\nseverity ratings and exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git log,\nrecent session discussions, and DECISIONS.md for story arcs worth writing\nabout. Suggest 3-5 blog post themes with: title, angle, target audience,\nkey commits/sessions to reference, and a 2-sentence pitch. Prioritize\nthemes that build a coherent narrative across posts.\n\n### 6. Roadmap & Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses:\nwhat are the highest-value improvements? Consider: user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with effort/impact estimates (not time estimates).\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and any user docs. Suggest improvements\nstructured as use-case pages: the problem, how ctx solves it, typical\nworkflow, gotchas. Identify gaps where a user would get stuck without\nreading source code. Output: list of documentation gaps and suggested\npage outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each: team composition (roles, agent types),\ntask distribution strategy, coordination approach, and which types of work\nit suits. Ground suggestions in actual project patterns, not generic advice.\n
    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#tips","level":2,"title":"Tips","text":"
    • Clean state matters: the prompt says \"no code changes\" but accidents happen. Start from a clean git state so you can git checkout . if needed.

    • Adjust scope: drop analyses you don't need. Analyses 1-4 are the most actionable. Analyses 5-8 are planning/creative and can be skipped if you just want a technical audit.

    • Reports feed TASKS.md: after the audit, read each report and create tasks in the appropriate Phase section. The reports are input, not output.

    • ideas/ is gitignored: reports saved there won't be committed. Move specific findings to TASKS.md, DECISIONS.md, or LEARNINGS.md to persist them.

    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#history","level":2,"title":"History","text":"
    • 2026-02-08: Original prompt created after a codebase audit sprint.
    • 2026-02-17: Improved with read-only agents, report structure template, CLI-scoped security review, and maintainability thresholds.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/","level":1,"title":"Docs Semantic Audit","text":"","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#documentation-semantic-audit","level":1,"title":"Documentation Semantic Audit","text":"

    Find structural problems that linters and link checkers cannot: weak pages that should be merged, heavy pages that should be split, missing cross-links, and narrative arcs that don't land.

    When to run: Before a release, after adding several new pages, when the site feels sprawling, or when you suspect narrative gaps.

    Time: ~20-40 minutes with an agent session.

    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#why-this-is-a-runbook","level":2,"title":"Why This Is a Runbook","text":"

    These judgments are inherently subjective and context-dependent. A page is \"weak\" relative to its neighbors; a narrative arc only matters if the docs intend to tell a story. Deterministic tools (broken-link checkers, word counters) can't do this. An LLM reading the full doc set can.

    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#prompt","level":2,"title":"Prompt","text":"

    Paste or adapt the following into a Claude Code session. The agent needs read access to docs/ and the site nav structure.

    Read every file under docs/ (including docs/blog/ and docs/recipes/).\nFor each file, note: title, word count, outbound links, inbound links\n(how many other pages link to it), and a one-line summary of its purpose.\n\nThen produce a report with these sections:\n\n## 1. Weak Dangling Pages\n\nPages that are thin, isolated, or redundant. Signs:\n- Under ~300 words with no unique content (just restates what another page says)\n- Zero or one inbound links (orphaned in the nav)\n- Content that would be stronger merged into an adjacent page\n- \"Try it in 5 minutes\" sections that assume installation already happened\n- Pages whose title doesn't work as a nav entry (too long, too vague)\n\nFor each: identify the page, explain why it's weak, and recommend\nmerge target or deletion.\n\n## 2. Overly Heavy Pages\n\nPages doing too much. Signs:\n- Over ~1500 words with multiple distinct topics\n- More than 4 H2 sections that could stand alone\n- Reader has to scroll past irrelevant content to find what they need\n- Mixed audience (beginner setup + advanced config on same page)\n\nFor each: identify the page, list the distinct topics, and suggest\nsplit points.\n\n## 3. Missing Cross-Links\n\nPlaces where a reader would naturally want to jump to related content\nbut no link exists. Look for:\n- Concepts mentioned but not linked (e.g., \"scratchpad\" without linking\n  to the scratchpad page)\n- Blog posts that describe features without linking to the reference docs\n- Recipes that reference workflows without linking to the relevant\n  getting-started section\n- Pages that end without a \"Next Up\" or \"See Also\" pointer\n\nFor each: source page, anchor text, suggested link target.\n\n## 4. Narrative Gaps\n\nThe docs should tell a coherent story: problem -> install -> first session\n-> daily workflow -> advanced patterns -> contributing. Look for:\n- Gaps in the progression (e.g., no bridge from \"first session\" to\n  \"daily habits\")\n- Blog posts that introduce concepts the reference docs don't cover\n- Recipes that assume knowledge no other page teaches\n- Features documented in CLI reference but missing from workflows/recipes\n\nFor each: describe the gap and suggest what page or section would fill it.\n\n## 5. Blog Cross-Linking Opportunities\n\nBlog posts are often written in isolation. Look for:\n- Posts that cover the same theme but don't reference each other\n- Posts that describe the evolution of a feature (natural \"part 1 / part 2\")\n- Posts that would benefit from a \"Related posts\" footer\n- Thematic clusters that could be linked from a recipe or reference page\n\nFor each: list the posts, the shared theme, and the suggested links.\n\n## Output Format\n\nFor every finding, include:\n- File path (docs/whatever.md)\n- Severity: high (actively confusing), medium (missed opportunity),\n  low (nice to have)\n- Concrete recommendation (merge into X, split at H2 Y, add link to Z)\n\nEnd with a prioritized action list: what to fix first.\n
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#after-the-audit","level":2,"title":"After the Audit","text":"
    1. Triage findings: not everything needs fixing. Focus on high severity.
    2. Merge weak pages first: fewer pages is almost always better.
    3. Add cross-links: cheapest improvement, highest reader impact.
    4. File split decisions in DECISIONS.md: page splits are architectural.
    5. Regenerate the site and spot-check nav after structural changes.
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#history","level":2,"title":"History","text":"
    • 2026-02-17: Created after merging docs/re-explaining.md into docs/about.md, which surfaced the pattern of weak standalone pages that dilute rather than add.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/hub-deployment/","level":1,"title":"Hub Deployment","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#hub-deployment","level":1,"title":"Hub Deployment","text":"

    Linear runbook for setting up a ctx Hub for yourself or a team. Consolidates pieces currently scattered across hub recipes and operations docs.

    When to use: First-time hub setup, or when onboarding a new team onto an existing hub.

    Prerequisites: ctx binary installed, network connectivity between hub and clients.

    Companion docs:

    • Hub overview: what the hub is and is not
    • Hub operations: data directory, systemd, backup, monitoring
    • Hub failure modes: what can go wrong
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"Quick Start (foreground)Production (systemd)
    ctx hub start\n

    See Hub Operations: Systemd Unit for the full unit file.

    sudo systemctl enable --now ctx-hub\n

    The hub creates admin.token on first start. Save this token; it is the only way to register clients.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-2-generate-the-admin-token","level":2,"title":"Step 2: Generate the Admin Token","text":"

    On first start, the hub writes admin.token to the data directory (default ~/.ctx/hub-data/):

    cat ~/.ctx/hub-data/admin.token\n

    This token has full admin privileges. Keep it secret.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-3-register-clients","level":2,"title":"Step 3: Register Clients","text":"

    For each client (person or machine) that will connect:

    # On the hub machine\nctx hub register --name \"volkan-laptop\" --admin-token <admin-token>\n

    This returns a client token. Distribute it securely to the client.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-4-connect-clients","level":2,"title":"Step 4: Connect Clients","text":"

    On each client machine, register the project with the hub. The ctx hub * commands above run on the hub server itself and don't need a project. The ctx connection * commands below are different: they live inside a project (the encrypted hub config is stored at .context/.connect.enc), so you have to tell ctx which project first.

    # In the project directory on the client machine:\neval \"$(ctx activate)\"\nctx connection register <hub-address> --token <client-token>\n

    Verify the connection:

    ctx connection status\n

    If the client doesn't have a project yet, run ctx init first, then eval \"$(ctx activate)\". See Activating a Context Directory.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-5-verify-sync","level":2,"title":"Step 5: Verify Sync","text":"

    Push a test entry from one client and verify it arrives. Make sure each client already ran eval \"$(ctx activate)\" from Step 4: otherwise ctx add and ctx status fail with Error: no context directory specified.

    # Client A (in its project directory, after activating):\nctx learning add \"Hub sync test\" --context \"Verifying hub setup\"\n\n# Client B (in its project directory, after activating):\nctx status   # should show the new learning\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-6-configure-backup","level":2,"title":"Step 6: Configure Backup","text":"

    Set up regular backups of the hub data directory. See Hub Operations: Backup and Restore.

    Minimum:

    # Add to cron\n0 */6 * * * cp ~/.ctx/hub-data/entries.jsonl ~/backups/entries-$(date +\\%F).jsonl\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-7-configure-tls-when-available","level":2,"title":"Step 7: Configure TLS (When Available)","text":"

    Coming Soon

    TLS support is planned (H-01/H-02). Until then, run the hub on a trusted network or behind a reverse proxy with TLS termination.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#team-onboarding-checklist","level":2,"title":"Team Onboarding Checklist","text":"

    When adding a new team member to an existing hub:

    • Generate a client token (ctx hub register --name \"<name>\")
    • Share the token and hub address securely
    • Have them run ctx connect <hub-address> --token <token>
    • Verify with ctx connection status
    • Point them to the Hub Getting Started recipe
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#connection-refused","level":3,"title":"\"Connection Refused\"","text":"

    The hub isn't running or the port is wrong. Check:

    ctx hub status          # on the hub machine\nss -tlnp | grep 9900   # default port\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#authentication-failed","level":3,"title":"\"Authentication Failed\"","text":"

    The client token is wrong or was never registered. Re-register:

    ctx hub register --name \"<name>\" --admin-token <admin-token>\n
    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#entries-not-syncing","level":3,"title":"Entries Not Syncing","text":"

    Check that the client is listening:

    ctx connection status\n

    If connected but not syncing, check the hub logs for sequence mismatch errors. See Hub Failure Modes for details.

    ","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/new-contributor/","level":1,"title":"New Contributor","text":"","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#new-contributor-onboarding","level":1,"title":"New Contributor Onboarding","text":"

    Step-by-step onboarding sequence for new contributors. Consolidates setup instructions currently scattered across the README, contributing guide, and setup docs.

    When to use: First-time contributor setup, or when verifying your development environment after a major upgrade.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-1-clone-the-repository","level":2,"title":"Step 1: Clone the Repository","text":"
    git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n

    Or fork first on GitHub, then clone your fork.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-2-initialize-context","level":2,"title":"Step 2: Initialize Context","text":"
    ctx init\neval \"$(ctx activate)\"\n

    ctx init creates the .context/ directory with knowledge files and the .claude/ directory with agent configuration. eval \"$(ctx activate)\" tells ctx to use that directory for the rest of this runbook. If you skip the second line, the later steps fail with Error: no context directory specified.

    If ctx is not yet installed, proceed to Step 3 first, then come back.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-3-build-and-install","level":2,"title":"Step 3: Build and Install","text":"
    make build\nsudo make install\n

    Verify:

    ctx --version\n
    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-4-install-the-plugin-claude-code-users","level":2,"title":"Step 4: Install the Plugin (Claude Code Users)","text":"

    If you use Claude Code, install the plugin from your local clone so skills and hooks reflect your working tree:

    1. Launch claude
    2. Type /plugin and press Enter
    3. Select Marketplaces -> Add Marketplace
    4. Enter the absolute path to your clone (e.g., ~/WORKSPACE/ctx)
    5. Back in /plugin, select Install and choose ctx

    Verify:

    claude /plugin list   # should show ctx\n

    See Contributing: Install the Plugin for details on cache clearing.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-5-switch-to-dev-profile","level":2,"title":"Step 5: Switch to Dev Profile","text":"
    ctx config switch dev\n

    This enables verbose logging and notify events (useful during development).

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-6-verify-hooks","level":2,"title":"Step 6: Verify Hooks","text":"

    Start a Claude Code session and check that hooks fire:

    claude\n

    You should see ctx session hooks (ceremonies reminder, context loading) on session start. If not, check that the plugin is installed correctly (Step 4).

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-7-run-your-first-session","level":2,"title":"Step 7: Run Your First Session","text":"

    In Claude Code:

    /ctx-status\n

    This should show context file health, active tasks, and recent decisions. If it works, your setup is complete.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-8-verify-context-persistence","level":2,"title":"Step 8: Verify Context Persistence","text":"

    End the session and start a new one:

    /ctx-remember\n

    The agent should recall what happened in the previous session. This confirms that context persistence is working end-to-end.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-9-run-tests","level":2,"title":"Step 9: Run Tests","text":"
    make test     # unit tests\nmake audit    # full check: fmt + vet + lint + drift + docs + test\n

    All tests should pass with a clean clone.

    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#quick-reference","level":2,"title":"Quick Reference","text":"Task Command Build make build Install sudo make install Test make test Full audit make audit Rebuild docs site make site Serve docs locally make site-serve Clear plugin cache make plugin-reload Switch config profile ctx config switch dev","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#next-steps","level":2,"title":"Next Steps","text":"
    • Read the contributing guide for project layout, code style, and PR process
    • Check TASKS.md for open work items
    • Ask /ctx-next for suggested work
    ","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/plugin-release/","level":1,"title":"Plugin Release","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#plugin-release","level":1,"title":"Plugin Release","text":"

    Plugin-specific release procedure. The general release checklist covers the full ctx release; this runbook covers the plugin-specific steps that are not part of that flow.

    When to use: When releasing plugin changes (new skills, hook updates, permission changes) independently of a ctx binary release, or as a sub-procedure within the full release.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#what-ships-in-the-plugin","level":2,"title":"What Ships in the Plugin","text":"

    The plugin lives at internal/assets/claude/ and includes:

    Component Path What it does Skills internal/assets/claude/skills/ User-facing /ctx-* slash commands Hooks internal/assets/claude/hooks/ Pre/post tool-use hooks Plugin manifest internal/assets/claude/.claude-plugin/plugin.json Declares skills, hooks, version Marketplace .claude-plugin/marketplace.json Points Claude Code to the plugin","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-1-update-hooksjson-if-hooks-changed","level":2,"title":"Step 1: Update hooks.json (If Hooks Changed)","text":"

    If you added, removed, or modified hooks:

    # Verify hook definitions match implementations\nmake audit\n

    Check that plugin.json lists all hooks correctly. Missing hooks silently fail to fire.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-2-bump-version","level":2,"title":"Step 2: Bump Version","text":"

    Update the version in three places:

    • internal/assets/claude/.claude-plugin/plugin.json
    • .claude-plugin/marketplace.json (two fields)
    • editors/vscode/package.json + package-lock.json (if VS Code extension is affected)

    The Release Script Does This

    If you're running make release, the script bumps these automatically from VERSION. Only bump manually if you're releasing the plugin independently.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-3-test-against-a-fresh-install","level":2,"title":"Step 3: Test Against a Fresh Install","text":"
    # Clear cached plugin\nmake plugin-reload\n\n# Restart Claude Code, then:\nclaude /plugin list    # verify version\n

    Test the critical paths:

    • /ctx-status works
    • Session hooks fire (ceremonies, context loading)
    • At least one user-facing skill works end-to-end
    • Pre-tool-use hooks block when they should
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-4-test-against-a-clean-project","level":2,"title":"Step 4: Test Against a Clean Project","text":"

    Create a temporary project to verify the plugin works outside the ctx repo:

    mkdir /tmp/test-ctx-plugin && cd /tmp/test-ctx-plugin\ngit init\nctx init\nclaude   # start a session, verify hooks fire\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-5-verify-skill-count","level":2,"title":"Step 5: Verify Skill Count","text":"

    The plugin manifest declares all user-invocable skills. Verify the count matches:

    # Count skills in plugin.json\njq '.skills | length' internal/assets/claude/.claude-plugin/plugin.json\n\n# Count skill directories\nls -d internal/assets/claude/skills/ctx-*/ | wc -l\n

    These numbers should match (some skills are not user-invocable and won't appear in both counts).

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-6-commit-and-tag","level":2,"title":"Step 6: Commit and Tag","text":"

    If releasing independently of a binary release:

    git add internal/assets/claude/ .claude-plugin/\ngit commit -m \"chore: release plugin v0.X.Y\"\ngit tag plugin-v0.X.Y\ngit push origin main --tags\n

    If part of a full release, the release checklist handles this.

    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#skills-dont-appear-after-update","level":3,"title":"Skills Don't Appear After Update","text":"

    Claude Code caches plugin files aggressively:

    make plugin-reload    # clears cache\n# restart Claude Code\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#hooks-dont-fire","level":3,"title":"Hooks Don't Fire","text":"

    Check that the hook is registered in plugin.json and that the command it calls exists:

    jq '.hooks' internal/assets/claude/.claude-plugin/plugin.json\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#version-mismatch","level":3,"title":"Version Mismatch","text":"

    If claude /plugin list shows an old version after updating:

    make plugin-reload\n# restart Claude Code\nclaude /plugin list   # should show new version\n
    ","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/release-checklist/","level":1,"title":"Release Checklist","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release-checklist","level":1,"title":"Release Checklist","text":"

    The canonical pre-release sequence. This runbook ties together the audits, tests, and release steps that are otherwise scattered across docs and the operator's head.

    When to run: Before every release. No exceptions.

    Companion: The /_ctx-release skill automates the tag-and-push portion; this checklist covers everything before and after that automation.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#pre-release","level":2,"title":"Pre-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#1-run-the-codebase-audit","level":3,"title":"1. Run the Codebase Audit","text":"

    Use the codebase audit runbook prompt with your agent. Focus on analyses 1-4 (extractable patterns, documentation drift, maintainability, security). Triage findings into TASKS.md; anything blocking ships before the release.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#2-run-the-docs-semantic-audit","level":3,"title":"2. Run the Docs Semantic Audit","text":"

    Use the docs semantic audit runbook prompt. Fix high-severity findings (weak pages, broken narrative arcs). Medium-severity items can be deferred.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#3-sanitize-permissions","level":3,"title":"3. Sanitize Permissions","text":"

    Follow the sanitize permissions runbook. Clean up .claude/settings.local.json before it gets committed as part of the release.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#4-run-the-full-test-suite","level":3,"title":"4. Run the Full Test Suite","text":"
    make audit    # fmt + vet + lint + drift + docs + test\nmake smoke    # integration smoke tests\n

    All tests must pass. No exceptions.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#5-check-context-health","level":3,"title":"5. Check Context Health","text":"

    Activate the project so the next commands know which .context/ to read:

    eval \"$(ctx activate)\"\nctx drift          # broken references, stale patterns\nctx status         # context file health\n/ctx-link-check    # dead links in docs\n

    Fix anything flagged. If you see Error: no context directory specified, you skipped the eval line above. See Activating a Context Directory.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#6-review-tasksmd","level":3,"title":"6. Review TASKS.md","text":"

    Scan for incomplete tasks tagged as release-blocking. Either finish them or explicitly defer with a reason in the task note.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release","level":2,"title":"Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#7-bump-version","level":3,"title":"7. Bump Version","text":"
    echo \"0.X.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.X.0\"\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#8-generate-release-notes","level":3,"title":"8. Generate Release Notes","text":"

    In Claude Code:

    /_ctx-release-notes\n

    Review dist/RELEASE_NOTES.md. Ensure it captures all user-visible changes.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#9-cut-the-release","level":3,"title":"9. Cut the Release","text":"
    make release\n

    Or in Claude Code: /_ctx-release. See Cutting a Release for the full step-by-step.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#post-release","level":2,"title":"Post-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#10-verify-the-github-release","level":3,"title":"10. Verify the GitHub Release","text":"
    • GitHub Releases shows the new version
    • All 6 binaries are attached
    • SHA256 checksums are attached
    • Release notes render correctly
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#11-update-the-plugin-marketplace","level":3,"title":"11. Update the Plugin Marketplace","text":"

    If the plugin version changed, verify the marketplace entry:

    claude /plugin list   # shows updated version\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#12-announce","level":3,"title":"12. Announce","text":"

    Post in the project's communication channels. Reference the release notes.

    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#13-clean-up","level":3,"title":"13. Clean Up","text":"
    rm dist/RELEASE_NOTES.md   # consumed by the release script\ngit stash pop              # if you stashed earlier\n
    ","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/","level":1,"title":"Sanitize Permissions","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#sanitize-permissions","level":1,"title":"Sanitize Permissions","text":"

    Manual procedure for cleaning up .claude/settings.local.json. The agent may analyze and recommend, but you make every edit.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#why-manual-not-automated","level":2,"title":"Why Manual, Not Automated","text":"

    settings.local.json controls what the agent can do without asking. An agent that can edit its own permission file is a self-escalation vector, especially if the skill is auto-accepted. Keep this manual.

    When to run: After busy sessions where you clicked \"Allow\" many times, weekly hygiene (pair with ctx drift), or before committing .claude/settings.local.json.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-1-snapshot","level":2,"title":"Step 1: Snapshot","text":"
    cp .claude/settings.local.json /tmp/settings-backup-$(date +%Y%m%d).json\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-2-extract-the-allow-list","level":2,"title":"Step 2: Extract the Allow List","text":"
    jq '.permissions.allow[]' .claude/settings.local.json | sort\n

    Eyeball it. You're looking for four categories:

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-3-identify-problems","level":2,"title":"Step 3: Identify Problems","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#a-garbage-nonsense","level":3,"title":"A. Garbage / Nonsense","text":"

    Entries that are clearly broken or meaningless:

    Bash(done)\nBash(__NEW_LINE_aa838494a90279c4__ echo \"\")\n

    Action: Delete.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#b-one-off-commands-session-debris","level":3,"title":"B. One-Off Commands (Session Debris)","text":"

    Entries with hardcoded paths, literal arguments, or exact commands that were accepted during a specific debugging session:

    Bash(git -C /home/jose/WORKSPACE/ctx log --oneline --all -20)\nBash(/home/jose/WORKSPACE/ctx/ctx decision add \"Use PostgreSQL\" --context ...)\n

    Signs of a one-off:

    • Full absolute paths to specific files
    • Literal string arguments (not wildcards)
    • Very specific flag combinations
    • Commands that look like they came from a single task

    Action: Delete unless you want to promote to a wildcard pattern.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#c-subsumed-entries-redundant","level":3,"title":"C. Subsumed Entries (Redundant)","text":"

    A narrow entry that's already covered by a broader one:

    # Narrow (redundant):\nBash(ctx journal source)\nBash(git -C /home/jose/WORKSPACE/ctx log --oneline -5)\n\n# Broad (already covers the above):\nBash(ctx journal source:*)\nBash(git -C:*)\n

    To find these, look for entries where removing the specific args would match an existing wildcard entry.

    Action: Delete the narrow entry.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#d-duplicate-intent-different-spelling","level":3,"title":"D. Duplicate Intent, Different Spelling","text":"

    Same command with env vars in different order, or slight variations:

    Bash(CGO_ENABLED=0 CTX_SKIP_PATH_CHECK=1 go test:*)\nBash(CTX_SKIP_PATH_CHECK=1 CGO_ENABLED=0 go test:*)\n

    Action: Keep one, delete the other.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-4-check-for-security-concerns","level":2,"title":"Step 4: Check for Security Concerns","text":"

    While you're in here, also flag:

    Pattern Risk Bash(git push:*) Bypasses block-git-push.sh hook Bash(rm -rf:*) Recursive delete, no confirmation Bash(sudo:*) Privilege escalation Bash(echo:*), Bash(cat:*) Can compose into writes to sensitive files Bash(curl:*), Bash(wget:*) Arbitrary network access Any write to .claude/ paths Agent self-modification

    See the /ctx-permission-sanitize skill for the full threat matrix.

    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-5-edit","level":2,"title":"Step 5: Edit","text":"

    Edit .claude/settings.local.json directly in your editor. Remove flagged entries. Keep the JSON valid.

    # Validate JSON after editing\njq . .claude/settings.local.json > /dev/null && echo \"valid\" || echo \"BROKEN\"\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-6-verify","level":2,"title":"Step 6: Verify","text":"
    # Compare before/after\ndiff /tmp/settings-backup-$(date +%Y%m%d).json .claude/settings.local.json\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-7-optionally-commit","level":2,"title":"Step 7: Optionally Commit","text":"
    git add .claude/settings.local.json\ngit commit -m \"chore: sanitize agent permissions\"\n
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#asking-the-agent-for-help","level":2,"title":"Asking the Agent for Help","text":"

    You can safely ask the agent to analyze the file:

    \"Look at my settings.local.json and tell me which permissions look like one-offs or are redundant.\"

    The agent can read and report. You do the edits.

    Do not add these to your allow list:

    • Skill(ctx-permission-sanitize)
    • Edit(.claude/settings.local.json)
    • Any Bash(...) pattern that writes to .claude/
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#history","level":2,"title":"History","text":"
    • 2026-02-15: Created as manual-only procedure after deciding against a self-modifying skill.
    • 2026-04-16: Moved from hack/runbooks/ to docs/operations/runbooks/.
    ","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"recipes/","level":1,"title":"Recipes","text":"

    Workflow recipes combining ctx commands and skills to solve specific problems.

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#getting-started","level":2,"title":"Getting Started","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#guide-your-agent","level":3,"title":"Guide Your Agent","text":"

    How commands, skills, and conversational patterns work together. Train your agent to be proactive through ask, guide, reinforce.

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#setup-across-ai-tools","level":3,"title":"Setup across AI Tools","text":"

    Initialize ctx and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf. Includes shell completion, watch mode for non-native tools, and verification.

    Uses: ctx init, ctx setup, ctx agent, ctx completion, ctx watch

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#multilingual-session-parsing","level":3,"title":"Multilingual Session Parsing","text":"

    Parse session journal entries written in other languages. Configure recognized session-header prefixes so the journal pipeline works for Turkish, Japanese, and any other locale.

    Uses: ctx journal source, ctx journal import, session_prefixes in .ctxrc

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#keeping-context-in-a-separate-repo","level":3,"title":"Keeping Context in a Separate Repo","text":"

    Store context files outside the project tree: in a private repo, shared directory, or anywhere else. Useful for open source projects with private context or multi-repo setups.

    Uses: ctx init, CTX_DIR, .ctxrc, /ctx-status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#build-a-knowledge-base","level":3,"title":"Build a Knowledge Base","text":"

    Stand up the editorial pipeline for knowledge-shaped work (research projects, vendor-spec analysis, post-incident reviews). Covers the pass-mode contract, source-coverage state-machine ledger, topic-adjacency pre-flight, cold-reader rubric, closeout/fold mechanism, and folder-shaped topic pages.

    Uses: ctx init, ctx kb topic new, ctx kb note, ctx kb reindex, ctx handover write, /ctx-kb-ingest, /ctx-kb-ask, /ctx-kb-site-review, /ctx-kb-ground, /ctx-kb-note, /ctx-handover

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#typical-kb-session","level":3,"title":"Typical KB Session","text":"

    The everyday flow once the pipeline is set up: session start recall, ingest a transcript, ask grounded questions, park findings, wrap up via the mandatory handover.

    Uses: /ctx-remember, /ctx-kb-ingest, /ctx-kb-ask, /ctx-kb-note, /ctx-wrap-up, /ctx-handover

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#recover-an-aborted-kb-session","level":3,"title":"Recover an Aborted KB Session","text":"

    What to do when the session ends after one or more editorial passes but before /ctx-handover. Closeouts survive the abort; the next session's /ctx-remember reads them as unfolded postdated artifacts; the next /ctx-handover folds them retroactively.

    Uses: /ctx-remember, /ctx-handover

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#sessions","level":2,"title":"Sessions","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#the-complete-session","level":3,"title":"The Complete Session","text":"

    Walk through a full ctx session from start to finish:

    • Loading context,
    • Picking what to work on,
    • Committing with context,
    • Capturing, reflecting, and saving a snapshot.

    Uses: ctx status, ctx agent, /ctx-remember, /ctx-next, /ctx-commit, /ctx-reflect

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#session-ceremonies","level":3,"title":"Session Ceremonies","text":"

    The two bookend rituals for every session: /ctx-remember at the start to load and confirm context, /ctx-wrap-up at the end to review the session and persist learnings, decisions, and tasks.

    Uses: /ctx-remember, /ctx-wrap-up, /ctx-commit, ctx agent, ctx add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#browsing-and-enriching-past-sessions","level":3,"title":"Browsing and Enriching Past Sessions","text":"

    Export your AI session history to a browsable journal site. Enrich entries with metadata and search across months of work.

    Uses: ctx journal source/import, ctx journal site, ctx journal obsidian, ctx serve, /ctx-history, /ctx-journal-enrich, /ctx-journal-enrich-all

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#session-reminders","level":3,"title":"Session Reminders","text":"

    Leave a message for your next session. Reminders surface automatically at session start and repeat until dismissed. Date-gate reminders to surface only after a specific date.

    Uses: ctx remind, ctx remind list, ctx remind dismiss, ctx system check-reminders

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#reviewing-session-changes","level":3,"title":"Reviewing Session Changes","text":"

    See what moved since your last session: context file edits, code commits, directories touched. Auto-detects session boundaries from state markers.

    Uses: ctx change, ctx agent, ctx status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#pausing-context-hooks","level":3,"title":"Pausing Context Hooks","text":"

    Silence all nudge hooks for a quick task that doesn't need ceremony overhead. Session-scoped: Other sessions are unaffected. Security hooks still fire.

    Uses: ctx hook pause, ctx hook resume, /ctx-pause, /ctx-resume

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-and-tasks","level":2,"title":"Knowledge and Tasks","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#persisting-decisions-learnings-and-conventions","level":3,"title":"Persisting Decisions, Learnings, and Conventions","text":"

    Record architectural decisions with rationale, capture gotchas and lessons learned, and codify conventions so they survive across sessions and team members.

    Uses: ctx decision add, ctx learning add, ctx convention add, ctx decision reindex, ctx learning reindex, /ctx-decision-add, /ctx-learning-add, /ctx-convention-add, /ctx-reflect

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#tracking-work-across-sessions","level":3,"title":"Tracking Work across Sessions","text":"

    Add, prioritize, complete, snapshot, and archive tasks. Keep TASKS.md focused as your project evolves across dozens of sessions.

    Uses: ctx task add, ctx task complete, ctx task archive, ctx task snapshot, /ctx-task-add, /ctx-archive, /ctx-next

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#using-the-scratchpad","level":3,"title":"Using the Scratchpad","text":"

    Use the encrypted scratchpad for quick notes, working memory, and sensitive values during AI sessions. Natural language in, encrypted storage out.

    Uses: ctx pad, /ctx-pad, ctx pad show, ctx pad edit

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#syncing-scratchpad-notes-across-machines","level":3,"title":"Syncing Scratchpad Notes across Machines","text":"

    Distribute your scratchpad encryption key, push and pull encrypted notes via git, and resolve merge conflicts when two machines edit simultaneously.

    Uses: ctx init, ctx pad, ctx pad resolve, scp

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#bridging-claude-code-auto-memory","level":3,"title":"Bridging Claude Code Auto Memory","text":"

    Mirror Claude Code's auto memory (MEMORY.md) into .context/ for version control, portability, and drift detection. Import entries into structured context files with heuristic classification.

    Uses: ctx memory sync, ctx memory status, ctx memory diff, ctx memory import, ctx memory publish, ctx system check-memory-drift

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hooks-and-notifications","level":2,"title":"Hooks and Notifications","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-output-patterns","level":3,"title":"Hook Output Patterns","text":"

    Choose the right output pattern for your Claude Code hooks: VERBATIM relay for user-facing reminders, hard gates for invariants, agent directives for nudges, and five more patterns across the spectrum.

    Uses: ctx plugin hooks, settings.local.json

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#customizing-hook-messages","level":3,"title":"Customizing Hook Messages","text":"

    Customize what hooks say without changing what they do. Override the QA gate for Python (pytest instead of make lint), silence noisy ceremony nudges, or tailor post-commit instructions for your stack.

    Uses: ctx hook message list, ctx hook message show, ctx hook message edit, ctx hook message reset

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-sequence-diagrams","level":3,"title":"Hook Sequence Diagrams","text":"

    Mermaid sequence diagrams for every system hook: entry conditions, state reads, output, throttling, and exit points. Includes throttling summary table and state file reference.

    Uses: All ctx system hooks

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#auditing-system-hooks","level":3,"title":"Auditing System Hooks","text":"

    The 12 system hooks that run invisibly during every session: what each one does, why it exists, and how to verify they're actually firing. Covers webhook-based audit trails, log inspection, and detecting silent hook failures.

    Uses: ctx system, ctx hook notify, .context/logs/, .ctxrc notify.events

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"

    Get push notifications when loops complete, hooks fire, or agents hit milestones. Webhook URL is encrypted: never stored in plaintext. Works with IFTTT, Slack, Discord, ntfy.sh, or any HTTP endpoint.

    Uses: ctx hook notify setup, ctx hook notify test, ctx hook notify --event, .ctxrc notify.events

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"

    Switch between dev and base runtime configurations without editing .ctxrc by hand. Verbose logging and webhooks for debugging, clean defaults for normal sessions.

    Uses: ctx config switch, ctx config status, /ctx-config

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#maintenance","level":2,"title":"Maintenance","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#detecting-and-fixing-drift","level":3,"title":"Detecting and Fixing Drift","text":"

    Keep context files accurate by detecting structural drift (stale paths, missing files, stale file ages) and task staleness.

    Uses: ctx drift, ctx sync, ctx compact, ctx status, /ctx-drift, /ctx-status, /ctx-prompt-audit

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#state-directory-maintenance","level":3,"title":"State Directory Maintenance","text":"

    Clean up session tombstones from .context/state/. Prune old per-session files, identify stale global markers, and keep the state directory lean.

    Uses: ctx prune

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#troubleshooting","level":3,"title":"Troubleshooting","text":"

    Diagnose hook failures, noisy nudges, stale context, and configuration issues. Start with ctx doctor for a structural health check, then use /ctx-doctor for agent-driven analysis of event patterns.

    Uses: ctx doctor, ctx hook event, /ctx-doctor

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#claude-code-permission-hygiene","level":3,"title":"Claude Code Permission Hygiene","text":"

    Keep .claude/settings.local.json clean: recommended safe defaults, what to never pre-approve, and a maintenance workflow for cleaning up session debris.

    Uses: ctx init, /ctx-drift, /ctx-permission-sanitize, ctx permission snapshot, ctx permission restore

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#permission-snapshots","level":3,"title":"Permission Snapshots","text":"

    Capture a known-good permission baseline as a golden image, then restore at session start to automatically drop session-accumulated permissions.

    Uses: ctx permission snapshot, ctx permission restore, /ctx-permission-sanitize

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#turning-activity-into-content","level":3,"title":"Turning Activity into Content","text":"

    Generate blog posts from project activity, write changelog posts from commit ranges, and publish a browsable journal site from your session history.

    The output is generic Markdown, but the skills are tuned for the ctx-style blog artifacts you see on this website.

    Uses: ctx journal site, ctx journal obsidian, ctx serve, ctx journal import, /ctx-blog, /ctx-blog-changelog, /ctx-journal-enrich

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#importing-claude-code-plans","level":3,"title":"Importing Claude Code Plans","text":"

    Import Claude Code plan files (~/.claude/plans/*.md) into specs/ as permanent project specs. Filter by date, select interactively, and optionally create tasks referencing each imported spec.

    Uses: /ctx-plan-import, /ctx-task-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#design-before-coding","level":3,"title":"Design Before Coding","text":"

    Front-load design with a four-skill chain: brainstorm the approach, spec the design, task the work, implement step-by-step. Each step produces an artifact that feeds the next.

    Uses: /ctx-brainstorm, /ctx-spec, /ctx-task-add, /ctx-implement, /ctx-decision-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#scrutinizing-a-plan","level":3,"title":"Scrutinizing a Plan","text":"

    Once a plan exists, run an adversarial interview to surface what's weak, missing, or unexamined before you commit. Walks the plan depth-first: assumptions, failure modes, alternatives, sequencing, reversibility. The complement to brainstorm: brainstorm produces plans, this attacks them.

    Uses: /ctx-plan, /ctx-spec, /ctx-decision-add, /ctx-learning-add

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#agents-and-automation","level":2,"title":"Agents and Automation","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#building-project-skills","level":3,"title":"Building Project Skills","text":"

    Encode repeating workflows into reusable skills the agent loads automatically. Covers the full cycle: identify a pattern, create the skill, test with realistic prompts, and iterate until it triggers correctly.

    Uses: /ctx-skill-create, ctx init

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#running-an-unattended-ai-agent","level":3,"title":"Running an Unattended AI Agent","text":"

    Set up a loop where an AI agent works through tasks overnight without you at the keyboard, using ctx for persistent memory between iterations.

    This recipe shows how ctx supports long-running agent loops without losing context or intent.

    Uses: ctx init, ctx loop, ctx watch, ctx load, /ctx-loop, /ctx-implement

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#when-to-use-a-team-of-agents","level":3,"title":"When to Use a Team of Agents","text":"

    Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.

    This recipe covers the file overlap test, when teams make things worse, and what ctx provides at each level.

    Uses: /ctx-worktree, /ctx-next, ctx status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#parallel-agent-development-with-git-worktrees","level":3,"title":"Parallel Agent Development with Git Worktrees","text":"

    Split a large backlog across 3-4 agents using git worktrees, each on its own branch and working directory. Group tasks by file overlap, work in parallel, merge back.

    Uses: /ctx-worktree, /ctx-next, git worktree, git merge

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#architecture-deep-dive","level":3,"title":"Architecture Deep Dive","text":"

    Three-pass pipeline for understanding a codebase: map what exists, enrich with code intelligence, then hunt for where it will silently fail. Produces architecture docs, quantified dependency data, and ranked failure hypotheses.

    Uses: /ctx-architecture, /ctx-architecture-enrich, /ctx-architecture-failure-analysis

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#writing-steering-files","level":3,"title":"Writing Steering Files","text":"

    Tell your AI assistant how to behave with rule-based prompt injection that fires automatically when prompts match a description. Walks through scaffolding a steering file, previewing matches, and syncing to each AI tool's native format.

    Uses: ctx steering add, ctx steering preview, ctx steering list, ctx steering sync

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#authoring-lifecycle-triggers","level":3,"title":"Authoring Lifecycle Triggers","text":"

    Run executable shell scripts at session-start, pre-tool-use, file-save, and other lifecycle events. Script-based automation (complementary to steering's rule-based prompts), with a security-first workflow: scaffold disabled, test with mock input, enable only after review.

    Uses: ctx trigger add, ctx trigger test, ctx trigger enable, ctx trigger disable, ctx trigger list

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#hub","level":2,"title":"Hub","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hub-overview","level":3,"title":"Hub Overview","text":"

    Mental model and three user stories for the ctx Hub. What flows, what doesn't, and when not to use it. Read this before any of the other Hub recipes.

    Uses: ctx hub, ctx connection, ctx add --share

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-getting-started","level":3,"title":"ctx Hub: Getting Started","text":"

    Stand up a single-node hub on localhost, register two projects, publish a decision from one, and watch it appear in the other. End-to-end in under five minutes.

    Uses: ctx hub start, ctx connection register, ctx connection subscribe, ctx connection sync, ctx connection listen, ctx add --share, ctx agent --include-hub

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"

    Story 1 day-to-day workflow: one developer, many projects, one hub on localhost. Records a learning in project A, watches it show up automatically in project B. Walks through a realistic day of using the hub as passive infrastructure (no manual sync, no git push, no ceremony).

    Uses: ctx add --share, ctx connection subscribe, ctx agent --include-hub

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#team-knowledge-bus","level":3,"title":"Team Knowledge Bus","text":"

    Story 2 day-to-day workflow: a small trusted team sharing decisions, learnings, and conventions via a hub on an internal server. Covers the team publishing culture, what belongs on the hub vs. local, token management, and the social rules that make a shared knowledge stream stay signal-rich.

    Uses: ctx add --share, ctx connection status, ctx connection subscribe, ctx hub status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-multi-machine","level":3,"title":"ctx Hub: Multi-Machine","text":"

    Run the hub on a LAN host as a daemon and connect from project directories on other workstations. Firewall guidance, TLS via a reverse proxy, and safe daemon restart semantics.

    Uses: ctx hub start --daemon, ctx hub stop, ctx connection register, ctx connection status

    ","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-ha-cluster","level":3,"title":"ctx Hub: HA Cluster","text":"

    Raft-based leader election across three or more nodes for redundancy. Covers bootstrap, runtime peer management, graceful stepdown, and the Raft-lite durability caveat.

    Uses: ctx hub start --peers, ctx hub status, ctx hub peer add/remove, ctx hub stepdown

    ","path":["Recipes"],"tags":[]},{"location":"recipes/activating-context/","level":1,"title":"Activating a Context Directory","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#the-problem","level":2,"title":"The Problem","text":"

    You ran a ctx command and got:

    Error: no context directory specified for this project\n

    This means ctx doesn't know which .context/ directory to operate on. It will not guess, and it will not walk up from your current working directory looking for one; that behavior was removed deliberately, because silent inference was the source of several bugs (stray agent-created directories, cross-project bleed-through, webhook-route misrouting, sub-agent fragmentation). Every ctx command requires you to declare the target directory explicitly.

    This page shows you the three ways to do that and when to use each.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#tldr","level":2,"title":"TL;DR","text":"

    If the project has already been initialized and you just need to bind it for your shell:

    eval \"$(ctx activate)\"\n

    That's 95% of the time. Add it to .zshrc / .bashrc per project with direnv, or run it once per terminal.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#when-you-see-the-error","level":2,"title":"When You See the Error","text":"

    The exact error message depends on how many .context/ directories are visible from the current directory:

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#zero-candidates","level":3,"title":"Zero Candidates","text":"
    Error: no context directory specified for this project\n

    Either you haven't initialized this project yet (run ctx init) or you're in a directory that doesn't belong to a ctx-tracked project. If you know the project lives elsewhere, use one of the declaration methods below with its absolute path.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-candidate","level":3,"title":"One Candidate","text":"
    Error: no context directory specified; a likely candidate is at\n    /Users/you/repos/myproject/.context\n

    ctx found a single .context/ on the way up from here but won't bind to it automatically. Run eval \"$(ctx activate)\" and ctx will emit the export for the candidate. Or set CTX_DIR by hand.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#multiple-candidates","level":3,"title":"Multiple Candidates","text":"
    Error: no context directory specified; multiple candidates visible:\n  /Users/you/repos/myproject/.context\n  /Users/you/repos/myproject/packages/web/.context\n

    You're inside nested projects. Pick the one you mean:

    ctx activate /Users/you/repos/myproject/.context\n# …copy and paste the `export` line it prints, or wrap in eval:\neval \"$(ctx activate /Users/you/repos/myproject/.context)\"\n
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#three-ways-to-declare","level":2,"title":"Three Ways to Declare","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#1-ctx-activate-recommended-for-shells","level":3,"title":"1. ctx activate (Recommended for Shells)","text":"

    ctx activate emits a shell-native export CTX_DIR=... line to stdout. Wrap it in eval and the binding takes effect for the current shell:

    # Walk up from current dir and bind the single visible candidate:\neval \"$(ctx activate)\"\n\n# Bind a specific path explicitly:\neval \"$(ctx activate /abs/path/to/.context)\"\n\n# Clear the binding:\neval \"$(ctx deactivate)\"\n

    ctx activate validates paths strictly: the target must exist, be a directory, and contain at least one canonical context file (CONSTITUTION.md or TASKS.md). It refuses to emit for multiple upward candidates; pick one explicitly in that case.

    Under the hood, the emitted line is just:

    export CTX_DIR='/abs/path/to/.context'\n

    So you can copy it into your .zshrc / .bashrc if you want the binding permanent for a given shell setup. Better: use direnv with a per-project .envrc.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#2-ctx_dir-env-var","level":3,"title":"2. CTX_DIR Env Var","text":"

    If you already know the path, export it directly:

    export CTX_DIR=/abs/path/to/.context\nctx status\n

    CTX_DIR is the same variable ctx activate writes; activate is just a convenience that figures out the path for you.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#3-inline-one-shot","level":3,"title":"3. Inline One-Shot","text":"

    For one-shot commands (CI jobs, scripts, debugging a specific project without changing your shell state), prefix the binding inline:

    CTX_DIR=/abs/path/to/.context ctx status\n

    This binds CTX_DIR for that invocation only.

    CTX_DIR must be an absolute path with .context as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (export CTX_DIR=$(pwd)) before stray writes can leak to the project root.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-ci-and-scripts","level":2,"title":"For CI and Scripts","text":"

    Do not rely on shell activation in automated flows. Set CTX_DIR explicitly at the top of the script:

    #!/usr/bin/env bash\nset -euo pipefail\n\nexport CTX_DIR=\"$GITHUB_WORKSPACE/.context\"\nctx status\nctx drift\n
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-claude-code-users","level":2,"title":"For Claude Code Users","text":"

    The ctx plugin's hooks are generated with CTX_DIR=\"$CLAUDE_PROJECT_DIR/.context\" prefixed to each command, so hook-driven ctx invocations resolve correctly without any per-session setup. You only need to activate manually when running ctx yourself in a terminal.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-project-one-context","level":2,"title":"One Project, One .context/","text":"

    The context directory is not a free-floating bag of files. It is pinned to a project by contract: filepath.Dir(ContextDir()) is the project root. That parent directory is what ctx sync, ctx drift, and the memory-drift hook scan for code, secret files, and MEMORY.md respectively.

    The practical consequences:

    • Don't share one .context/ across multiple projects. It holds per-project journals, per-session state, and per-project secrets. Pointing two codebases at the same directory corrupts all three.
    • If you want to share knowledge (CONSTITUTION, CONVENTIONS, ARCHITECTURE) across projects, use ctx hub. It cherry-picks entries at the right granularity and keeps the per-project bits where they belong.
    • The CTX_DIR you activate is implicitly a project-root declaration. Setting CTX_DIR=/weird/place/.context means you're telling ctx the project root is /weird/place/. That's your call to make; ctx does not police it.
    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#recommended-layout","level":3,"title":"Recommended Layout","text":"
    ~/WORKSPACE/my-to-do-list\n  ├── .git\n  ├── .context          ← owned by this project; do not share\n  ├── ideas\n  │   └── ...\n  ├── Makefile\n  ├── Makefile.ctx\n  └── specs\n      └── ...\n

    .context/ sits at the project root, next to .git. ctx activate binds to it; every ctx subsystem reads the project from its parent.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#why-not-walk-up-automatically","level":2,"title":"Why Not Walk Up Automatically?","text":"

    Nested projects, submodules, rogue agent-created .context/ directories, and sub-agent sessions all produced silent misrouting under the old walk-up model. ctx decided to stop guessing and require the caller to declare. Every other decision flows from there.

    ","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/architecture-deep-dive/","level":1,"title":"Architecture Deep Dive","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-problem","level":2,"title":"The Problem","text":"

    Understanding a codebase at the surface level is easy. Understanding where it will break under real-world conditions takes three passes: mapping what exists, quantifying how it connects, and hunting for where it silently fails. Most teams stop at the first pass.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tldr","level":2,"title":"TL;DR","text":"
    # Pass 1: Map the system\n/ctx-architecture\n\n# Pass 2: Enrich with code intelligence\n/ctx-architecture-enrich\n\n# Pass 3: Hunt for failure modes\n/ctx-architecture-failure-analysis\n

    Each pass builds on the previous one. Run them in order. The output accumulates in .context/; each pass reads the prior artifacts and extends them.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-architecture Skill Map modules, dependencies, data flow, patterns /ctx-architecture-enrich Skill Verify blast radius and flows with code intel /ctx-architecture-failure-analysis Skill Generate falsifiable incident hypotheses ctx drift CLI Detect stale paths and broken references ctx status CLI Quick structural overview","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-1-map-what-exists","level":3,"title":"Pass 1: Map What Exists","text":"
    /ctx-architecture\n

    Produces:

    • ARCHITECTURE.md: succinct project map (< 4000 tokens), loaded at every session start
    • DETAILED_DESIGN*.md: deep per-module reference with exported API, data flow, danger zones, extension points
    • CHEAT-SHEETS.md: lifecycle flow diagrams
    • map-tracking.json: coverage state with confidence scores

    This pass forces deep code reading. No shortcuts, no code intelligence tools; the agent reads every module it analyzes. That forced reading is what makes the subsequent passes useful.

    When to run: First time on a codebase, or after significant structural changes (new packages, moved files, changed dependencies).

    Principal mode: Add principal to get strategic analysis (ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md from P4):

    /ctx-architecture principal\n
    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-2-enrich-with-code-intelligence","level":3,"title":"Pass 2: Enrich with Code Intelligence","text":"
    /ctx-architecture-enrich\n

    Takes the Pass 1 artifacts as baseline and layers on verified, graph-backed data from GitNexus:

    • Blast radius numbers for key functions
    • Execution flow traces through hot paths
    • Domain clustering validation
    • Registration site discovery

    This pass does not replace reading; it quantifies what reading found. If Pass 1 says \"module X depends on module Y,\" Pass 2 says \"module X has 47 callers in module Y, and changing function Z would affect 12 downstream consumers.\"

    When to run: After Pass 1, when you need quantified confidence for refactoring decisions or risk assessment.

    Requires: GitNexus MCP server connected.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-3-hunt-for-failure-modes","level":3,"title":"Pass 3: Hunt for Failure Modes","text":"
    /ctx-architecture-failure-analysis\n

    The adversarial pass. Reads all prior artifacts, then systematically hunts for correctness bugs across 9 failure categories:

    1. Concurrency (races, deadlocks, goroutine leaks)
    2. Ordering assumptions (init, registration, shutdown)
    3. Cache staleness (TTL-less, read-your-writes, cross-process)
    4. Fan-out amplification (N+1, retry storms)
    5. Ownership and lifecycle (orphans, double-close)
    6. Error handling (silent swallowing, partial failure)
    7. Scaling cliffs (quadratic, unbounded, global locks)
    8. Idempotency failures (duplicate processing, retry mutations)
    9. State machine drift (illegal states, unvalidated transitions)

    Every finding must meet an evidence standard: code path, trigger, failure path, silence reason, and code evidence. A mandatory challenge phase attempts to disprove each finding before it is accepted. Findings carry a confidence level (High/Medium/Low) and explicit risk score.

    Produces DANGER-ZONES.md, a ranked inventory of findings split into Critical and Elevated tiers.

    When to run: Before releases, after major refactors, when investigating incident categories, or when onboarding.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#what-you-get","level":2,"title":"What You Get","text":"

    After all three passes, .context/ contains:

    File From Purpose ARCHITECTURE.md Pass 1 System map (session-start context) DETAILED_DESIGN*.md Pass 1 Module-level deep reference CHEAT-SHEETS.md Pass 1 Lifecycle flow diagrams map-tracking.json Pass 1 Coverage and confidence data CONVERGENCE-REPORT.md Pass 1 What's covered, what's not DANGER-ZONES.md Pass 3 Ranked failure hypotheses

    Pass 2 enriches Pass 1 artifacts in-place rather than creating new files.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tips","level":2,"title":"Tips","text":"
    • Run Pass 1 with focus areas if the codebase is large. The skill asks what to go deep on, so name the modules you're about to change.
    • You don't need all three passes every time. Pass 1 is the foundation. Pass 2 and 3 are for when you need quantified confidence or adversarial rigor.
    • Re-run Pass 1 incrementally. It tracks coverage in map-tracking.json and only re-analyzes stale modules.
    • Pass 3 is most valuable before releases. The ranked DANGER-ZONES.md is a pre-release checklist.
    • The trilogy maps to a question progression: How does it work? How well does it connect? Where will it break?
    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#see-also","level":2,"title":"See Also","text":"

    See also: Detecting and Fixing Context Drift to keep architecture artifacts fresh between deep-dive sessions.

    See also: Detecting and Fixing Context Drift for structural checks that complement architecture analysis.

    ","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/autonomous-loops/","level":1,"title":"Running an Unattended AI Agent","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-problem","level":2,"title":"The Problem","text":"

    You have a project with a clear list of tasks, and you want an AI agent to work through them autonomously: overnight, unattended, without you sitting at the keyboard.

    Each iteration needs to remember what the previous one did, mark tasks as completed, and know when to stop.

    Without persistent memory, every iteration starts fresh and the loop collapses. With ctx, each iteration can pick up where the last one left off, but only if the agent persists its context as part of the work.

    Unattended operation works because the agent treats context persistence as a first-class deliverable, not an afterthought.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                                    # 1. init context\neval \"$(ctx activate)\"                      # 2. bind CTX_DIR for this shell\n# Edit TASKS.md with phased work items\nctx loop --tool claude --max-iterations 10  # 3. generate loop.sh\n./loop.sh 2>&1 | tee /tmp/loop.log &        # 4. run the loop\nctx watch --log /tmp/loop.log               # 5. process context updates\n# Next morning:\nctx status && ctx load                      # 6. review the results\n

    Activate, or Set CTX_DIR Inline for Unattended Runs

    eval \"$(ctx activate)\" is fine for an interactive terminal. For an overnight unattended loop, put the binding at the top of loop.sh instead (export CTX_DIR=/abs/path/.context) so the loop doesn't depend on a live shell. If you skip both, ctx loop, ctx watch, ctx status, and ctx load fail with Error: no context directory specified. See Activating a Context Directory.

    Read on for permissions, isolation, and completion signals.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init Command Initialize project context and prompt templates ctx loop Command Generate the loop shell script ctx watch Command Monitor AI output and persist context updates ctx load Command Display assembled context (for debugging) /ctx-loop Skill Generate loop script from inside Claude Code /ctx-implement Skill Execute a plan step-by-step with verification","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-1-initialize-for-unattended-operation","level":3,"title":"Step 1: Initialize for Unattended Operation","text":"

    Start by creating a .context/ directory configured so the agent can work without human input.

    ctx init\n

    This creates .context/ with the template files (including a loop prompt at .context/loop.md), and seeds Claude Code permissions in .claude/settings.local.json. Install the ctx plugin for hooks and skills.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-2-populate-tasksmd-with-phased-work","level":3,"title":"Step 2: Populate TASKS.md with Phased Work","text":"

    Open .context/TASKS.md and organize your work into phases. The agent works through these systematically, top to bottom, using priority tags to break ties.

    # Tasks\n\n## Phase 1: Foundation\n\n- [ ] Set up project structure and build system `#priority:high`\n- [ ] Configure testing framework `#priority:high`\n- [ ] Create CI pipeline `#priority:medium`\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Hardening\n\n- [ ] Add rate limiting to API endpoints `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n- [ ] Write integration tests `#priority:medium`\n

    Phased organization matters because it gives the agent natural boundaries. Phase 1 tasks should be completable without Phase 2 code existing yet.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-3-configure-the-loop-prompt","level":3,"title":"Step 3: Configure the Loop Prompt","text":"

    The loop prompt at .context/loop.md instructs the agent to operate autonomously:

    1. Read .context/CONSTITUTION.md first (hard rules, never violated)
    2. Load context from .context/ files
    3. Pick one task per iteration
    4. Complete the task and update context files
    5. Commit changes (including .context/)
    6. Signal status with a completion signal

    You can customize .context/loop.md for your project. The critical parts are the one-task-per-iteration discipline, proactive context persistence, and completion signals at the end:

    ## Signal Status\n\nEnd your response with exactly ONE of:\n\n* `SYSTEM_CONVERGED`: All tasks in `TASKS.md` are complete (*this is the\n  signal the loop script detects by default*)\n* `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n* (*no signal*): More work remains, continue to the next iteration\n\nNote: the loop script only checks for `SYSTEM_CONVERGED` by default.\n`SYSTEM_BLOCKED` is a convention for the human reviewing the log.\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-4-configure-permissions","level":3,"title":"Step 4: Configure Permissions","text":"

    An unattended agent needs permission to use tools without prompting. By default, Claude Code asks for confirmation on file writes, bash commands, and other operations, which stops the loop and waits for a human who is not there.

    There are two approaches.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-a-explicit-allowlist-recommended","level":4,"title":"Option A: Explicit Allowlist (Recommended)","text":"

    Grant only the permissions the agent needs. In .claude/settings.local.json:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Bash(ctx:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n

    Adjust the Bash patterns for your project's toolchain. The agent can run make, go, git, and ctx commands but cannot run arbitrary shell commands.

    This is recommended even in sandboxed environments because it limits blast radius.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-b-skip-all-permission-checks","level":4,"title":"Option B: Skip All Permission Checks","text":"

    Claude Code supports a --dangerously-skip-permissions flag that disables all permission prompts:

    claude --dangerously-skip-permissions -p \"$(cat .context/loop.md)\"\n

    This Flag Means What It Says

    With --dangerously-skip-permissions, the agent can execute any shell command, write to any file, and make network requests without confirmation.

    Only use this on a sandboxed machine: ideally a virtual machine with no access to host credentials, no SSH keys, and no access to production systems.

    If you would not give an untrusted intern sudo on this machine, do not use this flag.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#enforce-isolation-at-the-os-level","level":4,"title":"Enforce Isolation at the OS Level","text":"

    The only controls an agent cannot override are the ones enforced by the operating system, the container runtime, or the hypervisor.

    Do Not Skip This Section

    This is not optional hardening:

    An unattended agent with unrestricted OS access is an unattended shell with unrestricted OS access.

    The allowlist above is a strong first layer, but do not rely on a single runtime boundary.

    For unattended runs, enforce isolation at the infrastructure level:

    Layer What to enforce User account Run the agent as a dedicated unprivileged user with no sudo access and no membership in privileged groups (docker, wheel, adm). Filesystem Restrict the project directory via POSIX permissions or ACLs. The agent should have no access to other users' files or system directories. Container Run inside a Docker/Podman sandbox. Mount only the project directory. Drop capabilities (--cap-drop=ALL). Disable network if not needed (--network=none). Never mount the Docker socket and do not run privileged containers. Prefer rootless containers. Virtual machine Prefer a dedicated VM with no shared folders, no host passthrough, and no keys to other machines. Network If the agent does not need the internet, disable outbound access entirely. If it does, restrict to specific domains via firewall rules. Resource limits Apply CPU, memory, and disk limits (cgroups/container limits). A runaway loop should not fill disk or consume all RAM. Self-modification Make instruction files read-only. CLAUDE.md, .claude/settings.local.json, and .context/CONSTITUTION.md should not be writable by the agent user. If using project-local hooks, protect those too.

    A minimal Docker setup for overnight runs:

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh 2>&1 | tee /tmp/loop.log\n

    Defense in Depth

    Use multiple layers together: OS-level isolation (the boundary the agent cannot cross), a permission allowlist (what Claude Code will do within that boundary), and CONSTITUTION.md (a soft nudge for the common case).

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-5-generate-the-loop-script","level":3,"title":"Step 5: Generate the Loop Script","text":"

    Use ctx loop to generate a loop.sh tailored to your AI tool:

    # Generate for Claude Code with a 10-iteration cap\nctx loop --tool claude --max-iterations 10\n\n# Generate for Aider\nctx loop --tool aider --max-iterations 10\n\n# Custom prompt file and output filename\nctx loop --tool claude --prompt my-prompt.md --output my-loop.sh\n

    The generated script reads .context/loop.md, runs the tool, checks for completion signals, and loops until done or the cap is reached.

    You can also use the /ctx-loop skill from inside Claude Code.

    A Shell Loop Is the Best Practice

    The shell loop approach spawns a fresh AI process each iteration, so the only state that carries between iterations is what lives in .context/ and git.

    Claude Code's built-in /loop runs iterations within the same session, which can allow context window state to leak between iterations. This can be convenient for short runs, but it is less reliable for unattended loops.

    See Shell Loop vs Built-in Loop for details.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-6-run-with-watch-mode","level":3,"title":"Step 6: Run with Watch Mode","text":"

    Open two terminals. In the first, run the loop. In the second, run ctx watch to process context updates from the AI output.

    # Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n

    The watch command parses XML context-update commands from the AI output and applies them:

    <context-update type=\"complete\">user registration</context-update>\n<context-update type=\"learning\"\n  context=\"Setting up user registration\"\n  lesson=\"Email verification needs SMTP configured\"\n  application=\"Add SMTP setup to deployment checklist\"\n>SMTP Requirement</context-update>\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-7-completion-signals-end-the-loop","level":3,"title":"Step 7: Completion Signals End the Loop","text":"

    The generated script checks for one completion signal per run. By default this is SYSTEM_CONVERGED. You can change it with the --completion flag:

    ctx loop --tool claude --completion BOOTSTRAP_COMPLETE --max-iterations 5\n

    The following signals are conventions used in .context/loop.md:

    Signal Convention How the script handles it SYSTEM_CONVERGED All tasks in TASKS.md are done Detected by default (--completion default value) SYSTEM_BLOCKED Agent cannot proceed Only detected if you set --completion to this BOOTSTRAP_COMPLETE Initial scaffolding done Only detected if you set --completion to this

    The script uses grep -q on the agent's output, so any string works as a signal. If you need to detect multiple signals in one run, edit the generated loop.sh to add additional grep checks.

    When you return in the morning, check the log and the context files:

    tail -100 /tmp/loop.log\nctx status\nctx load\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-8-use-ctx-implement-for-plan-execution","level":3,"title":"Step 8: Use /ctx-implement for Plan Execution","text":"

    Within each iteration, the agent can use /ctx-implement to execute multi-step plans with verification between steps. This is useful for complex tasks that touch multiple files.

    The skill breaks a plan into atomic, verifiable steps:

    Step 1/6: Create user model .................. OK\nStep 2/6: Add database migration ............. OK\nStep 3/6: Implement registration handler ..... OK\nStep 4/6: Write unit tests ................... OK\nStep 5/6: Run test suite ..................... FAIL\n  -> Fixed: missing test dependency\n  -> Re-verify ............................... OK\nStep 6/6: Update TASKS.md .................... OK\n

    Each step is verified (build, test, syntax check) before moving to the next.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A typical overnight run:

    ctx init\n# Edit TASKS.md and .context/loop.md\n\nctx loop --tool claude --max-iterations 20\n\n./loop.sh 2>&1 | tee /tmp/loop.log &\nctx watch --log /tmp/loop.log\n\n# Next morning:\nctx status\nctx load\n
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#why-autonomous-loops-work-proactive-context-persistence","level":2,"title":"Why Autonomous Loops Work: Proactive Context Persistence","text":"

    The autonomous loop pattern works because the agent persists context as part of the job.

    Without proactive persistence, the loop degrades into disconnected iterations that repeat work, contradict decisions, and lose track of progress. The agent cannot rely on a human to prompt it. It must treat context updates as part of every task, not as a separate step.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-agent-playbook-self-check-drives-autonomy","level":3,"title":"The Agent Playbook Self-Check Drives Autonomy","text":"

    The Agent Playbook defines a self-check prompt the agent applies continuously:

    \"If this session ended right now, would the next session know what happened?\"\n

    In interactive sessions, a human might remind the agent to save context. When the agent runs unattended, nobody is there to remind it.

    The self-check becomes the trigger: after meaningful work, the agent evaluates whether the context files reflect reality and updates them immediately if they do not.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#what-the-agent-does-proactively-between-iterations","level":3,"title":"What the Agent Does Proactively between Iterations","text":"

    At milestones within an iteration, the agent persists without waiting for instructions:

    Milestone What the agent does proactively Completes a task Marks it [x] in TASKS.md Discovers a gotcha Adds it to LEARNINGS.md Makes a design choice Records it in DECISIONS.md with rationale Identifies follow-up work Creates new tasks in TASKS.md with #added timestamp Hits an unexpected failure Documents the root cause before moving on","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#example-what-proactive-persistence-looks-like","level":3,"title":"Example: What Proactive Persistence Looks Like","text":"

    Here is what the agent does during a single iteration beyond writing code:

    Iteration 4:\n  1. Read TASKS.md -> pick \"Add email verification\" (#priority:high)\n  2. Add #started:2026-01-25-030012 to the task\n  3. Implement the feature (code, tests, docs if needed)\n  4. Tests pass -> mark task [x]\n  5. Add learning: \"SMTP config must be set before verification handler registers. Order matters in init().\"\n  6. Add decision: \"Use token-based verification links (not codes) because links work better in automated tests.\"\n  7. Create follow-up task: \"Add rate limiting to verification endpoint\" #added:...\n  8. Commit all changes including `.context/`\n  9. No signal emitted -> loop continues to iteration 5\n

    Steps 2, 4, 5, 6, and 7 are proactive context persistence:

    The agent was not asked to do any of them.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#context-persistence-at-milestones","level":3,"title":"Context Persistence at Milestones","text":"

    For long autonomous runs, the agent persists context at natural boundaries, often at phase transitions or after completing a cluster of related tasks. It updates TASKS.md, DECISIONS.md, and LEARNINGS.md as it goes.

    If the loop crashes at 4 AM, the context files tell you exactly where to resume. You can also use ctx journal source to review the session transcripts.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-persistence-contract","level":3,"title":"The Persistence Contract","text":"

    The autonomous loop has an implicit contract:

    1. Every iteration reads context: TASKS.md, DECISIONS.md, LEARNINGS.md
    2. Every iteration writes context: task updates, new learnings, decisions
    3. Every commit includes .context/ so the next iteration sees changes
    4. Context stays current: if the loop stopped right now, nothing important is lost

    Break any part of this contract and the loop degrades.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tips","level":2,"title":"Tips","text":"

    Markdown Is Not Enforcement

    Your real guardrails are permissions and isolation, not Markdown. CONSTITUTION.md can nudge the agent, but it is probabilistic.

    The permission allowlist and OS isolation are deterministic:

    For unattended runs, trust the sandbox and the allowlist, not the prose.

    • Start with a small iteration cap. Use --max-iterations 5 on your first run.
    • Keep tasks atomic. Each task should be completable in a single iteration.
    • Check signal discipline. If the loop runs forever, the agent is not emitting SYSTEM_CONVERGED or SYSTEM_BLOCKED. Make the signal requirement explicit in .context/loop.md.
    • Commit after context updates. Finish code, update .context/, commit including .context/, then signal.
    • Set up webhook notifications to get notified when the loop completes, hits max iterations, or when hooks fire nudges. The generated loop script includes ctx hook notify calls automatically.
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#next-up","level":2,"title":"Next Up","text":"

    When to Use a Team of Agents →: Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.

    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#see-also","level":2,"title":"See Also","text":"
    • Autonomous Loops: loop pattern, prompt templates, troubleshooting
    • CLI Reference: ctx loop: flags and options
    • CLI Reference: ctx watch: watch mode details
    • CLI Reference: ctx init: init flags
    • The Complete Session: interactive workflow
    • Tracking Work Across Sessions: structuring TASKS.md
    ","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/build-a-knowledge-base/","level":1,"title":"Build a Knowledge Base","text":"","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#the-problem","level":2,"title":"The Problem","text":"

    You are doing knowledge-shaped work (vendor-spec analysis, a research project, a post-incident review, domain modeling) and the standard five context files (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md, CONSTITUTION.md) don't fit. Because those files are tuned for code-development context, not for evidence-tracked knowledge with confidence bands, contradictions, and external citations.

    You need a place where:

    • Every claim is pinned to a source you can re-verify.
    • Topics grow into folders as they earn their depth.
    • Two passes against the same source don't silently disagree.
    • The next session knows what's incomplete, not just what's done.

    That's what the editorial pipeline is for.

    Prefer Skills to Raw Commands

    The pipeline is driven by skills (/ctx-kb-ingest, /ctx-kb-ask, etc.). The CLI form (ctx kb ingest, etc.) exists for scripting and for non-Claude environments; the skill is the natural surface.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#tldr","level":2,"title":"TL;DR","text":"
    git init && ctx init                    # lays down the kb + ingest tree\nctx kb topic new \"Cursor Hooks\"         # scaffold a topic folder\n/ctx-kb-ingest ./docs/cursor-hooks.md \"cursor hooks\" # editorial pass\n/ctx-kb-ask \"does the kb say hooks fire async?\"      # grounded Q&A\n/ctx-wrap-up                            # ceremony; delegates to /ctx-handover\n                                        # for the per-session handover\n
    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init Command Scaffold .context/kb/, .context/ingest/, etc. ctx kb topic new <name> Command Sole writer of topic-page scaffolds (folder shape) ctx kb note \"<text>\" Command Lightweight capture into .context/ingest/findings.md ctx kb reindex Command Refresh the CTX:KB:TOPICS managed block ctx handover write Command Per-session handover with closeout fold /ctx-kb-ingest Skill Mode-aware editorial pass (topic-page/triage/evidence) /ctx-kb-ask Skill Q&A grounded in the kb /ctx-kb-site-review Skill Mechanical structural audit /ctx-kb-ground Skill Re-grounding against external sources /ctx-kb-note Skill Capture a finding for the next ingest pass /ctx-wrap-up Skill End-of-session ceremony; delegates to the handover step","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-0-initialize-and-declare-scope","level":2,"title":"Step 0: Initialize and Declare Scope","text":"
    git init && ctx init\n

    ctx init lays down the editorial scaffolding alongside the standard context files:

    .context/\n├── kb/\n│   ├── index.md\n│   └── topics/.gitkeep\n├── ingest/\n│   ├── KB-RULES.md             # editorial constitution\n│   ├── 00-GROUND.md\n│   ├── 30-INGEST.md\n│   ├── 40-ASK.md\n│   ├── 50-SITE_REVIEW.md\n│   ├── OPERATOR.md\n│   ├── PROMPT.md               # hand-fallback router\n│   ├── closeouts/.gitkeep\n│   └── schemas/\n│       └── *.md                # 10 schema templates\n└── handovers/.gitkeep\n

    Open .context/kb/index.md and replace the placeholder ## Scope paragraph with a one-paragraph statement of what this kb covers and what it does not. /ctx-kb-ingest refuses to run against an undeclared kb; scope is the precondition.

    Git is required

    ctx init now refuses to run without .git/. The editorial pipeline's provenance (closeout sha/branch, evidence-index in-repo SHA pins) depends on it. Run git init first if the project does not already have one.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-1-scaffold-a-topic","level":2,"title":"Step 1: Scaffold a Topic","text":"

    Topic pages live in folders, not flat files:

    ctx kb topic new \"Cursor Hooks\"\n

    This creates .context/kb/topics/cursor-hooks/index.md from the embedded template. The slug is computed by lowercasing + kebab- casing; vendor-namespaced shapes like cursor/hooks are preserved so you can grow into nested topology (topics/cursor/hooks/, topics/cursor/skills/, topics/cursor/rules/) without breaking citations.

    ctx kb topic new is the sole writer of topic-page scaffolds. Skills invoke this command rather than synthesize a scaffold by hand; the embedded template is the single source of truth.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-2-run-an-editorial-pass","level":2,"title":"Step 2: Run an Editorial Pass","text":"
    /ctx-kb-ingest ./inputs/2026-04-12-call.md \"cursor hooks\"\n

    The skill begins with a pass-mode declaration:

    Pass-mode: topic-page Reason: the user supplied one primary source and the intended topic is clear. Definition of done: create or extend kb/topics/cursor-hooks/index.md, cite EV rows, run ctx kb site build, record cold-reader orientation.

    Then it:

    1. Resolves sources (paths / URLs / MCP resources) and updates the source-coverage ledger at .context/kb/source-coverage.md (a state machine across all sources the kb has touched).
    2. Scans for adjacent incomplete topics in the ledger and surfaces them so the new page acknowledges sibling gaps.
    3. Synthesizes prose section by section into the topic page, minting EV-### rows in evidence-index.md for every cited claim.
    4. Sets the Confidence floor (the page never claims more certainty than its weakest cited band).
    5. Writes a closeout under .context/ingest/closeouts/<TS>-ingest-closeout.md with frontmatter, the cold-reader orientation rubric, and a ledger-state advance per source.

    Three pass modes:

    • topic-page (default): write or extend a topic page.
    • triage: admit / skip sources against scope; no EV-### minted.
    • evidence-only: mint EV-### rows tagged evidence-only; do not touch a topic page (explicit-request-only escape hatch).

    Mid-pass mode-switching is forbidden: the skill commits to one mode and aborts cleanly if the work no longer fits.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-3-qa-grounded-in-the-kb","level":2,"title":"Step 3: Q&A Grounded in the KB","text":"
    /ctx-kb-ask \"does the kb say hooks fire async?\"\n

    /ctx-kb-ask reads the kb's prose, cites EV-### rows, and refuses to web-jump. If the kb cannot answer, it opens a Q-### row in outstanding-questions.md and reports the gap, which a future /ctx-kb-ingest pass can close.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-4-audit-re-ground","level":2,"title":"Step 4: Audit + Re-Ground","text":"
    /ctx-kb-site-review        # mechanical structural audit\n/ctx-kb-ground             # refresh sources listed in grounding-sources.md\n

    site-review coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence (those go through ingest).

    ground reads .context/ingest/grounding-sources.md and runs the equivalent of a fresh fetch + re-extract pass for each listed source, useful when an upstream source has changed.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-5-browse-the-kb-locally","level":2,"title":"Step 5: Browse the KB Locally","text":"

    .context/kb/ is a tree of Markdown files: topic pages live under topics/<slug>/index.md and cross-cutting artifacts (glossary.md, evidence-index.md, outstanding-questions.md, domain-decisions.md, contradictions.md, timeline.md, source-map.md, source-coverage.md, relationship-map.md) sit alongside them. Drop a minimal zensical.toml into .context/kb/ and hand it to ctx serve:

    ctx serve .context/kb/\n

    The KB renders the same way the docs site you are reading right now does. Use the in-place evidence-index links to jump from a topic page to its EV-### rows and back. The site build is read-only: no skill or CLI writes through it.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-6-wrap-up-with-a-handover","level":2,"title":"Step 6: Wrap Up with a Handover","text":"

    Run /ctx-wrap-up at session end; it owns the ceremony and delegates to the handover step (/ctx-handover) as its final action:

    /ctx-wrap-up \"Cursor Hooks deep dive\"\n

    The handover artifact lands at .context/handovers/<TS>-<slug>.md (timestamped so concurrent agent runs never overwrite). It folds postdated closeouts into a ## Folded closeouts section and archives the source closeout files under .context/archive/closeouts/. The next session's /ctx-remember reads the latest handover and folds any closeouts whose generated-at postdates it.

    The legitimate direct-invocation cases for /ctx-handover are --no-fold for a mid-session checkpoint, or recovery when a prior session ended before its wrap-up step. For the underlying CLI, see ctx handover write.

    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#how-it-ladders-together","level":2,"title":"How It Ladders Together","text":"
    sources you supply\n       │\n       ▼\n/ctx-kb-ingest (mode-declared, source-coverage advanced)\n       │\n       ├──▶ topic-page  ──▶ .context/kb/topics/<slug>/index.md\n       ├──▶ evidence    ──▶ .context/kb/evidence-index.md (EV-###)\n       ├──▶ side rails  ──▶ glossary.md, contradictions.md,\n       │                    outstanding-questions.md, timeline.md,\n       │                    source-map.md, relationship-map.md\n       └──▶ closeout    ──▶ .context/ingest/closeouts/<TS>-...md\n                               │\n                               ▼\n                       (next session)\n                               │\n                               ▼\n                  /ctx-wrap-up → /ctx-handover folds\n                  → .context/handovers/<TS>-<slug>.md\n                  + archives source closeouts under\n                  .context/archive/closeouts/\n                               │\n                               ▼\n                  /ctx-remember reads handover + postdated\n                  unfolded closeouts as the recall surface\n
    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#what-the-editorial-pipeline-is-not","level":2,"title":"What the Editorial Pipeline Is NOT","text":"
    • Not a substitute for DECISIONS.md. Project-level architectural decisions stay in .context/DECISIONS.md. The kb's domain-decisions.md is a kb-scoped artifact (different schema, different write authority, different lifecycle).
    • Not a substitute for LEARNINGS.md. Learnings have author intent; kb claims have evidence backing. They're different truth bases; do not cross-feed.
    • Not for casual notes. Use /ctx-kb-note or ctx kb note \"<text>\" to park a finding for the next ingest pass.
    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#reference","level":2,"title":"Reference","text":"
    • Editorial constitution: .context/ingest/KB-RULES.md (laid down by ctx init)
    • Skills reference: /ctx-kb-ingest, /ctx-kb-ask, /ctx-kb-site-review, /ctx-kb-ground, /ctx-kb-note, /ctx-handover
    • Related recipes: Typical KB Session, Recover an Aborted Session
    ","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/building-skills/","level":1,"title":"Building Project Skills","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-problem","level":2,"title":"The Problem","text":"

    You have workflows your agent needs to repeat across sessions: a deploy checklist, a review protocol, a release process. Each time, you re-explain the steps. The agent gets it mostly right but forgets edge cases you corrected last time.

    Skills solve this by encoding domain knowledge into a reusable document the agent loads automatically when triggered. A skill is not code - it is a structured prompt that captures what took you sessions to learn.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-skill-create\n

    The skill-creator walks you through: identify a repeating workflow, draft a skill, test with realistic prompts, iterate until it triggers correctly and produces good output.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-skill-create Skill Interactive skill creation and improvement workflow ctx init Command Deploys template skills to .claude/skills/ on first setup","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-1-identify-a-repeating-pattern","level":3,"title":"Step 1: Identify a Repeating Pattern","text":"

    Good skill candidates:

    • Checklists you repeat: deploy steps, release prep, code review
    • Decisions the agent gets wrong: if you keep correcting the same behavior, encode the correction
    • Multi-step workflows: anything with a sequence of commands and conditional branches
    • Domain knowledge: project-specific terminology, architecture constraints, or conventions the agent cannot infer from code alone

    Not good candidates: one-off instructions, things the platform already handles (file editing, git operations), or tasks too narrow to reuse.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-2-create-the-skill","level":3,"title":"Step 2: Create the Skill","text":"

    Invoke the skill-creator:

    You: \"I want a skill for our deploy process\"\n\nAgent: [Asks about the workflow: what steps, what tools,\n        what edge cases, what the output should look like]\n

    Or capture a workflow you just did:

    You: \"Turn what we just did into a skill\"\n\nAgent: [Extracts the steps from conversation history,\n        confirms understanding, drafts the skill]\n

    The skill-creator produces a SKILL.md file in .claude/skills/your-skill/.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-3-test-with-realistic-prompts","level":3,"title":"Step 3: Test with Realistic Prompts","text":"

    The skill-creator proposes 2-3 test prompts - the kind of thing a real user would say. It runs each one and shows the result alongside a baseline (same prompt without the skill) so you can compare.

    Agent: \"Here are test prompts I'd try:\n        1. 'Deploy to staging'\n        2. 'Ship the hotfix'\n        3. 'Run the release checklist'\n        Want to adjust these?\"\n
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-4-iterate-on-the-description","level":3,"title":"Step 4: Iterate on the Description","text":"

    The description field in frontmatter determines when a skill triggers. Claude tends to undertrigger - descriptions need to be specific and slightly \"pushy\":

    # Weak - too vague, will undertrigger\ndescription: \"Use for deployments\"\n\n# Strong - covers situations and synonyms\ndescription: >-\n  Use when deploying to staging or production, running the release\n  checklist, or when the user says 'ship it', 'deploy this', or\n  'push to prod'. Also use after merging to main when a deploy\n  is expected.\n

    The skill-creator helps you tune this iteratively.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-5-deploy-as-template-optional","level":3,"title":"Step 5: Deploy as Template (Optional)","text":"

    If the skill should be available to all projects (not just this one), place it in internal/assets/claude/skills/ so ctx init deploys it to new projects automatically.

    Most project-specific skills stay in .claude/skills/ and travel with the repo.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#skill-anatomy","level":2,"title":"Skill Anatomy","text":"
    my-skill/\n  SKILL.md         # Required: frontmatter + instructions (<500 lines)\n  scripts/         # Optional: deterministic code the skill can execute\n  references/      # Optional: detail loaded on demand (not always)\n  assets/          # Optional: output templates, not loaded into context\n

    Key sections in SKILL.md:

    Section Purpose Required? Frontmatter Name, description (trigger) Yes When to Use Positive triggers Yes When NOT to Use Prevents false activations Yes Process Steps and commands Yes Examples Good/bad output pairs Recommended Quality Checklist Verify before reporting completion For complex skills","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tips","level":2,"title":"Tips","text":"
    • Description is everything. A great skill with a vague description never fires. Spend time on trigger coverage - synonyms, concrete situations, edge cases.
    • Stay under 500 lines. If your skill is growing past this, move detail into references/ files and point to them from SKILL.md.
    • Do not duplicate the platform. If the agent already knows how to do something (edit files, run git commands), do not restate it. Tag paragraphs as Expert/Activation/Redundant and delete Redundant ones.
    • Explain why, not just what. \"Sort by date because users want recent results first\" beats \"ALWAYS sort by date.\" The agent generalizes from reasoning better than from rigid rules.
    • Test negative triggers. Make sure the skill does not fire on unrelated prompts. A skill that activates too broadly becomes noise.
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#next-up","level":2,"title":"Next Up","text":"

    Parallel Agent Development with Git Worktrees ->: Split work across multiple agents using git worktrees.

    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: full listing of all bundled and project-local skills
    • Guide Your Agent: how commands, skills, and conversational patterns work together
    • Design Before Coding: the four-skill chain for front-loading design work
    ","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/claude-code-permissions/","level":1,"title":"Claude Code Permission Hygiene","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code's .claude/settings.local.json controls what the agent can do without asking. Over time, this file accumulates one-off permissions from individual sessions: Exact commands with hardcoded paths, duplicate entries, and stale skill references.

    A noisy \"allowlist\" makes it harder to spot dangerous permissions and increases the surface area for unintended behavior.

    Since settings.local.json is .gitignored, it drifts independently of your codebase. There is no PR review, no CI check: just whatever you clicked \"Allow\" on.

    This recipe shows what a well-maintained permission file looks like and how to keep it clean.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                            # seeds safe defaults\n/ctx-drift                          # detects missing/stale permissions\n/ctx-permission-sanitize               # audits for dangerous patterns\n

    See Recommended Defaults for the full list.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx init Populates default ctx permissions /ctx-drift Detects missing or stale permission entries /ctx-permission-sanitize Audits for dangerous patterns (security-focused)","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#recommended-defaults","level":2,"title":"Recommended Defaults","text":"

    After running ctx init, your settings.local.json will have the ctx defaults pre-populated. Here is an opinionated safe starting point for a Go project using ctx:

    {\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(/tmp/ctx-*:*)\",\n      \"Bash(CGO_ENABLED=0 go build:*)\",\n      \"Bash(CGO_ENABLED=0 go test:*)\",\n      \"Bash(ctx:*)\",\n      \"Bash(git add:*)\",\n      \"Bash(git branch:*)\",\n      \"Bash(git check-ignore:*)\",\n      \"Bash(git checkout:*)\",\n      \"Bash(git commit:*)\",\n      \"Bash(git diff:*)\",\n      \"Bash(git log:*)\",\n      \"Bash(git remote:*)\",\n      \"Bash(git restore:*)\",\n      \"Bash(git show:*)\",\n      \"Bash(git stash:*)\",\n      \"Bash(git status:*)\",\n      \"Bash(git tag:*)\",\n      \"Bash(go build:*)\",\n      \"Bash(go fmt:*)\",\n      \"Bash(go test:*)\",\n      \"Bash(go vet:*)\",\n      \"Bash(golangci-lint run:*)\",\n      \"Bash(grep:*)\",\n      \"Bash(ls:*)\",\n      \"Bash(make:*)\",\n      \"Skill(ctx-convention-add)\",\n      \"Skill(ctx-decision-add)\",\n      \"Skill(ctx-learning-add)\",\n      \"Skill(ctx-task-add)\",\n      \"Skill(ctx-agent)\",\n      \"Skill(ctx-archive)\",\n      \"Skill(ctx-blog)\",\n      \"Skill(ctx-blog-changelog)\",\n      \"Skill(absorb)\",\n      \"Skill(ctx-commit)\",\n      \"Skill(ctx-drift)\",\n      \"Skill(ctx-implement)\",\n      \"Skill(ctx-journal-enrich)\",\n      \"Skill(ctx-journal-enrich-all)\",\n      \"Skill(ctx-loop)\",\n      \"Skill(ctx-next)\",\n      \"Skill(ctx-pad)\",\n      \"Skill(ctx-prompt-audit)\",\n      \"Skill(ctx-history)\",\n      \"Skill(ctx-reflect)\",\n      \"Skill(ctx-remember)\",\n      \"Skill(ctx-status)\",\n      \"Skill(ctx-worktree)\",\n      \"WebSearch\"\n    ],\n    \"deny\": [\n      \"Bash(sudo *)\",\n      \"Bash(git push *)\",\n      \"Bash(git push)\",\n      \"Bash(rm -rf /*)\",\n      \"Bash(rm -rf ~*)\",\n      \"Bash(curl *)\",\n      \"Bash(wget *)\",\n      \"Bash(chmod 777 *)\",\n      \"Read(**/.env)\",\n      \"Read(**/.env.*)\",\n      \"Read(**/*credentials*)\",\n      \"Read(**/*secret*)\",\n      \"Read(**/*.pem)\",\n      \"Read(**/*.key)\",\n      \"Edit(**/.env)\",\n      \"Edit(**/.env.*)\"\n    ]\n  }\n}\n

    This Is a Starting Point, Not a Mandate

    Your project may need more or fewer entries.

    The goal is intentional permissions: Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#design-principles","level":3,"title":"Design Principles","text":"

    Use wildcards for trusted binaries: If you trust the binary (your own project's CLI, make, go), a single wildcard like Bash(ctx:*) beats twenty subcommand entries. It reduces noise and means new subcommands work without re-prompting.

    Keep git commands granular: Unlike ctx or make, git has both safe commands (git log, git status) and destructive ones (git reset --hard, git clean -f). Listing safe commands individually prevents accidentally pre-approving dangerous ones.

    Pre-approve all ctx- skills: Skills shipped with ctx (Skill(ctx-*)) are safe to pre-approve. They are part of your project and you control their content. This prevents the agent from prompting on every skill invocation.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#default-deny-rules","level":3,"title":"Default Deny Rules","text":"

    ctx init automatically populates permissions.deny with rules that block dangerous operations. Deny rules are evaluated before allow rules: A denied pattern always prompts the user, even if it also matches an allow entry.

    The defaults block:

    Pattern Why Bash(sudo *) Cannot enter password; will hang Bash(git push *) Must be explicit user action Bash(rm -rf /*) etc. Recursive delete of system/home directories Bash(curl *) / wget Arbitrary network requests Bash(chmod 777 *) World-writable permissions Read/Edit(**/.env*) Secrets and credentials Read(**/*.pem, *.key) Private keys

    Read/Edit Deny Rules

    Read() and Edit() deny rules have known upstream enforcement issues (claude-code#6631,#24846).

    They are included as defense-in-depth and intent documentation.

    Blocked by default deny rules: no action needed, ctx init handles these:

    Pattern Risk Bash(git push:*) Must be explicit user action Bash(sudo:*) Privilege escalation Bash(rm -rf:*) Recursive delete with no confirmation Bash(curl:*) / Bash(wget:*) Arbitrary network requests

    Requires manual discipline: Never add these to allow:

    Pattern Risk Bash(git reset:*) Can discard uncommitted work Bash(git clean:*) Deletes untracked files Skill(ctx-permission-sanitize) Edits this file: self-modification vector Skill(release) Runs the release pipeline: high impact","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#hooks-regex-safety-net","level":2,"title":"Hooks: Regex Safety Net","text":"

    Deny rules handle prefix-based blocking natively. Hooks complement them by catching patterns that require regex matching: Things deny rules can't express.

    The ctx plugin ships these blocking hooks:

    Hook What it blocks ctx system block-non-path-ctx Running ctx from wrong path

    Project-local hooks (not part of the plugin) catch regex edge cases:

    Hook What it blocks block-dangerous-commands.sh Mid-command sudo/git push (after &&), copies to bin dirs, absolute-path ctx

    Pre-Approved + Hook-Blocked = Silent Block

    If you pre-approve a command that a hook blocks, the user never sees the confirmation dialog. The agent gets a block response and must handle it, which is confusing.

    It's better not to pre-approve commands that hooks are designed to intercept.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-maintenance-workflow","level":2,"title":"The Maintenance Workflow","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#after-busy-sessions","level":3,"title":"After Busy Sessions","text":"

    Permissions accumulate fastest during debugging and exploration sessions. After a session where you clicked \"Allow\" many times:

    1. Open .claude/settings.local.json in your editor;
    2. Look for entries at the bottom of the allowlist (new entries append there);
    3. Delete anything that looks session-specific:
      • Exact commands with hardcoded paths,
      • Commands with literal string arguments,
      • Entries that duplicate an existing wildcard.

    See the Sanitize Permissions runbook for a step-by-step procedure.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#periodically","level":3,"title":"Periodically","text":"

    Run /ctx-drift to catch permission drift:

    • Missing Bash(ctx:*) wildcard;
    • Missing Skill(ctx-*) entries for installed skills;
    • Stale Skill(ctx-*) entries for removed skills;
    • Granular Bash(ctx <subcommand>:*) entries that should be consolidated.

    Run /ctx-permission-sanitize to catch security issues:

    • Hook bypass patterns
    • Destructive commands
    • Overly broad permissions
    • Injection vectors
    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#when-adding-new-skills","level":3,"title":"When Adding New Skills","text":"

    If you create a custom ctx-* skill, add its Skill() entry to the allowlist manually.

    ctx init only populates the default permissions: It won't pick up custom skills.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#golden-image-snapshots","level":3,"title":"Golden Image Snapshots","text":"

    If manual cleanup is too tedious, use a golden image to automate it:

    Snapshot a curated permission set, then restore at session start to automatically drop session-accumulated permissions. See the Permission Snapshots recipe for the full workflow.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#adapting-for-other-languages","level":2,"title":"Adapting for Other Languages","text":"

    The recommended defaults above are Go-specific. For other stacks, swap the build/test tooling:

    Node.js / TypeScript:

    \"Bash(npm run:*)\",\n\"Bash(npm test:*)\",\n\"Bash(npx:*)\",\n\"Bash(node:*)\"\n

    Python:

    \"Bash(pytest:*)\",\n\"Bash(python:*)\",\n\"Bash(pip show:*)\",\n\"Bash(ruff:*)\"\n

    Rust:

    \"Bash(cargo build:*)\",\n\"Bash(cargo test:*)\",\n\"Bash(cargo clippy:*)\",\n\"Bash(cargo fmt:*)\"\n

    The ctx, git, and skill entries remain the same across all stacks.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#next-up","level":2,"title":"Next Up","text":"

    Permission Snapshots →: Save and restore permission baselines for reproducible setups.

    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#see-also","level":2,"title":"See Also","text":"
    • Setting Up ctx Across AI Tools: full setup recipe including settings.local.json creation
    • Context Health: keeping .context/ files accurate
    • Sanitize Permissions runbook: manual cleanup procedure
    ","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/configuration-profiles/","level":1,"title":"Configuration Profiles","text":"","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#configuration-profiles","level":1,"title":"Configuration Profiles","text":"

    Switch between dev and base runtime configurations without editing .ctxrc by hand. Useful when you want verbose logging and webhook notifications during development, then clean defaults for normal sessions.

    Uses: ctx config switch, ctx config status, /ctx-config

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#how-it-works","level":2,"title":"How It Works","text":"

    The ctx repo ships two source profiles committed to git:

    File Profile Description .ctxrc.base base All defaults, notifications off .ctxrc.dev dev Verbose logging, webhook notifications on

    The working copy (.ctxrc) is gitignored. Switching profiles copies the source file over .ctxrc, so your runtime configuration is always a clean snapshot of one of the two sources.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#switching-profiles","level":2,"title":"Switching Profiles","text":"
    # Switch to dev (verbose logging, notifications)\nctx config switch dev\n\n# Switch to base (defaults)\nctx config switch base\n\n# Toggle to the opposite profile\nctx config switch\n\n# \"prod\" is an alias for \"base\"\nctx config switch prod\n

    The detection heuristic checks for an uncommented notify: line in .ctxrc: present means dev, absent means base.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#checking-the-active-profile","level":2,"title":"Checking the Active Profile","text":"
    ctx config status\n

    Output examples:

    active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n
    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#typical-workflow","level":2,"title":"Typical Workflow","text":"
    1. Start of a debugging session: switch to dev for verbose logging and webhook notifications so you can trace hook activity and get push alerts.
    ctx config switch dev\n
    1. Work through the issue: hooks log verbosely, webhooks fire on key events (commits, ceremony nudges, drift warnings).

    2. Done debugging: switch back to base to silence the noise.

    ctx config switch base\n
    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#customizing-profiles","level":2,"title":"Customizing Profiles","text":"

    Edit the source files directly:

    • .ctxrc.dev: add any .ctxrc keys you want active during development (e.g., log_level: debug, notify.events, notify.webhook_url).
    • .ctxrc.base: keep this minimal. It represents your \"production\" defaults.

    After editing a source file, re-run ctx config switch <profile> to apply the changes to the working copy.

    Commit Your Profiles

    Both .ctxrc.base and .ctxrc.dev should be committed to git so team members share the same profile definitions. The working copy .ctxrc stays gitignored.

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#using-the-skill","level":2,"title":"Using the Skill","text":"

    In a Claude Code session, say any of:

    • \"switch to dev mode\"
    • \"switch to base\"
    • \"what profile am I on?\"
    • \"toggle verbose logging\"

    The /ctx-config skill handles the rest.

    See also: ctx config reference, Configuration

    ","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/context-health/","level":1,"title":"Detecting and Fixing Drift","text":"","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-problem","level":2,"title":"The Problem","text":"

    ctx files drift: you rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist, TASKS.md is 80 percent completed checkboxes, and CONVENTIONS.md describes patterns you stopped using two months ago.

    Stale context is worse than no context:

    An AI tool that trusts outdated references will hallucinate confidently.

    This recipe shows how to detect drift, fix it, and keep your .context/ directory lean and accurate.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tldr","level":2,"title":"TL;DR","text":"
    ctx drift                      # detect problems\nctx drift --fix                # auto-fix the easy ones\nctx sync --dry-run && ctx sync # reconcile after refactors\nctx compact --archive          # archive old completed tasks\nctx fmt                        # normalize line widths\nctx status                     # verify\n

    Or just ask your agent: \"Is our context clean?\"

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, every command above fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx drift Command Detect stale paths, missing files, violations ctx drift --fix Command Auto-fix simple issues ctx sync Command Reconcile context with codebase structure ctx compact Command Archive completed tasks, clean up empty sections ctx fmt Command Normalize context files to 80-char line width ctx status Command Quick health overview /ctx-drift Skill Structural plus semantic drift detection /ctx-architecture Skill Refresh ARCHITECTURE.md from actual codebase /ctx-status Skill In-session context summary /ctx-prompt-audit Skill Audit prompt quality and token efficiency","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-workflow","level":2,"title":"The Workflow","text":"

    The best way to maintain context health is conversational: Ask your agent, guide it, and let it detect problems, explain them, and fix them with your approval. CLI commands exist for CI pipelines, scripting, and fine-grained control.

    For day-to-day maintenance, talk to your agent.

    Your Questions Reinforce the Pattern

    Asking \"is our context clean?\" does two things:

    • It triggers a drift check right now
    • It reinforces the habit

    This is reinforcement, not enforcement.

    Do not wait for the agent to be proactive on its own:

    Guide your agent, especially in early sessions.

    Over time, you will ask less and the agent will start offering more.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-1-ask-your-agent","level":3,"title":"Step 1: Ask Your Agent","text":"

    The simplest way to check context health:

    Is our context clean?\nAnything stale?\nHow healthy are our context files?\n

    Or invoke the skill directly:

    /ctx-drift\n

    The agent performs two layers of analysis:

    Layer 1, structural checks (via ctx drift): Dead paths, missing files, completed task counts, constitution violations. Fast and programmatic.

    Layer 2, semantic analysis (agent-driven): Does CONVENTIONS.md describe patterns the code no longer follows? Does DECISIONS.md contain entries whose rationale no longer applies? Are there learnings about bugs that are now fixed? This is where the agent adds value the CLI cannot: It reads both context files and source code and compares them.

    The agent reports both layers together, explains each finding in plain language, and offers to fix what it can.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-2-maintenance-at-session-start","level":3,"title":"Step 2: Maintenance at Session Start","text":"

    You do not need to ask explicitly.

    Using Claude Code

    ctx ships with Claude Code hooks that remind the agent at the right time to take initiative.

    Checking context health at the session start, offering to persist learnings before you quit, and flagging drift when it matters. The agent stays proactive without you having to prompt it:

    Agent: Good morning. I've loaded the context files. A few things\n       before we start:\n\n       - ARCHITECTURE.md references `pkg/auth/` which is now empty\n       - DECISIONS.md hasn't been updated in 40 days\n       - There are 18 completed tasks ready for archival\n\n       Want me to run a quick maintenance pass, or should we jump\n       straight into today's work?\n

    ☝️️ this is what persistent, initiative-driven sessions feel like when context is treated as a system instead of a prompt.

    If the agent does not offer this on its own, a gentle nudge is enough:

    Anything stale before we start?\nHow's the context looking?\n

    This turns maintenance from a scheduled chore into a conversation that happens when it matters.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-3-real-time-detection-during-work","level":3,"title":"Step 3: Real-Time Detection during Work","text":"

    Agents can notice drift while working: When a mismatch is directly in the path of their current task. If an agent reads ARCHITECTURE.md to find where to add a handler and internal/handlers/ doesn't exist, it will notice because the stale reference blocks its work:

    Agent: ARCHITECTURE.md references `internal/handlers/` but that directory\n       doesn't exist. I'll look at the actual source tree to find where\n       handlers live now.\n

    This happens reliably when the drift intersects the task. What is less reliable is the agent generalizing from one mismatch to \"there might be more stale references; let me run drift detection\" That leap requires the agent to know /ctx-drift exists and to decide the current task should pause for maintenance.

    If you want that behavior, reinforce it:

    Good catch. Yes, run /ctx-drift and clean up any other stale references.\n

    Over time, agents that have seen this pattern will start offering proactively. But do not expect it from a cold start.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-4-archival-and-cleanup","level":3,"title":"Step 4: Archival and Cleanup","text":"

    ctx drift detects when TASKS.md has more than 10 completed items and flags it as a staleness warning. Running ctx drift --fix archives completed tasks automatically.

    You can also run /ctx-archive to compact on demand.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#knowledge-health-flow","level":3,"title":"Knowledge Health Flow","text":"

    Over time, LEARNINGS.md and DECISIONS.md accumulate entries that overlap or partially repeat each other. The check-persistence hook detects when entry counts exceed a configurable threshold and surfaces a nudge:

    \"LEARNINGS.md has 25+ entries. Consider running /ctx-consolidate to merge overlapping items.\"

    The consolidation workflow:

    1. Review: /ctx-consolidate groups entries by keyword similarity and presents candidate merges for your approval.
    2. Merge: Approved groups are combined into single entries that preserve the key information from each original.
    3. Archive: Originals move to .context/archive/, not deleted -- the full history is preserved in git and the archive directory.
    4. Verify: Run ctx drift after consolidation to confirm no cross-references were broken by the merge.

    This replaces ad-hoc cleanup with a repeatable, nudge-driven cycle: detect accumulation, review candidates, merge with approval, archive originals.

    See also: Knowledge Capture for the recording workflow that feeds into this maintenance cycle.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-doctor-the-superset-check","level":2,"title":"ctx doctor: The Superset Check","text":"

    ctx doctor combines drift detection with hook auditing, configuration checks, event logging status, and token size reporting in a single command. If you want one command that covers structural health, hooks, and state:

    ctx doctor          # everything in one pass\nctx doctor --json   # machine-readable for scripting\n

    Use /ctx-doctor Too

    For agent-driven diagnosis that adds semantic analysis on top of the structural checks, use /ctx-doctor.

    See the Troubleshooting recipe for the full workflow.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#cli-reference","level":2,"title":"CLI Reference","text":"

    The conversational approach above uses CLI commands under the hood. When you need direct control, use the commands directly.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift","level":3,"title":"ctx drift","text":"

    Scan context files for structural problems:

    ctx drift\n

    Sample output:

    Drift Report\n============\n\nWarnings (3):\n  ARCHITECTURE.md:14  path \"internal/api/router.go\" does not exist\n  ARCHITECTURE.md:28  path \"pkg/auth/\" directory is empty\n  CONVENTIONS.md:9    path \"internal/handlers/\" not found\n\nViolations (1):\n  TASKS.md            31 completed tasks (recommend archival)\n\nStaleness:\n  DECISIONS.md        last modified 45 days ago\n  LEARNINGS.md        last modified 32 days ago\n\nExit code: 1 (warnings found)\n
    Level Meaning Action Warning Stale path references, missing files Fix or remove Violation Constitution rule heuristic failures, heavy clutter Fix soon Staleness Files not updated recently Review content

    Exit codes: 0 equals clean, 1 equals warnings, 3 equals violations.

    For CI integration:

    ctx drift --json | jq '.warnings | length'\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift-fix","level":3,"title":"ctx drift --fix","text":"

    Auto-fix mechanical issues:

    ctx drift --fix\n

    This handles removing dead path references, updating unambiguous renames, clearing empty sections. Issues requiring judgment are flagged but left for you.

    Run ctx drift again afterward to confirm what remains.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-sync","level":3,"title":"ctx sync","text":"

    After a refactor, reconcile context with the actual codebase structure:

    ctx sync --dry-run   # preview first\nctx sync             # apply\n

    ctx sync scans for structural changes, compares with ARCHITECTURE.md, checks for new dependencies worth documenting, and identifies context referring to code that no longer exists.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-compact","level":3,"title":"ctx compact","text":"

    Consolidate completed tasks and clean up empty sections:

    ctx compact            # move completed tasks to Completed section,\n                       # remove empty sections\nctx compact --archive  # also archive old tasks to .context/archive/\n
    • Tasks: moves completed items (with all subtasks done) into the Completed section of TASKS.md
    • All files: removes empty sections left behind
    • With --archive: writes tasks older than 7 days to .context/archive/tasks-YYYY-MM-DD.md

    Without --archive, nothing is deleted: Tasks are reorganized in place.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-fmt","level":3,"title":"ctx fmt","text":"

    Normalize context file line widths:

    ctx fmt              # wrap long lines to 80 chars\nctx fmt --check      # CI: exit 1 if files need formatting\n

    Long task descriptions, decision rationale, and learning entries accumulate as single-line entries. ctx fmt wraps them at word boundaries with 2-space continuation indent for list items. Headings, tables, and comments are preserved.

    Idempotent: safe to run repeatedly.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-status","level":3,"title":"ctx status","text":"

    Quick health overview:

    ctx status --verbose\n

    Shows file counts, token estimates, modification times, and drift warnings in a single glance.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-prompt-audit","level":3,"title":"/ctx-prompt-audit","text":"

    Checks whether your context files are readable, compact, and token-efficient for the model.

    /ctx-prompt-audit\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Conversational approach (recommended):

    Is our context clean?  -> agent runs structural plus semantic checks\nFix what you can       -> agent auto-fixes and proposes edits\nArchive the done tasks -> agent runs ctx compact --archive\nHow's token usage?     -> agent checks ctx status\n

    CLI approach (for CI, scripts, or direct control):

    ctx drift                      # 1. Detect problems\nctx drift --fix                # 2. Auto-fix the easy ones\nctx sync --dry-run && ctx sync # 3. Reconcile after refactors\nctx compact --archive          # 4. Archive old completed tasks\nctx fmt                        # 5. Normalize line widths\nctx status                     # 6. Verify\n
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tips","level":2,"title":"Tips","text":"

    Agents cross-reference context files with source code during normal work. When drift intersects their current task, they will notice: a renamed package, a deleted directory, a path that doesn't resolve. But they rarely generalize from one mismatch to a full audit on their own. Reinforce the pattern: when an agent mentions a stale reference, ask it to run /ctx-drift. Over time, it starts offering.

    When an agent says \"this reference looks stale,\" it is usually right.

    Semantic drift is more damaging than structural drift: ctx drift catches dead paths. But CONVENTIONS.md describing a pattern your code stopped following three weeks ago is worse. When you ask \"is our context clean?\", the agent can do both checks.

    Use ctx status as a quick check: It shows file counts, token estimates, and drift warnings in a single glance. Good for a fast \"is everything ok?\" before diving into work.

    Drift detection in CI: add ctx drift --json to your CI pipeline and fail on exit code 3 (violations). This catches constitution-level problems before they reach upstream.

    Do not over-compact: Completed tasks have historical value. The --archive flag preserves them in .context/archive/ so you can search past work without cluttering active context.

    Sync is cautious by default: Use --dry-run after large refactors, then apply.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#next-up","level":2,"title":"Next Up","text":"

    Claude Code Permission Hygiene →: Recommended permission defaults and maintenance workflow for Claude Code.

    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#see-also","level":2,"title":"See Also","text":"
    • Troubleshooting: full diagnostic workflow using ctx doctor, event logs, and /ctx-doctor
    • Tracking Work Across Sessions: task lifecycle and archival
    • Persisting Decisions, Learnings, and Conventions: keeping knowledge files current
    • The Complete Session: where maintenance fits in the daily workflow
    • CLI Reference: full flag documentation for all commands
    • Context Files: structure and purpose of each .context/ file
    ","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/customizing-hook-messages/","level":1,"title":"Customizing Hook Messages","text":"","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-problem","level":2,"title":"The Problem","text":"

    ctx hooks speak ctx's language, not your project's. The QA gate says \"lint the ENTIRE project\" and \"make build,\" but your Python project uses pytest and ruff. The post-commit nudge suggests running lints, but your project uses npm test. You could remove the hook entirely, but then you lose the logic (counting, state tracking, adaptive frequency) just to change the words.

    How do you customize what hooks say without removing what they do?

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tldr","level":2,"title":"TL;DR","text":"
    ctx hook message list                     # see all hooks and their messages\nctx hook message show qa-reminder gate    # view the current template\nctx hook message edit qa-reminder gate    # copy default to .context/ for editing\nctx hook message reset qa-reminder gate   # revert to embedded default\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root: hook message overrides live in your .context/ directory, so ctx needs to know which one. If you skip the eval, ctx hook message ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose ctx hook message list CLI command Show all hook messages with category and override status ctx hook message show CLI command Print the effective message template ctx hook message edit CLI command Copy embedded default to .context/ for editing ctx hook message reset CLI command Delete user override, revert to default","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#how-it-works","level":2,"title":"How It Works","text":"

    Hook messages use a 3-tier fallback:

    1. User override: .context/hooks/messages/{hook}/{variant}.txt
    2. Embedded default: compiled into the ctx binary
    3. Hardcoded fallback: belt-and-suspenders safety net

    The hook logic (when to fire, counting, state tracking, cooldowns) is unchanged. Only the content (what text gets emitted) comes from the template. You customize what the hook says without touching how it decides to speak.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#finding-the-original-templates","level":3,"title":"Finding the Original Templates","text":"

    The default templates live in the ctx source tree at:

    internal/assets/hooks/messages/{hook}/{variant}.txt\n

    You can also browse them on GitHub: internal/assets/hooks/messages/

    Or use ctx hook message show to print any template without digging through source code:

    ctx hook message show qa-reminder gate        # QA gate instructions\nctx hook message show check-persistence nudge  # persistence nudge\nctx hook message show post-commit nudge        # post-commit reminder\n

    The show output includes the template source and available variables -- everything you need to write a replacement.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables","level":3,"title":"Template Variables","text":"

    Some messages use Go text/template variables for dynamic content:

    No context files updated in {{.PromptsSinceNudge}}+ prompts.\nHave you discovered learnings, made decisions,\nestablished conventions, or completed tasks\nworth persisting?\n

    The show and edit commands list available variables for each message. When writing a replacement, keep the same {{.VariableName}} placeholders to preserve dynamic content. Variables that you omit render as <no value>: no error, but the output may look odd.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#intentional-silence","level":3,"title":"Intentional Silence","text":"

    An empty template file (0 bytes or whitespace-only) means \"don't emit a message\". The hook still runs its logic but produces no output. This lets you silence specific messages without removing the hook from hooks.json.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-python-project-qa-gate","level":2,"title":"Example: Python Project QA Gate","text":"

    The default QA gate says \"lint the ENTIRE project\" and references make lint. For a Python project, you want pytest and ruff:

    # See the current default\nctx hook message show qa-reminder gate\n\n# Copy it to .context/ for editing\nctx hook message edit qa-reminder gate\n\n# Edit the override\n

    Replace the content in .context/hooks/messages/qa-reminder/gate.txt:

    HARD GATE! DO NOT COMMIT without completing ALL of these steps first:\n(1) Run the full test suite: pytest -x\n(2) Run the linter: ruff check .\n(3) Verify a clean working tree\nRun tests and linter BEFORE every git commit, no exceptions.\n

    The hook still fires on every Edit call. The logic is identical. Only the instructions changed.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-silencing-ceremony-nudges","level":2,"title":"Example: Silencing Ceremony Nudges","text":"

    The ceremony check nudges you to use /ctx-remember and /ctx-wrap-up. If your team has a different workflow and finds these noisy:

    ctx hook message edit check-ceremonies both\nctx hook message edit check-ceremonies remember\nctx hook message edit check-ceremonies wrapup\n

    Then empty each file:

    echo -n \"\" > .context/hooks/messages/check-ceremonies/both.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/remember.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/wrapup.txt\n

    The hooks still track ceremony usage internally, but they no longer emit any visible output.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-javascript-project-post-commit","level":2,"title":"Example: JavaScript Project Post-Commit","text":"

    The default post-commit nudge mentions generic \"lints and tests.\" For a JavaScript project:

    ctx hook message edit post-commit nudge\n

    Replace with:

    Commit succeeded. 1. Offer context capture to the user: Decision (design\nchoice?), Learning (gotcha?), or Neither. 2. Ask the user: \"Want me to\nrun npm test and eslint before you push?\" Do NOT push. The user pushes\nmanually.\n
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-two-categories","level":2,"title":"The Two Categories","text":"

    Not all messages are equal. The list command shows each message's category:

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#customizable-17-messages","level":3,"title":"Customizable (17 Messages)","text":"

    Messages that are opinions: project-specific wording that benefits from customization. These are the primary targets for override.

    Hook Variant Description check-freshness stale Technology constant freshness warning check-ceremonies both Both ceremonies missing check-ceremonies remember Start-of-session ceremony check-ceremonies wrapup End-of-session ceremony check-context-size checkpoint Context capacity warning check-context-size oversize Injection oversize nudge check-context-size window Context window usage warning (>80%) check-journal both Unimported sessions + unenriched entries check-journal unenriched Unenriched journal entries check-journal unimported Unimported sessions check-knowledge warning Knowledge file growth check-map-staleness stale Architecture map staleness check-persistence nudge Context persistence nudge post-commit nudge Post-commit context capture qa-reminder gate Pre-commit QA gate","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#ctx-specific-10-messages","level":3,"title":"ctx-Specific (10 Messages)","text":"

    Messages specific to ctx's own development workflow. You can customize them, but edit will warn you first.

    Hook Variant Description block-dangerous-commands cp-to-bin Block copy to bin dirs block-dangerous-commands install-to-local-bin Block copy to ~/.local/bin block-dangerous-commands mid-git-push Block git push block-dangerous-commands mid-sudo Block sudo block-non-path-ctx absolute-path Block absolute path invocation block-non-path-ctx dot-slash Block ./ctx invocation block-non-path-ctx go-run Block go run invocation check-reminders reminders Pending reminders relay check-resources alert Resource pressure alert check-version key-rotation Key rotation nudge check-version mismatch Version mismatch","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables-reference","level":2,"title":"Template Variables Reference","text":"Hook Variant Variables check-freshness stale {{.StaleFiles}} check-context-size checkpoint (none) check-context-size oversize {{.TokenCount}} check-context-size window {{.TokenCount}}, {{.Percentage}} check-ceremonies both, remember, wrapup (none) check-journal both {{.UnimportedCount}}, {{.UnenrichedCount}} check-journal unenriched {{.UnenrichedCount}} check-journal unimported {{.UnimportedCount}} check-knowledge warning {{.FileWarnings}} check-map-staleness stale {{.LastRefreshDate}}, {{.ModuleCount}} check-persistence nudge {{.PromptsSinceNudge}} check-reminders reminders {{.ReminderList}} check-resources alert {{.AlertMessages}} check-version key-rotation {{.KeyAgeDays}} check-version mismatch {{.BinaryVersion}}, {{.PluginVersion}} post-commit nudge (none) qa-reminder gate (none) block-dangerous-commands all variants (none) block-non-path-ctx all variants (none)

    Templates that reference undefined variables render <no value>: no error, graceful degradation.

    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tips","level":2,"title":"Tips","text":"
    • Override files are version-controlled: they live in .context/ alongside your other context files. Team members get the same customized messages.
    • Start with show: always check the current default before editing. The embedded template is the baseline your override replaces.
    • Use reset to undo: if a customization causes confusion, reset reverts to the embedded default instantly.
    • Empty file = silence: you don't need to delete the hook. An empty override file silences the message while preserving the hook's logic.
    • JSON output for scripting: ctx hook message list --json returns structured data for automation.
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#see-also","level":2,"title":"See Also","text":"
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Auditing System Hooks: verifying hooks are running and auditing their output
    • Configuration: project-level settings via .ctxrc
    ","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/design-before-coding/","level":1,"title":"Design Before Coding","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-problem","level":2,"title":"The Problem","text":"

    You start coding a feature. Halfway through, you realize the approach doesn't handle a key edge case. You refactor. Then you discover the CLI interface doesn't fit the existing patterns. More refactoring.

    The design work happened during implementation, mixed in with debugging and trial-and-error. The result works, but the spec was never written down, the trade-offs were never recorded, and the next session has no idea why things are shaped this way.

    How do you front-load design so the implementation is straightforward?

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-brainstorm          # explore the design space\n/ctx-spec                # write the spec document\n/ctx-task-add            # break it into tasks\n/ctx-implement           # execute step-by-step\n

    Four skills, used in sequence. Each produces an artifact that feeds the next.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-brainstorm Skill Structured design dialogue: explore approaches /ctx-spec Skill Scaffold and fill out a spec from the template /ctx-task-add Skill Add implementation tasks to TASKS.md /ctx-implement Skill Execute a plan step-by-step with verification /ctx-decision-add Skill Record design choices made during brainstorming","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-1-brainstorm-the-design","level":3,"title":"Step 1: Brainstorm the Design","text":"

    Start with /ctx-brainstorm when you have a rough idea but haven't committed to an approach. The skill follows a gated process:

    1. Understand context: what exists today, what's the gap
    2. Clarify the idea: one question at a time, not a wall of questions
    3. Surface non-functional requirements: performance, security, compatibility
    4. Lock understanding: confirm before exploring solutions
    5. Explore 2-3 approaches: with trade-offs for each
    6. Stress-test: poke holes in the chosen approach
    7. Present the design: detailed enough to implement from
    You: \"I want to add RSS feed generation to the blog.\"\n\nAgent: \"Before we design, let me understand: is this for the journal\n  site, the docs site, or both?\"\n\nYou: \"Journal site only.\"\n\nAgent: [asks about update frequency, content scope, existing infra...]\n       [presents 3 approaches with trade-offs]\n\nYou: \"Option 2 - generate at build time.\"\n\nAgent: \"Let me stress-test that. What happens when...\"\n       [probes edge cases]\n       \"Design looks solid. Want me to record the approach as a\n       decision and write a spec?\"\n

    The brainstorm produces a validated design in conversation. It doesn't produce a file - that's what the spec is for.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-2-write-the-spec","level":3,"title":"Step 2: Write the Spec","text":"

    Once the design is clear, use /ctx-spec to produce the written artifact. The skill reads the project's spec template (specs/tpl/spec-template.md) and walks through each section:

    You: /ctx-spec (rss feed generation)\n\nAgent: \"Creating specs/rss-feed.md from template.\"\n\n       \"Problem: What user-visible problem does this solve? Why now?\"\n\nYou: \"Users can't subscribe to blog updates. They have to check\n      the site manually.\"\n\nAgent: [writes the Problem section, moves to Approach]\n\n       \"Approach: High-level, how does this work?\"\n\nYou: \"Generate feed.xml during ctx journal site. Atom format.\"\n\nAgent: [writes Approach, moves to Happy Path, then Edge Cases...]\n

    The skill spends extra time on Edge Cases and Error Handling: These are where specs earn their value. The skill pushes for at least three edge cases and won't accept \"none\" without a challenge.

    Sections that don't apply can be skipped. The result is a complete spec at specs/{feature-name}.md.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-3-break-into-tasks","level":3,"title":"Step 3: Break into Tasks","text":"

    After the spec is written, the skill offers to create tasks:

    Agent: \"Spec written to specs/rss-feed.md. Want me to break this\n  into tasks in TASKS.md?\"\n\nYou: \"Yes.\"\n\nAgent: [creates a Phase section referencing the spec, adds tasks]\n       \"Added Phase 0.8: RSS/Atom Feed Generation with 4 tasks:\n         - [ ] Implement feed.xml generation in ctx journal site\n         - [ ] Add --feed flag and .ctxrc feed config\n         - [ ] Write tests for feed generation\n         - [ ] Add recipe and update docs\"\n

    Each task is independently completable and references the spec via Spec: specs/rss-feed.md in the Phase header.

    You can also invoke /ctx-task-add directly at any point to add individual tasks.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-4-implement-step-by-step","level":3,"title":"Step 4: Implement Step-by-Step","text":"

    Pick up tasks with /ctx-implement. The skill loads the spec, breaks work into atomic steps, and checkpoints after every 3-5 steps with a build and test verification:

    You: /ctx-implement (specs/rss-feed.md)\n\nAgent: \"Loaded spec. Breaking into steps:\n         1. Add FeedEntry type to journal package\n         2. Implement feed.xml template\n         3. Wire into site generation pipeline\n         [builds and tests after step 3]\n         4. Add --feed flag to ctx journal site\n         5. Add .ctxrc feed configuration\n         [builds and tests after step 5]\n         ...\"\n

    If a build or test fails, the agent stops, diagnoses, and fixes before continuing.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#when-to-skip-steps","level":2,"title":"When to Skip Steps","text":"

    Not every feature needs all four steps. Use your judgment:

    Situation Start at Vague idea, multiple valid approaches Step 1: Brainstorm Clear approach, need to document it Step 2: Spec Spec already exists, need to plan work Step 3: Tasks Tasks exist, ready to code Step 4: Implement

    A brainstorm without a spec is fine for small decisions. A spec without a brainstorm is fine when the design is obvious. The full chain is for features complex enough to warrant front-loaded design.

    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need skill names. Natural language works:

    You say What happens \"Let's think through this feature\" /ctx-brainstorm \"Spec this out\" /ctx-spec \"Write a design doc for...\" /ctx-spec \"Break this into tasks\" /ctx-task-add \"Implement the spec\" /ctx-implement \"Let's design before we build\" Starts at brainstorm","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tips","level":2,"title":"Tips","text":"
    • Brainstorm first when uncertain. If you can articulate the approach in two sentences, skip to spec. If you can't, brainstorm.
    • Specs prevent scope creep. The Non-Goals section is as important as the approach. Writing down what you won't do keeps implementation focused.
    • Edge cases are the point. A spec that only describes the happy path isn't a spec - it's a wish. The /ctx-spec skill pushes for at least 3 edge cases because that's where designs break.
    • Record decisions during brainstorming. When you choose between approaches, the agent offers to persist the trade-off via /ctx-decision-add. Accept - future sessions need to know why, not just what.
    • Specs are living documents. Update them when implementation reveals new constraints. A spec that diverges from reality is worse than no spec.
    • The spec template is customizable. Edit specs/tpl/spec-template.md to match your project's needs. The /ctx-spec skill reads whatever template it finds there.
    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: /ctx-brainstorm: structured design dialogue
    • Skills Reference: /ctx-spec: spec scaffolding from template
    • Skills Reference: /ctx-implement: step-by-step execution with verification
    • Tracking Work Across Sessions: task lifecycle and archival
    • Importing Claude Code Plans: turning ephemeral plans into permanent specs
    • Persisting Decisions, Learnings, and Conventions: capturing design trade-offs
    ","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/external-context/","level":1,"title":"Keeping Context in a Separate Repo","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-problem","level":2,"title":"The Problem","text":"

    ctx files contain project-specific decisions, learnings, conventions, and tasks. By default, they live in .context/ inside the project tree, and that works well when the context can be public.

    But sometimes you need the context outside the project:

    • Open-source projects with private context: Your architectural notes, internal task lists, and scratchpad entries shouldn't ship with the public repo.
    • Compliance or IP concerns: Context files reference sensitive design rationale that belongs in a separate access-controlled repository.
    • Personal preference: You want to keep notes separate from code.

    ctx supports this by letting you point CTX_DIR anywhere. This recipe shows how to set that up and how to tell your AI assistant where to find the context.

    One .context/ per project

    The parent of the context directory is the project root by contract. ctx sync, ctx drift, and the memory-drift hook all read the codebase at filepath.Dir(ContextDir()). Pointing two projects at the same directory corrupts their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use ctx hub, not a shared .context/.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tldr","level":2,"title":"TL;DR","text":"

    Create the external context directory, initialize it, and bind it:

    mkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\ncd ~/repos/myproject\n\n# Bind CTX_DIR to the external location, then init creates files there.\nexport CTX_DIR=~/repos/myproject-context/.context\nctx init\n

    All ctx commands now use the external directory. If you share the setup across shells, add the export CTX_DIR=... line to your shell rc, or source a per-project .envrc with direnv.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#what-works-what-quietly-degrades","level":2,"title":"What Works, What Quietly Degrades","text":"

    The single-source-anchor contract states that filepath.Dir(CTX_DIR) is the project root. When the context lives outside the project tree, ctx still resolves correctly for every operation that reads or writes inside .context/. But any operation that scans the codebase scans the wrong tree, and does so silently:

    Operation Behavior with external .context/ ctx status, agent, add ✅ Works. Operates on files inside CTX_DIR. Journal, scratchpad, hub ✅ Works. Same reason. ctx sync ⚠️ Scans the context repo, not the code repo. ctx drift ⚠️ Same. Reports nothing useful. Memory-drift hook (MEMORY.md) ⚠️ Looks for MEMORY.md next to the external .context/, not the code.

    Nothing errors. The code-aware operations just find an empty or unrelated tree where the project root should be.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#workaround-symlink-the-context-into-the-code-tree","level":3,"title":"Workaround: symlink the .context/ into the code tree","text":"

    If you want both the privacy of an external git repo and working ctx sync / drift / memory-drift, symlink the external .context/ into the code repo and point CTX_DIR at the symlink:

    # External repo holds the real files\nmkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\n\n# Symlink it into the code repo\nln -s ~/repos/myproject-context/.context ~/repos/myproject/.context\n\n# Bind CTX_DIR to the symlink path; ctx init will follow it\nexport CTX_DIR=~/repos/myproject/.context\nctx init\n

    Now filepath.Dir(CTX_DIR) is the code repo, so code-aware operations scan the right tree. The actual files still live in the external repo and commit there. Add .context to the code repo's .gitignore (or .git/info/exclude) so the symlink itself isn't tracked by the code repo.

    The basename guard is permissive about symlinks: it checks the declared name, not the resolved target, so a .context symlink pointing anywhere is accepted as long as the declared basename is .context.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init CLI command Initialize context directory ctx activate CLI command Emit export CTX_DIR=... for the shell CTX_DIR Env variable Declare context directory per-session .ctxrc Config file Per-project configuration /ctx-status Skill Verify context is loading correctly","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-1-create-the-private-context-repo","level":3,"title":"Step 1: Create the Private Context Repo","text":"

    Create a separate repository for your context files. This can live anywhere: a private GitHub repo, a shared drive, a sibling directory:

    # Create the context repo\nmkdir -p ~/repos/myproject-context\ncd ~/repos/myproject-context\ngit init\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-2-initialize-ctx-pointing-at-it","level":3,"title":"Step 2: Initialize ctx Pointing at It","text":"

    From your project root, declare CTX_DIR pointing to the external location, then initialize:

    cd ~/repos/myproject\nCTX_DIR=~/repos/myproject-context/.context ctx init\n

    This creates the canonical .context/ file set inside ~/repos/myproject-context/ instead of ~/repos/myproject/.context/.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-3-make-it-stick","level":3,"title":"Step 3: Make It Stick","text":"

    Declaring CTX_DIR on every command is tedious. Pick one of these methods to make the configuration permanent. The context directory itself must be declared via CTX_DIR; .ctxrc does not carry the path.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-a-ctx_dir-environment-variable-recommended","level":4,"title":"Option A: CTX_DIR Environment Variable (Recommended)","text":"
    # Direct path. Works for ctx status / agent / add but degrades\n# code-aware operations. See \"What Works, What Quietly Degrades\".\nexport CTX_DIR=~/repos/myproject-context/.context\n\n# Or, with the symlink approach above, point at the symlink path\n# inside the code repo so code-aware operations stay healthy.\nexport CTX_DIR=~/repos/myproject/.context\n

    Put either form in your shell profile (~/.bashrc, ~/.zshrc) or a direnv .envrc.

    For a single session, run eval \"$(ctx activate)\" from any directory inside the project where exactly one .context/ candidate is visible (the symlink counts). activate does not accept a path argument; bind a specific path by exporting CTX_DIR directly instead.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-b-ctxrc-for-other-settings","level":4,"title":"Option B: .ctxrc for Other Settings","text":"

    Put any settings (token budget, priority order, freshness files) in a .ctxrc at the project root (dirname(CTX_DIR)), which here is the parent of the external .context/:

    # ~/repos/myproject-context/.ctxrc\ntoken_budget: 16000\n

    .ctxrc is always read from the parent of CTX_DIR, so this file is picked up whenever CTX_DIR points at ~/repos/myproject-context/.context.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#resolution","level":4,"title":"Resolution","text":"

    ctx reads the context directory from a single channel: the CTX_DIR environment variable. When CTX_DIR is unset, ctx errors with a \"no context directory specified\" hint pointing at ctx activate and this recipe. When set, the value must be an absolute path with .context as its basename; relative paths and other names are rejected on first use.

    See Activating a Context Directory for the full recipe.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-4-agent-auto-discovery-via-bootstrap","level":3,"title":"Step 4: Agent Auto-Discovery via Bootstrap","text":"

    When context lives outside the project tree, your AI assistant needs to know where to find it. The ctx system bootstrap command resolves the configured context directory and communicates it to the agent automatically:

    $ ctx system bootstrap\nctx system bootstrap\n====================\n\ncontext_dir: /home/user/repos/myproject-context/.context\n\nFiles:\n  CONSTITUTION.md, TASKS.md, DECISIONS.md, ...\n

    The CLAUDE.md template generated by ctx init already instructs the agent to run ctx system bootstrap at session start. Because CTX_DIR is inherited by child processes, your agent picks up the external path automatically.

    Here is the relevant section from CLAUDE.md for reference:

    <!-- CLAUDE.md -->\n1. **Run `ctx system bootstrap`**: CRITICAL, not optional.\n   This tells you where the context directory is. If it returns any\n   error, relay the error output to the user verbatim, point them at\n   https://ctx.ist/recipes/activating-context/ for setup, and STOP.\n   Do not try to recover; the user decides.\n

    Moreover, every nudge (context checkpoint, persistence reminder, etc.) also includes a Context: /home/user/repos/myproject-context/.context footer, so the agent remains anchored to the correct directory even in long sessions.

    Export CTX_DIR in your shell profile so every hook process inherits it:

    export CTX_DIR=~/repos/myproject-context/.context\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-5-share-with-teammates","level":3,"title":"Step 5: Share with Teammates","text":"

    Teammates clone both repos and export CTX_DIR:

    # Clone the project\ngit clone git@github.com:org/myproject.git\ncd myproject\n\n# Clone the private context repo\ngit clone git@github.com:org/myproject-context.git ~/repos/myproject-context\nexport CTX_DIR=~/repos/myproject-context/.context\n

    If teammates use different paths, each developer sets their own CTX_DIR.

    For encryption key distribution across the team, see the Syncing Scratchpad Notes recipe.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-6-day-to-day-sync","level":3,"title":"Step 6: Day-to-Day Sync","text":"

    The external context repo has its own git history. Treat it like any other repo: commit and push after sessions:

    cd ~/repos/myproject-context\n\n# After a session\ngit add -A\ngit commit -m \"Session: refactored auth module, added rate-limit learning\"\ngit push\n

    Your AI assistant can do this too. When ending a session:

    You: \"Save what we learned and push the context repo.\"\n\nAgent: [runs ctx learning add, then commits and pushes the context repo]\n

    You can also set up a post-session habit: project code gets committed to the project repo, context gets committed to the context repo.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember the flags; simply ask your assistant:

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#set-up-your-system-using-natural-language","level":3,"title":"Set Up Your System Using Natural Language","text":"
    You: \"Set up ctx to use ~/repos/myproject-context as the context directory.\"\n\nAgent: \"I'll set CTX_DIR to that path, run ctx init to materialize\n       it, and show you the export line to add to your shell\n       profile. Want me to seed the core context files too?\"\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#configure-separate-repo-for-context-folder-using-natural-language","level":3,"title":"Configure Separate Repo for .context Folder Using Natural Language","text":"
    You: \"My context is in a separate repo. Can you load it?\"\n\nAgent: [reads CTX_DIR, loads context from the external dir]\n       \"Loaded. You have 3 pending tasks, last session was about the auth\n       refactor.\"\n
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tips","level":2,"title":"Tips","text":"
    • Start simple. If you don't need external context yet, don't set it up. The default .context/ in-tree is the easiest path. Move to an external repo when you have a concrete reason.
    • One context repo per project. Sharing a single context directory across multiple projects corrupts journals, state, and secrets. Use ctx hub for cross-project knowledge sharing.
    • Export CTX_DIR in your shell profile so hooks and tools inherit the path without per-command flags.
    • Commit both repos at session boundaries. Context without code history (or code without context history) loses half the value.
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#next-up","level":2,"title":"Next Up","text":"

    The Complete Session →: Walk through a full ctx session from start to finish.

    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#see-also","level":2,"title":"See Also","text":"
    • Setting Up ctx Across AI Tools: initial setup recipe
    • Syncing Scratchpad Notes Across Machines: distribute encryption keys when context is shared
    • CLI Reference: full command list and global options
    ","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/guide-your-agent/","level":1,"title":"Guide Your Agent","text":"

    Commands vs. Skills

    Commands (ctx status, ctx task add) run in your terminal.

    Skills (/ctx-reflect, /ctx-next) run inside your AI coding assistant.

    Recipes combine both.

    Think of commands as structure and skills as behavior.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#proactive-behavior","level":2,"title":"Proactive Behavior","text":"

    These recipes show explicit commands and skills, but agents trained on the ctx playbook are proactive: They offer to save learnings after debugging, record decisions after trade-offs, create follow-up tasks after completing work, and suggest what to work on next.

    Your questions train the agent. Asking \"what have we learned?\" or \"is our context clean?\" does two things:

    • It triggers the workflow right now,
    • and it reinforces the pattern.

    The more you guide, the more the agent habituates the behavior and begins offering on its own.

    Each recipe includes a Conversational Approach section showing these natural-language patterns.

    Tip

    Don't wait passively for proactive behavior: especially in early sessions.

    Ask, guide, reinforce. Over time, you ask less and the agent offers more.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#next-up","level":2,"title":"Next Up","text":"

    Setup Across AI Tools →: Initialize ctx and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf.

    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle from start to finish
    • Prompting Guide: general tips for working effectively with AI coding assistants
    ","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/hook-output-patterns/","level":1,"title":"Hook Output Patterns","text":"","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code hooks can output text, JSON, or nothing at all. But the format of that output determines who sees it and who acts on it.

    Choose the wrong pattern, and your carefully crafted warning gets silently absorbed by the agent, or your agent-directed nudge gets dumped on the user as noise.

    This recipe catalogs the known hook output patterns and explains when to use each one.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#tldr","level":2,"title":"TL;DR","text":"

    Eight patterns from full control to full invisibility:

    • hard gate (exit 2),
    • VERBATIM relay (agent MUST show),
    • agent directive (context injection),
    • and silent side-effect (background work).

    Most hooks belong in the middle.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-spectrum","level":2,"title":"The Spectrum","text":"

    These patterns form a spectrum based on who decides what the user sees:

    Pattern Who decides? Hard gate Hook decides (agent can't proceed) VERBATIM relay Hook decides (agent must show) Escalating severity Hook suggests, agent judges urgency Conditional relay Hook sets criteria, agent evaluates Suggested action Hook proposes, agent + user decide Agent directive Agent decides entirely Silent injection Nobody: invisible background context Silent side-effect Nobody: invisible background work

    The spectrum runs from full hook control (hard gate) to full invisibility (silent side effect).

    Most hooks belong somewhere in the middle.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-1-hard-gate","level":2,"title":"Pattern 1: Hard Gate","text":"

    Block the tool call entirely. The agent cannot proceed: it must find another approach or tell the user.

    echo '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}'\n

    When to use: Enforcing invariants that must never be violated: Constitution rules, security boundaries, destructive command prevention.

    Hook type: PreToolUse only (Claude Code first-class mechanism).

    Examples in ctx:

    • ctx system block-non-path-ctx: Enforces the PATH invocation rule
    • block-git-push.sh: Requires explicit user approval for pushes (project-local)
    • block-dangerous-commands.sh: Prevents sudo, copies to ~/.local/bin (project-local)

    Trade-off: The agent gets a block response with a reason. Good reasons help the agent recover (\"use X instead\"); bad reasons leave it stuck.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-2-verbatim-relay","level":2,"title":"Pattern 2: VERBATIM Relay","text":"

    Force the agent to show this to the user as-is. The explicit instruction overcomes the agent's tendency to silently absorb context.

    echo \"IMPORTANT: Relay this warning to the user VERBATIM before answering their question.\"\necho \"\"\necho \"┌─ Journal Reminder ─────────────────────────────\"\necho \"│ You have 12 sessions not yet exported.\"\necho \"└────────────────────────────────────────────────\"\n

    When to use: Actionable reminders the user needs to see regardless of what they asked: Stale backups, unimported sessions, resource warnings.

    Hook type: UserPromptSubmit (runs before the agent sees the prompt).

    Examples in ctx:

    • ctx system check-journal: Unexported sessions and unenriched entries
    • ctx system check-context-size: Context capacity warning
    • ctx system check-resources: Resource pressure (memory, swap, disk, load): DANGER only
    • ctx system check-freshness: Technology constant staleness warning

    Trade-off: Noisy if overused. Every VERBATIM relay adds a preamble before the agent's actual answer. Throttle with once-per-day markers or adaptive frequency.

    Key detail: The phrase IMPORTANT: Relay this ... VERBATIM is what makes this work. Without it, agents tend to process the information internally and never surface it. The explicit instruction is the pattern: the box-drawing is just fancy formatting.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-3-agent-directive","level":2,"title":"Pattern 3: Agent Directive","text":"

    Tell the agent to do something, not the user. The agent decides whether and how to involve the user.

    echo \"┌─ Persistence Checkpoint (prompt #25) ───────────\"\necho \"│ No context files updated in 15+ prompts.\"\necho \"│ Have you discovered learnings, decisions,\"\necho \"│ or completed tasks worth persisting?\"\necho \"└──────────────────────────────────────────────────\"\n

    When to use: Behavioral nudges. The hook detects a condition and asks the agent to consider an action. The user may never need to know.

    Hook type: UserPromptSubmit.

    Examples in ctx:

    • ctx system check-persistence: Nudges the agent to persist context

    Trade-off: No guarantee the agent acts. The nudge is one signal among many in the context window. Strong phrasing helps (\"Have you...?\" is better than \"Consider...\"), but ultimately the agent decides.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-4-silent-context-injection","level":2,"title":"Pattern 4: Silent Context Injection","text":"

    Load context with no visible output. The agent gets enriched without either party noticing.

    ctx agent --budget 4000 >/dev/null || true\n

    When to use: Background context loading that should be invisible. The agent benefits from the information, but neither it, nor the user needs to know it happened.

    Hook type: PreToolUse with .* matcher (runs on every tool call).

    Examples in ctx:

    • The ctx agent PreToolUse hook: injects project context silently

    Trade-off: Adds latency to every tool call. Keep the injected content small and fast to generate.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-5-silent-side-effect","level":2,"title":"Pattern 5: Silent Side-Effect","text":"

    Do work, produce no output: Housekeeping that needs no acknowledgment.

    find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n

    When to use: Cleanup, log rotation, temp file management. Anything where the action is the point and nobody needs to know it happened.

    Hook type: Any hook where output is irrelevant.

    Examples in ctx:

    • Log rotation, marker file cleanup, state directory maintenance

    Trade-off: None, if the action is truly invisible. If it can fail in a way that matters, consider logging.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-6-conditional-relay","level":3,"title":"Pattern 6: Conditional Relay","text":"

    Tell the agent to relay only if a condition holds in context.

    echo \"If the user's question involves modifying .context/ files,\"\necho \"relay this warning VERBATIM:\"\necho \"\"\necho \"┌─ Context Integrity ─────────────────────────────\"\necho \"│ CONSTITUTION.md has not been verified in 7 days.\"\necho \"└────────────────────────────────────────────────\"\necho \"\"\necho \"Otherwise, proceed normally.\"\n

    When to use: Warnings that only matter in certain contexts. Avoids noise when the user is doing unrelated work.

    Trade-off: Depends on the agent's judgment about when the condition holds. More fragile than VERBATIM relay, but less noisy.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-7-suggested-action","level":3,"title":"Pattern 7: Suggested Action","text":"

    Give the agent a specific command to propose to the user.

    echo \"┌─ Stale Dependencies ──────────────────────────\"\necho \"│ go.sum is 30+ days newer than go.mod.\"\necho \"│ Suggested: run \\`go mod tidy\\`\"\necho \"│ Ask the user before proceeding.\"\necho \"└───────────────────────────────────────────────\"\n

    When to use: The hook detects a fixable condition and knows the fix. Goes beyond a nudge: Gives the agent a concrete next step. The agent still asks for permission but knows exactly what to propose.

    Trade-off: The suggestion might be wrong or outdated. The \"ask the user before proceeding\" part is critical.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-8-escalating-severity","level":3,"title":"Pattern 8: Escalating Severity","text":"

    Different urgency tiers with different relay expectations.

    # INFO: agent processes silently, mentions if relevant\necho \"INFO: Last test run was 3 days ago.\"\n\n# WARN: agent should mention to user at next natural pause\necho \"WARN: 12 uncommitted changes across 3 branches.\"\n\n# CRITICAL: agent must relay immediately, before any other work\necho \"CRITICAL: Relay VERBATIM before answering. Disk usage at 95%.\"\n

    When to use: When you have multiple hooks producing output and need to avoid overwhelming the user. INFO gets absorbed, WARN gets mentioned, CRITICAL interrupts.

    Examples in ctx:

    • ctx system check-resources: Uses two tiers (WARNING/DANGER) internally but only fires the VERBATIM relay at DANGER level: WARNING is silent. See ctx system for the user-facing command that shows both tiers.

    Trade-off: Requires agent training or convention to recognize the tiers. Without a shared protocol, the prefixes are just text.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#choosing-a-pattern","level":2,"title":"Choosing a Pattern","text":"
    Is the agent about to do something forbidden?\n  └─ Yes → Hard gate\n\nDoes the user need to see this regardless of what they asked?\n  └─ Yes → VERBATIM relay\n  └─ Sometimes → Conditional relay\n\nShould the agent consider an action?\n  └─ Yes, with a specific fix → Suggested action\n  └─ Yes, open-ended → Agent directive\n\nIs this background context the agent should have?\n  └─ Yes → Silent injection\n\nIs this housekeeping?\n  └─ Yes → Silent side-effect\n
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#design-tips","level":2,"title":"Design Tips","text":"

    Throttle aggressively: VERBATIM relays that fire every prompt will be ignored or resented. Use once-per-day markers (touch $REMINDED), adaptive frequency (every Nth prompt), or staleness checks (only fire if condition persists).

    Include actionable commands: \"You have 12 unimported sessions\" is less useful than \"You have 12 unimported sessions. Run: ctx journal import --all.\" Give the user (or agent) the exact next step.

    Use box-drawing for visual structure: The ┌─ ─┐ │ └─ ─┘ pattern makes hook output visually distinct from agent prose. It also signals \"this is machine-generated, not agent opinion.\"

    Test the silence path: Most hook runs should produce no output (the condition isn't met). Make sure the common case is fast and silent.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"

    Lessons from 19 days of hook debugging in ctx. Every one of these was encountered, debugged, and fixed in production.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#silent-misfire-wrong-key-name","level":3,"title":"Silent Misfire: Wrong Key Name","text":"
    { \"PreToolUseHooks\": [ ... ] }\n

    The key is PreToolUse, not PreToolUseHooks. Claude Code validates silently: A misspelled key means the hook is ignored with no error. Always test with a debug echo first to confirm the hook fires before adding real logic.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#json-escaping-breaks-shell-commands","level":3,"title":"JSON Escaping Breaks Shell Commands","text":"

    Go's json.Marshal escapes >, <, and & as Unicode sequences (\\u003e) by default. This breaks shell commands in generated config:

    \"command\": \"ctx agent 2\\u003e/dev/null\"\n

    Fix: use json.Encoder with SetEscapeHTML(false) when generating hook configuration.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#stdin-not-environment-variables","level":3,"title":"stdin, Not Environment Variables","text":"

    Hook input arrives as JSON via stdin, not environment variables:

    # Wrong:\nCOMMAND=\"$CLAUDE_TOOL_INPUT\"\n\n# Right:\nHOOK_INPUT=$(cat)\nCOMMAND=$(echo \"$HOOK_INPUT\" | jq -r '.tool_input.command // empty')\n
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#regex-overfitting","level":3,"title":"Regex Overfitting","text":"

    A regex meant to catch ctx as a binary will also match ctx as a directory component:

    # Too broad: blocks: git -C /home/jose/WORKSPACE/ctx status\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# Narrow to binary only:\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n

    Test hook regexes against paths that contain the target string as a substring, not just as the final component.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#repetition-fatigue","level":3,"title":"Repetition Fatigue","text":"

    Injecting context on every tool call sounds safe. In practice, after seeing the same context injection fifteen times, the agent treats it as background noise: Conventions stated in the injected context get violated because salience has been destroyed by repetition.

    Fix: cooldowns. ctx agent --session $PPID --cooldown 10m injects at most once per ten minutes per session using a tombstone file in /tmp/. This is not an optimization; it is a correction for a design flaw. Every injection consumes attention budget: 50 tool calls at 4,000 tokens each means 200,000 tokens of repeated context, most of it wasted.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#hardcoded-paths","level":3,"title":"Hardcoded Paths","text":"

    A username rename (parallels to jose) broke every hook at once. Use $CLAUDE_PROJECT_DIR instead of absolute paths:

    \"command\": \"\\\"$CLAUDE_PROJECT_DIR\\\"/.claude/hooks/block-git-push.sh\"\n

    If the platform provides a runtime variable for paths, always use it.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#next-up","level":2,"title":"Next Up","text":"

    Webhook Notifications →: Get push notifications when loops complete, hooks fire, or agents hit milestones.

    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#see-also","level":2,"title":"See Also","text":"
    • Customizing Hook Messages: override what hooks say without changing what they do
    • Claude Code Permission Hygiene: how permissions and hooks work together
    • Defense in Depth: why hooks matter for agent security
    ","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/","level":1,"title":"Hook Sequence Diagrams","text":"","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#hook-lifecycle","level":2,"title":"Hook Lifecycle","text":"

    This page documents the ctx system hooks: the built-in ctx system * subcommands that Claude Code invokes via .claude/hooks.json at lifecycle events. These are owned by ctx itself, not authored by users.

    Not to Be Confused with ctx trigger

    ctx has three distinct hook-like layers:

    • ctx system hooks (this page): built-in, owned by ctx, wired into Claude Code via internal/assets/claude/hooks/hooks.json.
    • ctx trigger: user-authored shell scripts in .context/hooks/<type>/*.sh. See ctx trigger reference and the trigger authoring recipe.
    • Claude Code hooks configured directly in .claude/settings.local.json, tool-specific, not portable across AI tools.

    This page is only about the first category.

    Every ctx system hook is a Go binary invoked by Claude Code at one of three lifecycle events: PreToolUse (before a tool runs, can block), PostToolUse (after a tool completes), or UserPromptSubmit (on every user prompt, before any tools run). Hooks receive JSON on stdin and emit JSON or plain text on stdout.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#pretooluse-hooks","level":2,"title":"PreToolUse Hooks","text":"

    These fire before a tool executes. They can block, gate, or inject context.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#context-load-gate","level":3,"title":"Context-Load-Gate","text":"

    Matcher: .* (all tools)

    Injects the full context packet on first tool use of a session. One-shot per session.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as context-load-gate\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Git as git log\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized\n    alt not initialized\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check ctx-loaded-{session} marker\n    alt marker exists\n        Hook-->>CC: (silent exit, already fired)\n    end\n    Hook->>State: Create marker (one-shot guard)\n    Hook->>State: Prune stale session files\n    loop Each file in ReadOrder\n        alt GLOSSARY or TASK\n            Note over Hook: Skip (Task mentioned in footer only)\n        else DECISION or LEARNING\n            Hook->>Ctx: Extract index table only\n        else other files\n            Hook->>Ctx: Read full content\n        end\n        Hook->>Hook: Estimate tokens per file\n    end\n    Hook->>Git: Detect changes since last session\n    Hook->>Hook: Build injection (files + changes + token counts)\n    Hook-->>CC: JSON {additionalContext: injection}\n    Hook->>Hook: Send webhook (metadata only)\n    Hook->>State: Write oversize flag if tokens > threshold
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-non-path-ctx","level":3,"title":"Block-Non-Path-ctx","text":"

    Matcher: Bash

    Blocks ./ctx, go run ./cmd/ctx, or absolute-path ctx invocations. Constitutionally enforced.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-non-path-ctx\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Test regex: relative-path, go-run, absolute-path\n    alt no match\n        Hook-->>CC: (silent exit)\n    end\n    alt absolute-path + test exception\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason + constitution suffix}\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#qa-reminder","level":3,"title":"Qa-Reminder","text":"

    Matcher: Bash

    Gate nudge before any git command. Reminds agent to lint/test.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as qa-reminder\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check command contains \"git\"\n    alt no git command\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, gate, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: QA gate}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#specs-nudge","level":3,"title":"Specs-Nudge","text":"

    Matcher: EnterPlanMode

    Nudges agent to save plans/specs when new implementation detected.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as specs-nudge\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: specs nudge}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#posttooluse-hooks","level":2,"title":"PostToolUse Hooks","text":"

    These fire after a tool completes. They observe, nudge, and track state.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#post-commit","level":3,"title":"Post-Commit","text":"

    Matcher: Bash

    Fires after git commit (not amend). Nudges for context capture and checks version drift.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as post-commit\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"git commit\"?\n    alt not a git commit\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"--amend\"?\n    alt is amend\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: post-commit nudge}\n    Hook->>Hook: Relay(message)\n    Hook->>Hook: CheckVersionDrift()
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-task-completion","level":3,"title":"Check-Task-Completion","text":"

    Matcher: Edit, Write

    Configurable-interval nudge after edits. Per-session counter resets after firing.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-task-completion\n    participant State as .context/state/\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read task nudge interval\n    alt interval <= 0 (disabled)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read per-session counter\n    Hook->>Hook: Increment counter\n    alt counter < interval\n        Hook->>State: Write counter\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Reset counter to 0\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: JSON {additionalContext: task nudge}\n    Hook->>Hook: Relay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#userpromptsubmit-hooks","level":2,"title":"UserPromptSubmit Hooks","text":"

    These fire on every user prompt, before any tools run. They perform health checks, track state, and nudge for housekeeping.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-context-size","level":3,"title":"Check-Context-Size","text":"

    Adaptive context window monitoring. Fires checkpoints, window warnings, and billing alerts based on prompt count and token usage.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-context-size\n    participant State as .context/state/\n    participant Session as Session JSONL\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized\n    Hook->>Hook: Read input, resolve session ID\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: Pause acknowledgment message\n    end\n    Hook->>State: Increment session prompt counter\n    Hook->>Session: Read token info (tokens, model, window)\n\n    rect rgb(255, 240, 240)\n        Note over Hook: Billing check (independent, never suppressed)\n        alt tokens >= billing threshold (one-shot)\n            Hook->>Tpl: LoadMessage(hook, billing, vars)\n            Hook-->>CC: Billing warning nudge box\n            Hook->>Hook: NudgeAndRelay(billing message)\n        end\n    end\n\n    Hook->>State: Check wrap-up marker\n    alt wrapped up recently (< 2h)\n        Hook->>State: Write stats (event: suppressed)\n        Hook-->>CC: (silent exit)\n    end\n\n    rect rgb(240, 248, 255)\n        Note over Hook: Adaptive frequency check\n        alt count > 30 and count % 3 == 0\n            Note over Hook: High frequency trigger\n        else count > 15 and count % 5 == 0\n            Note over Hook: Medium frequency trigger\n        else\n            Hook->>State: Write stats (event: silent)\n            Hook-->>CC: (silent exit)\n        end\n    end\n\n    alt context window >= 80%\n        Hook->>Tpl: LoadMessage(hook, window, vars)\n        Hook-->>CC: Window warning nudge box\n        Hook->>Hook: NudgeAndRelay(window message)\n    else checkpoint trigger\n        Hook->>Tpl: LoadMessage(hook, checkpoint)\n        Hook-->>CC: Checkpoint nudge box\n        Hook->>Hook: NudgeAndRelay(checkpoint message)\n    end\n    Hook->>State: Write session stats
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-ceremonies","level":3,"title":"Check-Ceremonies","text":"

    Daily check for /ctx-remember and /ctx-wrap-up usage in recent journal entries.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-ceremonies\n    participant State as .context/state/\n    participant Journal as Journal files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Read recent files (lookback window)\n    alt no journal files\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Scan for /ctx-remember and /ctx-wrap-up\n    alt both ceremonies present\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Note over Hook: variant: both | remember | wrapup\n    Hook-->>CC: Nudge box (missing ceremonies)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-freshness","level":3,"title":"Check-Freshness","text":"

    Daily check for technology-dependent constants that may need review.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-freshness\n    participant State as .context/state/\n    participant FS as Filesystem\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>FS: Stat tracked files (5 source files)\n    alt all files modified within 6 months\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {StaleFiles})\n    Hook-->>CC: Nudge box (stale file list + review URL)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-journal","level":3,"title":"Check-Journal","text":"

    Daily check for unimported sessions and unenriched journal entries.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-journal\n    participant State as .context/state/\n    participant Journal as Journal dir\n    participant Claude as Claude projects dir\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Check dir exists\n    Hook->>Claude: Check dir exists\n    alt either dir missing\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Get newest entry mtime\n    Hook->>Claude: Count .jsonl files newer than journal\n    Hook->>Journal: Count unenriched entries\n    alt unimported == 0 and unenriched == 0\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, {counts})\n    Note over Hook: variant: both | unimported | unenriched\n    Hook-->>CC: Nudge box (counts)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-knowledge","level":3,"title":"Check-Knowledge","text":"

    Daily check for knowledge file entry/line counts exceeding configured thresholds.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-knowledge\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read thresholds (decisions, learnings, conventions)\n    alt all thresholds disabled (0)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Ctx: Parse DECISIONS.md entry count\n    Hook->>Ctx: Parse LEARNINGS.md entry count\n    Hook->>Ctx: Count CONVENTIONS.md lines\n    Hook->>Hook: Compare against thresholds\n    alt all within limits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, warning, {FileWarnings})\n    Hook-->>CC: Nudge box (file warnings)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-map-staleness","level":3,"title":"Check-Map-Staleness","text":"

    Daily check for architecture map age and relevant code changes.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-map-staleness\n    participant State as .context/state/\n    participant Tracking as map-tracking.json\n    participant Git as git log\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tracking: Read map-tracking.json\n    alt missing, invalid, or opted out\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Parse LastRun date\n    alt map not stale (< N days)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Git: Count commits touching internal/ since LastRun\n    alt no relevant commits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {date, count})\n    Hook-->>CC: Nudge box (last refresh + commit count)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-memory-drift","level":3,"title":"Check-Memory-Drift","text":"

    Per-session check for MEMORY.md changes since last sync.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-memory-drift\n    participant State as .context/state/\n    participant Mem as memory.Discover\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check session tombstone\n    alt already nudged this session\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: DiscoverMemoryPath(projectRoot)\n    alt auto memory not active\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: HasDrift(contextDir, sourcePath)\n    alt no drift\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: Nudge box (drift reminder)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch session tombstone
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-persistence","level":3,"title":"Check-Persistence","text":"

    Tracks context file modification and nudges when edits happen without persisting context. Adaptive threshold based on prompt count.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-persistence\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read persistence state {Count, LastNudge, LastMtime}\n    alt first prompt (no state)\n        Hook->>State: Initialize state {Count:1, LastNudge:0, LastMtime:now}\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Increment Count\n    Hook->>Ctx: Get current context mtime\n    alt context modified since LastMtime\n        Hook->>State: Reset LastNudge = Count, update LastMtime\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: sinceNudge = Count - LastNudge\n    Hook->>Hook: PersistenceNudgeNeeded(Count, sinceNudge)?\n    alt threshold not reached\n        Hook->>State: Write state\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, vars)\n    Hook-->>CC: Nudge box (prompt count, time since last persist)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Update LastNudge = Count, write state
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-reminders","level":3,"title":"Check-Reminders","text":"

    Per-prompt check for due reminders. No throttle.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-reminders\n    participant Store as Reminders store\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Store: ReadReminders()\n    alt load error\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter by due date (After <= today)\n    alt no due reminders\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, reminders, {list})\n    Hook-->>CC: Nudge box (reminder list + dismiss hints)\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-resources","level":3,"title":"Check-Resources","text":"

    Checks system resources (memory, swap, disk, load). Fires on every prompt. No initialization required.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-resources\n    participant Sys as sysinfo\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: HookPreamble (parse input, check pause)\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Sys: Collect snapshot (memory, swap, disk, load)\n    Hook->>Sys: Evaluate thresholds per metric\n    alt max severity < Danger\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter alerts to Danger level only\n    Hook->>Hook: Build alertMessages from danger alerts\n    Hook->>Tpl: LoadMessage(hook, alert, {alertMessages}, fallback)\n    Hook-->>CC: Nudge box (danger alerts)\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-version","level":3,"title":"Check-Version","text":"

    Daily binary-vs-plugin version comparison with piggybacked key rotation check.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-version\n    participant State as .context/state/\n    participant Config as Binary + Plugin version\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read binary version\n    alt dev build\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read plugin version\n    alt plugin version not found or parse error\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Compare major.minor\n    alt versions match\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, mismatch, {versions})\n    Hook-->>CC: Nudge box (version mismatch)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle\n    Hook->>Hook: CheckKeyAge() (piggybacked)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#heartbeat","level":3,"title":"Heartbeat","text":"

    Silent per-prompt pulse. Tracks prompt count, context modification, and token usage. The agent never sees this hook's output.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as heartbeat\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Notify as Webhook + EventLog\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Increment heartbeat counter\n    Hook->>Ctx: Get latest context file mtime\n    Hook->>State: Compare with last recorded mtime\n    Hook->>State: Update mtime record\n    Hook->>State: Read session token info\n    Hook->>Notify: Send heartbeat notification\n    Hook->>Notify: Append to event log\n    Hook->>State: Write heartbeat log entry\n    Note over Hook: No stdout - agent never sees this
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#project-local-hooks","level":2,"title":"Project-Local Hooks","text":"

    These hooks are configured in settings.local.json and are not shipped with ctx. They are specific to individual developer setups.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-dangerous-commands","level":3,"title":"Block-Dangerous-Commands","text":"

    Lifecycle: PreToolUse. Matcher: Bash

    Blocks dangerous shell patterns (sudo, git push, cp to bin). No initialization or pause checks: always active.

    sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-dangerous-commands\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Note over Hook: Cascade: first matching regex wins\n    Hook->>Hook: Test MidSudo regex\n    alt match\n        Hook->>Hook: variant = sudo\n    end\n    Hook->>Hook: Test MidGitPush regex (if no variant)\n    alt match\n        Hook->>Hook: variant = git-push\n    end\n    Hook->>Hook: Test CpMvToBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = cp-to-bin\n    end\n    Hook->>Hook: Test InstallToLocalBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = install-to-bin\n    end\n    alt no variant matched\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason}\n    Hook->>Hook: NudgeAndRelay(message)
    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#throttling-summary","level":2,"title":"Throttling Summary","text":"Hook Lifecycle Throttle Type Scope context-load-gate PreToolUse One-shot marker Per session block-non-path-ctx PreToolUse None Every match qa-reminder PreToolUse None Every git command specs-nudge PreToolUse None Every prompt post-commit PostToolUse None Every git commit check-task-completion PostToolUse Configurable interval Per session check-context-size UserPromptSubmit Adaptive counter Per session check-ceremonies UserPromptSubmit Daily marker Once per day check-freshness UserPromptSubmit Daily marker Once per day check-journal UserPromptSubmit Daily marker Once per day check-knowledge UserPromptSubmit Daily marker Once per day check-map-staleness UserPromptSubmit Daily marker Once per day check-memory-drift UserPromptSubmit Session tombstone Once per session check-persistence UserPromptSubmit Adaptive counter Per session check-reminders UserPromptSubmit None Every prompt check-resources UserPromptSubmit None Every prompt check-version UserPromptSubmit Daily marker Once per day heartbeat UserPromptSubmit None Every prompt block-dangerous-commands PreToolUse * None Every match

    * Project-local hook (settings.local.json), not shipped with ctx.

    ","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#state-file-reference","level":2,"title":"State File Reference","text":"

    All state files live in .context/state/.

    File Pattern Hook Purpose ctx-loaded-{session} context-load-gate One-shot injection marker ctx-paused-{session} (all) Session pause marker ctx-wrapped-up check-context-size Suppress nudges after wrap-up (2h expiry) freshness-checked check-freshness Daily throttle ceremony-reminded check-ceremonies Daily throttle journal-reminded check-journal Daily throttle knowledge-reminded check-knowledge Daily throttle map-staleness-reminded check-map-staleness Daily throttle version-checked check-version Daily throttle memory-drift-nudged-{session} check-memory-drift Per-session tombstone ctx-context-count-{session} check-context-size Prompt counter stats-{session}.jsonl check-context-size Session stats log persist-{session} check-persistence Counter + mtime state ctx-task-count-{session} check-task-completion Prompt counter heartbeat-count-{session} heartbeat Prompt counter heartbeat-mtime-{session} heartbeat Last context mtime","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hub-cluster/","level":1,"title":"HA Cluster","text":"","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#ctx-hub-high-availability-cluster","level":1,"title":"ctx Hub: High-Availability Cluster","text":"

    Run multiple hub nodes with Raft-based leader election for redundancy. Any follower can take over if the leader dies.

    This recipe assumes you've read the ctx Hub overview and the Multi-machine setup. HA only makes sense in the \"small trusted team\" story; a personal cross-project brain on one workstation does not need three Raft peers.

    Raft-Lite

    ctx uses Raft only for leader election, not for data consensus. Entry replication happens via sequence-based gRPC sync on the append-only JSONL store. This is simpler than full Raft log replication and is possible because the store is append-only and clients are idempotent. The implication: a write accepted by the leader is durable on the leader immediately; followers catch up asynchronously. If the leader crashes between accepting a write and replicating it, that write can be lost. Do not use the hub as a bank ledger.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#topology","level":2,"title":"Topology","text":"

    A minimum HA cluster is three nodes. Two is worse than one: it doubles failure probability without providing quorum.

             +-------------+\n         |  client(s)  |\n         +------+------+\n                |\n    +-----------+-----------+\n    |           |           |\n+---v---+   +---v---+   +---v---+\n| hub A |   | hub B |   | hub C |\n| :9900 |   | :9900 |   | :9900 |\n+-------+   +-------+   +-------+\n    ^           ^           ^\n    +-----------+-----------+\n        Raft (leader election)\n        gRPC (data sync)\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-1-bootstrap-the-first-node","level":2,"title":"Step 1: Bootstrap the First Node","text":"
    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n

    The node starts a Raft election as soon as it sees its peers.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-2-start-the-other-nodes","level":2,"title":"Step 2: Start the Other Nodes","text":"

    On hub-b.lan:

    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-c.lan:9900\n

    On hub-c.lan:

    ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-b.lan:9900\n

    After a few seconds, one node wins the election and becomes the leader. The other two are followers.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-3-verify-cluster-state","level":2,"title":"Step 3: Verify Cluster State","text":"

    From any node:

    ctx hub status\n

    Expected output:

    role:       leader\npeers:      hub-a.lan:9900 (leader)\n            hub-b.lan:9900 (follower, in-sync)\n            hub-c.lan:9900 (follower, in-sync)\nentries:    1248\nuptime:     3h42m\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-4-register-clients-with-failover-peers","level":2,"title":"Step 4: Register Clients with Failover Peers","text":"

    The ctx hub * commands above run on the hub nodes themselves and don't need a project. The ctx connection * commands below are different: they live inside a project (the encrypted hub config is stored at .context/.connect.enc), so you have to tell ctx which project first.

    When registering a client, give it the full peer list:

    # In the project directory on the client:\neval \"$(ctx activate)\"\nctx connection register hub-a.lan:9900 \\\n  --token ctx_adm_... \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n

    If the leader becomes unreachable, the client reconnects to the next peer. Followers redirect to the current leader, so writes always land on the right node.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#runtime-membership-changes","level":2,"title":"Runtime Membership Changes","text":"

    Add a new peer without downtime:

    ctx hub peer add hub-d.lan:9900\n

    Remove a decommissioned peer:

    ctx hub peer remove hub-c.lan:9900\n
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#planned-maintenance","level":2,"title":"Planned Maintenance","text":"

    Before taking a leader offline, hand off leadership:

    ssh hub-a.lan 'ctx hub stepdown'\n

    stepdown triggers a new election among the remaining followers before the leader goes offline. In-flight clients briefly pause, then reconnect to the new leader.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#failure-modes-at-a-glance","level":2,"title":"Failure Modes at a Glance","text":"Event What happens Leader crashes New election; clients reconnect to new leader Follower crashes No write impact; catches up on restart Network partition (majority) Majority side keeps serving; minority read-only Network partition (split) No quorum; all nodes read-only Disk full on leader Writes rejected; read traffic continues

    For the full list, see Hub failure modes.

    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#see-also","level":2,"title":"See Also","text":"
    • Multi-machine recipe: single-node deployment
    • Hub operations: backup and maintenance
    • Hub security model: TLS, tokens
    ","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-getting-started/","level":1,"title":"Getting Started","text":"","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#ctx-hub-getting-started","level":1,"title":"ctx Hub: Getting Started","text":"

    Stand up a single-node ctx Hub on localhost, register two projects, publish a decision from one, and see it appear in the other, all in under five minutes.

    Read This First

    If you haven't already, skim the ctx Hub overview. It explains the mental model, names the two user stories (personal vs small team), and (importantly) lists what the hub does not do. This recipe assumes you already know you want the feature.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-youll-get-out-of-this-recipe","level":2,"title":"What You'll Get out of This Recipe","text":"

    By the end, you will have:

    1. A local hub process running on port 9900.
    2. Two project directories both registered with the ctx Hub.
    3. A decision published from project alpha that appears automatically in project beta's .context/hub/ and in ctx agent --include-hub output.

    Concretely, the payoff this unlocks: a lesson you record in one project becomes visible to your agent the next time you open another project, without touching local files in the second project or opening another editor window.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-this-recipe-does-not-cover","level":2,"title":"What This Recipe Does Not Cover","text":"
    • Sharing .context/journal/, .context/pad, or any other local state. The hub only fans out decision, learning, convention, and task entries. Everything else stays local.
    • Multi-user attribution. The hub identifies projects, not people.
    • Running over a LAN; see Multi-machine setup.
    • Redundancy; see HA cluster.
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"
    • ctx installed and on PATH
    • Two project directories, each already initialized with ctx init
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"

    In a dedicated terminal:

    ctx hub start\n

    On first run, the hub generates an admin token and prints it to stdout. Copy it; you'll need it for each project registration:

    ctx hub listening on :9900\nadmin token: ctx_adm_7f3a1c2d...\ndata dir: ~/.ctx/hub-data/\n

    The admin token is written to ~/.ctx/hub-data/admin.token so you can recover it later. Treat it like a password.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-2-register-the-first-project","level":2,"title":"Step 2: Register the First Project","text":"

    ctx hub start above runs on the hub server and doesn't need a project. Step 2 is different: the encrypted hub config is stored inside a project at .context/.connect.enc, so you have to tell ctx which project first.

    cd ~/projects/alpha\neval \"$(ctx activate)\"\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\n

    This stores an encrypted connection config in .context/.connect.enc. The admin token is exchanged for a per-project client token; the admin token itself is never persisted in the project.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-3-choose-what-to-receive","level":2,"title":"Step 3: Choose What to Receive","text":"
    ctx connection subscribe decision learning convention\n

    Only the entry types you subscribe to will be delivered by sync and listen.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-4-publish-a-decision","level":2,"title":"Step 4: Publish a Decision","text":"

    Either use ctx add --share to write locally and push to the ctx Hub:

    ctx decision add \"Use UTC timestamps everywhere\" --share \\\n  --context \"We had timezone drift between the API and journal\" \\\n  --rationale \"Single source of truth avoids conversion bugs\" \\\n  --consequence \"The UI does conversion at render time\"\n

    Or publish an existing entry directly:

    ctx connection publish decision \"Use UTC timestamps everywhere\"\n
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-5-register-a-second-project-and-sync","level":2,"title":"Step 5: Register a Second Project and Sync","text":"
    cd ~/projects/beta\neval \"$(ctx activate)\"   # bind CTX_DIR for this project\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\nctx connection subscribe decision learning convention\nctx connection sync\n

    The decision from alpha now appears in ~/projects/beta/.context/hub/decisions.md with an origin tag and timestamp.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-6-watch-entries-arrive-live","level":2,"title":"Step 6: Watch Entries Arrive Live","text":"

    Instead of re-running sync, stream new entries as they land:

    ctx connection listen\n

    Leave this running in a terminal; every --share publish from any registered project will appear in .context/hub/ immediately.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-7-feed-shared-knowledge-into-the-agent","level":2,"title":"Step 7: Feed Shared Knowledge into the Agent","text":"

    Once entries exist in .context/hub/, include them in the agent context packet:

    ctx agent --include-hub\n

    Shared entries are added as a dedicated tier in the budget-aware assembly, scored by recency and type relevance.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#auto-sync-on-session-start","level":2,"title":"Auto-Sync on Session Start","text":"

    After register, the check-hub-sync hook pulls new entries at the start of each session (daily throttled). Most users never need to call ctx connection sync manually.

    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#where-to-go-next","level":2,"title":"Where to Go Next","text":"
    • Multi-machine hub: run the hub on a LAN host and connect from other workstations.
    • HA cluster: Raft-based leader election for high availability.
    • Hub operations: daemon mode, backup, log rotation, JSONL store layout.
    • Hub security model: token lifecycle, encryption at rest, threat model.
    • ctx connect reference and ctx hub start reference.
    ","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-multi-machine/","level":1,"title":"Multi-Machine","text":"","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#ctx-hub-multi-machine","level":1,"title":"ctx Hub: Multi-Machine","text":"

    Run the hub on a LAN host and connect from project directories on other workstations. This recipe is the Story 2 (\"small trusted team\") shape described in the ctx Hub overview; read that first if you haven't, especially the trust-model warnings.

    This recipe assumes you've already walked through Getting Started and understand what flows through the hub (decisions, learnings, conventions, tasks, not journals, scratchpad, or raw context files).

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#topology","level":2,"title":"Topology","text":"
    +------------------+        +------------------+\n| workstation A    |        | workstation B    |\n|  ~/projects/x    |        |  ~/projects/y    |\n|  ctx connection  |        |  ctx connection  |\n+---------+--------+        +---------+--------+\n          |                           |\n          +-----------+   +-----------+\n                      v   v\n              +-------------------+\n              | LAN host \"nexus\"  |\n              | ctx hub start     |\n              | --daemon          |\n              | :9900             |\n              +-------------------+\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-1-start-the-daemon-on-the-lan-host","level":2,"title":"Step 1: Start the Daemon on the LAN Host","text":"

    On the machine that will hold the hub (call it nexus):

    ctx hub start --daemon --port 9900\n

    The daemon writes a PID file to ~/.ctx/hub-data/hub.pid. Stop it later with:

    ctx hub stop\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-2-firewall-and-port","level":2,"title":"Step 2: Firewall and Port","text":"

    Open port 9900/tcp on nexus to the LAN only. Never expose the hub to the public internet without a reverse proxy and TLS in front of it (see Hub security model).

    Typical LAN allowlist rules:

    firewalldufwnftables
    sudo firewall-cmd --zone=internal \\\n  --add-port=9900/tcp --permanent\nsudo firewall-cmd --reload\n
    sudo ufw allow from 192.168.1.0/24 to any port 9900 proto tcp\n
    sudo nft add rule inet filter input ip saddr 192.168.1.0/24 \\\n  tcp dport 9900 accept\n
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-3-retrieve-the-admin-token","level":2,"title":"Step 3: Retrieve the Admin Token","text":"

    The daemon prints the admin token to stdout on first run. Running as a daemon, that output goes to the log instead:

    cat ~/.ctx/hub-data/admin.token\n

    Copy the token over a trusted channel (SSH, password manager, or an encrypted note). Do not email it or put it in chat.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-4-register-projects-from-each-workstation","level":2,"title":"Step 4: Register Projects from Each Workstation","text":"

    The ctx hub * commands above run on the LAN host (nexus) and don't need a project. Step 4 is different: each workstation registers from inside a project (the encrypted hub config and the fan-out inbox both live under .context/), so you have to tell ctx which project first.

    On workstation A:

    cd ~/projects/x\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n

    On workstation B:

    cd ~/projects/y\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n

    Each registration exchanges the admin token for a per-project client token. Only the client token is persisted in .context/.connect.enc, encrypted with the same AES-256-GCM scheme ctx uses for notification credentials.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-5-verify","level":2,"title":"Step 5: Verify","text":"

    From either workstation:

    ctx connection status\n

    You should see the ctx Hub address, role (leader for single-node), subscription filters, and the sequence number you're synced to.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#tls-recommended","level":2,"title":"TLS (Recommended)","text":"

    For anything beyond a trusted home LAN, terminate TLS in front of the hub. The hub speaks gRPC, so the reverse proxy must speak HTTP/2:

    server {\n    listen 443 ssl http2;\n    server_name nexus.example.com;\n\n    ssl_certificate     /etc/letsencrypt/live/nexus.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/nexus.example.com/privkey.pem;\n\n    location / {\n        grpc_pass grpc://127.0.0.1:9900;\n    }\n}\n

    Point ctx connection register at the public hostname and port 443.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#handling-daemon-restarts","level":2,"title":"Handling Daemon Restarts","text":"

    The hub is append-only JSONL, so restarts are safe. Clients keep their last-seen sequence in .context/hub/.sync-state.json and pick up exactly where they left off on the next sync or listen reconnect.

    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#see-also","level":2,"title":"See Also","text":"
    • HA cluster recipe: for redundancy
    • Hub operations: backup, rotation
    • Hub failure modes
    • Hub security model
    ","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-overview/","level":1,"title":"Overview","text":"","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#ctx-hub-overview","level":1,"title":"ctx Hub: Overview","text":"

    Start here before the other hub recipes. This page answers what the hub is, who it's for, why you'd run one, and, equally important, what it is not.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#mental-model-in-one-paragraph","level":2,"title":"Mental Model in One Paragraph","text":"

    The hub is a fan-out channel for structured knowledge entries across projects. When you publish a decision, learning, convention, or task with --share, the hub stores it in an append-only log and delivers it to every other project subscribed to that type. The next time your agent loads context in any of those projects, shared entries can be included in the context packet alongside local ones.

    That's the whole feature. It is a project-to-project knowledge bus for a small, curated set of entry types. It is not a shared memory, a shared journal, or a multi-user database.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-flows-through-the-hub","level":2,"title":"What Flows through the Hub","text":"

    Only four entry types:

    Type What it is decision Architectural decisions with rationale learning Gotchas, lessons, surprising behaviors convention Coding patterns and standards task Work items worth sharing across projects

    Each entry is an immutable record with a content blob, the publishing project's name as Origin, a timestamp, and a hub-assigned sequence number. Once published, entries are never rewritten.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-does-not-flow-through-the-hub","level":2,"title":"What Does Not Flow through the Hub","text":"

    This is the part new users get wrong most often:

    • Session journals (~/.claude/ logs, .context/journal/) stay local. The hub does not sync your AI session history.
    • Scratchpad (.context/pad) stays local. Encrypted notes never leave the machine they were written on.
    • Local context files as a whole (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) are not mirrored wholesale. Only entries you explicitly --share, or publish later with ctx connection publish, cross the boundary.
    • Anything under .context/ that isn't one of the four entry types above. Configuration, state, logs, memory, journal metadata: all local.

    If you were expecting \"now my agent in project B can see everything my agent did in project A,\" that's not this feature. Local session density still lives on the local machine.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#two-user-stories","level":2,"title":"Two User Stories","text":"

    The hub makes sense in two different shapes. Pick the one that matches your situation; the mechanics are identical but the trust model and threat surface are very different.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-1-personal-cross-project-brain","level":3,"title":"Story 1: Personal Cross-Project Brain","text":"

    One developer, many projects, one hub, usually on localhost.

    You're working across several projects on the same machine (or a handful of machines you own). You want a lesson learned debugging project A to show up when you open project B a week later, without re-discovering it. You want a convention you codified in one project to be visible as-you-type in another.

    Concrete payoff:

    • ctx learning add --share \"...\" in project A → ctx agent --include-hub in project B shows that learning in the next context packet.
    • A decision recorded in your personal \"dotfiles\" project is instantly visible to every other project on your workstation.
    • Cross-project conventions (e.g., \"use UTC timestamps everywhere\") live in one place and propagate.

    Trust model: high, because you trust every participant since every participant is you. Run the hub on localhost or on your own LAN, use the default single-node setup, don't worry about TLS.

    Start here: Getting Started for the one-time setup, then Personal cross-project brain for the day-to-day workflow.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-2-small-trusted-team","level":3,"title":"Story 2: Small Trusted Team","text":"

    A few teammates, projects they each own, one hub on a LAN host they all trust.

    Your team has a handful of services and you want a shared \"things we've learned the hard way\" stream. Someone on the platform team records a convention about timestamp handling; everyone else's agents see it the next session. An on-call engineer records a learning from a 3 AM incident; the rest of the team inherits the lesson without needing to read the postmortem.

    Concrete payoff:

    • Team conventions propagate without needing a wiki or chat.
    • Lessons from one team member become available to everyone else's agent context packets automatically.
    • Cross-project decisions (shared libraries, deployment patterns, naming rules) live in a single log the whole team reads.

    Trust model: the hub assumes everyone holding a client token is friendly. There is no per-user attribution you can rely on, Origin is self-asserted by the publishing client, and there is no read ACL beyond the subscription filter. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.

    Operational shape: run the hub on a LAN host (or a three-node HA cluster for redundancy), put TLS in front of it for anything beyond a home LAN, distribute client tokens over a trusted channel.

    Start here: Multi-machine setup for the deployment, Team knowledge bus for the day-to-day team workflow, then HA cluster if you need redundancy.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#identity-projects-not-users","level":2,"title":"Identity: Projects, Not Users","text":"

    The hub has no concept of users. Its unit of identity is the project. ctx connection register binds a hub token to a project directory, not to a person. Two developers working on the same project share either:

    • The same .connect.enc, copied between machines over a trusted channel, or
    • Different project names (alpha@laptop-a, alpha@laptop-b), because the hub rejects duplicate registrations of the same project name.

    Either works; neither gives you per-human attribution. If you need \"who wrote this,\" the hub is the wrong tool.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#when-not-to-use-it","level":2,"title":"When Not to Use It","text":"
    • Solo, single-project work. Local .context/ files are enough. The hub adds operational surface for no payoff.
    • Untrusted participants. The hub assumes everyone with a client token is friendly. It is not hardened against hostile insiders or compromised tokens.
    • Compliance-sensitive environments. There is no audit trail that can prove who published what, only which project published what, and Origin is self-asserted.
    • Secrets or PII. Entry content is stored plaintext on the hub and fanned out to every subscribed client. Don't publish anything you wouldn't paste in a team chat.
    • Wholesale journal sharing. See \"what does not flow\" above. If that's what you want, this feature won't provide it. Talk to us in the issue tracker about what would.
    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#how-entries-reach-your-agent","level":2,"title":"How Entries Reach Your Agent","text":"

    Once a project is registered and subscribed, entries arrive by three mechanisms:

    1. ctx connection sync: an on-demand pull, replays everything new since the last sequence you saw.
    2. ctx connection listen: a long-lived gRPC stream that writes new entries to .context/hub/ as they arrive.
    3. check-hub-sync hook: runs at session start, daily throttled, so most users never call sync manually.

    Once entries exist in .context/hub/, ctx agent --include-hub adds a dedicated tier to the budget-aware context packet, scored by recency and type relevance. That's the end of the pipeline.

    ","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#where-to-go-next","level":2,"title":"Where to Go Next","text":"If you're… Read Trying it for yourself on one machine Getting Started A solo developer using the hub day-to-day Personal cross-project brain Setting up for a small team on a LAN Multi-machine setup A small team using the hub day-to-day Team knowledge bus Running redundant nodes HA cluster Operating a hub in production Operations Assessing the security posture Security model Debugging a hub in trouble Failure modes Just reading the commands ctx connect, ctx serve, ctx hub","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-personal/","level":1,"title":"Personal Cross-Project Brain","text":"","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#personal-cross-project-brain","level":1,"title":"Personal Cross-Project Brain","text":"

    This recipe shows how one developer uses a ctx Hub across their own projects day-to-day, the \"Story 1\" shape from the Hub overview. You're not setting up infrastructure for a team; you're making a lesson you learned last Tuesday in project A automatically surface when you open project B next Thursday.

    Prerequisites: a working ctx Hub on localhost (see Getting Started for the roughly five-minute setup). This recipe assumes the hub is already running and you've registered at least two projects.

    Activate Each Project First

    Run eval \"$(ctx activate)\" after each cd <project> (or wire it into direnv). The hub server (ctx hub start, etc.) runs on the server and doesn't need this; the commands in this recipe (ctx add --share, ctx agent --include-hub, ctx connection ...) live inside a project and do. If you skip the eval, they'll fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#the-core-loop","level":2,"title":"The Core Loop","text":"

    Every day, the same three verbs matter:

    1. Record: notice a decision, learning, or convention and capture it with ctx add --share.
    2. Subscribe: every project you care about is subscribed to the types you want delivered (set once with ctx connection subscribe).
    3. Load: your agent picks up shared entries on next session start via the auto-sync hook, or explicitly via ctx agent --include-hub.

    That's the whole workflow. The rest of this recipe fills in the concrete moments where each verb matters.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#a-realistic-day","level":2,"title":"A Realistic Day","text":"

    You have three projects on your workstation:

    • ~/projects/api, a Go service you're actively developing
    • ~/projects/cli, a companion CLI that consumes the API
    • ~/projects/dotfiles, your personal conventions and cross-project learnings

    All three are registered with a single hub running on localhost:9900 (started once at boot, or via a systemd user unit; see Hub operations). All three subscribe to decision, learning, and convention.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#0900-start-work-on-api","level":3,"title":"09:00 - Start Work on api","text":"

    You cd ~/projects/api and start a Claude Code session. Behind the scenes, the plugin's PreToolUse hook calls ctx agent --budget 8000 --include-hub before the first tool call. Agent loads:

    • Local .context/ (TASKS, DECISIONS, LEARNINGS, etc.)
    • Foundation steering files (always-inclusion)
    • Everything you've shared from the other two projects

    So the \"use UTC timestamps everywhere\" decision you recorded in dotfiles last week is already in Claude's context for this session, without any manual sync.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1030-you-discover-a-gotcha","level":3,"title":"10:30 - You Discover a Gotcha","text":"

    While debugging, you find that the API's retry loop silently drops the last error when the transport times out. This is the kind of thing you'd normally add to LEARNINGS.md in api/. But it's useful across every Go service you'll ever write, not just this one. So:

    ctx learning add --share \\\n  --context \"Go http.Client retries mask the final error\" \\\n  --lesson  \"Transport timeouts don't surface as errors when the retry loop re-assigns err without wrapping. Check for context.DeadlineExceeded on the request context instead.\" \\\n  --application \"Any retry loop over http.Client.Do that uses a per-attempt timeout\"\n

    The --share flag does two things:

    1. Writes the learning to api/.context/LEARNINGS.md locally (as a normal ctx learning add would).
    2. Publishes the same entry to the ctx Hub, which stores it in the append-only JSONL and fans it out to every subscribed client.

    Within seconds, cli/.context/hub/learnings.md and dotfiles/.context/hub/learnings.md both contain a copy of this learning (the ctx connection listen daemon picks it up from the ctx Hub's Listen stream).

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1200-you-switch-to-cli","level":3,"title":"12:00 - You Switch to cli","text":"

    cd ~/projects/cli, open a new session. The agent packet for cli now includes the learning you just recorded in api, because cli is subscribed to learning and the entry has already been synced into cli/.context/hub/learnings.md.

    You don't have to re-explain the retry-loop gotcha. Claude already sees it.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1400-you-codify-a-convention","level":3,"title":"14:00 - You Codify a Convention","text":"

    You've been writing error messages in api and decided you want a consistent pattern: lowercase start, no trailing period, single-sentence. This is a convention, not a decision; it applies to every Go project you touch. Record it in dotfiles (since that's your \"personal standards\" project), and share it:

    cd ~/projects/dotfiles\nctx convention add --share \\\n  \"Error messages: lowercase start, no trailing period, single sentence (follows Go's stdlib style)\"\n

    The convention lands in dotfiles/CONVENTIONS.md locally and fans out to api and cli via the hub. The next Claude Code session in either project gets the convention injected into the steering-adjacent slot of the agent packet.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1630-end-of-day","level":3,"title":"16:30 - End of Day","text":"

    You didn't run ctx connection sync once. You didn't git push anything between projects. You didn't remember to tell your agent about the retry-loop gotcha in the new project. The hub did all of it for you.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-the-workflow-actually-looks-like","level":2,"title":"What the Workflow Actually Looks Like","text":"

    Stripped of prose, the day's commands were:

    # Morning: nothing. Agent loads --include-hub automatically.\n\n# Mid-morning: record a learning that should cross projects\nctx learning add --share \\\n  --context \"...\" --lesson \"...\" --application \"...\"\n\n# Afternoon: codify a convention in the \"standards\" project\nctx convention add --share \"...\"\n\n# Evening: nothing. Everything's already propagated.\n

    The hub is passive infrastructure. You never talk to it directly; you talk through it by using --share on commands you were already running.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#tips-for-solo-use","level":2,"title":"Tips for Solo Use","text":"

    Pick a \"standards\" project. One of your projects should play the role of \"canonical source for rules you want everywhere.\" Your dotfiles, a personal scratch repo, or a dedicated ctx-standards project all work. Record cross-cutting conventions there and let the hub propagate them to everything else.

    Subscribe to task only if you want cross-project todos. The four subscribable types are decision, learning, convention, task. Tasks are usually project-local; subscribing makes every hub-shared task from every project show up in every other project's agent packet. That's probably not what you want. Skip task in ctx connection subscribe unless you have a specific reason.

    Run the hub as a user-level daemon so you don't have to remember to start it. On Linux with systemd:

    # ~/.config/systemd/user/ctx-hub.service\n[Unit]\nDescription=ctx Hub (personal)\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/ctx hub start\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n
    systemctl --user enable --now ctx-hub.service\n

    Don't overthink subscription filters. For personal use, subscribe every project to all four types at first (or three, if you skip task). Tune later if the context packets get noisy.

    Local storage is fine; no TLS needed. The hub runs on localhost. No one else is on the network. Skip the TLS setup from the Multi-machine recipe; it's relevant when the hub is on a LAN host serving multiple workstations, not when it's a personal daemon.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"

    Not a setup guide. For the one-time hub install and project registration, use Getting Started.

    Not a team guide. If you're sharing across humans, not just across your own projects, read Team knowledge bus instead; the trust model and operational concerns are different.

    Not production operations. For backup, log rotation, failure recovery, and HA, see Hub operations and Hub failure modes.

    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#see-also","level":2,"title":"See Also","text":"
    • Hub overview: when to use the Hub and when not to.
    • Team knowledge bus: the multi-human companion recipe.
    • ctx connect: the client-side commands used above (subscribe, publish, sync, listen, status).
    • ctx add: the --share flag reference.
    • ctx hub: operator commands for starting, stopping, and inspecting the hub.
    ","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-team/","level":1,"title":"Team Knowledge Bus","text":"","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#team-knowledge-bus","level":1,"title":"Team Knowledge Bus","text":"

    This recipe shows how a small trusted team uses a ctx Hub as a shared knowledge bus, the \"Story 2\" shape from the Hub overview. You're not building a wiki, you're not replacing your issue tracker, and you're not running a multi-tenant service. You're connecting 3-10 developers who trust each other so that lessons, decisions, and conventions flow between them without ceremony.

    Prerequisites:

    • A running ctx Hub on a LAN host or internal server everyone on the team can reach. See Multi-machine setup for the deployment guide.
    • Each team member has ctx installed and has ctx connection register-ed their working projects with the hub.
    • Each project on each workstation has been activated for the shell with eval \"$(ctx activate)\". The hub server (ctx hub start, etc.) doesn't need this, but the client side (ctx connection ..., ctx add --share) lives in a project and does. If you skip activation, those client commands fail with Error: no context directory specified. See Activating a Context Directory.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#trust-model-read-this-first","level":2,"title":"Trust Model: Read This First","text":"

    The hub assumes everyone holding a client token is friendly. There's no per-user attribution you can rely on, no read ACL beyond subscription filters, and Origin is self-asserted by the publishing client. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.

    If your team is:

    • ✅ 3-10 engineers, all known to each other, all trusted with production access
    • ✅ On a single internal network or behind a VPN
    • ✅ Comfortable with \"the hub assumes friendly participants\"

    …this recipe fits. If your team is:

    • ❌ Larger than ~15, with turnover
    • ❌ Includes contractors, untrusted agents, or compromised-workstation concerns
    • ❌ Needs audit trails that prove who published what
    • ❌ Requires per-team-member isolation

    …you're in \"Story 3\" territory, which the hub does not support today. Use a wiki or a dedicated knowledge platform instead.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#the-teams-three-verbs","level":2,"title":"The Team's Three Verbs","text":"

    Everyone on the team does three things, same as in the personal recipe, but with different social expectations:

    1. Record: when you learn something that would save a teammate time, capture it with ctx add --share.
    2. Subscribe: every engineer's project directories subscribe to the types the team cares about.
    3. Load: agents pick up shared entries automatically via the auto-sync hook and the --include-hub flag in the PreToolUse hook pipeline.

    The operational shape is identical to solo use. What's different is the culture around publishing: when do you --share, and what belongs on the hub vs. in your local .context/.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-goes-on-the-hub-team-rules-of-thumb","level":2,"title":"What Goes on the Hub (Team Rules of Thumb)","text":"

    Share it if it's true for more than one person. The central question: \"would the next teammate who hits this problem save time if they already knew this?\" If yes, --share. If no, record it locally and move on.

    Decisions:

    • ✅ Cross-service decisions (database choice, auth model, deployment pattern, monitoring stack).
    • ✅ Policy decisions that apply to all services (naming, API versioning, error-message format).
    • ❌ Internal implementation decisions inside a single service (\"chose a map over a slice here because lookups dominate\").
    • ❌ One-off tactical calls for a specific PR.

    Learnings:

    • ✅ Gotchas, surprising behavior, flaky infrastructure quirks, anything you'd tell a teammate over coffee with \"watch out for X\".
    • ✅ Lessons from incidents, right after the postmortem is the highest-value time to share.
    • ❌ Internal debugging notes that only make sense with context from your current branch.

    Conventions:

    • ✅ Repo layout, commit message format, pre-commit hooks, review expectations.
    • ✅ Language-level style decisions that apply across services.
    • ❌ Per-service idioms (\"in billing/ we prefer…\").

    Tasks: almost always project-local. Don't subscribe to task unless the team has a specific reason (e.g., a cross-cutting migration you want visible everywhere).

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#a-realistic-week","level":2,"title":"A Realistic Week","text":"

    Monday, 3 AM incident, shared learning

    On-call engineer Alice gets paged: the payment service starts returning 500s after a dependency update. After an hour she finds the culprit: a breaking change in a transitive gRPC dep that only manifests under high concurrency. Postmortem on Tuesday, but right now she records the learning:

    ctx learning add --share \\\n  --context \"Payment service 3 AM incident, 2026-04-03\" \\\n  --lesson  \"grpc-go v1.62+ changes DialContext behavior under high \\\n  concurrency: connections from a single channel can deadlock if the \\\n  server emits GOAWAY mid-stream. Symptom: 500 errors cluster in \\\n  30s bursts, no error in grpc client logs.\" \\\n  --application \"Any service on grpc-go. Pin to v1.61 or patch with \\\n  keepalive: https://github.com/grpc/grpc-go/issues/...\" \n

    By Tuesday morning, every other engineer's agent context packet contains this learning. When Bob starts work on the ledger service (which also uses grpc-go), his Claude Code session already knows about the gotcha without Bob having to read the incident channel.

    Wednesday, cross-service decision

    The team agrees on a new pattern for API versioning: header-based instead of URL-based. Platform lead Carol records the decision:

    ctx decision add --share \\\n  --context \"Need consistent API versioning across all 6 services. \\\n  Current URL-based /v1/ isn't working for gradual rollouts.\" \\\n  --rationale \"Header-based versioning lets us route by header at the \\\n  edge, which makes canary rollouts trivial. URL-based versioning \\\n  forces clients to update their paths.\" \\\n  --consequence \"All new endpoints use X-API-Version header. \\\n  Existing /v1/ endpoints stay. Deprecation schedule in q3.\" \\\n  \"Use header-based API versioning for new endpoints\"\n

    Every engineer's next session knows about this decision automatically. When Dave starts adding endpoints to the inventory service on Thursday, Claude already prompts him for the header pattern instead of defaulting to /v1/.

    Friday, convention drift caught at review

    Dave notices that his PR auto-formatted some error messages to end with periods. He recalls the team convention is \"no trailing period\" but can't remember where it was documented. He runs ctx connection status, sees the hub is healthy, greps his local .context/hub/conventions.md, and finds:

    ## [2026-03-12] Error message format\nLowercase start, no trailing period, single sentence.\n

    He fixes the PR. No lookup on the wiki, no question in chat, no context-switch penalty.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#workflow-tips-for-teams","level":2,"title":"Workflow Tips for Teams","text":"

    Designate a \"champion\" for decisions. The team lead or platform engineer should be the person who explicitly --shares cross-cutting decisions. Other team members share learnings freely but should ask \"should this be a decision?\" in review before --shareing a decision. This keeps the decision stream signal-rich.

    Publish postmortem learnings immediately, not after the meeting. The postmortem itself is a document; the actionable rules that come out of it belong on the hub, and they should land within an hour of the incident. \"Share fast, edit later\" is the rule.

    Delete noisy entries, don't tolerate them. The hub is append-only, but the .context/hub/ mirror on each client is just Markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate entries.jsonl (see Hub operations). Noisy shared feeds lose trust fast.

    Don't subscribe every project to every type. For backend engineers, subscribing to decision + learning + convention is usually right. For platform or DevOps projects, adding task makes sense. For a prototype or experiment project, subscribing only to convention might be enough.

    Run a single hub, not one per team. If two teams need to share knowledge, they should share a hub. Splitting hubs by team creates silos, which is often exactly the thing you were trying to solve.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#operational-concerns","level":2,"title":"Operational Concerns","text":"

    The team recipe assumes someone owns the hub host. That person (or a small group) is responsible for:

    • Uptime: the hub is infrastructure; treat it like any other internal service you run. See Hub operations.
    • Backups: entries.jsonl is the source of truth. Snapshot it to the same backup tier as your other internal data.
    • Upgrades: cadence the team agrees on. Major upgrades may require everyone to re-register, so do them at natural breaks.
    • Failures: see Hub failure modes for the standard oncall playbook.

    Optional but recommended: run a 3-node Raft cluster so the hub survives individual node failures. See HA cluster. For teams under 10 people, a single-node hub with daily backups is usually fine.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#token-management","level":2,"title":"Token Management","text":"

    Every team member has a client token stored in their .context/.connect.enc. Rules of thumb:

    • One token per engineer per project. Not one token per team; not one shared token. Each engineer registers each of their working projects separately.
    • Token compromise = revoke immediately. When an engineer leaves, their tokens should be removed from clients.json on the hub. This is a manual operation today; see Hub security for the revocation steps.
    • No checked-in tokens. .context/.connect.enc is encrypted with the local machine key, but don't push it to shared repos; it's per-workstation.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"

    Not a wiki replacement. The hub is for structured entries, not prose. Put your architecture overviews, onboarding docs, and design discussions in a real wiki.

    Not an audit log. Origin on the hub is self-asserted. If compliance requires provenance, the hub is the wrong tool.

    Not a ticket system. Task sharing works, but mature teams already have Jira/Linear/Github Issues. Don't try to replace those with hub tasks; use the hub for lightweight cross-project todos that your existing tracker doesn't capture well.

    Not a production service for end users. This is internal team infrastructure. Do not expose the hub to customers, partners, or the open internet.

    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#see-also","level":2,"title":"See Also","text":"
    • Hub overview: when to use the hub and when not to.
    • Personal cross-project brain: the single-developer companion recipe.
    • Multi-machine setup: standing up the hub on a LAN host.
    • HA cluster: optional redundancy for larger teams.
    • Hub operations: backup, rotation, monitoring.
    • Hub security: threat model and hardening checklist.
    ","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/import-plans/","level":1,"title":"Importing Claude Code Plans","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code plan files (~/.claude/plans/*.md) are ephemeral: They have structured context, approach, and file lists, but they're orphaned after the session ends. The filenames are UUIDs, so you can't tell what's in them without opening each one.

    How do you turn a useful plan into a permanent project spec?

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tldr","level":2,"title":"TL;DR","text":"
    You: /ctx-plan-import\nAgent: [lists plans with dates and titles]\n       1. 2026-02-28  Add authentication middleware\n       2. 2026-02-27  Refactor database connection pool\nYou: \"import 1\"\nAgent: [copies to specs/add-authentication-middleware.md]\n

    Plans are copied (not moved) to specs/, slugified by their H1 heading.

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-plan-import Skill List, filter, and import plan files to specs /ctx-task-add Skill Optionally add a task referencing the spec","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-1-list-available-plans","level":3,"title":"Step 1: List Available Plans","text":"

    Invoke the skill and it lists plans with modification dates and titles:

    You: /ctx-plan-import\n\nAgent: Found 3 plan files:\n         1. 2026-02-28  Add authentication middleware\n         2. 2026-02-27  Refactor database connection pool\n         3. 2026-02-25  Import plans skill\n       Which plans would you like to import?\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-2-filter-optional","level":3,"title":"Step 2: Filter (Optional)","text":"

    You can narrow the list with arguments:

    Argument Effect --today Only plans modified today --since YYYY-MM-DD Only plans modified on or after the date --all Import everything without prompting (none) Interactive selection
    You: /ctx-plan-import --today\nYou: /ctx-plan-import --since 2026-02-27\nYou: /ctx-plan-import --all\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-3-select-and-import","level":3,"title":"Step 3: Select and Import","text":"

    Pick one or more plans by number:

    You: \"import 1 and 3\"\n\nAgent: Imported 2 plan(s):\n         ~/.claude/plans/abc123.md -> specs/add-authentication-middleware.md\n         ~/.claude/plans/ghi789.md -> specs/import-plans-skill.md\n       Want me to add tasks referencing these specs?\n

    The agent reads the H1 heading from each plan and slugifies it for the filename. If a plan has no H1 heading, the original filename (minus extension) is used as the slug.

    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-4-add-follow-up-tasks-optional","level":3,"title":"Step 4: Add Follow-Up Tasks (Optional)","text":"

    If you say yes, the agent creates tasks in TASKS.md that reference the imported specs:

    You: \"yes, add tasks\"\n\nAgent: [runs /ctx-task-add for each spec]\n       Added:\n         - [ ] Implement authentication middleware (spec: specs/add-authentication-middleware.md)\n         - [ ] Import plans skill (spec: specs/import-plans-skill.md)\n
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember the exact skill name:

    You say What happens \"import my plans\" /ctx-plan-import (interactive) \"save today's plans as specs\" /ctx-plan-import --today \"import all plans from this week\" /ctx-plan-import --since ... \"turn that plan into a spec\" /ctx-plan-import (filtered)","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tips","level":2,"title":"Tips","text":"
    • Plans are copied, not moved: The originals stay in ~/.claude/plans/. Claude Code manages that directory; ctx doesn't delete from it.
    • Conflict handling: If specs/{slug}.md already exists, the agent asks whether to overwrite or pick a different name.
    • Specs are project memory: Once imported, specs are tracked in git and available to future sessions. Reference them from TASKS.md phase headers with Spec: specs/slug.md.
    • Pair with /ctx-implement: After importing a plan as a spec, use /ctx-implement to execute it step-by-step with verification.
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#see-also","level":2,"title":"See Also","text":"
    • Skills Reference: /ctx-plan-import: full skill description
    • The Complete Session: where plan import fits in the session flow
    • Tracking Work Across Sessions: managing tasks that reference imported specs
    ","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/knowledge-capture/","level":1,"title":"Persisting Decisions, Learnings, and Conventions","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-problem","level":2,"title":"The Problem","text":"

    You debug a subtle issue, discover the root cause, and move on.

    Three weeks later, a different session hits the same issue. The knowledge existed briefly in one session's memory but was never written down.

    Architectural decisions suffer the same fate: you weigh trade-offs, pick an approach, and six sessions later the AI suggests the alternative you already rejected.

    How do you make sure important context survives across sessions?

    Prefer Skills to Raw Commands

    Use /ctx-decision-add and /ctx-learning-add instead of raw ctx add commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx add ... / ctx reindex / ctx decision ... / ctx learning ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-reflect               # surface items worth persisting\n/ctx-decision-add \"Title\"  # record with context/rationale/consequence\n/ctx-learning-add \"Title\"  # record with context/lesson/application\n

    Or just tell your agent: \"What have we learned this session?\"

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx decision add Command Record an architectural decision ctx learning add Command Record a gotcha, tip, or lesson ctx convention add Command Record a coding pattern or standard ctx reindex Command Rebuild both quick-reference indices ctx decision reindex Command Rebuild the DECISIONS.md index ctx learning reindex Command Rebuild the LEARNINGS.md index /ctx-decision-add Skill AI-guided decision capture with validation /ctx-learning-add Skill AI-guided learning capture with validation /ctx-convention-add Skill AI-guided convention recording with placement /ctx-reflect Skill Surface items worth persisting at breakpoints","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-workflow","level":2,"title":"The Workflow","text":"

    Decision, Learning, or Convention?

    • If you chose between alternatives, it is a decision.
    • If you discovered something surprising, it is a learning.
    • If you are codifying a repeated pattern, it is a convention.
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-1-understand-what-to-persist","level":3,"title":"Step 1: Understand What to Persist","text":"

    Three context files serve different purposes:

    Decisions (DECISIONS.md) answer \"why is it this way?\" They record trade-offs between alternatives with structured fields: context, rationale, and consequences. Decisions prevent re-debating settled questions.

    Learnings (LEARNINGS.md) answer \"what did we discover the hard way?\" They record gotchas and debugging insights specific to this project with structured fields: context, lesson, and application.

    Learnings prevent repeating past mistakes.

    Conventions (CONVENTIONS.md) answer \"how do we do things here?\" They record patterns and standards. No structured fields required: just a name, a rule, and an example. Conventions keep code consistent across sessions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-2-record-decisions","level":3,"title":"Step 2: Record Decisions","text":"

    Decisions have three structured fields:

    • context,
    • rationale,
    • and consequences.

    The easiest way to record one is to tell your agent:

    You: \"Save that as a decision: we picked file-based cooldown\n      tokens over env vars.\"\n\nAgent: \"Recorded in DECISIONS.md:\n  - Decision: Use file-based cooldown tokens instead of env vars\n  - Context: Hook subprocesses cannot persist env vars to parent shell\n  - Rationale: File tokens survive across processes. Simpler than\n    IPC. Cleanup is automatic via TTL.\n  - Consequence: Tombstone files accumulate in /tmp. Cannot share\n    state across machines.\"\n

    The agent was present for the trade-off discussion. It fills in the structured fields from what it observed, which is hard to reconstruct later as a terminal one-liner.

    When multiple options were considered, the agent includes rejected alternatives in the rationale automatically.

    The /ctx-decision-add skill also guides you through the fields interactively. For quick decisions, it supports a Y-statement:

    \"In the context of [situation], facing [constraint], we decided for [choice] and against [alternatives], to achieve [benefit], accepting that [trade-off].\"

    CLI Command for Scripting and Automation

    When no agent is in the loop (CI pipelines, shell scripts, hooks), use the CLI directly with structured flags:

    ctx decision add \"Use file-based cooldown tokens instead of env vars\" \\\n  --context \"Hook subprocesses cannot persist env vars to parent shell\" \\\n  --rationale \"File tokens survive across processes. Simpler than IPC. Cleanup is automatic via TTL.\" \\\n  --consequence \"Tombstone files accumulate in /tmp. Cannot share state across machines.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-3-record-learnings","level":3,"title":"Step 3: Record Learnings","text":"

    Learnings have three structured fields: context, lesson, and application.

    Again, your agent is the best tool for this. It was there when you discovered the gotcha and can express it more completely than a backslash-escaped terminal command:

    You: \"That subprocess thing was painful. Save it as a learning.\"\n\nAgent: \"Added to LEARNINGS.md:\n  - Learning: Claude Code hooks run in a subprocess\n  - Context: Set env var in PreToolUse hook, but it was not visible\n    in the main session\n  - Lesson: Hook scripts execute in a child process. Env changes do\n    not propagate to parent.\n  - Application: Use tombstone files for hook-to-session\n    communication. Never rely on hook env vars.\"\n

    You said one sentence. The agent produced a structured entry with all three fields filled from the session context.

    That is the point: the agent has the full conversation and can be far more expressive than what you would type at a command prompt.

    The /ctx-learning-add skill applies three quality filters:

    1. Could someone Google this in 5 minutes?
    2. Is it specific to this codebase?
    3. Did it take real effort to discover?

    All three must pass.

    Learnings capture principles and heuristics, not code snippets.

    CLI Command for Scripting and Automation

    When no agent is in the loop:

    ctx learning add \"Claude Code hooks run in a subprocess\" \\\n  --context \"Set env var in PreToolUse hook, but it was not visible in the main session\" \\\n  --lesson \"Hook scripts execute in a child process. Env changes do not propagate to parent.\" \\\n  --application \"Use tombstone files for hook-to-session communication. Never rely on hook env vars.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-4-record-conventions","level":3,"title":"Step 4: Record Conventions","text":"

    Conventions are simpler: just a name, a rule, and optionally a section.

    These are short enough that either approach works:

    You: \"We've been using kebab-case for every CLI flag. Codify that.\"\n\nAgent: \"Added to CONVENTIONS.md under Naming:\n  'Use kebab-case for all CLI flag names.'\"\n

    Or from the terminal:

    ctx convention add \"Use kebab-case for all CLI flag names\" --section \"Naming\"\n

    Conventions work best for rules that come up repeatedly. Codify a pattern the third time you see it, not the first.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-5-reindex-after-manual-edits","level":3,"title":"Step 5: Reindex After Manual Edits","text":"

    DECISIONS.md and LEARNINGS.md maintain a quick-reference index at the top: a compact table of date and title for each entry. The index updates automatically via ctx add, but falls out of sync after hand edits.

    ctx reindex\n

    This single command regenerates both indices. You can also reindex individually with ctx decision reindex or ctx learning reindex.

    Run reindex after any manual edit. The index lets AI tools scan all entries without reading the full file, which matters when token budgets are tight.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-6-use-ctx-reflect-to-surface-what-to-capture","level":3,"title":"Step 6: Use /ctx-reflect to Surface What to Capture","text":"

    Keep It Conversational

    /ctx-reflect is not the only way to trigger reflection.

    Agents trained on the ctx playbook naturally surface persist-worthy items at breakpoints, even without invoking the skill explicitly.

    A conversational prompt like \"anything worth saving?\" or \"let's wrap up\" can trigger the same review.

    The skill provides a structured checklist, but the behavior is available through natural conversation.

    At natural breakpoints (after completing a feature, fixing a bug, or before ending a session) use /ctx-reflect to identify items worth persisting.

    /ctx-reflect\n

    The skill walks through learnings, decisions, tasks, and session notes, skipping categories with nothing to report. The output includes specific commands for each suggested persist:

    This session implemented file-based cooldown for `ctx agent` and\ndiscovered that hook subprocesses cannot set env vars in the parent.\n\nI'd suggest persisting:\n- **Learning**: Hook subprocesses cannot propagate env vars\n  `ctx learning add \"...\" --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Decision**: File-based cooldown tokens over env vars\n  `ctx decision add \"...\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n

    The skill always asks before persisting.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-7-the-conversational-approach","level":3,"title":"Step 7: The Conversational Approach","text":"

    The commands shown in Steps 2 to 4 are precise, but you rarely need to type them yourself. When working with an agent that has loaded the ctx playbook, natural language triggers the same operations.

    You ask, the agent acts:

    You say What the agent does \"What have we learned this session?\" Reviews recent work, identifies learnings, offers to persist \"Save that as a decision\" Captures the trade-off you just discussed with structured fields \"Any gotchas worth remembering?\" Scans recent work for non-obvious lessons \"Record that convention\" Adds the pattern you just agreed on to CONVENTIONS.md \"Let's wrap up\" Runs through the reflect checklist conversationally

    Example exchange:

    You: \"What did we learn from that debugging session?\"\n\nAgent: \"We discovered three things worth persisting:\n  1. Hook subprocesses can't propagate env vars to the parent. We\n     spent 20 minutes on this before switching to tombstone files.\n  2. Tombstone files need a TTL or they accumulate in /tmp.\n  3. The cooldown logic must check file mtime, not just existence.\n  Want me to save these as learnings?\"\n\nYou: \"Yes, all of them.\"\n\nAgent: \"Done. Added all three to LEARNINGS.md with structured fields.\"\n

    The agent can also be proactive without being asked. The playbook encourages agents to persist context at natural milestones.

    You may see:

    • After fixing a tricky bug: \"I noticed this was a non-obvious issue. Want me to save it as a learning?\"
    • After choosing between approaches: \"We just made a trade-off between file-based tokens and IPC. Should I record this as a decision?\"
    • At session milestones: the agent checks what is worth persisting and offers a brief summary.

    When agents run unattended (no human in the loop), they may persist directly:

    • \"I've added the subprocess env var gotcha to LEARNINGS.md so we don't hit it again.\"
    • \"Recorded the decision to use file-based tokens over env vars in DECISIONS.md.\"
    • \"Marked the cooldown task done and added a follow-up for TTL cleanup.\"

    This is behavior by design.

    The playbook's self-check prompt, \"If this session ended right now, would the next session know what happened?\" drives agents to persist early and often rather than waiting for explicit instructions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#command-line-approach-scripting-and-automation","level":3,"title":"Command-Line Approach (Scripting and Automation)","text":"
    # Decision: record the trade-off\nctx decision add \"Use PostgreSQL over SQLite\" \\\n  --context \"Need concurrent multi-user access\" \\\n  --rationale \"SQLite locks on writes; Postgres handles concurrency\" \\\n  --consequence \"Requires a database server; team needs Postgres training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Learning: record the gotcha\nctx learning add \"SQL migrations must be idempotent\" \\\n  --context \"Deploy failed when migration ran twice after rollback\" \\\n  --lesson \"CREATE TABLE without IF NOT EXISTS fails on retry\" \\\n  --application \"Always use IF NOT EXISTS guards in migrations\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Convention: record the pattern\nctx convention add \"API handlers return structured errors\" --section \"API\"\n\n# Reindex after manual edits\nctx reindex\n\n# Reflect at breakpoints (from AI assistant)\n# /ctx-reflect\n
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#conversational-approach-interactive-sessions","level":3,"title":"Conversational Approach (Interactive Sessions)","text":"
    You: \"We just picked Postgres over SQLite for the concurrency\n      reasons. Save that?\"\n\nAgent: \"Recorded. Added to DECISIONS.md:\n  - Decision: Use PostgreSQL over SQLite\n  - Context: Need concurrent multi-user access\n  - Rationale: SQLite locks on writes; Postgres handles concurrency\n  - Consequence: Requires a database server; team needs Postgres\n    training\"\n\nYou: \"Also that migration thing. The deploy blew up when the\n      migration ran twice.\"\n\nAgent: \"Got it. Added to LEARNINGS.md:\n  - Learning: SQL migrations must be idempotent\n  - Lesson: CREATE TABLE without IF NOT EXISTS fails on retry\n  - Application: Always use IF NOT EXISTS guards in migrations\"\n\nYou: \"Good. Anything else worth saving before we wrap up?\"\n\nAgent: \"We've been using structured error returns in every API\n  handler this session. Want me to codify that as a convention?\"\n\nYou: \"Do it.\"\n\nAgent: \"Done. Added 'API handlers return structured errors' to\n  CONVENTIONS.md under the API section.\"\n

    Both approaches produce the same structured entries in the same context files.

    • The conversational approach is the natural fit for interactive sessions;
    • the CLI commands are better suited for scripts, hooks, and automation pipelines.
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tips","level":2,"title":"Tips","text":"
    • Record decisions at the moment of choice. The alternatives you considered and the reasons you rejected them fade quickly. Capture trade-offs while they are fresh.
    • Learnings should fail the Gemini test. If someone could find it in a 5-minute Gemini search, it does not belong in LEARNINGS.md.
    • Conventions earn their place through repetition. Add a convention the third time you see a pattern, not the first.
    • Use /ctx-reflect at natural breakpoints. The checklist catches items you might otherwise lose.
    • Keep the entries self-contained. Each entry should make sense on its own. A future session may load only one due to token budget constraints.
    • Reindex after every hand edit. It takes less than a second. A stale index causes AI tools to miss entries.
    • Prefer the structured fields. The verbosity forces clarity. A decision without a rationale is just a fact. A learning without an application is just a story.
    • Talk to your agent, do not type commands. In interactive sessions, the conversational approach is the recommended way to capture knowledge. Say \"save that as a learning\" or \"any decisions worth recording?\" and let the agent handle the structured fields. Reserve the CLI commands for scripting, automation, and CI/CD pipelines where there is no agent in the loop.
    • Trust the agent's proactive instincts. Agents trained on the ctx playbook will offer to persist context at milestones. A brief \"want me to save this?\" is cheaper than re-discovering the same lesson three sessions later.
    • Relax provenance per-project if --session-id, --branch, or --commit are impractical (e.g., manual notes outside an AI session). Add to .ctxrc:

      provenance_required:\n  session_id: false   # allow entries without --session-id\n  branch: true        # still require --branch\n  commit: true        # still require --commit\n

      Default is all three required. Only human config relaxes: Agents cannot bypass, and that's by design.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#next-up","level":2,"title":"Next Up","text":"

    Tracking Work Across Sessions →: Add, prioritize, complete, and archive tasks across sessions.

    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#see-also","level":2,"title":"See Also","text":"
    • Tracking Work Across Sessions: managing the tasks that decisions and learnings support
    • The Complete Session: full session lifecycle including reflection and context persistence
    • Detecting and Fixing Drift: keeping knowledge files accurate as the codebase evolves
    • CLI Reference: full documentation for ctx add, ctx decision, ctx learning
    • Context Files: format and conventions for DECISIONS.md, LEARNINGS.md, and CONVENTIONS.md
    ","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/memory-bridge/","level":1,"title":"Bridging Claude Code Auto Memory","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code maintains per-project auto memory at ~/.claude/projects/<slug>/memory/MEMORY.md. This file is:

    • Outside the repo - not version-controlled, not portable
    • Machine-specific - tied to one ~/.claude/ directory
    • Invisible to ctx - context loading and hooks don't read it

    Meanwhile, ctx maintains structured context files (DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) that are git-tracked, portable, and token-budgeted - but Claude Code doesn't automatically write to them.

    The two systems hold complementary knowledge with no bridge between them.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#tldr","level":2,"title":"TL;DR","text":"
    ctx memory sync          # Mirror MEMORY.md into .context/memory/mirror.md\nctx memory status        # Check for drift\nctx memory diff          # See what changed since last sync\n

    The check-memory-drift hook nudges automatically when MEMORY.md changes - you don't need to remember to sync manually.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx memory ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx memory sync CLI command Copy MEMORY.md to mirror, archive previous ctx memory status CLI command Show drift, timestamps, line counts ctx memory diff CLI command Show changes since last sync ctx memory import CLI command Classify and promote entries to .context/ files ctx memory publish CLI command Push curated .context/ content to MEMORY.md ctx memory unpublish CLI command Remove published block from MEMORY.md ctx system check-memory-drift Hook Nudge when MEMORY.md has changed (once/session)","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#how-it-works","level":2,"title":"How It Works","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#discovery","level":3,"title":"Discovery","text":"

    Claude Code encodes project paths as directory names under ~/.claude/projects/. The encoding replaces / with - and prefixes with -:

    /home/jose/WORKSPACE/ctx  →  ~/.claude/projects/-home-jose-WORKSPACE-ctx/\n

    ctx memory uses this encoding to locate MEMORY.md automatically from your project root - no configuration needed.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#mirroring","level":3,"title":"Mirroring","text":"

    When you run ctx memory sync:

    1. The previous mirror is archived to .context/memory/archive/mirror-<timestamp>.md
    2. MEMORY.md is copied to .context/memory/mirror.md
    3. Sync state is updated in .context/state/memory-import.json

    The mirror is git-tracked, so it travels with the project. Archives provide a fallback for projects that don't use git.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#drift-detection","level":3,"title":"Drift Detection","text":"

    The check-memory-drift hook compares MEMORY.md's modification time against the mirror. When drift is detected, the agent sees:

    ┌─ Memory Drift ────────────────────────────────────────────────\n│ MEMORY.md has changed since last sync.\n│ Run: ctx memory sync\n│ Context: .context\n└────────────────────────────────────────────────────────────────\n

    The nudge fires once per session to avoid noise.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#typical-workflow","level":2,"title":"Typical Workflow","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#at-session-start","level":3,"title":"At Session Start","text":"

    If the hook fires a drift nudge, sync before diving into work:

    ctx memory diff     # Review what changed\nctx memory sync     # Mirror the changes\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#periodic-check","level":3,"title":"Periodic Check","text":"
    ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#dry-run","level":3,"title":"Dry Run","text":"

    Preview what sync would do without writing:

    ctx memory sync --dry-run\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#storage-layout","level":2,"title":"Storage Layout","text":"
    .context/\n├── memory/\n│   ├── mirror.md                          # Raw copy of MEMORY.md (often git-tracked)\n│   └── archive/\n│       ├── mirror-2026-03-05-143022.md    # Timestamped pre-sync snapshots\n│       └── mirror-2026-03-04-220015.md\n├── state/\n│   └── memory-import.json                 # Sync tracking state\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#edge-cases","level":2,"title":"Edge Cases","text":"Scenario Behavior Auto memory not active sync exits 1 with message. status reports \"not active\". Hook skips silently. First sync (no mirror) Creates mirror without archiving. MEMORY.md is empty Syncs to empty mirror (valid). Not initialized Init guard rejects (same as all ctx commands).","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#importing-entries","level":2,"title":"Importing Entries","text":"

    Once you've synced, you can classify and promote entries into structured .context/ files:

    ctx memory import --dry-run    # Preview classification\nctx memory import              # Actually promote entries\n

    Each entry is classified by keyword heuristics:

    Keywords Target always use, prefer, never use, standard CONVENTIONS.md decided, chose, trade-off, approach DECISIONS.md gotcha, learned, watch out, bug, caveat LEARNINGS.md todo, need to, follow up TASKS.md Everything else Skipped

    Entries that don't match any pattern are skipped - they stay in the mirror for manual review. Deduplication (hash-based) prevents re-importing the same entry on subsequent runs.

    Review Before Importing

    Use --dry-run first. The heuristic classifier is deliberately simple - it may misclassify ambiguous entries. Review the plan, then import.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-workflow","level":3,"title":"Full Workflow","text":"
    ctx memory sync                # 1. Mirror MEMORY.md\nctx memory import --dry-run    # 2. Preview what would be imported\nctx memory import              # 3. Promote entries to .context/ files\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#publishing-context-to-memorymd","level":2,"title":"Publishing Context to MEMORY.md","text":"

    Push curated .context/ content back into MEMORY.md so Claude Code sees structured project context on session start - without needing hooks.

    ctx memory publish --dry-run    # Preview what would be published\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter line budget\n

    Published content is wrapped in markers:

    <!-- ctx:published -->\n# Project Context (managed by ctx)\n\n## Pending Tasks\n- [ ] Implement feature X\n...\n<!-- ctx:end -->\n

    Rules:

    • ctx owns everything between the markers
    • Claude owns everything outside the markers
    • ctx memory import reads only outside the markers
    • ctx memory publish replaces only inside the markers

    To remove the published block entirely:

    ctx memory unpublish\n

    Publish at Wrap-Up, Not on Commit

    The best time to publish is during session wrap-up, after persisting decisions and learnings. Never auto-publish - give yourself a chance to review what's going into MEMORY.md.

    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-bidirectional-workflow","level":3,"title":"Full Bidirectional Workflow","text":"
    ctx memory sync                 # 1. Mirror MEMORY.md\nctx memory import --dry-run     # 2. Check what Claude wrote\nctx memory import               # 3. Promote entries to .context/\nctx memory publish --dry-run    # 4. Check what would be published\nctx memory publish              # 5. Push context to MEMORY.md\n
    ","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/multi-tool-setup/","level":1,"title":"Setup Across AI Tools","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-problem","level":2,"title":"The Problem","text":"

    You have installed ctx and want to set it up with your AI coding assistant so that context persists across sessions. Different tools have different integration depths. For example:

    • Claude Code supports native hooks that load and save context automatically.
    • Cursor injects context via its system prompt.
    • Aider reads context files through its --read flag.

    This recipe walks through the complete setup for each tool, from initialization through verification, so you end up with a working memory layer regardless of which AI tool you use.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tldr","level":2,"title":"TL;DR","text":"
    cd your-project\nctx init                      # creates .context/\neval \"$(ctx activate)\"        # bind CTX_DIR for this shell\nsource <(ctx completion zsh)  # shell completion (or bash/fish)\n\n# ## Claude Code (automatic after plugin install) ##\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n\n# ## OpenCode ##\nctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n\n# ## Cursor / Aider / Copilot / Windsurf ##\nctx setup cursor # or: aider, copilot, windsurf\n\n# ## Companion tools (highly recommended) ##\nnpx gitnexus analyze          # code knowledge graph\n# Add Gemini Search MCP server for grounded web search\n

    Activate the Project Once Per Shell

    Run eval \"$(ctx activate)\" after ctx init. The ctx setup, ctx init, and ctx completion commands work without it, but if you skip the eval, most others (ctx agent, ctx load, ctx watch, ctx journal ...) fail with Error: no context directory specified. See Activating a Context Directory.

    Create a .ctxrc in your project root to configure token budgets, context directory, drift thresholds, and more.

    Then start your AI tool and ask: \"Do you remember?\"

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx init Create .context/ directory, templates, and permissions ctx setup Generate integration configuration for a specific AI tool ctx agent Print a token-budgeted context packet for AI consumption ctx load Output assembled context in read order (for manual pasting) ctx watch Auto-apply context updates from AI output (non-native tools) ctx completion Generate shell autocompletion for bash, zsh, or fish ctx journal import Import sessions to editable journal Markdown","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-1-initialize-ctx","level":3,"title":"Step 1: Initialize ctx","text":"

    Run ctx init in your project root. This creates the .context/ directory with all template files and seeds ctx permissions in settings.local.json.

    cd your-project\nctx init\n

    This produces the following structure:

    .context/\n  CONSTITUTION.md     # Hard rules the AI must never violate\n  TASKS.md            # Current and planned work\n  CONVENTIONS.md      # Code patterns and standards\n  ARCHITECTURE.md     # System overview\n  DECISIONS.md        # Architectural decisions with rationale\n  LEARNINGS.md        # Lessons learned, gotchas, tips\n  GLOSSARY.md         # Domain terms and abbreviations\n  AGENT_PLAYBOOK.md   # How AI tools should use this system\n

    Using a Different .context Directory

    The .context/ directory doesn't have to live inside your project. Point ctx to an external folder by exporting CTX_DIR (the only declaration channel).

    Useful when context must stay private while the code is public, or when you want to commit notes to a separate repo.

    Caveats (the recipe covers both with workarounds):

    • Code-aware operations degrade silently. ctx sync, ctx drift, and the memory-drift hook read the codebase from dirname(CTX_DIR). With an external .context/, that's the context repo, not your code repo. They scan the wrong tree without erroring. The recipe shows a symlink workaround that keeps both healthy.
    • One .context/ per project, always. Sharing one directory across multiple projects corrupts journals, state, and secrets. For cross-project knowledge sharing (CONSTITUTION, CONVENTIONS, ARCHITECTURE, etc.) use ctx hub, not a shared .context/.

    See External Context for the full recipe and Configuration for the resolver details.

    For Claude Code, install the ctx plugin to get hooks and skills:

    claude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n

    If you only need the core files (useful for lightweight setups), use the --minimal flag:

    ctx init --minimal\n

    This creates only TASKS.md, DECISIONS.md, and CONSTITUTION.md.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-2-generate-tool-specific-hooks","level":3,"title":"Step 2: Generate Tool-Specific Hooks","text":"

    If you are using a tool other than Claude Code (which is configured automatically by ctx init), generate its integration configuration:

    # For Cursor\nctx setup cursor\n\n# For Aider\nctx setup aider\n\n# For GitHub Copilot\nctx setup copilot\n\n# For Windsurf\nctx setup windsurf\n

    Each command prints the configuration you need. How you apply it depends on the tool.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#claude-code","level":4,"title":"Claude Code","text":"

    No action needed. Just install ctx from the Marketplace as ActiveMemory/ctx.

    Claude Code Is a First-Class Citizen

    With the ctx plugin installed, Claude Code gets hooks and skills automatically. The PreToolUse hook runs ctx agent --budget 4000 on every tool call (with a 10-minute cooldown so it only fires once per window).

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#opencode","level":4,"title":"OpenCode","text":"

    Run the one-liner from the project root:

    ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n

    This deploys a lifecycle plugin, slash command skills, AGENTS.md, and registers the ctx MCP server globally. See ctx for OpenCode for full details.

    OpenCode Is a First-Class Citizen

    With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, and persists at session end, with no manual steps needed.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#vs-code","level":4,"title":"VS Code","text":"

    Install the ctx extension from the VS Code Marketplace (publisher: activememory). Then, from your project root:

    ctx init && eval \"$(ctx activate)\"\n

    Open Copilot Chat and type @ctx /init to verify. The extension auto-downloads the ctx CLI if it isn't on PATH. See ctx for VS Code for full details.

    VS Code Is a First-Class Citizen

    The extension carries its own runtime. No ctx setup step is needed. It registers a @ctx chat participant with 45 slash commands, automatic hooks (file save, git commit, .context/ change, dependency-file edit), and a reminder status-bar indicator. Unlike embedded harnesses, the extension ships through its own pipeline to the VS Code Marketplace.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#cursor","level":4,"title":"Cursor","text":"

    Add the system prompt snippet to .cursor/settings.json:

    {\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and .context/CONVENTIONS.md before responding. Follow rules in .context/CONSTITUTION.md.\"\n}\n

    Context files appear in Cursor's file tree. You can also paste a context packet directly into chat:

    ctx agent --budget 4000 | xclip    # Linux\nctx agent --budget 4000 | pbcopy   # macOS\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#aider","level":4,"title":"Aider","text":"

    Create .aider.conf.yml so context files are loaded on every session:

    read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n

    Then start Aider normally:

    aider\n

    Or specify files on the command line:

    aider --read .context/TASKS.md --read .context/CONVENTIONS.md\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-3-set-up-shell-completion","level":3,"title":"Step 3: Set Up Shell Completion","text":"

    Shell completion lets you tab-complete ctx subcommands and flags, which is especially useful while learning the CLI.

    # Bash (add to ~/.bashrc)\nsource <(ctx completion bash)\n\n# Zsh (add to ~/.zshrc)\nsource <(ctx completion zsh)\n\n# Fish\nctx completion fish > ~/.config/fish/completions/ctx.fish\n

    After sourcing, typing ctx a<TAB> completes to ctx agent, and ctx journal <TAB> shows list, show, and export.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-4-verify-the-setup-works","level":3,"title":"Step 4: Verify the Setup Works","text":"

    Start a fresh session in your AI tool and ask:

    \"Do you remember?\"

    A correctly configured tool responds with specific context: current tasks from TASKS.md, recent decisions, and previous session topics. It should not say \"I don't have memory\" or \"Let me search for files.\"

    This question checks the passive side of memory. A properly set-up agent is also proactive: it treats context maintenance as part of its job:

    • After a debugging session, it offers to save a learning.
    • After a trade-off discussion, it asks whether to record the decision.
    • After completing a task, it suggests follow-up items.

    The \"do you remember?\" check verifies both halves: recall and responsibility.

    For example, after resolving a tricky bug, a proactive agent might say:

    That Redis timeout issue was subtle. Want me to save this as a *learning*\nso we don't hit it again?\n

    If you see behavior like this, the setup is working end to end.

    In Claude Code, you can also invoke the /ctx-status skill:

    /ctx-status\n

    This prints a summary of all context files, token counts, and recent activity, confirming that hooks are loading context.

    If context is not loading, check the basics:

    Symptom Fix ctx: command not found Ensure ctx is in your PATH: which ctx Hook errors Verify plugin is installed: claude /plugin list Context not refreshing Cooldown may be active; wait 10 minutes or set --cooldown 0","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-5-enable-watch-mode-for-non-native-tools","level":3,"title":"Step 5: Enable Watch Mode for Non-Native Tools","text":"

    Tools like Aider, Copilot, and Windsurf do not support native hooks for saving context automatically. For these, run ctx watch alongside your AI tool.

    Pipe the AI tool's output through ctx watch:

    # Terminal 1: Run Aider with output logged\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch the log for context updates\nctx watch --log /tmp/aider.log\n

    Or for any generic tool:

    your-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n

    When the AI emits structured update commands, ctx watch parses and applies them automatically:

    <context-update type=\"learning\"\n  context=\"Debugging rate limiter\"\n  lesson=\"Redis MULTI/EXEC does not roll back on error\"\n  application=\"Wrap rate-limit checks in Lua scripts instead\"\n>Redis Transaction Behavior</context-update>\n

    To preview changes without modifying files:

    ctx watch --dry-run --log /tmp/ai.log\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-6-import-session-transcripts-optional","level":3,"title":"Step 6: Import Session Transcripts (Optional)","text":"

    If you want to browse past session transcripts, import them to the journal:

    ctx journal import --all\n

    This converts raw session data into editable Markdown files in .context/journal/. You can then enrich them with metadata using /ctx-journal-enrich-all inside your AI assistant.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Here is the condensed setup for all three tools:

    # ## Common (run once per project) ##\ncd your-project\nctx init\nsource <(ctx completion zsh)       # or bash/fish\n\n# ## Claude Code (automatic, just verify) ##\n# Start Claude Code, then ask: \"Do you remember?\"\n\n# ## OpenCode ##\nctx setup opencode --write\n# Start OpenCode, then ask: \"Do you remember?\"\n\n# ## Cursor ##\nctx setup cursor\n# Add the system prompt to .cursor/settings.json\n# Paste context: ctx agent --budget 4000 | pbcopy\n\n# ## Aider ##\nctx setup aider\n# Create .aider.conf.yml with read: paths\n# Run watch mode alongside: ctx watch --log /tmp/aider.log\n\n# ## Verify any Tool ##\n# Ask your AI: \"Do you remember?\"\n# Expect: specific tasks, decisions, recent context\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tips","level":2,"title":"Tips","text":"
    • Start with ctx init (not --minimal) for your first project. The full template set gives the agent more to work with, and you can always delete files later.
    • For Claude Code, the token budget is configured in the plugin's hooks.json. To customize, adjust the --budget flag in the ctx agent hook command.
    • The --session $PPID flag isolates cooldowns per Claude Code process, so parallel sessions do not suppress each other.
    • Commit your .context/ directory to version control. Several ctx features (journals, changelogs, blog generation) rely on git history.
    • For Cursor and Copilot, keep CONVENTIONS.md visible. These tools treat open files as higher-priority context.
    • Run ctx drift periodically to catch stale references before they confuse the agent.
    • The agent playbook instructs the agent to persist context at natural milestones (completed tasks, decisions, gotchas). In practice, this works best when you reinforce the habit: a quick \"anything worth saving?\" after a debugging session goes a long way.
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#companion-tools-highly-recommended","level":2,"title":"Companion Tools (Highly Recommended)","text":"

    ctx skills can leverage external MCP servers for web search and code intelligence. ctx works without them, but they significantly improve agent behavior across sessions. The investment is small and the benefits compound. Skills like /ctx-code-review, /ctx-explain, and /ctx-refactor all become noticeably better with these tools connected.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gemini-search","level":3,"title":"Gemini Search","text":"

    Provides grounded web search with citations. Used by skills and the agent playbook as the preferred search backend (faster and more accurate than built-in web search).

    Setup: Add the Gemini Search MCP server to your Claude Code settings. See the Gemini Search MCP documentation for installation.

    Verification:

    # The agent checks this automatically during /ctx-remember\n# Manual test: ask the agent to search for something\n

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gitnexus","level":3,"title":"GitNexus","text":"

    Provides a code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Used by skills like /ctx-refactor (impact analysis) and /ctx-code-review (dependency awareness).

    Setup: Add the GitNexus MCP server to your Claude Code settings, then index your project:

    npx gitnexus analyze\n

    Verification:

    # The agent checks this automatically during /ctx-remember\n# If the index is stale, it will suggest rehydrating\n

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#suppressing-the-check","level":3,"title":"Suppressing the Check","text":"

    If you don't use companion tools and want to skip the availability check at session start, add to .ctxrc:

    companion_check: false\n
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#future-direction","level":3,"title":"Future Direction","text":"

    The companion tool integration is evolving toward a pluggable model: bring your own search engine, bring your own code intelligence. The current integration is MCP-based and limited to Gemini Search and GitNexus. If you use a different search or code intelligence tool, skills will degrade gracefully to built-in capabilities.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#next-up","level":2,"title":"Next Up","text":"

    Keeping Context in a Separate Repo →: Store context files outside the project tree for multi-repo or open source setups.

    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle recipe
    • Multilingual Session Parsing: configure session header prefixes for other languages
    • CLI Reference: all commands and flags
    • Integrations: detailed per-tool integration docs
    ","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multilingual-sessions/","level":1,"title":"Multilingual Session Parsing","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#the-problem","level":2,"title":"The Problem","text":"

    Your team works across languages. Session files written by AI tools might use headers like # Oturum: 2026-01-15 - API Düzeltme (Turkish) or # セッション: 2026-01-15 - テスト (Japanese) instead of # Session: 2026-01-15 - Fix API.

    By default, ctx only recognizes Session: as a session header prefix. Files with other prefixes are silently skipped during journal import and journal generation: They look like regular Markdown, not sessions.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#tldr","level":2,"title":"TL;DR","text":"

    Add recognized prefixes to .ctxrc:

    session_prefixes:\n  - \"Session:\"      # English (include to keep default)\n  - \"Oturum:\"       # Turkish\n  - \"セッション:\"     # Japanese\n

    Restart your session. All configured prefixes are now recognized.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#how-it-works","level":2,"title":"How It Works","text":"

    The Markdown session parser detects session files by looking for an H1 header that starts with a known prefix followed by a date:

    # Session: 2026-01-15 - Fix API Rate Limiting\n# Oturum: 2026-01-15 - API Düzeltme\n# セッション: 2026-01-15 - テスト\n

    The list of recognized prefixes comes from session_prefixes in .ctxrc. When the key is absent or empty, ctx falls back to the built-in default: [\"Session:\"].

    Date-only headers (# 2026-01-15 - Morning Work) are always recognized regardless of prefix configuration.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#configuration","level":2,"title":"Configuration","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#adding-a-language","level":3,"title":"Adding a Language","text":"

    Add the prefix with a trailing colon to your .ctxrc:

    session_prefixes:\n  - \"Session:\"\n  - \"Sesión:\"       # Spanish\n

    Include Session: Explicitly

    When you override session_prefixes, the default is replaced, not extended. If you still want English headers recognized, include \"Session:\" in your list.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#team-setup","level":3,"title":"Team Setup","text":"

    Commit .ctxrc to the repo so all team members share the same prefix list. This ensures ctx journal import and journal generation pick up sessions from all team members regardless of language.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#common-prefixes","level":3,"title":"Common Prefixes","text":"Language Prefix English Session: Turkish Oturum: Spanish Sesión: French Session: German Sitzung: Japanese セッション: Korean 세션: Portuguese Sessão: Chinese 会话:","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#verifying","level":3,"title":"Verifying","text":"

    After configuring, test with ctx journal source. Sessions with the new prefixes should appear in the output.

    Activate the Project First

    Run eval \"$(ctx activate)\" from the project root. If you skip it, ctx journal ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#what-this-does-not-do","level":2,"title":"What This Does NOT Do","text":"
    • Change the interface language: ctx output is always English. This setting only controls which session files ctx can parse.
    • Generate headers: ctx never writes session headers. The prefix list is recognition-only (input, not output).
    • Affect JSONL sessions: Claude Code JSONL transcripts don't use header prefixes. This only applies to Markdown session files in .context/sessions/.
    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#see-also","level":2,"title":"See Also","text":"

    See also: Setup Across AI Tools - complete multi-tool setup including Markdown session configuration.

    See also: CLI Reference - full .ctxrc field reference including session_prefixes.

    ","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/parallel-worktrees/","level":1,"title":"Parallel Agent Development with Git Worktrees","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-problem","level":2,"title":"The Problem","text":"

    You have a large backlog (10, 20, 30 open tasks) and many of them are independent: docs work that doesn't touch Go code, a new package that doesn't overlap with existing ones, test coverage for a stable module.

    Running one agent at a time means serial execution. You want 3-4 agents working in parallel, each on its own track, without stepping on each other's files.

    Git worktrees solve this.

    Each worktree is a separate working directory with its own branch, but they share the same .git object database. Combined with ctx's persistent context, each agent session picks up the full project state and works independently.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-worktree                                   # 1. group tasks by file overlap\ngit worktree add ../myproject-docs -b work/docs # 2. create worktrees\ncd ../myproject-docs && claude                  # 3. launch agents (one per track)\n/ctx-worktree teardown docs                     # 4. merge back and clean up\n

    TASKS.md will conflict on merge: Accept all [x] completions from both sides.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-worktree Skill Create, list, and tear down worktrees /ctx-next Skill Pick tasks from the backlog for each track git worktree Command Underlying git worktree management git merge Command Merge completed tracks back to main","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-1-assess-the-backlog","level":3,"title":"Step 1: Assess the Backlog","text":"

    Start in your main checkout. Ask the agent to analyze your tasks and group them by blast radius: which files and directories each task touches.

    /ctx-worktree\nLook at TASKS.md and group the pending tasks into 2-3 independent\ntracks based on which files they'd touch. Show me the grouping\nbefore creating anything.\n

    The agent reads TASKS.md, estimates file overlap, and proposes groups:

    Proposed worktree groups:\n\n  work/docs   # recipe updates, blog post (touches: docs/)\n  work/crypto # scratchpad encryption infra (touches: internal/crypto/)\n  work/tests  # journal test coverage (touches: internal/cli/journal/)\n
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-2-create-the-worktrees","level":3,"title":"Step 2: Create the Worktrees","text":"

    Once you approve the grouping, the agent creates worktrees as sibling directories:

    Create the worktrees for those three groups.\n

    Behind the scenes:

    git worktree add ../myproject-docs -b work/docs\ngit worktree add ../myproject-crypto -b work/crypto\ngit worktree add ../myproject-tests -b work/tests\n

    Each worktree is a full working copy on its own branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-3-launch-agents","level":3,"title":"Step 3: Launch Agents","text":"

    Open a separate terminal (or editor window) for each worktree and start a Claude Code session:

    # Terminal 1\ncd ../myproject-docs\nclaude\n\n# Terminal 2\ncd ../myproject-crypto\nclaude\n\n# Terminal 3\ncd ../myproject-tests\nclaude\n

    Each agent sees the full project, including .context/, and can work independently.

    Do Not Initialize Context in Worktrees

    Do not run ctx init in worktrees: The .context directory is already tracked in git.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-4-work","level":3,"title":"Step 4: Work","text":"

    Each agent works through its assigned tasks. They can read TASKS.md to know what's assigned to their track, use /ctx-next to pick the next item, and commit normally on their work/* branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-5-merge-back","level":3,"title":"Step 5: Merge Back","text":"

    As each track finishes, return to the main checkout and merge:

    /ctx-worktree teardown docs\n

    The agent checks for uncommitted changes, merges work/docs into your current branch, removes the worktree, and deletes the branch.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-6-handle-tasksmd-conflicts","level":3,"title":"Step 6: Handle TASKS.md Conflicts","text":"

    TASKS.md will almost always conflict when merging: Multiple agents will mark different tasks as [x]. This is expected and easy to resolve:

    Accept all completions from both sides. No task should go from [x] back to [ ]. The merge resolution is always additive.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-7-cleanup","level":3,"title":"Step 7: Cleanup","text":"

    After all tracks are merged, verify everything is clean:

    /ctx-worktree list\n

    Should show only the main working tree. All work/* branches should be gone.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't have to use the skill directly for every step. These natural prompts work:

    • \"I have a big backlog. Can we split it across worktrees?\"
    • \"Which of these tasks can run in parallel without conflicts?\"
    • \"Merge the docs track back in.\"
    • \"Clean up all the worktrees, we're done.\"
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#what-works-differently-in-worktrees","level":2,"title":"What Works Differently in Worktrees","text":"

    The encryption key lives at ~/.ctx/.ctx.key (user-level, outside the project). Because all worktrees on the same machine share this path, ctx pad and ctx hook notify work in worktrees automatically - no special setup needed.

    One thing to watch:

    • Journal enrichment: ctx journal import and ctx journal enrich write files relative to the current working directory. Enrichments created in a worktree stay there and are discarded on teardown. Enrich journals on the main branch after merging: the JSONL session logs are always intact, and you don't lose any data.

    Context Files Will Merge Just Fine

    Tracked context files (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) work normally; git handles them.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tips","level":2,"title":"Tips","text":"
    • 3-4 worktrees max. Beyond that, merge complexity outweighs the parallelism benefit. The skill enforces this limit.
    • Group by package or directory, not by priority. Two high-priority tasks that touch the same files must be in the same track.
    • TASKS.md will conflict on merge. This is normal. Accept all [x] completions: The resolution is always additive.
    • Don't run ctx init in worktrees. The .context/ directory is tracked in git. Running init overwrites shared context files.
    • Name worktrees by concern, not by number. work/docs and work/crypto are more useful than work/track-1 and work/track-2.
    • Commit frequently in each worktree. Smaller commits make merge conflicts easier to resolve.
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#next-up","level":2,"title":"Next Up","text":"

    Back to the beginning: Guide Your Agent →

    Or explore the full recipe list.

    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#see-also","level":2,"title":"See Also","text":"
    • Running an Unattended AI Agent: for serial autonomous loops instead of parallel tracks
    • Tracking Work Across Sessions: managing the task backlog that feeds into parallelization
    • The Complete Session: the complete session workflow end-to-end, with examples
    ","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/permission-snapshots/","level":1,"title":"Permission Snapshots","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-problem","level":2,"title":"The Problem","text":"

    Claude Code's .claude/settings.local.json accumulates one-off permissions every time you click \"Allow\". After busy sessions the file is full of session-specific entries that expand the agent's surface area beyond intent.

    Since settings.local.json is .gitignored, there is no PR review or CI check. The file drifts independently on every machine, and there is no built-in way to reset to a known-good state.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-permission-sanitize               # audit for dangerous patterns\nctx permission snapshot            # save golden image\n# ... sessions accumulate cruft ...\nctx permission restore             # reset to golden state\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx permission ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-solution","level":2,"title":"The Solution","text":"

    Save a curated settings.local.json as a golden image, then restore from it to drop session-accumulated permissions. The golden file (.claude/settings.golden.json) is committed to version control and shared with the team.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow ctx permission snapshot Save settings.local.json as golden image ctx permission restore Reset settings.local.json from golden image /ctx-permission-sanitize Audit for dangerous patterns before snapshotting","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#step-by-step","level":2,"title":"Step by Step","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#1-curate-your-permissions","level":3,"title":"1. Curate Your Permissions","text":"

    Start with a clean settings.local.json. Optionally run /ctx-permission-sanitize to remove dangerous patterns first.

    Review the file manually. Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.

    See the Permission Hygiene recipe for recommended defaults.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#2-take-a-snapshot","level":3,"title":"2. Take a Snapshot","text":"
    ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n

    This creates a byte-for-byte copy. No re-encoding, no indent changes.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#3-commit-the-golden-file","level":3,"title":"3. Commit the Golden File","text":"
    git add .claude/settings.golden.json\ngit commit -m \"Add permission golden image\"\n

    The golden file is not gitignored (unlike settings.local.json). This is intentional: it becomes a team-shared baseline.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#4-auto-restore-at-the-session-start","level":3,"title":"4. Auto-Restore at the Session Start","text":"

    Add this instruction to your CLAUDE.md:

    ## On Session Start\n\nRun `ctx permission restore` to reset permissions to the golden image.\n

    The agent will restore the golden image at the start of every session, automatically dropping any permissions accumulated during previous sessions.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#5-update-when-intentional-changes-are-made","level":3,"title":"5. Update When Intentional Changes Are Made","text":"

    When you add a new permanent permission (not a one-off debugging entry):

    # Edit settings.local.json with the new permission\n# Then update the golden image:\nctx permission snapshot\ngit add .claude/settings.golden.json\ngit commit -m \"Update permission golden image: add cargo test\"\n
    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#conversational-approach","level":2,"title":"Conversational Approach","text":"

    You don't need to remember exact commands. These natural-language prompts work with agents trained on the ctx playbook:

    What you say What happens \"Save my current permissions as baseline\" Agent runs ctx permission snapshot \"Reset permissions to the golden image\" Agent runs ctx permission restore \"Clean up my permissions\" Agent runs /ctx-permission-sanitize then snapshot \"What permissions did I accumulate?\" Agent diffs local vs golden","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#next-up","level":2,"title":"Next Up","text":"

    Turning Activity into Content →: Generate blog posts, changelogs, and journal sites from your project activity.

    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#see-also","level":2,"title":"See Also","text":"
    • Permission Hygiene: recommended defaults and maintenance workflow
    • CLI Reference: ctx permission: full command documentation
    ","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/publishing/","level":1,"title":"Turning Activity into Content","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-problem","level":2,"title":"The Problem","text":"

    Your .context/ directory is full of decisions, learnings, and session history.

    Your git log tells the story of a project evolving.

    But none of this is visible to anyone outside your terminal.

    You want to turn this raw activity into:

    • a browsable journal site,
    • blog posts,
    • changelog posts.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tldr","level":2,"title":"TL;DR","text":"
    ctx journal import --all             # 1. import sessions to markdown\n\n/ctx-journal-enrich-all             # 2. add metadata and tags\n\nctx journal site --serve            # 3. build and serve the journal\n\n/ctx-blog about the caching layer   # 4. draft a blog post\n/ctx-blog-changelog v0.1.0 \"v0.2\"   # 5. write a changelog post\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx journal ... fails with Error: no context directory specified. See Activating a Context Directory.

    Read on for details on each stage.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx journal import Command Import session JSONL to editable Markdown ctx journal site Command Generate a static site from journal entries ctx journal obsidian Command Generate an Obsidian vault from journal entries ctx serve Command Serve any zensical directory (default: journal) ctx site feed Command Generate Atom feed from finalized blog posts make journal Makefile Shortcut for import + site rebuild /ctx-journal-enrich-all Skill Full pipeline: import if needed, then batch-enrich (recommended) /ctx-journal-enrich Skill Add metadata, summaries, and tags to one entry /ctx-blog Skill Draft a blog post from recent project activity /ctx-blog-changelog Skill Write a themed post from a commit range","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-1-import-sessions-to-markdown","level":3,"title":"Step 1: Import Sessions to Markdown","text":"

    Raw session data lives as JSONL files in Claude Code's internal storage. The first step is converting these into readable, editable Markdown.

    # Import all sessions from the current project\nctx journal import --all\n\n# Import from all projects (if you work across multiple repos)\nctx journal import --all --all-projects\n\n# Import a single session by ID or slug\nctx journal import abc123\nctx journal import gleaming-wobbling-sutherland\n

    Imported files land in .context/journal/ as individual Markdown files with session metadata and the full conversation transcript.

    --all is safe by default: Only new sessions are imported. Existing files are skipped. Use --regenerate to re-import existing files (YAML frontmatter is preserved). Use --regenerate --keep-frontmatter=false -y to regenerate everything including frontmatter.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-2-enrich-entries-with-metadata","level":3,"title":"Step 2: Enrich Entries with Metadata","text":"

    Raw entries have timestamps and conversations but lack the structured metadata that makes a journal searchable. Use /ctx-journal-enrich-all to process your entire backlog at once:

    /ctx-journal-enrich-all\n

    The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.

    For large backlogs (20+ entries), it can spawn subagents to process entries in parallel.

    To enrich a single entry instead:

    /ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich 2026-01-24\n

    After enrichment, an entry gains YAML frontmatter:

    ---\ntitle: \"Implement Redis caching for API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n

    This metadata powers better navigation in the journal site:

    • titles replace slugs,
    • summaries appear in the index,
    • and search covers topics and technologies.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-3-generate-the-journal-site","level":3,"title":"Step 3: Generate the Journal Site","text":"

    With entries exported and enriched, generate the static site:

    # Generate site files\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally (opens at http://localhost:8000)\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n

    The site is generated in .context/journal-site/ by default. It uses zensical for static site generation (pipx install zensical).

    Or use the Makefile shortcut that combines export and rebuild:

    make journal\n

    This runs ctx journal import --all followed by ctx journal site --build, then reminds you to enrich before rebuilding. To serve the built site, use make journal-serve or ctx serve (serve-only, no regeneration).

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#alternative-export-to-obsidian-vault","level":3,"title":"Alternative: Export to Obsidian Vault","text":"

    If you use Obsidian for knowledge management, generate a vault instead of (or alongside) the static site:

    ctx journal obsidian\nctx journal obsidian --output ~/vaults/ctx-journal\n

    This produces an Obsidian-ready directory with wikilinks, MOC (Map of Content) pages for topics/files/types, and a \"Related Sessions\" footer on each entry for graph connectivity. Open the output directory in Obsidian as a vault.

    The vault uses the same enriched source entries as the static site. Both outputs can coexist: The static site goes to .context/journal-site/, the vault to .context/journal-obsidian/.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-4-draft-blog-posts-from-activity","level":3,"title":"Step 4: Draft Blog Posts from Activity","text":"

    When your project reaches a milestone worth sharing, use /ctx-blog to draft a post from recent activity. The skill gathers context from multiple sources: git log, DECISIONS.md, LEARNINGS.md, completed tasks, and journal entries.

    /ctx-blog about the caching layer we just built\n/ctx-blog last week's refactoring work\n/ctx-blog lessons learned from the migration\n

    The skill gathers recent commits, decisions, and learnings; identifies a narrative arc; drafts an outline for approval; writes the full post; and saves it to docs/blog/YYYY-MM-DD-slug.md.

    Posts are written in first person with code snippets, commit references, and an honest discussion of what went wrong.

    The Output Is zensical-Flavored Markdown

    The blog skills produce Markdown tuned for a zensical site: topics: frontmatter (zensical's tag field), a docs/blog/ output path, and a banner image reference.

    The content is still standard Markdown and can be adapted to other static site generators, but the defaults assume a zensical project structure.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-5-write-changelog-posts-from-commit-ranges","level":3,"title":"Step 5: Write Changelog Posts from Commit Ranges","text":"

    For release notes or \"what changed\" posts, /ctx-blog-changelog takes a starting commit and a theme, then analyzes everything that changed:

    /ctx-blog-changelog 040ce99 \"building the journal system\"\n/ctx-blog-changelog HEAD~30 \"what's new in v0.2.0\"\n/ctx-blog-changelog v0.1.0 \"the road to v0.2.0\"\n

    The skill diffs the commit range, identifies the most-changed files, and constructs a narrative organized by theme rather than chronology, including a key commits table and before/after comparisons.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-6-generate-the-blog-feed","level":3,"title":"Step 6: Generate the Blog Feed","text":"

    After publishing blog posts, generate the Atom feed so readers and automation can discover new content:

    ctx site feed\n

    This scans docs/blog/ for finalized posts (reviewed_and_finalized: true), extracts title, date, author, topics, and summary, and writes a valid Atom 1.0 feed to site/feed.xml. The feed is also generated automatically as part of make site.

    The feed is available at ctx.ist/feed.xml.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-conversational-approach","level":2,"title":"The Conversational Approach","text":"

    You can also drive your publishing anytime with natural language:

    \"write about what we did this week\"\n\"turn today's session into a blog post\"\n\"make a changelog post covering everything since the last release\"\n\"enrich the last few journal entries\"\n

    The agent has full visibility into your .context/ state (tasks completed, decisions recorded, learnings captured), so its suggestions are grounded in what actually happened.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    The full pipeline from raw transcripts to published content:

    # 1. Import all sessions\nctx journal import --all\n\n# 2. In Claude Code: enrich all entries with metadata\n/ctx-journal-enrich-all\n\n# 3. Build and serve the journal site\nmake journal\nmake journal-serve\n\n# 3b. Or generate an Obsidian vault\nctx journal obsidian\n\n# 4. In Claude Code: draft a blog post\n/ctx-blog about the features we shipped this week\n\n# 5. In Claude Code: write a changelog post\n/ctx-blog-changelog v0.1.0 \"what's new in v0.2.0\"\n

    The journal pipeline is idempotent at every stage. You can rerun ctx journal import --all without losing enrichment. You can rebuild the site as many times as you want.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tips","level":2,"title":"Tips","text":"
    • Import regularly. Run ctx journal import --all after each session to keep your journal current. Only new sessions are imported: Existing files are skipped by default.
    • Use batch enrichment. /ctx-journal-enrich-all filters noise (suggestion sessions, trivial sessions, multipart continuations) so you do not have to decide what is worth enriching.
    • Keep journal files in .gitignore. Session journals can contain sensitive data: file contents, commands, internal discussions, and error messages with stack traces. Add .context/journal/ and .context/journal-site/ to .gitignore.
    • Use /ctx-blog for narrative posts and /ctx-blog-changelog for release posts. One finds a story in recent activity, the other explains a commit range by theme.
    • Edit the drafts. These skills produce drafts, not final posts. Review the narrative, add your perspective, and remove anything that does not serve the reader.
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#next-up","level":2,"title":"Next Up","text":"

    Running an Unattended AI Agent →: Set up an AI agent that works through tasks overnight without you at the keyboard.

    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#see-also","level":2,"title":"See Also","text":"
    • Session Journal: journal system, enrichment schema
    • CLI Reference: ctx journal: import, list, show session history
    • CLI Reference: ctx journal site: static site generation
    • CLI Reference: ctx journal obsidian: Obsidian vault export
    • CLI Reference: ctx serve: serve-only (no regeneration)
    • Browsing and Enriching Past Sessions: journal browsing workflow
    • The Complete Session: capturing context during a session
    ","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/recover-aborted-session/","level":1,"title":"Recover an Aborted KB Session","text":"","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#the-problem","level":2,"title":"The Problem","text":"

    You ran one or more /ctx-kb-ingest passes, then the session ended before /ctx-wrap-up. Maybe you closed the laptop, the connection dropped, or you just forgot the wrap-up step.

    You come back the next day and ask \"do you remember?\" and the agent picks up the previous handover, but the editorial work since the last handover seems to be missing from the readback.

    It isn't missing. It's unfolded. Here's how the pipeline handles it and how to close the loop manually.

    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-remember                                       # picks up the unfolded \n                                                    # closeouts automatically\n/ctx-handover \"recovery: fold the orphan closeouts\" # direct invocation is \n                                                    # appropriate for recovery\n

    The recovery path is the one legitimate place to invoke /ctx-handover directly. Normally /ctx-wrap-up owns session-end and delegates to the handover step; the abort broke that path, so a hand-rolled handover invocation is how you close the loop without re-running the full wrap-up ceremony.

    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#how-the-fold-mechanism-survives-an-abort","level":2,"title":"How the Fold Mechanism Survives an Abort","text":"

    Two artifacts make abort-recovery work without any cleanup:

    1. Closeouts are immutable once written. Every editorial pass writes a closeout under .context/ingest/closeouts/<TS>-<mode>-closeout.md before the pass reports done. If the session dies, the closeout is already on disk.

    2. /ctx-remember folds unfolded closeouts into the readback. The skill always reads the latest handover. When .context/kb/ exists, it additionally reads any closeouts whose generated-at postdates the handover. The ## What changed and ## Source-coverage updates sections from each unfolded closeout are surfaced in recall.

    So an aborted session never loses editorial work; it just delays the handover fold by one session.

    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-1-confirm-the-orphan-closeouts","level":2,"title":"Step 1: Confirm the Orphan Closeouts","text":"
    ls -la .context/ingest/closeouts/\n

    Files there with generated-at postdating your latest handover are the unfolded ones. You can read any closeout directly to see what it claims about its pass:

    cat .context/ingest/closeouts/<TS>-ingest-closeout.md\n

    Look at:

    • The Pass-mode body block (Declared / Reason / Definition of done / Result): what the pass committed to and whether it claimed success or deferred.
    • The Source-coverage updates section: what state transitions hit the ledger.
    • The Next pass hint: the exact resumption invocation the closeout recommends, if the pass deferred.
    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-2-run-ctx-remember","level":2,"title":"Step 2: Run /ctx-remember","text":"
    /ctx-remember\n

    The readback will include the editorial-state summary as part of the standard readback shape. If everything looks consistent, proceed to Step 3.

    If the readback surfaces something surprising (a closeout claiming topic-page: produced for a slug whose file is missing, a comprehensive ledger advance against a source whose page is speculative, etc.), fix the underlying inconsistency before folding. (Doctor advisories for these shapes are on the Phase-7 backlog.)

    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-3-write-the-recovery-handover","level":2,"title":"Step 3: Write the Recovery Handover","text":"

    This step is the one legitimate direct invocation of /ctx-handover. In normal session-end the call goes through /ctx-wrap-up; here the prior session aborted, so you reach for the handover step directly to retire the orphan closeouts:

    /ctx-handover \"recovery: fold orphan closeouts from yesterday\"\n

    Or via the CLI:

    ctx handover write \"recovery: fold orphan closeouts from yesterday\" \\\n  --summary \"Folded N orphan closeouts from the aborted session.\" \\\n  --next \"Resume <topic> per the closeout's Next pass hint.\"\n

    The handover:

    • Reads the latest handover cursor.
    • Finds all closeouts whose generated-at is after the cursor.
    • Folds their summaries into a ## Folded closeouts section.
    • Archives the source closeout files under .context/archive/closeouts/ (closeouts are append-never-rewrite; archival moves bytes but does not modify them).

    After the handover lands, the orphan closeouts are now durably tied to a session boundary; the next /ctx-remember reads just the new handover (and any closeouts postdating it), without re-folding the recovered ones.

    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#edge-cases","level":2,"title":"Edge Cases","text":"Case Behavior Closeout has malformed frontmatter Handover fold skips it with a warning to stderr. Hand-edit the malformed file (typically a missing generated-at) and re-run ctx handover write to fold it next time. Closeout's generated-at is before the last handover but was never folded Treated as already-folded (silently skipped; the cursor is the source of truth). If you genuinely want to re-fold it, hand-edit the closeout's generated-at forward. You aborted during an ingest pass, before its closeout was written No closeout exists; the pass left no recall residue. Treat the source(s) as un-ingested and re-run /ctx-kb-ingest. The source-coverage ledger row may show stale residue from a prior pass; the next ingest will advance it correctly. Multiple sessions piled up unfolded closeouts One handover run folds them all in a single shot. The fold is cursor-driven, not session-driven. You want recall without consuming closeouts ctx handover write ... --no-fold writes a handover with frontmatter but leaves the closeouts in place. The next handover (without --no-fold) folds everything postdating the latest handover cursor.","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#when-this-matters","level":2,"title":"When This Matters","text":"
    • After a network drop / laptop close mid-session.
    • When you ran /ctx-kb-ingest from a sub-agent that finished without calling /ctx-handover.
    • After porting work from another environment (e.g. you rsynced .context/ingest/closeouts/ from a different machine) and want to integrate the work into the destination project's recall thread.
    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#reference","level":2,"title":"Reference","text":"
    • Recipe: Build a Knowledge Base
    • Recipe: Typical KB Session
    • Editorial constitution: .context/ingest/KB-RULES.md
    ","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/scratchpad-sync/","level":1,"title":"Syncing Scratchpad Notes Across Machines","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-problem","level":2,"title":"The Problem","text":"

    You work from multiple machines: a desktop and a laptop, or a local machine and a remote dev server.

    The scratchpad entries are encrypted. The ciphertext (.context/scratchpad.enc) travels with git, but the encryption key lives outside the project at ~/.ctx/.ctx.key and is never committed. Without the key on each machine, you cannot read or write entries.

    How do you distribute the key and keep the scratchpad in sync?

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tldr","level":2,"title":"TL;DR","text":"
    ctx init                                                  # 1. generates key\neval \"$(ctx activate)\"                                    # 2. bind CTX_DIR\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key        # 3. copy key\nchmod 600 ~/.ctx/.ctx.key                                 # 4. secure it\n# Normal git push/pull syncs the encrypted scratchpad.enc\n# On conflict: ctx pad resolve → rebuild → git add + commit\n

    Activate Each Machine

    Run eval \"$(ctx activate)\" from the project root on every machine that reads or writes the scratchpad: after each ctx init, or after each clone on machine B. If you skip it, ctx pad ... fails with Error: no context directory specified. See Activating a Context Directory.

    Finding Your Key File

    The key is always at ~/.ctx/.ctx.key - one key, one machine.

    Treat the Key like a Password

    The scratchpad key is the only thing protecting your encrypted entries.

    Store a backup in a secure enclave such as a password manager, and treat it with the same care you would give passwords, certificates, or API tokens.

    Anyone with the key can decrypt every scratchpad entry.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx init CLI command Initialize context (generates the key automatically) ctx pad add CLI command Add a scratchpad entry ctx pad rm CLI command Remove entries by stable ID (supports ranges) ctx pad edit CLI command Edit a scratchpad entry ctx pad resolve CLI command Show both sides of a merge conflict ctx pad merge CLI command Merge entries from other scratchpad files ctx pad import CLI command Bulk-import lines from a file ctx pad export CLI command Export blob entries to a directory scp Shell Copy the key file between machines git push / git pull Shell Sync the encrypted file via git /ctx-pad Skill Natural language interface to pad commands","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-1-initialize-on-machine-a","level":3,"title":"Step 1: Initialize on Machine A","text":"

    Run ctx init on your first machine. The key is created automatically at ~/.ctx/.ctx.key:

    ctx init\n# ...\n# Created ~/.ctx/.ctx.key (0600)\n# Created .context/scratchpad.enc\n

    The key lives outside the project directory and is never committed. The .enc file is tracked in git.

    Key Folder Change (v0.7.0+)

    If you built ctx from source or upgraded past v0.6.0, the key location changed to ~/.ctx/.ctx.key. Check these legacy folders and copy your key manually:

    # Old locations (pick whichever exists)\nls ~/.local/ctx/keys/        # pre-v0.7.0 user-level\nls .context/.ctx.key         # pre-v0.6.0 project-local\n\n# Copy to the new location\nmkdir -p ~/.ctx && chmod 700 ~/.ctx\ncp <old-key-path> ~/.ctx/.ctx.key\nchmod 600 ~/.ctx/.ctx.key\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-2-copy-the-key-to-machine-b","level":3,"title":"Step 2: Copy the Key to Machine B","text":"

    Use any secure transfer method. The key is always at ~/.ctx/.ctx.key:

    # scp - create the target directory first\nssh user@machine-b \"mkdir -p ~/.ctx && chmod 700 ~/.ctx\"\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key\n\n# Or use a password manager, USB drive, etc.\n

    Set permissions on Machine B:

    chmod 600 ~/.ctx/.ctx.key\n

    Secure the Transfer

    The key is a raw 256-bit AES key. Anyone with the key can decrypt the scratchpad. Use an encrypted channel (SSH, password manager, vault).

    Never paste it in plaintext over email or chat.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-3-normal-pushpull-workflow","level":3,"title":"Step 3: Normal Push/Pull Workflow","text":"

    The encrypted file is committed, so standard git sync works:

    # Machine A: add entries and push\nctx pad add \"staging API key: sk-test-abc123\"\ngit add .context/scratchpad.enc\ngit commit -m \"Update scratchpad\"\ngit push\n\n# Machine B: pull and read\ngit pull\nctx pad\n#   1. staging API key: sk-test-abc123\n

    Both machines have the same key, so both can decrypt the same .enc file.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-4-read-and-write-from-either-machine","level":3,"title":"Step 4: Read and Write from Either Machine","text":"

    Once the key is distributed, all ctx pad commands work identically on both machines. Entries added on Machine A are visible on Machine B after a git pull, and vice versa.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-5-handle-merge-conflicts","level":3,"title":"Step 5: Handle Merge Conflicts","text":"

    If both machines add entries between syncs, pulling will create a merge conflict on .context/scratchpad.enc. Git cannot merge binary (encrypted) content automatically.

    The fastest approach is ctx pad merge: It reads both conflict sides, deduplicates, and writes the union:

    # Extract theirs to a temp file, then merge it in\ngit show :3:.context/scratchpad.enc > /tmp/theirs.enc\ngit checkout --ours .context/scratchpad.enc\nctx pad merge /tmp/theirs.enc\n\n# Done: Commit the resolved scratchpad:\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n

    Alternatively, use ctx pad resolve to inspect both sides manually:

    ctx pad resolve\n# === Ours (this machine) ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n#\n# === Theirs (incoming) ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n

    Then reconstruct the merged scratchpad:

    # Start fresh with all entries from both sides\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\n# Mark the conflict resolved\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#merge-conflict-walkthrough","level":2,"title":"Merge Conflict Walkthrough","text":"

    Here's a full scenario showing how conflicts arise and how to resolve them:

    1. Both machines start in sync (1 entry):

    Machine A: 1. staging API key: sk-test-abc123\nMachine B: 1. staging API key: sk-test-abc123\n

    2. Both add entries independently:

    Machine A adds: \"check DNS after deploy\"\nMachine B adds: \"new endpoint: api.example.com/v2\"\n

    3. Machine A pushes first. Machine B pulls and gets a conflict:

    git pull\n# CONFLICT (content): Merge conflict in .context/scratchpad.enc\n

    4. Machine B runs ctx pad resolve:

    ctx pad resolve\n# === Ours ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n#\n# === Theirs ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n

    5. Rebuild with entries from both sides and commit:

    # Clear and rebuild (or use the skill to guide you)\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\ngit add .context/scratchpad.enc\ngit commit -m \"Merge scratchpad: keep entries from both machines\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#conversational-approach","level":3,"title":"Conversational Approach","text":"

    When working with an AI assistant, you can resolve conflicts naturally:

    You: \"I have a scratchpad merge conflict. Can you resolve it?\"\n\nAgent: \"Let me extract theirs and merge it in.\"\n       [runs git show :3:.context/scratchpad.enc > /tmp/theirs.enc]\n       [runs git checkout --ours .context/scratchpad.enc]\n       [runs ctx pad merge /tmp/theirs.enc]\n       \"Merged 2 new entries (1 duplicate skipped). Want me to\n       commit the resolution?\"\n
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tips","level":2,"title":"Tips","text":"
    • Back up the key: If you lose it, you lose access to all encrypted entries. Store a copy in your password manager.
    • One key per project: Each ctx init generates a unique key. Don't reuse keys across projects.
    • Keys work in worktrees: Because the key lives at ~/.ctx/.ctx.key (outside the project), git worktrees on the same machine share the key automatically. No special setup needed.
    • Plaintext fallback for non-sensitive projects: If encryption adds friction and you have nothing sensitive, set scratchpad_encrypt: false in .ctxrc. Merge conflicts become trivial text merges.
    • Never commit the key: The key is stored outside the project at ~/.ctx/.ctx.key and should never be copied into the repository.
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#next-up","level":2,"title":"Next Up","text":"

    Hook Output Patterns →: Choose the right output pattern for your Claude Code hooks.

    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#see-also","level":2,"title":"See Also","text":"
    • Scratchpad: feature overview, all commands, when to use scratchpad vs context files
    • Persisting Decisions, Learnings, and Conventions: for structured knowledge that outlives the scratchpad
    ","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-with-claude/","level":1,"title":"Using the Scratchpad","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-problem","level":2,"title":"The Problem","text":"

    During a session you accumulate quick notes, reminders, intermediate values, and sometimes sensitive tokens. They don't fit TASKS.md (not work items) or DECISIONS.md (not decisions). They don't have the structured fields that LEARNINGS.md requires.

    Without somewhere to put them, they get lost between sessions.

    How do you capture working memory that persists across sessions without polluting your structured context files?

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tldr","level":2,"title":"TL;DR","text":"
    ctx pad add \"check DNS propagation after deploy\"\nctx pad         # list entries\nctx pad show 1  # print entry (pipe-friendly)\n

    Entries are encrypted at rest and travel with git.

    Use the /ctx-pad skill to manage entries from inside your AI session.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx pad ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx pad CLI command List all scratchpad entries ctx pad show N CLI command Output raw text of entry N (pipe-friendly) ctx pad add CLI command Add a new entry ctx pad edit CLI command Replace, append to, or prepend to an entry ctx pad add --file CLI command Ingest a file as a blob entry ctx pad show N --out CLI command Extract a blob entry to a file ctx pad rm CLI command Remove entries by stable ID (supports ranges) ctx pad normalize CLI command Reassign entry IDs as 1..N ctx pad mv CLI command Reorder entries ctx pad --tag CLI command Filter entries by tag ctx pad tags CLI command List all tags with counts ctx pad import CLI command Bulk-import lines from a file (or stdin) ctx pad export CLI command Export all blob entries to a directory /ctx-pad Skill Natural language interface to all pad commands","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-1-add-a-note-naturally","level":3,"title":"Step 1: Add a Note Naturally","text":"

    You don't need to remember any syntax. Just tell your assistant what to jot down:

    You: \"jot down: check DNS propagation after deploy\"\n\nAgent: \"Added to scratchpad:\n  1. check DNS propagation after deploy\"\n

    Behind the scenes, the agent runs ctx pad add \"check DNS propagation after deploy\".

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-2-view-your-scratchpad","level":3,"title":"Step 2: View Your Scratchpad","text":"
    You: \"show my scratchpad\"\n\nAgent: \"Your scratchpad has 3 entries:\n  1. check DNS propagation after deploy\n  2. staging API endpoint: api.staging.example.com/v2\n  3. retry limit should be 5, not 3\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-3-edit-an-entry","level":3,"title":"Step 3: Edit an Entry","text":"
    You: \"update entry 2 to include the new endpoint\"\n\nAgent: \"Updated entry 2:\n  was: staging API endpoint: api.staging.example.com/v2\n  now: staging API endpoint: api.prod.example.com/v2\"\n

    For partial edits, the agent can use --append or --prepend:

    You: \"append the port number to entry 2\"\n\nAgent: \"Updated entry 2:\n  staging API endpoint: api.prod.example.com/v2 :8443\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-4-compose-entries","level":3,"title":"Step 4: Compose Entries","text":"

    Use ctx pad show N to pipe entry content into other operations:

    You: \"append the content of entry 3 to entry 1\"\n\nAgent: [runs ctx pad edit 1 --append \"$(ctx pad show 3)\"]\n       \"Done. Entry 1 is now:\n        check DNS propagation after deploy retry limit should be 5, not 3\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-5-reorder-by-priority","level":3,"title":"Step 5: Reorder by Priority","text":"
    You: \"move entry 4 to the top\"\n\nAgent: [runs ctx pad mv 4 1]\n       \"Moved entry 4 to position 1. Scratchpad reordered.\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-6-clean-up","level":3,"title":"Step 6: Clean Up","text":"
    You: \"remove entry 2, it's done\"\n\nAgent: [runs ctx pad rm 2]\n       \"Removed entry 2. 3 entries remaining.\n        (IDs are stable; remaining entries keep their IDs.)\"\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-7-store-a-file-as-a-blob","level":3,"title":"Step 7: Store a File as a Blob","text":"

    The scratchpad can hold small files (up to 64 KB) as encrypted blob entries. The file is base64-encoded and stored alongside a label you provide:

    # Ingest a file: the first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# List shows the label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-8-extract-a-blob","level":3,"title":"Step 8: Extract a Blob","text":"

    Use show --out to write the decoded file back to disk:

    # Write blob entry to a file\nctx pad show 2 --out ./recovered-deploy.yaml\n\n# Or print to stdout (for piping)\nctx pad show 2 | head -5\n

    Blob entries are encrypted identically to text entries: They're just base64-encoded before encryption. The --out flag decodes and writes the raw bytes.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-9-bulk-import-notes","level":3,"title":"Step 9: Bulk Import Notes","text":"

    When you have a file with many notes (one per line), import them in bulk instead of adding one at a time:

    # Import from a file: Each non-empty line becomes an entry\nctx pad import notes.txt\n\n# Or pipe from stdin\ngrep TODO *.go | ctx pad import -\n

    All entries are written in a single encrypt/write cycle, regardless of how many lines the file contains.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-10-export-blobs-to-disk","level":3,"title":"Step 10: Export Blobs to Disk","text":"

    Export all blob entries to a directory as individual files. Each blob's label becomes the filename:

    # Export to a directory (created if needed)\nctx pad export ./ideas\n\n# Preview what would be exported\nctx pad export --dry-run ./ideas\n\n# Force overwrite existing files\nctx pad export --force ./backup\n

    When a file already exists, a unix timestamp is prepended to the filename to avoid collisions. Use --force to overwrite instead.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-11-tag-entries-for-organization","level":3,"title":"Step 11: Tag Entries for Organization","text":"

    Tags let you categorize entries without any structure beyond a #word token in the text. Add them when creating or editing entries:

    You: \"jot down: check DNS propagation #later\"\nYou: \"tag entry 2 as urgent\"\n\nAgent: [runs ctx pad edit 2 --tag urgent]\n       \"Updated entry 2.\"\n

    Filter your scratchpad by tag:

    You: \"show me everything tagged later\"\n\nAgent: [runs ctx pad --tag later]\n       \"  1. check DNS propagation #later\n        3. review PR feedback #later #ci\"\n

    Entry IDs are stable; they don't shift when other entries are deleted, so ctx pad rm 3 always targets the same entry regardless of deletions or active filters. Use ctx pad normalize to reassign IDs as 1..N.

    Exclude a tag with ~:

    ctx pad --tag ~later         # everything NOT tagged #later\nctx pad --tag later --tag ci # entries with BOTH tags (AND logic)\n

    See what tags you're using:

    You: \"what tags do I have?\"\n\nAgent: [runs ctx pad tags]\n       \"ci       1\n        later    2\n        urgent   1\"\n

    Tags work on blob entries too; they're extracted from the label:

    ctx pad add \"deploy config #prod\" --file ./deploy.yaml\nctx pad --tag prod\n#   1. deploy config #prod [BLOB]\n
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#using-ctx-pad-in-a-session","level":2,"title":"Using /ctx-pad in a Session","text":"

    Invoke the /ctx-pad skill first, then describe what you want in natural language. Without the skill prefix, the agent may route your request to TASKS.md or another context file instead of the scratchpad.

    You: /ctx-pad jot down: check DNS after deploy\nYou: /ctx-pad show my scratchpad\nYou: /ctx-pad delete entry 3\n

    Once the skill is active, it translates intent into commands:

    You say (after /ctx-pad) What the agent does \"jot down: check DNS after deploy\" ctx pad add \"check DNS after deploy\" \"remember this: retry limit is 5\" ctx pad add \"retry limit is 5\" \"show my scratchpad\" / \"what's on my pad\" ctx pad \"show me entry 3\" ctx pad show 3 \"delete the third one\" / \"remove entry 3\" ctx pad rm 3 \"remove entries 3 through 5\" ctx pad rm 3-5 \"renumber my scratchpad\" ctx pad normalize \"change entry 2 to ...\" ctx pad edit 2 \"new text\" \"append ' +important' to entry 3\" ctx pad edit 3 --append \" +important\" \"prepend 'URGENT:' to entry 1\" ctx pad edit 1 --prepend \"URGENT: \" \"prioritize entry 4\" / \"move to the top\" ctx pad mv 4 1 \"import my notes from notes.txt\" ctx pad import notes.txt \"export all blobs to ./ideas\" ctx pad export ./ideas \"show entries tagged later\" ctx pad --tag later \"show everything except later\" ctx pad --tag ~later \"what tags do I have\" ctx pad tags \"tag entry 5 as urgent\" ctx pad edit 5 --tag urgent

    When in Doubt, Use the CLI Directly

    The ctx pad commands work the same whether you run them yourself or let the skill invoke them.

    If the agent misroutes a request, fall back to ctx pad add \"...\" in your terminal.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#when-to-use-scratchpad-vs-context-files","level":2,"title":"When to Use Scratchpad vs Context Files","text":"Situation Use Temporary reminders (\"check X after deploy\") Scratchpad Session-start reminders (\"remind me next session\") ctx remind Working values during debugging (ports, endpoints, counts) Scratchpad Sensitive tokens or API keys (short-term storage) Scratchpad Quick notes that don't fit anywhere else Scratchpad Work items with completion tracking TASKS.md Trade-offs between alternatives with rationale DECISIONS.md Reusable lessons with context/lesson/application LEARNINGS.md Codified patterns and standards CONVENTIONS.md

    Decision Guide

    • If it has structured fields (context, rationale, lesson, application), it belongs in a context file like DECISIONS.md or LEARNINGS.md.
    • If it's a work item you'll mark done, it belongs in TASKS.md.
    • If you want a message relayed VERBATIM at the next session start, it belongs in ctx remind.
    • If it's a quick note, reminder, or working value (especially if it's sensitive or ephemeral) it belongs on the scratchpad.

    Scratchpad Is Not a Junk Drawer

    The scratchpad is for working memory, not long-term storage.

    If a note is still relevant after several sessions, promote it:

    A persistent reminder becomes a task, a recurring value becomes a convention, a hard-won insight becomes a learning.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tips","level":2,"title":"Tips","text":"
    • Entries persist across sessions: The scratchpad is committed (encrypted) to git, so entries survive session boundaries. Pick up where you left off.
    • Entries are numbered and reorderable: Use ctx pad mv to put high-priority items at the top.
    • ctx pad show N enables unix piping: Output raw entry text with no numbering prefix. Compose with --append, --prepend, or other shell tools.
    • Never mention the key file contents to the AI: The agent knows how to use ctx pad commands but should never read or print the encryption key (~/.ctx/.ctx.key) directly.
    • Encryption is transparent: You interact with plaintext; the encryption/decryption happens automatically on every read/write.
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#next-up","level":2,"title":"Next Up","text":"

    Syncing Scratchpad Notes Across Machines →: Distribute encryption keys and scratchpad data across environments.

    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#see-also","level":2,"title":"See Also","text":"
    • Scratchpad: feature overview, all commands, encryption details, plaintext override
    • Persisting Decisions, Learnings, and Conventions: for structured knowledge that outlives the scratchpad
    • The Complete Session: full session lifecycle showing how the scratchpad fits into the broader workflow
    ","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/","level":1,"title":"Scrutinizing a Plan","text":"

    When you have a plan and want it attacked, not validated, the /ctx-plan skill runs an adversarial interview. It surfaces what's weak, missing, or unexamined before you commit.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#when-to-use-it","level":2,"title":"When to Use It","text":"
    • Before starting a multi-day implementation.
    • After writing a spec but before opening the first PR.
    • When the team aligned suspiciously fast on a complex change.
    • When you've drafted something and the obvious next step is to hand it to a senior reviewer.

    /ctx-plan is not a brainstorming tool. Use /ctx-brainstorm for that. /ctx-plan assumes the plan exists and asks: does it survive contact with reality?

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#stop-conditions","level":2,"title":"Stop Conditions","text":"

    The skill runs depth-first through assumptions, tradeoffs, and failure modes. Stop when one of these is true:

    • Plan converges. The bet is clear, the sequencing is justified, and you can articulate what would falsify it.
    • Plan dissolves. The interview surfaces a load-bearing assumption you can't defend; the plan needs rework, not more scrutiny.
    • You hit information you don't have. Pause, gather it, resume.

    If the agent loops on a single angle (asking variations of the same question), say so. The skill is designed to walk depth-first, not exhaust one branch.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#worked-example","level":2,"title":"Worked Example","text":"
    You: /ctx-plan\n\nI want to migrate our queue from RabbitMQ to NATS. I've drafted a\ntwo-week plan: spike NATS in staging, port one consumer, port the\nrest, decommission RabbitMQ.\n\nAgent: One question at a time. What breaks if your message ordering\n       guarantees aren't equivalent across the two systems?\n\nYou: NATS gives us per-subject ordering by default; we don't actually\n     need cross-subject ordering, so I think we're fine.\n\nAgent: How would you notice if you were wrong? What's the loudest\n       symptom of a stray out-of-order message in production today?\n\nYou: ...probably a duplicate notification. Which we already see\n     occasionally. So we wouldn't notice immediately.\n\nAgent: Then your migration's correctness check needs to be stronger\n       than \"no incidents.\" Before you port consumer two, what\n       evidence would convince you that consumer one's ordering\n       behavior is correct under load?\n\nYou: Right. Add a dedicated assertion test in staging that replays\n     a known-out-of-order sequence and verifies the consumer\n     dedupes correctly. Otherwise we're just hoping.\n\nAgent: Good. Next: what would change your mind about NATS itself?\n       What rejected alternative is closest to coming back?\n

    The interview keeps going until the bet is articulated, the falsifiable conditions are written down, and the rejected alternatives have a recall trigger.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#output","level":2,"title":"Output","text":"

    /ctx-plan produces a clearer plan, not a document. Persist the deltas via:

    • /ctx-spec if the conclusions belong in a feature spec.
    • /ctx-decision-add if a tradeoff resolved into an architectural decision.
    • /ctx-learning-add if you discovered a project-specific gotcha during the interview.

    The skill itself is in internal/assets/claude/skills/ctx-plan/SKILL.md; the working contract lives there, the recipe is the on-ramp.

    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#see-also","level":2,"title":"See Also","text":"
    • Design Before Coding: the brainstorming counterpart, used before a plan exists.
    • ctx-spec: scaffolds a feature spec from the project template.
    ","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/session-archaeology/","level":1,"title":"Browsing and Enriching Past Sessions","text":"","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-problem","level":2,"title":"The Problem","text":"

    After weeks of AI-assisted development you have dozens of sessions scattered across JSONL files in ~/.claude/projects/. Finding the session where you debugged the Redis connection pool, or remembering what you decided about the caching strategy three Tuesdays ago, often means grepping raw JSON.

    There is no table of contents, no search, and no summaries.

    This recipe shows how to turn that raw session history into a browsable, searchable, and enriched journal site you can navigate in your browser.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tldr","level":2,"title":"TL;DR","text":"

    Export and Generate

    ctx journal import --all\nctx journal site --serve\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx journal ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    Enrich

    /ctx-journal-enrich-all\n

    Rebuild

    ctx journal site --serve\n

    Read on for what each stage does and why.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx journal source Command List parsed sessions with metadata ctx journal source --show Command Inspect a specific session in detail ctx journal import Command Import sessions to editable journal Markdown ctx journal site Command Generate a static site from journal entries ctx journal obsidian Command Generate an Obsidian vault from journal entries ctx journal schema check Command Validate JSONL files and report schema drift ctx journal schema dump Command Print the embedded JSONL schema definition ctx serve Command Serve any zensical directory (default: journal) /ctx-history Skill Browse sessions inside your AI assistant /ctx-journal-enrich Skill Add frontmatter metadata to a single entry /ctx-journal-enrich-all Skill Full pipeline: import if needed, then batch-enrich","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-workflow","level":2,"title":"The Workflow","text":"

    The session journal follows a four-stage pipeline.

    Each stage is idempotent and safe to re-run:

    By default, each stage skips entries that have already been processed.

    import -> enrich -> rebuild\n
    Stage Tool What it does Skips if Where Import ctx journal import --all Converts session JSONL to Markdown File already exists (safe default) CLI or agent Enrich /ctx-journal-enrich-all Adds frontmatter, summaries, topic tags Frontmatter already present Agent only Rebuild ctx journal site --build Generates browsable static HTML N/A CLI only Obsidian ctx journal obsidian Generates Obsidian vault with wikilinks N/A CLI only

    Where Do You Run Each Stage?

    Import (Steps 1 to 3) works equally well from the terminal or inside your AI assistant via /ctx-history. The CLI is fine here: the agent adds no special intelligence, it just runs the same command.

    Enrich (Step 4) requires the agent: it reads conversation content and produces structured metadata.

    Rebuild and serve (Step 5) is a terminal operation that starts a long-running server.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-1-list-your-sessions","level":3,"title":"Step 1: List Your Sessions","text":"

    Start by seeing what sessions exist for the current project:

    ctx journal source\n

    Sample output:

    Sessions (newest first)\n=======================\n\n  Slug                           Project   Date         Duration  Turns  Tokens\n  gleaming-wobbling-sutherland   ctx       2026-02-07   1h 23m    47     82,341\n  twinkly-stirring-kettle        ctx       2026-02-06   0h 45m    22     38,102\n  bright-dancing-hopper          ctx       2026-02-05   2h 10m    63     124,500\n  quiet-flowing-dijkstra         ctx       2026-02-04   0h 18m    11     15,230\n  ...\n

    Slugs Look Cryptic?

    These auto-generated slugs (gleaming-wobbling-sutherland) are hard to recognize later.

    Use /ctx-journal-enrich to add human-readable titles, topic tags, and summaries to exported journal entries, making them easier to find.

    Filter by project or tool if you work across multiple codebases:

    ctx journal source --project ctx --limit 10\nctx journal source --tool claude-code\nctx journal source --all-projects\n
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-2-inspect-a-specific-session","level":3,"title":"Step 2: Inspect a Specific Session","text":"

    Before exporting everything, inspect a single session to see its metadata and conversation summary:

    ctx journal source --show --latest\n

    Or look up a specific session by its slug, partial ID, or UUID:

    ctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show twinkly\nctx journal source --show abc123\n

    Add --full to see the complete message content instead of the summary view:

    ctx journal source --show --latest --full\n

    This is useful for checking what happened before deciding whether to export and enrich it.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-3-import-sessions-to-the-journal","level":3,"title":"Step 3: Import Sessions to the Journal","text":"

    Import converts raw session data into editable Markdown files in .context/journal/:

    # Import all sessions from the current project\nctx journal import --all\n\n# Import a single session\nctx journal import gleaming-wobbling-sutherland\n\n# Include sessions from all projects\nctx journal import --all --all-projects\n

    --keep-frontmatter=false Discards Enrichments

    --keep-frontmatter=false discards enriched YAML frontmatter during regeneration.

    Back up your journal before using this flag.

    Each imported file contains session metadata (date, time, duration, model, project, git branch), a tool usage summary, and the full conversation transcript.

    Re-importing is safe. Running ctx journal import --all only imports new sessions: Existing files are never touched. Use --dry-run to preview what would be imported without writing anything.

    To re-import existing files (e.g., after a format improvement), use --regenerate: Conversation content is regenerated while preserving any YAML frontmatter you or the enrichment skill has added. You'll be prompted before any files are overwritten.

    --regenerate Replaces the Markdown Body

    --regenerate preserves YAML frontmatter but replaces the entire Markdown body with freshly generated content from the source JSONL.

    If you manually edited the conversation transcript (added notes, redacted sensitive content, restructured sections), those edits will be lost.

    BACK UP YOUR JOURNAL FIRST.

    To protect entries you've hand-edited, you can explicitly lock them:

    ctx journal lock <pattern>\n

    Locked entries are always skipped, regardless of flags.

    If you prefer to add locked: true directly in frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json:

    ctx journal sync\n

    See ctx journal lock --help and ctx journal sync --help for details.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-4-enrich-with-metadata","level":3,"title":"Step 4: Enrich with Metadata","text":"

    Raw imports have timestamps and transcripts but lack the semantic metadata that makes sessions searchable: topics, technology tags, outcome status, and summaries. The /ctx-journal-enrich* skills add this structured frontmatter.

    Locked entries are skipped by enrichment skills, just as they are by import. Lock entries you want to protect before running batch enrichment.

    Batch enrichment (recommended):

    /ctx-journal-enrich-all\n

    The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.

    It shows you a grouped summary before applying changes so you can scan quickly rather than reviewing one by one.

    For large backlogs (20+ entries), the skill can spawn subagents to process entries in parallel.

    Single-entry enrichment:

    /ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-02-06\n

    Each enriched entry gets YAML frontmatter like this:

    ---\ntitle: \"Implement Redis caching middleware\"\ndate: 2026-02-06\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/api/middleware/cache.go\n---\n

    The skill also generates a summary and can extract decisions, learnings, and tasks mentioned during the session.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-5-generate-and-serve-the-site","level":3,"title":"Step 5: Generate and Serve the Site","text":"

    With imported and enriched journal files, generate the static site:

    # Generate site structure only\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate, build, and serve locally\nctx journal site --serve\n

    Then open http://localhost:8000 to browse.

    The site includes a date-sorted index, individual session pages with full conversations, search (press /), dark mode, and enriched titles in the navigation when frontmatter exists.

    You can also serve an already-generated site without regenerating using ctx serve (serve-only, no regeneration).

    The site generator requires zensical (pipx install zensical).

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#where-does-agent-add-value","level":2,"title":"Where Does Agent Add Value?","text":"

    Export, list, and show are mechanical: The agent runs the same CLI commands you would, so you can stay in your terminal for those.

    The agent earns its keep in enrich. It reads conversation content, understands what happened, and produces structured metadata. That is agent work, not CLI work.

    You can also ask your agent to browse sessions conversationally instead of remembering flags:

    What did we work on last week?\nShow me the session about Redis.\nImport everything to the journal.\n

    This is convenient but not required: ctx journal source gives you the same inventory.

    Where the agent genuinely helps is chaining the pipeline:

    You:   What happened last Tuesday?\nAgent: Last Tuesday you worked on two sessions:\n       - bright-dancing-hopper (2h 10m): refactored the middleware\n         pipeline and added Redis caching\n       - quiet-flowing-dijkstra (18m): quick fix for a nil pointer\n         in the config loader\n       Want me to export and enrich them?\nYou:   Yes, do it.\nAgent: Exports both, enriches, then proposes frontmatter.\n

    The value is staying in one context while the agent runs import -> enrich without you manually switching tools.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A typical pipeline from raw sessions to a browsable site:

    # Terminal: import and generate\nctx journal import --all\nctx journal site --serve\n
    # AI assistant: enrich\n/ctx-journal-enrich-all\n
    # Terminal: rebuild with enrichments\nctx journal site --serve\n

    If your project includes Makefile.ctx (deployed by ctx init), use make journal to combine import and rebuild stages. Then enrich inside Claude Code, then make journal again to pick up enrichments.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#session-retention-and-cleanup","level":2,"title":"Session Retention and Cleanup","text":"

    Claude Code does not keep JSONL transcripts forever. Understanding its cleanup behavior helps you avoid losing session history.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#default-behavior","level":3,"title":"Default Behavior","text":"

    Claude Code retains session transcripts for approximately 30 days. After that, JSONL files are automatically deleted during cleanup. Once deleted, ctx journal can no longer see those sessions - the data is gone.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-cleanupperioddays-setting","level":3,"title":"The cleanupPeriodDays Setting","text":"

    Claude Code exposes a cleanupPeriodDays setting in its configuration (~/.claude/settings.json) that controls retention:

    Value Behavior 30 (default) Transcripts older than 30 days are deleted 60, 90, etc. Extends the retention window 0 Disables writing new transcripts entirely - not \"keep forever\"

    Setting cleanupPeriodDays To 0

    Setting this to 0 does not mean \"never delete.\" It disables transcript creation altogether. No new JSONL files are written, which means ctx journal sees nothing new. This is rarely what you want.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#why-journal-import-matters","level":3,"title":"Why Journal Import Matters","text":"

    The journal import pipeline (Steps 1-4 above) is your archival mechanism. Imported Markdown files in .context/journal/ persist independently of Claude Code's cleanup cycle. Even after the source JSONL files are deleted, your journal entries remain.

    Recommendation: import regularly - weekly, or after any session worth revisiting. A quick ctx journal import --all takes seconds and ensures nothing falls through the 30-day window.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#quick-archival-checklist","level":3,"title":"Quick Archival Checklist","text":"
    1. Run ctx journal import --all at least weekly
    2. Enrich high-value sessions with /ctx-journal-enrich before the details fade from your own memory
    3. Lock enriched entries (ctx journal lock <pattern>) to protect them from accidental regeneration
    4. Rebuild the journal site periodically to keep it current
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tips","level":2,"title":"Tips","text":"
    • Start with /ctx-history inside your AI assistant. If you want to quickly check what happened in a recent session without leaving your editor, /ctx-history lets you browse interactively without importing.
    • Large sessions may be split automatically. Sessions with 200+ messages can be split into multiple parts (session-abc123.md, session-abc123-p2.md, session-abc123-p3.md) with navigation links between them. The site generator can handle this.
    • Suggestion sessions can be separated. Claude Code can generate short suggestion sessions for autocomplete. These may appear under a separate section in the site index, so they do not clutter your main session list.
    • Your agent is a good session browser. You do not need to remember slugs, dates, or flags. Ask \"what did we do yesterday?\" or \"find the session about Redis\" and it can map the question to recall commands.

    Journal Files Are Sensitive

    Journal files MUST be .gitignored.

    Session transcripts can contain sensitive data such as file contents, commands, error messages with stack traces, and potentially API keys.

    Add .context/journal/, .context/journal-site/, and .context/journal-obsidian/ to your .gitignore.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#next-up","level":2,"title":"Next Up","text":"

    Persisting Decisions, Learnings, and Conventions →: Record decisions, learnings, and conventions so they survive across sessions.

    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: where session saving fits in the daily workflow
    • Turning Activity into Content: generating blog posts from session history
    • Session Journal: full documentation of the journal system
    • CLI Reference: ctx journal: all journal subcommands and flags
    • CLI Reference: ctx serve: serve-only (no regeneration)
    • Context Files: the .context/ directory structure
    ","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-ceremonies/","level":1,"title":"Session Ceremonies","text":"","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#the-problem","level":2,"title":"The Problem","text":"

    Sessions have two critical moments: the start and the end.

    • At the start, you need the agent to load context and confirm it knows what is going on.
    • At the end, you need to capture whatever the session produced before the conversation disappears.

    Most ctx skills work conversationally: \"jot down: check DNS after deploy\" is as good as /ctx-pad add \"check DNS after deploy\". But session boundaries are different. They are well-defined moments with specific requirements, and partial execution is costly.

    If the agent only half-loads context at the start, it works from stale assumptions. If it only half-persists at the end, learnings and decisions are lost.

    This Is One of the Few Times Being Explicit Matters

    Session ceremonies are the two bookend skills that mark these boundaries.

    They are the exception to the conversational rule:

    Invoke /ctx-remember and /ctx-wrap-up explicitly as slash commands.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tldr","level":2,"title":"TL;DR","text":"

    Start: /ctx-remember: load context, get a structured readback.

    End: /ctx-wrap-up: review session, propose candidates, persist approved items.

    Use the slash commands, not conversational triggers, for completeness.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#explicit-invocation-matters","level":2,"title":"Explicit Invocation Matters","text":"

    Most ctx skills encourage natural language. These two are different:

    Well-defined moments: Sessions have clear boundaries. A slash command marks the boundary unambiguously.

    Ambiguity risk: \"Do you remember?\" could mean many things. /ctx-remember means exactly one thing: load context and present a structured readback.

    Completeness: Conversational triggers risk partial execution. The agent might load some files but skip the session history, or persist one learning but forget to check for uncommitted changes. The slash command runs the full ceremony.

    Muscle memory: Typing /ctx-remember at session start and /ctx-wrap-up at session end becomes a habit, like opening and closing braces.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-remember Skill Load context and present structured readback /ctx-wrap-up Skill Gather session signal, propose and persist context /ctx-commit Skill Commit with context capture (offered by wrap-up) ctx agent CLI Load token-budgeted context packet ctx journal source CLI List recent sessions ctx add CLI Persist learnings, decisions, conventions, tasks","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-start-ctx-remember","level":2,"title":"Session Start: /ctx-remember","text":"

    Invoke at the beginning of every session:

    /ctx-remember\n

    The skill silently:

    1. Loads the context packet via ctx agent --budget 4000
    2. Reads TASKS.md, DECISIONS.md, LEARNINGS.md
    3. Checks recent sessions via ctx journal source --limit 3

    Then presents a structured readback with four sections:

    • Last session: topic, date, what was accomplished
    • Active work: pending and in-progress tasks
    • Recent context: 1-2 relevant decisions or learnings
    • Next step: suggestion or question about what to focus on

    The readback should feel like recall, not a file system tour. If the agent says \"Let me check if there are files...\" instead of a confident summary, the skill is not working correctly.

    What about 'do you remember?'

    The conversational trigger still works. But /ctx-remember guarantees the full ceremony runs:

    • context packet,
    • file reads,
    • session history,
    • and all four readback sections.

    The conversational version may cut corners.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-end-ctx-wrap-up","level":2,"title":"Session End: /ctx-wrap-up","text":"

    Invoke before ending a session where meaningful work happened:

    /ctx-wrap-up\n

    The skill runs four phases:

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-1-gather-signal","level":3,"title":"Phase 1: Gather Signal","text":"

    Silently checks git diff --stat, recent commits, and scans the conversation for themes: architectural choices, gotchas, patterns established, follow-up work identified.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-2-propose-candidates","level":3,"title":"Phase 2: Propose Candidates","text":"

    Presents a structured list grouped by type:

    ## Session Wrap-Up\n\n### Learnings (2 candidates)\n1. **PyMdownx details extension breaks pre/code rendering**\n   - Context: Journal site showed broken code blocks inside details tags\n   - Lesson: details extension wraps content in <details> HTML, which\n     interferes with <pre><code> rendering\n   - Application: Use fenced code blocks instead of indented code inside\n     admonitions when details extension is active\n\n2. **Hook subprocesses cannot propagate env vars**\n   - Context: Set env var in PreToolUse hook, invisible in main session\n   - Lesson: Hooks execute in child processes; env changes don't propagate\n   - Application: Use tombstone files for hook-to-session communication\n\n### Decisions (1 candidate)\n1. **File-based cooldown tokens over env vars**\n   - Context: Need session-scoped cooldown for ctx agent auto-loading\n   - Rationale: File tokens survive across processes, simpler than IPC\n   - Consequence: Tombstone files accumulate in /tmp; need TTL cleanup\n\nPersist all? Or select which to keep?\n

    Each candidate has complete structured fields, not just a title. Empty categories are omitted.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-3-persist","level":3,"title":"Phase 3: Persist","text":"

    After you approve (all, some, or modified), the skill runs the appropriate ctx add commands and reports results.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#nudge-suppression","level":3,"title":"Nudge Suppression","text":"

    After persisting, the skill marks the session as wrapped up via ctx system mark-wrapped-up. This suppresses context checkpoint nudges for 2 hours so the wrap-up ceremony itself does not trigger noisy reminders.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-4-commit-offer","level":3,"title":"Phase 4: Commit Offer","text":"

    If there are uncommitted changes, offers to run /ctx-commit. Does not auto-commit.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#when-to-skip","level":2,"title":"When to Skip","text":"

    Not every session needs ceremonies.

    Skip /ctx-remember when:

    • You are doing a quick one-off lookup (reading a file, checking a value)
    • Context was already loaded this session via /ctx-agent
    • You are continuing immediately after a previous session and context is still fresh

    Skip /ctx-wrap-up when:

    • Nothing meaningful happened (only read files, answered a question)
    • You already persisted everything manually during the session
    • The session was trivial (typo fix, quick config change)

    A good heuristic: if the session produced something a future session should know about, run /ctx-wrap-up. If not, just close.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#quick-reference","level":2,"title":"Quick Reference","text":"
    # Session start\n/ctx-remember\n\n# ... do work ...\n\n# Session end\n/ctx-wrap-up\n

    That is the complete ceremony. Two commands, bookending your session.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#relationship-to-other-skills","level":2,"title":"Relationship to Other Skills","text":"Skill When Purpose /ctx-remember Session start Load and confirm context /ctx-reflect Mid-session breakpoints Checkpoint at milestones /ctx-wrap-up Session end Full session review and persist /ctx-commit After completing work Commit with context capture

    /ctx-reflect is for mid-session checkpoints. /ctx-wrap-up is for end-of-session: it is more thorough, covers the full session arc, and includes the commit offer. If you already ran /ctx-reflect recently, /ctx-wrap-up avoids proposing the same candidates again.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tips","level":2,"title":"Tips","text":"
    • Make it a habit: The value of ceremonies compounds over sessions. Each /ctx-wrap-up makes the next /ctx-remember richer.
    • Trust the candidates: The agent scans the full conversation. It often catches learnings you forgot about.
    • Edit before approving: If a proposed candidate is close but not quite right, tell the agent what to change. Do not settle for a vague learning when a precise one is possible.
    • Do not force empty ceremonies: If /ctx-wrap-up finds nothing worth persisting, that is fine. A session that only read files and answered questions does not need artificial learnings.
    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#next-up","level":2,"title":"Next Up","text":"

    Browsing and Enriching Past Sessions →: Export session history to a browsable journal and enrich entries with metadata.

    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: the full session workflow that ceremonies bookend
    • Persisting Decisions, Learnings, and Conventions: deep dive on what gets persisted during wrap-up
    • Detecting and Fixing Drift: keeping context files accurate between ceremonies
    • Pausing Context Hooks: skip ceremonies entirely for quick tasks that don't need them
    ","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-changes/","level":1,"title":"Reviewing Session Changes","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-changed-while-you-were-away","level":2,"title":"What Changed While You Were Away?","text":"

    Between sessions, teammates commit code, context files get updated, and decisions pile up. ctx change gives you a single-command summary of everything that moved since your last session.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#quick-start","level":2,"title":"Quick Start","text":"
    # Auto-detects your last session and shows what changed\nctx change\n\n# Check what changed in the last 48 hours\nctx change --since 48h\n\n# Check since a specific date\nctx change --since 2026-03-10\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx change fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#how-reference-time-works","level":2,"title":"How Reference Time Works","text":"

    ctx change needs a reference point to compare against. It tries these sources in order:

    1. --since flag: explicit duration (24h, 72h) or date (2026-03-10, RFC3339 timestamp)
    2. Session markers: ctx-loaded-* files in .context/state/; picks the second-most-recent (your previous session start)
    3. Event log: last context-load-gate event from .context/state/events.jsonl
    4. Fallback: 24 hours ago

    The marker-based detection means ctx change usually just works without any flags: it knows when you last loaded context and shows everything after that.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-it-reports","level":2,"title":"What It Reports","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#context-file-changes","level":3,"title":"Context File Changes","text":"

    Any .md file in .context/ modified after the reference time:

    ### Context File Changes\n- `TASKS.md` - modified 2026-03-11 14:30\n- `DECISIONS.md` - modified 2026-03-11 09:15\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#code-changes","level":3,"title":"Code Changes","text":"

    Git activity since the reference time:

    ### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#integrating-into-session-start","level":2,"title":"Integrating into Session Start","text":"

    Pair ctx change with the /ctx-remember ceremony for a complete session-start picture:

    # 1. Load context (this also creates the session marker)\nctx agent --budget 4000\n\n# 2. See what changed since your last session\nctx change\n

    Or script it:

    # .context/hooks/session-start.sh\nctx agent --budget 4000\necho \"---\"\nctx change\n
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#team-workflows","level":2,"title":"Team Workflows","text":"

    When multiple people share a .context/ directory, ctx change shows who changed what:

    # After pulling from remote\ngit pull\nctx change --since 72h\n

    This surfaces context file changes from teammates that you might otherwise miss in the commit log.

    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#tips","level":2,"title":"Tips","text":"
    • No changes? If nothing shows up, the reference time might be wrong. Use --since 48h to widen the window.
    • Works without git. Context file changes are detected by filesystem mtime, not git. Code changes require git.
    • Hook integration. The context-load-gate hook writes the session marker that ctx change uses for auto-detection. If you're not using the ctx plugin, markers won't exist and it falls back to the event log or 24h window.
    ","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-lifecycle/","level":1,"title":"The Complete Session","text":"","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-problem","level":2,"title":"The Problem","text":"

    \"What does a full ctx session look like from start to finish?\"

    You have ctx installed and your .context/ directory initialized, but the individual commands and skills feel disconnected.

    How do they fit together into a coherent workflow?

    This recipe walks through a complete session, from opening your editor to persisting context before you close it, so you can see how each piece connects.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tldr","level":2,"title":"TL;DR","text":"
    1. Load: /ctx-remember: load context, get structured readback.
    2. Orient: /ctx-status: check file health and token usage.
    3. Pick: /ctx-next: choose what to work on.
    4. Work: implement, test, iterate.
    5. Commit: /ctx-commit: commit and capture decisions/learnings.
    6. Reflect: /ctx-reflect: identify what to persist (at milestones)
    7. Wrap up: /ctx-wrap-up: end-of-session ceremony.

    Read on for the full walkthrough with examples.

    Before You Start: Activate the Project

    ctx commands (and the skills that call them) require CTX_DIR to be declared for the shell you're working in; ctx does not walk the filesystem to find .context/. Once per shell (or via your shell rc / direnv):

    eval \"$(ctx activate)\"\n

    If you skip this, every skill below will surface an error naming the fix. See Activating a Context Directory for the full recipe.

    What Is a Readback?

    A readback is a structured summary where the agent plays back what it knows:

    • last session,
    • active tasks,
    • recent decisions.

    This way, you can confirm it loaded the right context.

    The term \"readback\" comes from aviation, where pilots repeat instructions back to air traffic control to confirm they heard correctly.

    Same idea in ctx: The agent tells you what it \"thinks\" is going on, and you correct anything that's off before the work begins.

    • Last session: topic, date, what was accomplished
    • Active work: pending and in-progress tasks
    • Recent context: 1-2 decisions or learnings that matter now
    • Next step: suggestion or question about what to focus on
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx status CLI command Quick health check on context files ctx agent CLI command Load token-budgeted context packet ctx journal source CLI command List previous sessions ctx journal source --show CLI command Inspect a specific session in detail /ctx-remember Skill Recall project context with structured readback /ctx-agent Skill Load full context packet inside the assistant /ctx-status Skill Show context summary with commentary /ctx-next Skill Suggest what to work on with rationale /ctx-commit Skill Commit code and prompt for context capture /ctx-reflect Skill Structured reflection checkpoint /ctx-history Skill Browse session history inside your AI assistant","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-workflow","level":2,"title":"The Workflow","text":"

    The session lifecycle has seven steps. You will not always use every step (for example, a quick bugfix might skip reflection, and a research session might skip committing), but the full arc looks like this:

    Load context > Orient > Pick a Task > Work > Commit > Reflect

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-1-load-context","level":3,"title":"Step 1: Load Context","text":"

    Start every session by loading what you know. The fastest way is a single prompt:

    Do you remember what we were working on?\n

    This triggers the /ctx-remember skill. Behind the scenes, the assistant runs ctx agent --budget 4000, reads the files listed in the context packet (TASKS.md, DECISIONS.md, LEARNINGS.md, CONVENTIONS.md), checks ctx journal source --limit 3 for recent sessions, and then presents a structured readback.

    The readback should feel like a recall, not a file system tour. If you see \"Let me check if there are files...\" instead of a confident summary, the context system is not loaded properly.

    As an alternative, if you want raw data instead of a readback, run ctx status in your terminal or invoke /ctx-status for a summarized health check showing file counts, token usage, and recent activity.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-2-orient","level":3,"title":"Step 2: Orient","text":"

    After loading context, verify you understand the current state.

    /ctx-status\n

    The status output shows which context files are populated, how many tokens they consume, and which files were recently modified. Look for:

    • Empty core files: TASKS.md or CONVENTIONS.md with no content means the context is sparse
    • High token count (over 30k): the context is bloated and might need ctx compact
    • No recent activity: files may be stale and need updating

    If the status looks healthy and the readback from Step 1 gave you enough context, skip ahead.

    If something seems off (stale tasks, missing decisions...), spend a minute reading the relevant file before proceeding.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"

    With context loaded, choose a task. You can pick one yourself, or ask the assistant to recommend:

    /ctx-next\n

    The skill reads TASKS.md, checks recent sessions to avoid re-suggesting completed work, and presents 1-3 ranked recommendations with rationale.

    It prioritizes in-progress tasks over new starts (finishing is better than starting), respects explicit priority tags, and favors momentum: continuing a thread from a recent session is cheaper than context-switching.

    If you already know what you want to work on, state it directly:

    Let's work on the session enrichment feature.\n
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-4-do-the-work","level":3,"title":"Step 4: Do the Work","text":"

    This is the main body of the session: write code, fix bugs, refactor, research: whatever the task requires.

    During this phase, a few ctx-specific patterns help:

    Check decisions before choosing: when you face a design choice, check if a prior decision covers it.

    Is this consistent with our decisions?\n

    Constrain scope: keep the assistant focused on the task at hand.

    Only change files in internal/cli/session/. Nothing else.\n

    Use /ctx-implement for multistep plans: if the task has multiple steps, this skill executes them one at a time with build/test verification between each step.

    Context monitoring runs automatically: the check-context-size hook monitors context capacity at adaptive intervals. Early in a session it stays silent. After 16+ prompts it starts monitoring, and past 30 prompts it checks frequently. If context capacity is running high, it will suggest saving unsaved work. No manual invocation is needed.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-5-commit-with-context","level":3,"title":"Step 5: Commit with Context","text":"

    When the work is ready, use the context-aware commit instead of raw git commit:

    /ctx-commit\n

    The Agent May Recommend Committing

    You do not always need to invoke /ctx-commit explicitly.

    After a commit, the agent may proactively offer to capture context:

    \"We just made a trade-off there. Want me to record it as a decision?\"

    This is normal: The Agent Playbook encourages persisting at milestones, and a commit is a natural milestone.

    As an alternative, you can ask the assistant \"can we commit this?\" and it will pick up the /ctx-commit skill for you.

    The skill runs a pre-commit build check (for Go projects, go build), reviews the staged changes, drafts a commit message focused on \"why\" rather than \"what\", and then commits.

    After the commit succeeds, it prompts you:

    **Any context to capture?**\n\n- **Decision**: Did you make a design choice or trade-off?\n- **Learning**: Did you hit a gotcha or discover something?\n- **Neither**: No context to capture; we are done.\n

    If you made a decision, the skill records it with ctx decision add. If you learned something, it records it with ctx learning add including context, lesson, and application fields. This is the bridge between committing code and remembering why the code looks the way it does.

    If source code changed in areas that affect documentation, the skill also offers to check for doc drift.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-6-reflect","level":3,"title":"Step 6: Reflect","text":"

    At natural breakpoints (after finishing a feature, resolving a complex bug, or before switching tasks) pause to reflect:

    /ctx-reflect\n

    Agents Reflect at Milestones

    Agents often reflect without explicit invocation.

    After completing a significant piece of work, the agent may naturally surface items worth persisting:

    \"We discovered that $PPID resolves differently inside hooks. Should I save that as a learning?\"

    This is the agent following the Work-Reflect-Persist cycle from the Agent Playbook.

    You do not need to say /ctx-reflect for this to happen; the agent treats milestones as reflection triggers on its own.

    The skill works through a checklist: learnings discovered, decisions made, tasks completed or created, and whether there are items worth persisting. It then presents a summary with specific items to persist, each with the exact command to run:

    I would suggest persisting:\n\n- **Learning**: `$PPID` in PreToolUse hooks resolves to the Claude Code PID\n  `ctx learning add --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Task**: mark \"Add cooldown to ctx agent\" as done\n- **Decision**: tombstone-based cooldown with 10m default\n  `ctx decision add \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n

    The skill asks before persisting anything. You choose what to keep.

    Not every commit needs reflection. A typo fix does not. But when you have been debugging for an hour and finally understand the root cause, that is worth a reflection checkpoint.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-7-persist-before-ending","level":3,"title":"Step 7: Persist Before Ending","text":"

    Before ending the session, run the wrap-up ceremony to capture outstanding learnings, decisions, conventions, and tasks:

    /ctx-wrap-up\n

    Ceremony Skills

    /ctx-remember at session start and /ctx-wrap-up at session end are ceremony skills: Invoke them as explicit slash commands for completeness, not conversationally.

    See Session Ceremonies for details on why these two skills are the exception to the conversational rule.

    The skill gathers signal from git diff, recent commits, and the conversation, then proposes structured candidates for your approval. After you select which to keep, it persists them via ctx add and offers /ctx-commit if uncommitted changes remain.

    As its final step, /ctx-wrap-up writes a handover under .context/handovers/<TS>-<slug>.md — a former-agent-to-next-agent note with a past-tense summary and a future-tense \"first action for the next session\". The filename is timestamped so concurrent agent runs never overwrite each other. The next /ctx-remember reads this file as the authoritative recall surface; skipping /ctx-wrap-up means the next session has no handover to read and recall degrades to probabilistic reconstruction from canonical files plus journal.

    Session transcripts are automatically captured by Claude Code and can be browsed later with ctx journal source and ctx journal source --show.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#conversational-session-flow","level":2,"title":"Conversational Session Flow","text":"

    The steps above describe the mechanics. In practice, a session with a ctx-aware agent feels more like a conversation than a command sequence.

    The agent knows the lifecycle and moves through it naturally, often without explicit invocation. Here is a realistic example:

    You: Do you remember what we were working on?

    Agent (loads context silently, presents structured readback):

    Last session (Feb 7, caching-layer): we implemented the in-memory cache with\nTTL expiration and wrote tests for the eviction policy. \n\nYou have 3 pending tasks; the biggest one is adding Redis as a backend.\n\nRecent decision:<br>\nwe chose LRU over LFU because the access pattern is mostly recency-based.\n\nWant to continue with the Redis backend, or tackle something else?\n

    You: Let's work on the Redis caching backend.

    Agent (picks up the task, begins implementation):

    Got it. I see the task in TASKS.md: \"Add Redis backend for cache layer.\"\n\nI will start with the connection pool setup. Only touching\n`internal/cache/redis/` for now.\n

    (The agent works... writes code, runs tests, iterates.)

    Agent (after implementing the connection pool and choosing a retry strategy):

    The connection pool is working. I went with exponential backoff with jitter\nfor retries instead of fixed intervals; it handles thundering herd better\nunder reconnection storms. That is a design choice worth recording. \n\nWant me to save it as a decision?\n

    You: Yes, go ahead.

    Agent:

    Done. Added to DECISIONS.md: \"Exponential backoff with jitter for Redis\nreconnection, to avoid thundering herd on pool recovery.\"\n

    (More work happens... the agent implements the cache read/write paths.)

    You: What else did we learn during this session?

    Agent:

    A few things worth capturing:\n\n* **Learning**: Redis `SET` with `NX` and `PX` flags handles atomic\n  set-if-not-exists with TTL in a single call. No need for a separate\n  `EXPIRE` command. We initially did it in two calls and hit a race condition.\n* **Learning**: The `go-redis` library's `PoolSize` default is 10 per CPU.\n  For our workload, explicit sizing at 20 was more predictable.\n* **Task update**: The connection pool subtask is done; the read/write path\n  is implemented but needs integration tests.\n\nWant me to persist these?\n

    You: Yes. And let's wrap up.

    Agent (reflects and persists... all in one flow):

    Persisted:\n\n* 2 learnings added to LEARNINGS.md\n* Marked \"connection pool setup\" done in TASKS.md\n* Added \"write integration tests for Redis cache\" as a new task\n\nContext is up to date. You are good to close.\n

    Notice What Happened

    In the above workflow, the user never typed /ctx-reflect or ctx learning add.

    The agent moved through Load, Orient, Pick, Work, Commit, and Reflect driven by natural conversation.

    \"Let's wrap up\" was enough to trigger the full reflect-and-persist flow.

    The agent surfaced persist-worthy items at milestones: after a design choice, after discovering a gotcha: without waiting to be asked.

    This is the intended experience.

    The commands and skills still exist for when you want precise control, but the agent is a proactive partner in the lifecycle, not a passive executor of slash commands.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    Quick-reference checklist for a complete session:

    • Load: /ctx-remember: load context and confirm readback
    • Orient: /ctx-status: check file health and token usage
    • Pick: /ctx-next: choose what to work on
    • Work: implement, test, iterate (scope with \"only change X\")
    • Commit: /ctx-commit: commit and capture decisions/learnings
    • Reflect: /ctx-reflect: identify what to persist (at milestones)
    • Wrap up: /ctx-wrap-up: end-of-session ceremony

    Conversational equivalents: you can drive the same lifecycle with plain language:

    Step Slash command Natural language Load /ctx-remember \"Do you remember?\" / \"What were we working on?\" Orient /ctx-status \"How's our context looking?\" Pick /ctx-next \"What should we work on?\" / \"Let's do the caching task\" Work (none) \"Only change files in internal/cache/\" Commit /ctx-commit \"Commit this\" / \"Ship it\" Reflect /ctx-reflect \"What did we learn?\" / (agent offers at milestones) Wrap up /ctx-wrap-up (use the slash command for completeness)

    The agent understands both columns.

    In practice, most sessions use a mix:

    • Explicit Commands when you want precision;
    • Natural Language when you want flow and agentic autonomy.

    The agent will also initiate steps on its own (particularly \"Reflect\") when it recognizes a milestone.

    Short sessions (quick bugfix) might only use: Load, Work, Commit.

    Long sessions should Reflect after each major milestone and persist learnings and decisions before ending.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tips","level":2,"title":"Tips","text":"

    Persist early if context is running low. A hook monitors context capacity and notifies you when it gets high, but do not wait for the notification. If you have been working for a while and have unpersisted learnings, persist proactively.

    Browse previous sessions by topic. If you need context from a prior session, ctx journal source --show auth will match by keyword. You do not need to remember the exact date or slug.

    Reflection is optional but valuable. You can skip /ctx-reflect for small changes, but always persist learnings and decisions before ending a session where you did meaningful work. These are what the next session loads.

    Let the hook handle context loading. The PreToolUse hook runs ctx agent automatically with a cooldown, so context loads on first tool use without you asking. The /ctx-remember prompt at session start is for your benefit (to get a readback), not because the assistant needs it.

    The agent is a proactive partner, not a passive tool. A ctx-aware agent follows the Agent Playbook: it watches for milestones (completed tasks, design decisions, discovered gotchas) and offers to persist them without being asked. If you finish a tricky debugging session, it may say \"That root cause is worth saving as a learning. Want me to record it?\" before you think to ask. This is by design.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#next-up","level":2,"title":"Next Up","text":"

    Session Ceremonies →: The two bookend rituals for every session: /ctx-remember at the start, /ctx-wrap-up at the end.

    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#see-also","level":2,"title":"See Also","text":"
    • Session Ceremonies: why /ctx-remember and /ctx-wrap-up are explicit slash commands, not conversational
    • CLI Reference: full documentation for all ctx commands
    • Prompting Guide: effective prompts for ctx-enabled projects
    • Tracking Work Across Sessions: deep dive on task management
    • Persisting Decisions, Learnings, and Conventions: deep dive on knowledge capture
    • Detecting and Fixing Drift: keeping context files accurate
    • Pausing Context Hooks: shortcut the full lifecycle for quick tasks that don't need ceremony overhead
    ","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-pause/","level":1,"title":"Pausing Context Hooks","text":"","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#the-problem","level":2,"title":"The Problem","text":"

    Not every session needs the full ceremony. Quick investigations, one-off questions, small fixes unrelated to active project work: These tasks don't benefit from persistence nudges, ceremony reminders, or knowledge checks. Every hook still fires, consuming tokens and attention on work that won't produce learnings or decisions worth capturing.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tldr","level":2,"title":"TL;DR","text":"Command What it does ctx hook pause or /ctx-pause Silence all nudge hooks for this session ctx hook resume or /ctx-resume Restore normal hook behavior

    Pause is session-scoped: It only affects the current session. Other sessions (same project, different terminal) are unaffected.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-gets-paused","level":2,"title":"What Gets Paused","text":"

    All nudge and reminder hooks go silent:

    • Context size checkpoints
    • Ceremony adoption nudges
    • Persistence reminders
    • Journal maintenance reminders
    • Knowledge growth nudges
    • Map staleness nudges
    • Version update nudges
    • Resource pressure warnings
    • QA reminders
    • Post-commit nudges
    • Specs nudges
    • Backup age warnings
    • Context load gate
    • Pending reminders relay
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-still-fires","level":2,"title":"What Still Fires","text":"

    Security hooks always run, even when paused:

    • block-non-path-ctx: prevents ./ctx invocations
    • block-dangerous-commands: blocks sudo, force push, etc.
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#workflow","level":2,"title":"Workflow","text":"
    # 1. Session starts: Context loads normally.\n\n# 2. You realize this is a quick task\nctx hook pause\n\n# 3. Work without interruption: hooks are silent\n\n# 4. Session evolves into real work? Resume first\nctx hook resume\n\n# 5. Now wrap up normally\n# /ctx-wrap-up\n
    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#graduated-reminder","level":2,"title":"Graduated Reminder","text":"

    Paused hooks aren't completely invisible. A minimal indicator appears so you always know the state:

    Paused turns What you see 1-5 ctx:paused 6+ ctx:paused (N turns): resume with /ctx-resume

    This prevents the \"forgot I paused\" problem during long sessions.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tips","level":2,"title":"Tips","text":"
    • Resume before wrapping up. If your quick task turns into real work, resume hooks before running /ctx-wrap-up. The wrap-up ceremony needs active hooks to capture learnings properly.

    • Initial context load is unaffected. The ~8k token startup injection (CLAUDE.md, playbook, constitution) happens before any command runs. Pause only affects hooks that fire during the session.

    • Use for quick investigations. Debugging a stack trace? Checking a git log? Answering a colleague's question? Pause, do the work, close the session. No ceremony needed.

    • Don't use for real work. If you're implementing features, fixing bugs, or making decisions: keep hooks active. The nudges exist to prevent context loss.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#see-also","level":2,"title":"See Also","text":"

    See also: Session Ceremonies: the bookend rituals that pause lets you skip when they aren't needed.

    See also: Customizing Hook Messages: if you want to change what hooks say rather than silencing them entirely.

    See also: The Complete Session: the full session workflow that pause shortcuts for quick tasks.

    ","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-reminders/","level":1,"title":"Session Reminders","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-problem","level":2,"title":"The Problem","text":"

    You're deep in a session and realize: \"I need to refactor the swagger definitions next time.\" You could add a task, but this isn't a work item: it's a note to future-you. You could jot it on the scratchpad, but scratchpad entries don't announce themselves.

    How do you leave a message that your next session opens with?

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tldr","level":2,"title":"TL;DR","text":"
    ctx remind \"refactor the swagger definitions\"\nctx remind list\nctx remind dismiss 1       # or batch: ctx remind dismiss 1 3-5\n

    Reminders surface automatically at session start: VERBATIM, every session, until you dismiss them.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx remind ... fails with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx remind CLI command Add a reminder (default action) ctx remind list CLI command Show all pending reminders ctx remind dismiss CLI command Remove a reminder by ID (or --all) /ctx-remind Skill Natural language interface to reminders","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-1-leave-a-reminder","level":3,"title":"Step 1: Leave a Reminder","text":"

    Tell your agent what to remember, or run it directly:

    You: \"remind me to refactor the swagger definitions\"\n\nAgent: [runs ctx remind \"refactor the swagger definitions\"]\n       \"Reminder set:\n         + [1] refactor the swagger definitions\"\n

    Or from the terminal:

    ctx remind \"refactor the swagger definitions\"\n
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-2-set-a-date-gate-optional","level":3,"title":"Step 2: Set a Date Gate (Optional)","text":"

    If the reminder shouldn't fire until a specific date:

    You: \"remind me to check the deploy logs after Tuesday\"\n\nAgent: [runs ctx remind \"check the deploy logs\" --after 2026-02-25]\n       \"Reminder set:\n         + [2] check the deploy logs  (after 2026-02-25)\"\n

    The reminder stays silent until that date, then fires every session.

    The agent converts natural language dates (\"tomorrow\", \"next week\", \"after the release on Friday\") to YYYY-MM-DD. If it's ambiguous, it asks.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-3-start-a-new-session","level":3,"title":"Step 3: Start a New Session","text":"

    Next session, the reminder appears automatically before anything else:

    ┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n

    No action needed: The check-reminders hook fires on UserPromptSubmit and the agent relays the box verbatim.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-4-dismiss-when-done","level":3,"title":"Step 4: Dismiss When Done","text":"

    After you've acted on a reminder (or decided to skip it):

    You: \"dismiss reminder 1\"\n\nAgent: [runs ctx remind dismiss 1]\n       \"Dismissed:\n         - [1] refactor the swagger definitions\"\n\n# Batch dismiss also works:\n# \"dismiss reminders 3, 5 through 7\"\n# → ctx remind dismiss 3 5-7\n

    Or clear everything:

    ctx remind dismiss --all\n
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-5-check-whats-pending","level":3,"title":"Step 5: Check What's Pending","text":"
    ctx remind list\n
      [1] refactor the swagger definitions\n  [3] review auth token expiry logic\n  [4] check deploy logs  (after 2026-02-25, not yet due)\n

    Date-gated reminders that haven't reached their date show (not yet due).

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#using-ctx-remind-in-a-session","level":2,"title":"Using /ctx-remind in a Session","text":"

    Invoke the /ctx-remind skill, then describe what you want:

    You: /ctx-remind remind me to update the API docs\nYou: /ctx-remind what reminders do I have?\nYou: /ctx-remind dismiss reminder 3\n
    You say (after /ctx-remind) What the agent does \"remind me to update the API docs\" ctx remind \"update the API docs\" \"remind me next week to check staging\" ctx remind \"check staging\" --after 2026-03-02 \"what reminders do I have?\" ctx remind list \"dismiss reminder 3\" ctx remind dismiss 3 \"dismiss reminders 3, 5 through 7\" ctx remind dismiss 3 5-7 \"clear all reminders\" ctx remind dismiss --all","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#reminders-vs-scratchpad-vs-tasks","level":2,"title":"Reminders vs Scratchpad vs Tasks","text":"You want to... Use Leave a note that announces itself next session ctx remind Jot down a quick value or sensitive token ctx pad Track work with status and completion TASKS.md Record a decision or lesson for all sessions Context files

    Decision guide:

    • If it should announce itself at session start → ctx remind
    • If it's a quiet note you'll check manually → ctx pad
    • If it's a work item you'll mark done → TASKS.md

    Reminders Are Sticky Notes, Not Tasks

    A reminder has no status, no priority, no lifecycle. It's a message to \"future you\" that fires until dismissed.

    If you need tracking, use a task in TASKS.md.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tips","level":2,"title":"Tips","text":"
    • Reminders fire every session: Unlike nudges (which throttle to once per day), reminders repeat until you dismiss them. This is intentional: You asked to be reminded.
    • Date gating is session-scoped, not clock-scoped: --after 2026-02-25 means \"don't show until sessions on or after Feb 25.\" It does not mean \"alarm at midnight on Feb 25.\"
    • The agent handles date parsing: Say \"next week\" or \"after Friday\": The agent converts it to YYYY-MM-DD. The CLI only accepts the explicit date format.
    • Reminders are committed to git: They travel with the repo. If you switch machines, your reminders follow.
    • IDs never reuse: After dismissing reminder 3, the next reminder gets ID 4 (or higher). No confusion from recycled numbers.
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#next-up","level":2,"title":"Next Up","text":"

    Using the Scratchpad →: For quiet notes and sensitive values that don't need session-start announcements.

    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#see-also","level":2,"title":"See Also","text":"
    • CLI Reference: ctx remind: full command syntax and flags
    • The Complete Session: how reminders fit into the session lifecycle
    • Managing Tasks: for work items that need status tracking
    ","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/state-maintenance/","level":1,"title":"State Directory Maintenance","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-problem","level":2,"title":"The Problem","text":"

    Every session creates tombstone files in .context/state/ - small markers that suppress repeat hook nudges (\"already checked context size\", \"already sent persistence reminder\"). Over days and weeks, these accumulate into hundreds of files from long-dead sessions.

    The files are harmless individually, but the clutter makes it harder to reason about state, and stale global tombstones can suppress nudges across sessions entirely.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tldr","level":2,"title":"TL;DR","text":"
    ctx prune --dry-run     # preview what would be removed\nctx prune               # prune files older than 7 days\nctx prune --days 1      # more aggressive: keep only today\n

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, ctx prune / ctx status fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose ctx prune Command Remove old per-session state files ctx status Command Quick health overview including state dir","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#understanding-state-files","level":2,"title":"Understanding State Files","text":"

    State files fall into two categories:

    Session-scoped (contain a UUID in the filename): Created per-session to suppress repeat nudges. Safe to prune once the session ends. Examples:

    context-check-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\nheartbeat-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\npersistence-nudge-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\n

    Global (no UUID): Persist across sessions. ctx prune preserves these automatically. Some are legitimate state (events.jsonl, memory-import.json); others may be stale tombstones that need manual review.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-workflow","level":2,"title":"The Workflow","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-1-preview","level":3,"title":"Step 1: Preview","text":"

    Always dry-run first to see what would be removed:

    ctx prune --dry-run\n

    The output shows each file, its age, and a summary:

      would prune: context-check-abc123... (age: 3d)\n  would prune: heartbeat-abc123... (age: 3d)\n\nDry run - would prune 150 files (skip 70 recent, preserve 14 global)\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-2-prune","level":3,"title":"Step 2: Prune","text":"

    Choose an age threshold. The default is 7 days:

    ctx prune               # older than 7 days\nctx prune --days 3      # older than 3 days\nctx prune --days 1      # older than 1 day (aggressive)\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-3-review-global-files","level":3,"title":"Step 3: Review Global Files","text":"

    After pruning, check what prune preserved:

    ls .context/state/ | grep -v '[0-9a-f]\\{8\\}-[0-9a-f]\\{4\\}'\n

    Legitimate global files (keep):

    • events.jsonl - event log
    • memory-import.json - import tracking state

    Stale global tombstones (safe to delete):

    • Files like backup-reminded, ceremony-reminded, version-checked with no session UUID are one-shot markers. If they are from a previous session, they are stale and can be removed manually.
    rm .context/state/backup-reminded .context/state/ceremony-reminded\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-4-verify","level":3,"title":"Step 4: Verify","text":"
    ls .context/state/ | wc -l    # should be manageable\n
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#when-to-prune","level":2,"title":"When to Prune","text":"
    • Weekly: ctx prune with default 7-day threshold
    • After heavy parallel work: Multiple concurrent sessions create many tombstones. Prune with --days 1 afterward.
    • When state directory exceeds ~100 files: A sign that pruning hasn't run recently
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tips","level":2,"title":"Tips","text":"

    Pruning active sessions is safe but noisy: If you prune a file belonging to a still-running session, the corresponding hook will re-fire its nudge on the next prompt. Minor UX annoyance, not data loss.

    No context files are stored in state: The state directory contains only tombstones, counters, and diagnostic data. Nothing in .context/state/ affects your decisions, learnings, tasks, or conventions.

    Test artifacts sneak in: Files like context-check-statstest or heartbeat-unknown are artifacts from development or testing. They lack UUIDs so prune preserves them. Delete manually.

    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#see-also","level":2,"title":"See Also","text":"
    • Detecting and Fixing Drift: broader context maintenance including drift detection and archival
    • Troubleshooting: diagnostic workflow using ctx doctor and event logs
    • CLI Reference: system: full flag documentation for ctx prune and related commands
    ","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/steering/","level":1,"title":"Writing Steering Files","text":"","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#writing-steering-files","level":1,"title":"Writing Steering Files","text":"

    Steering files tell your AI assistant how to behave, not what was decided or how the codebase is written. This recipe walks through writing a steering file from scratch, validating which prompts will trigger it, and syncing it out to your configured AI tools.

    Before You Start

    If you're unsure whether a rule belongs in steering/, DECISIONS.md, or CONVENTIONS.md, read the \"Steering vs decisions vs conventions\" admonition on the ctx steering reference page. The short version: if the rule is \"the AI should always do X when asked about Y,\" that's steering. Otherwise it's probably a decision or convention.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#start-here-customize-the-foundation-files","level":2,"title":"Start Here: Customize the Foundation Files","text":"

    ctx init scaffolds four foundation steering files for you the first time you initialize a project:

    File Purpose .context/steering/product.md Product context, goals, target users .context/steering/tech.md Tech stack, constraints, key dependencies .context/steering/structure.md Directory layout, naming conventions .context/steering/workflow.md Branch strategy, commit rules, pre-commit

    Each file opens with an inline HTML comment that explains the three inclusion modes, what priority means, and the tools scope. The comment is invisible in rendered Markdown but visible when you edit the file. Delete it once the file is yours.

    All four default to inclusion: always and priority: 10, so they fire on every AI tool call until you customize them. If you're reading this recipe and haven't touched them yet, open each one now and replace the placeholder bullet list with actual rules for your project. That's the highest-leverage five minutes you can spend in a new ctx setup.

    What to fill in, by file:

    product.md: The elevator pitch plus hard scope:

    • One-sentence product description.
    • Primary users and their top job-to-be-done.
    • Two or three \"this is explicitly out of scope\" items so the AI doesn't wander.

    tech.md: Technology and constraints:

    • Languages and versions (Go 1.22, Node 20, etc.).
    • Frameworks and key libraries.
    • Runtime and deployment target.
    • Hard constraints: \"no CGO\", \"no network at test time\", \"no external DB for unit tests\". These are the things that burn agents when they don't know them.

    structure.md: Layout and naming:

    • Top-level directories and their purpose.
    • Where new files should go (and where they should NOT).
    • Naming conventions for packages, files, types.

    workflow.md: Process rules:

    • Branch strategy (main-only, trunk-based, feature branches).
    • Commit message format, signed-off-by requirement.
    • Pre-commit and pre-push checks.
    • Review expectations.

    After editing, the next AI tool call in Claude Code will pick up the new rules automatically via the plugin's PreToolUse hook, with no sync step and no restart. Other tools (Cursor, Cline, Kiro) need ctx steering sync to export into their native format.

    Prefer a Bare .context/steering/ Directory?

    Re-run ctx init --no-steering-init and delete the scaffolded files. ctx init leaves existing files alone, so the flag is only needed if you want to opt out of the initial scaffold.

    The rest of this recipe walks through creating an additional, scenario-specific steering file beyond the four foundation defaults.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#scenario","level":2,"title":"Scenario","text":"

    You're working on a project with a strict input-validation policy: every new API handler must validate request bodies before touching the database. You want the AI to flag this concern automatically whenever it's asked to write an HTTP handler, without you having to remind it every session.

    Claude Code Users: Pick always, Not auto

    This walkthrough uses inclusion: auto because the scenario is a scoped rule that matches a specific kind of prompt. That works natively on Cursor, Cline, and Kiro (they resolve the description keyword match themselves).

    On Claude Code, auto does not fire through the plugin's PreToolUse hook. The hook passes an empty prompt to ctx agent, so only always files match. Claude can still reach an auto file by calling the ctx_steering_get MCP tool, but that requires Claude to decide to call it; there's no automatic injection.

    If Claude Code is your tool, set inclusion: always in Step 2 instead of auto. The rule will fire on every tool call regardless of topic. You may want to narrow the rule body so the extra tokens per turn aren't wasted on unrelated work.

    See the ctx steering reference \"Prefer inclusion: always for Claude Code\" section for the full trade-off.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-1-scaffold-the-file","level":2,"title":"Step 1: Scaffold the File","text":"
    ctx steering add api-validation\n

    That creates .context/steering/api-validation.md with default frontmatter:

    ---\nname: api-validation\ndescription:\ninclusion: manual\ntools: []\npriority: 50\n---\n

    The defaults are deliberately conservative: inclusion: manual means the file won't be applied until you opt in, which keeps the rules out of the prompt until you've reviewed them.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-2-fill-in-the-rule","level":2,"title":"Step 2: Fill in the Rule","text":"

    Open the file and write the rule body plus a focused description. The description is what inclusion: auto matches against later.

    ---\nname: api-validation\ndescription: HTTP handler input validation and request parsing\ninclusion: auto\ntools: []\npriority: 20\n---\n\n# API request validation\n\nEvery new HTTP handler MUST:\n\n1. Parse request bodies into typed structs, never `map[string]any`.\n2. Validate required fields before any database call.\n3. Return 400 with a machine-readable error for validation failures.\n4. Use `context.Context` from the request for all downstream calls.\n\nPrefer existing validation helpers in `internal/validate/`\nrather than inline checks.\n

    Notes on the choices:

    • inclusion: auto: this rule should fire automatically on HTTP-handler-shaped prompts, not always.
    • priority: 20: lower than the default, so this rule appears near the top of the prompt alongside other high-priority rules.
    • Description is keyword-rich (\"HTTP handler input validation and request parsing\"); the auto matcher scores prompts against these words.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-3-preview-which-prompts-match","level":2,"title":"Step 3: Preview Which Prompts Match","text":"

    Before committing the file, validate your description catches the prompts you care about:

    ctx steering preview \"add an endpoint for updating user email\"\n

    Expected output:

    Steering files matching prompt \"add an endpoint for updating user email\":\n  api-validation       inclusion=auto     priority=20  tools=all\n

    Good, the prompt matches. Try a negative case:

    ctx steering preview \"fix a bug in the JSON renderer\"\n

    Expected: empty match (or whatever else is currently auto). If api-validation incorrectly fires for unrelated prompts, tighten the description. If it misses prompts it should catch, add more keywords.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-4-list-to-confirm-metadata","level":2,"title":"Step 4: List to Confirm Metadata","text":"
    ctx steering list\n

    Should show api-validation alongside any other files, with its inclusion mode and priority. If the list is wrong, check the frontmatter for typos.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-5-get-the-rules-in-front-of-the-ai","level":2,"title":"Step 5: Get the Rules in Front of the AI","text":"

    Steering files are authored once in .context/steering/, but how they reach the AI depends on which tool you use. There are two delivery mechanisms:

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-a-native-rules-tools-cursor-cline-kiro","level":3,"title":"Path A: Native-Rules Tools (Cursor, Cline, Kiro)","text":"

    These tools read a specific directory for rules. ctx steering sync exports your files into that directory with tool-specific frontmatter:

    ctx steering sync\n

    Depending on the active tool in .ctxrc or --tool:

    Tool Target Cursor .cursor/rules/ Cline .clinerules/ Kiro .kiro/steering/

    The sync is idempotent; unchanged files are skipped. Run it whenever you edit a steering file.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-b-claude-code-and-codex-hook-mcp","level":3,"title":"Path B: Claude Code and Codex (Hook + MCP)","text":"

    Claude Code and Codex have no native rules primitive, so ctx steering sync is a no-op for them; it deliberately skips both. Instead, steering reaches these tools through two non-sync channels:

    1. PreToolUse hook (automatic). The ctx setup claude-code plugin installs a hook that runs ctx agent --budget 8000 before each tool call. ctx agent loads your steering files, filters them against the active prompt, and includes matching bodies as Tier 6 of the context packet. The packet gets injected into Claude's context automatically.

    2. ctx_steering_get MCP tool (on-demand). Claude can call this MCP tool mid-task to fetch matching steering files for a specific prompt. Automatic activation comes from Claude's judgment, not a hook.

    Both channels activate when you run:

    ctx setup claude-code --write\n

    That installs the plugin, wires the hook, and registers the MCP server. After that, steering files you edit are picked up on the next tool call, with no sync step needed.

    Running ctx steering sync with Claude Code

    It won't error; it will simply report that Claude and Codex aren't sync targets and skip them. If Claude Code is your only tool, you never need to run sync. If you use both Claude Code and (say) Cursor, run sync to keep Cursor up to date; the Claude pipeline takes care of itself via the hook.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-6-verify-the-ai-sees-it","level":2,"title":"Step 6: Verify the AI Sees It","text":"

    Open your AI tool and ask it something the rule should fire on:

    \"Add a POST /users endpoint that accepts email and name.\"

    If the rule is working, the AI's first response should mention input validation, typed structs, and the internal/validate/ package, because that's what the steering file told it to do.

    If nothing happens, the fix depends on which path you're on:

    Path A (Cursor/Cline/Kiro):

    1. Re-run ctx steering preview with the literal prompt to confirm the match.
    2. Run ctx steering list and verify inclusion is auto, not manual.
    3. Check the tool's own config directory (e.g. .cursor/rules/); the file should be there after ctx steering sync.

    Path B (Claude Code):

    1. Re-run ctx steering preview with the literal prompt to confirm the match.
    2. Verify the plugin is installed: cat .claude/hooks.json should include ctx agent --budget 8000 under PreToolUse. If not, re-run ctx setup claude-code --write.
    3. Run ctx agent --budget 8000 manually and grep the output for your rule body. If it's there, the data is fine; if it's missing, the inclusion mode or description is at fault.
    4. As a last resort, ask Claude directly: \"Call the ctx_steering_get MCP tool with my prompt and show me the result.\" If the MCP tool returns your rule, Claude has access but isn't pulling it into the initial context packet; tighten the description keywords.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    Too-generic descriptions. description: general coding will match almost every prompt and flood the context window. Keep descriptions specific to the scenario the rule applies to.

    Overlapping rules. If two steering files match the same prompt and contradict each other, the result is confusing. Use priority to resolve, but better: merge the files or narrow the descriptions so they don't overlap.

    Putting decisions in steering. \"We decided to use PostgreSQL\" is a decision, not a rule for the AI to follow on every prompt. Record decisions with ctx decision add, not ctx steering add.

    Committing inclusion: always without thinking. Rules marked always fire on every prompt, consuming tier-6 budget permanently. Only use always for true invariants (security, safety, licensing). Everything else should be auto or manual.

    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#see-also","level":2,"title":"See Also","text":"
    • ctx steering reference: full command, flag, and frontmatter reference.
    • ctx setup: configure which tools the steering sync writes to.
    • Authoring triggers: if you want script-based automation, not rule-based prompt injection.
    ","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/system-hooks-audit/","level":1,"title":"Auditing System Hooks","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-problem","level":2,"title":"The Problem","text":"

    ctx runs 14 system hooks behind the scenes: nudging your agent to persist context, warning about resource pressure, gating commits on QA. But these hooks are invisible by design. You never see them fire. You never know if they stopped working.

    How do you verify your hooks are actually running, audit what they do, and get alerted when they go silent?

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tldr","level":2,"title":"TL;DR","text":"
    ctx system check-resources # run a hook manually\nls -la .context/logs/      # check hook execution logs\nctx hook notify setup      # get notified when hooks fire\n

    Or ask your agent: \"Are our hooks running?\"

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx system <hook> CLI command Run a system hook manually ctx sysinfo CLI command Show system resource status ctx usage CLI command Stream or dump per-session token stats ctx hook notify setup CLI command Configure webhook for audit trail ctx hook notify test CLI command Verify webhook delivery .ctxrc notify.events Configuration Subscribe to relay for full hook audit .context/logs/ Log files Local hook execution ledger","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-are-system-hooks","level":2,"title":"What Are System Hooks?","text":"

    System hooks are plumbing commands that ctx registers with your AI tool (Claude Code, Cursor, etc.) via the plugin's hooks.json. They fire automatically at specific events during your AI session:

    Event When Hooks UserPromptSubmit Before the agent sees your prompt 10 check hooks + heartbeat PreToolUse Before the agent uses a tool block-non-path-ctx, qa-reminder PostToolUse After a tool call succeeds post-commit

    You never run these manually. Your AI tool runs them for you: That's the point.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-complete-hook-catalog","level":2,"title":"The Complete Hook Catalog","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#prompt-time-checks-userpromptsubmit","level":3,"title":"Prompt-Time Checks (UserPromptSubmit)","text":"

    These fire before every prompt, but most are throttled to avoid noise.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-context-size-context-capacity-warning","level":4,"title":"check-context-size: Context Capacity Warning","text":"

    What: Adaptive prompt counter. Silent for the first 15 prompts, then nudges with increasing frequency (every 5th, then every 3rd).

    Why: Long sessions lose coherence. The nudge reminds both you and the agent to persist context before the window fills up.

    Output: VERBATIM relay box with prompt count.

    ┌─ Context Checkpoint (prompt #20) ────────────────\n│ This session is getting deep. Consider wrapping up\n│ soon. If there are unsaved learnings, decisions, or\n│ conventions, now is a good time to persist them.\n│ ⏱ Context window: ~45k tokens (~22% of 200k)\n└──────────────────────────────────────────────────\n

    Usage: Every prompt records token usage to .context/state/stats-{session}.jsonl. Monitor live with ctx usage --follow or query with ctx usage --json. Usage is recorded even during wrap-up suppression (event: suppressed).

    Billing guard: When billing_token_warn is set in .ctxrc, a one-shot warning fires if session tokens exceed the threshold. This warning is independent of all other triggers - it fires even during wrap-up suppression.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-persistence-context-staleness-nudge","level":4,"title":"check-persistence: Context Staleness Nudge","text":"

    What: Tracks when .context/*.md files were last modified. If too many prompts pass without a write, nudges the agent to persist.

    Why: Sessions produce insights that evaporate if not recorded. This catches the \"we talked about it but never wrote it down\" failure mode.

    Output: VERBATIM relay after 20+ prompts without a context file change.

    ┌─ Persistence Checkpoint (prompt #20) ───────────\n│ No context files updated in 20+ prompts.\n│ Have you discovered learnings, made decisions,\n│ established conventions, or completed tasks\n│ worth persisting?\n│\n│ Run /ctx-wrap-up to capture session context.\n└──────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-ceremonies-session-ritual-adoption","level":4,"title":"check-ceremonies: Session Ritual Adoption","text":"

    What: Scans your last 3 journal entries for /ctx-remember and /ctx-wrap-up usage. Nudges once per day if missing.

    Why: Session ceremonies are the highest-leverage habit in ctx. This hook bootstraps the habit until it becomes automatic.

    Output: Tailored nudge depending on which ceremony is missing.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-journal-unimported-session-reminder","level":4,"title":"check-journal: Unimported Session Reminder","text":"

    What: Detects unimported Claude Code sessions and unenriched journal entries. Fires once per day.

    Why: Exported sessions become searchable history. Unenriched entries lack metadata for filtering. Both decay in value over time.

    Output: VERBATIM relay with counts and exact commands.

    ┌─ Journal Reminder ─────────────────────────────\n│ You have 3 new session(s) not yet exported.\n│ 5 existing entries need enrichment.\n│\n│ Export and enrich:\n│   ctx journal import --all\n│   /ctx-journal-enrich-all\n└────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-resources-system-resource-pressure","level":4,"title":"check-resources: System Resource Pressure","text":"

    What: Monitors memory, swap, disk, and CPU load. Only fires at DANGER severity (memory >= 90%, swap >= 75%, disk >= 95%, load >= 1.5x CPU count).

    Why: Resource exhaustion mid-session can corrupt work. This provides early warning to persist and exit.

    Output: VERBATIM relay listing critical resources.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-knowledge-knowledge-file-growth","level":4,"title":"check-knowledge: Knowledge File Growth","text":"

    What: Counts entries in LEARNINGS.md, DECISIONS.md, and lines in CONVENTIONS.md. Fires once per day when thresholds are exceeded.

    Why: Large knowledge files dilute agent context. 35 learnings compete for attention; 15 focused ones get applied. Thresholds are configurable in .ctxrc.

    Default thresholds:

    # .ctxrc\nentry_count_learnings: 30\nentry_count_decisions: 20\nconvention_line_count: 200\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-version-binaryplugin-version-drift","level":4,"title":"check-version: Binary/Plugin Version Drift","text":"

    What: Compares the ctx binary version against the plugin version. Fires once per day. Also checks encryption key age for rotation nudge.

    Why: Version drift means hooks reference features the binary doesn't have. The key rotation nudge prevents indefinite key reuse.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-reminders-pending-reminder-relay","level":4,"title":"check-reminders: Pending Reminder Relay","text":"

    What: Reads .context/reminders.json and surfaces any due reminders via VERBATIM relay. No throttle: fires every session until dismissed.

    Why: Reminders are sticky notes to future-you. Unlike nudges (which throttle to once per day), reminders repeat deliberately until the user dismisses them.

    Output: VERBATIM relay box listing due reminders.

    ┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-freshness-technology-constant-staleness","level":4,"title":"check-freshness: Technology Constant Staleness","text":"

    What: Stats files listed in .ctxrc freshness_files and warns if any haven't been modified in over 6 months. Daily throttle. Silent when no files are configured (opt-in via .ctxrc).

    Why: Model capabilities evolve - token budgets, attention limits, and context window sizes that were accurate 6 months ago may no longer reflect best practices. This hook reminds you to review and touch the file to confirm values are still current.

    Config (.ctxrc):

    freshness_files:\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # optional\n

    Each entry has a path (relative to project root), desc (what constants live there), and optional review_url (where to check current values). When review_url is set, the nudge includes \"Review against: {url}\". When absent, just \"Touch the file to mark it as reviewed.\"

    Output: VERBATIM relay listing stale files, silent otherwise.

    ┌─ Technology Constants Stale ──────────────────────\n│   config/thresholds.yaml (210 days ago)\n│     - Model token limits and batch sizes\n│   Review against: https://docs.example.com/limits\n│ Touch each file to mark it as reviewed.\n└───────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-map-staleness-architecture-map-drift","level":4,"title":"check-map-staleness: Architecture Map Drift","text":"

    What: Checks whether map-tracking.json is older than 30 days and there are commits touching internal/ since the last map refresh. Daily throttle prevents repeated nudges.

    Why: Architecture documentation drifts silently as code evolves. This hook detects structural changes that the map hasn't caught up with and suggests running /ctx-architecture to refresh.

    Output: VERBATIM relay when stale and modules changed, silent otherwise.

    ┌─ Architecture Map Stale ────────────────────────────\n│ ARCHITECTURE.md hasn't been refreshed since 2026-01-15\n│ and there are commits touching 12 modules.\n│ /ctx-architecture keeps architecture docs drift-free.\n│\n│ Want me to run /ctx-architecture to refresh?\n└─────────────────────────────────────────────────────\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#heartbeat-session-heartbeat-webhook","level":4,"title":"heartbeat: Session Heartbeat Webhook","text":"

    What: Fires on every prompt. Sends a webhook notification with prompt count, session ID, context modification status, and token usage telemetry. Never produces stdout.

    Why: Other hooks only send webhooks when they \"speak\" (nudge/relay). When silent, you have no visibility into session activity. The heartbeat provides a continuous session-alive signal with token consumption data for observability dashboards or liveness monitoring.

    Output: None (webhook + event log only).

    Payload:

    {\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  }\n}\n

    Token fields (tokens, context_window, usage_pct) are included when usage data is available from the session JSONL file.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tool-time-hooks-pretooluse-posttooluse","level":3,"title":"Tool-Time Hooks (PreToolUse / PostToolUse)","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#block-non-path-ctx-path-enforcement-hard-gate","level":4,"title":"block-non-path-ctx: PATH Enforcement (Hard Gate)","text":"

    What: Blocks any Bash command that invokes ./ctx, ./dist/ctx, go run ./cmd/ctx, or an absolute path to ctx. Only PATH invocations are allowed.

    Why: Enforces CONSTITUTION.md's invocation invariant. Running a dev-built binary in production context causes version confusion and silent behavior drift.

    Output: Block response (prevents the tool call):

    {\"decision\": \"block\", \"reason\": \"Use 'ctx' from PATH, not './ctx'...\"}\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#qa-reminder-pre-commit-qa-gate","level":4,"title":"qa-reminder: Pre-Commit QA Gate","text":"

    What: Fires on every Edit tool use. Reminds the agent to lint and test the entire project before committing.

    Why: Agents tend to \"I'll test later\" and then commit untested code. Repetition is intentional: the hook reinforces the habit on every edit, not just before commits.

    Output: Agent directive with hard QA gate instructions.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#post-commit-context-capture-after-commit","level":4,"title":"post-commit: Context Capture After Commit","text":"

    What: Fires after any git commit (excludes --amend). Prompts the agent to offer context capture (decision? learning?) and suggest running lints/tests before pushing.

    Why: Commits are natural reflection points. The nudge converts mechanical git operations into context-capturing opportunities.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-the-local-event-log","level":2,"title":"Auditing Hooks via the Local Event Log","text":"

    If you don't need an external audit trail, enable the local event log for a self-contained record of hook activity:

    # .ctxrc\nevent_log: true\n

    Once enabled, every hook that fires writes an entry to .context/state/events.jsonl. Query it with ctx hook event:

    ctx hook event                    # last 50 events\nctx hook event --hook qa-reminder # filter by hook\nctx hook event --session <id>     # filter by session\nctx hook event --json | jq '.'    # raw JSONL for processing\n

    The event log is local, queryable, and doesn't require any external service. For a full diagnostic workflow combining event logs with structural health checks, see Troubleshooting.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-webhooks","level":2,"title":"Auditing Hooks via Webhooks","text":"

    The most powerful audit setup pipes all hook output to a webhook, giving you a real-time external record of what your agent is being told.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-1-set-up-the-webhook","level":3,"title":"Step 1: Set Up the Webhook","text":"
    ctx hook notify setup\n# Enter your webhook URL (Slack, Discord, ntfy.sh, IFTTT, etc.)\n

    See Webhook Notifications for service-specific setup.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-2-subscribe-to-relay-events","level":3,"title":"Step 2: Subscribe to relay Events","text":"
    # .ctxrc\nnotify:\n  events:\n    - relay   # all hook output: VERBATIM relays, directives, blocks\n    - nudge   # just the user-facing VERBATIM relays\n

    The relay event fires for every hook that produces output. This includes:

    Hook Event sent check-context-size relay + nudge check-persistence relay + nudge check-ceremonies relay + nudge check-journal relay + nudge check-resources relay + nudge check-knowledge relay + nudge check-version relay + nudge check-reminders relay + nudge check-freshness relay + nudge check-map-staleness relay + nudge heartbeat heartbeat only block-non-path-ctx relay only post-commit relay only qa-reminder relay only","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-3-cross-reference","level":3,"title":"Step 3: Cross-Reference","text":"

    With relay enabled, your webhook receives a JSON payload every time a hook fires:

    {\n  \"event\": \"relay\",\n  \"message\": \"check-persistence: No context updated in 20+ prompts\",\n  \"session_id\": \"b854bd9c\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"my-project\"\n}\n

    This creates an external audit trail independent of the agent. You can now cross-verify: did the agent actually relay the checkpoint the hook told it to relay?

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#verifying-hooks-actually-fire","level":2,"title":"Verifying Hooks Actually Fire","text":"

    Hooks are invisible. An invisible thing that breaks is indistinguishable from an invisible thing that never existed. Three verification methods, from simplest to most robust:

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-1-ask-the-agent","level":3,"title":"Method 1: Ask the Agent","text":"

    The simplest check. After a few prompts into a session:

    \"Did you receive any hook output this session? Print the last\ncontext checkpoint or persistence nudge you saw.\"\n

    The agent should be able to recall recent hook output from its context window. If it says \"I haven't received any hook output\", either:

    • The hooks aren't firing (check installation);
    • The session is too short (hooks throttle early);
    • The hooks fired but the agent absorbed them silently.

    Limitation: You are trusting the agent to report accurately. Agents sometimes confabulate or miss context. Use this as a quick smoke test, not definitive proof.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-2-check-the-webhook-trail","level":3,"title":"Method 2: Check the Webhook Trail","text":"

    If you have relay events enabled, check your webhook receiver. Every hook that fires sends a timestamped notification. No notification = no fire.

    This is the ground truth. The webhook is called directly by the ctx binary, not by the agent. The agent cannot fake, suppress, or modify webhook deliveries.

    Compare what the webhook received against what the agent claims to have relayed. Discrepancies mean the agent is absorbing nudges instead of surfacing them.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-3-read-the-local-logs","level":3,"title":"Method 3: Read the Local Logs","text":"

    Hooks that support logging write to .context/logs/:

    # Check context-size hook activity\ncat .context/logs/check-context-size.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] prompt#1 silent\n# [2026-02-22 09:17:33] [session:b854bd9c] prompt#16 CHECKPOINT\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 CHECKPOINT\n
    # Check persistence nudge activity\ncat .context/logs/check-persistence.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] init count=1 mtime=1770646611\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 NUDGE since_nudge=20\n

    Logs are append-only and written by the ctx binary, not the agent.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#detecting-silent-hook-failures","level":2,"title":"Detecting Silent Hook Failures","text":"

    The hardest failure mode: hooks that stop firing without error. The plugin config changes, a binary update drops a hook, or a PATH issue silently breaks execution. Nothing errors: The hook just never runs.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-staleness-signal","level":3,"title":"The Staleness Signal","text":"

    If .context/logs/check-context-size.log has no entries newer than 5 days but you've been running sessions daily, something is wrong. The absence of evidence is evidence of absence: but only if you control for inactivity.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#false-positive-protection","level":3,"title":"False Positive Protection","text":"

    A naive \"hooks haven't fired in N days\" alert fires incorrectly when you simply haven't used ctx. The correct check needs two inputs:

    1. Last hook fire time: from .context/logs/ or webhook history
    2. Last session activity: from journal entries or ctx journal source

    If sessions are happening but hooks aren't firing, that's a real problem. If neither sessions nor hooks are happening, that's a vacation.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-to-check","level":3,"title":"What to Check","text":"

    When you suspect hooks aren't firing:

    # 1. Verify the plugin is installed\nls ~/.claude/plugins/\n\n# 2. Check hook registration\ncat ~/.claude/plugins/ctx/hooks.json | head -20\n\n# 3. Run a hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-context-size\n\n# 4. Check for PATH issues\nwhich ctx\nctx --version\n
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tips","level":2,"title":"Tips","text":"
    • Start with nudge, graduate to relay: The nudge event covers user-facing VERBATIM relays. Add relay when you want full visibility into agent directives and hard gates.
    • Webhooks are your trust anchor: The agent can ignore a nudge, but it can't suppress the webhook. If the webhook fired and the agent didn't relay, you have proof of a compliance gap.
    • Hooks are throttled by design: Most check hooks fire once per day or use adaptive frequency. Don't expect a notification every prompt: Silence usually means the throttle is working, not that the hook is broken.
    • Daily markers live in .context/state/: Throttle files are stored in .context/state/ alongside other project-scoped state. If you need to force a hook to re-fire during testing, delete the corresponding marker file.
    • The QA reminder is intentionally noisy: Unlike other hooks, qa-reminder fires on every Edit call with no throttle. This is deliberate: The commit quality degrades when the reminder fades from salience.
    • Log files are safe to commit: .context/logs/ contains only timestamps, session IDs, and status keywords. No secrets, no code.
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#next-up","level":2,"title":"Next Up","text":"

    Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.

    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#see-also","level":2,"title":"See Also","text":"
    • Troubleshooting: full diagnostic workflow using ctx doctor, event logs, and /ctx-doctor
    • Customizing Hook Messages: override what hooks say without changing what they do
    • Webhook Notifications: setting up and configuring the webhook system
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Detecting and Fixing Drift: structural checks that complement runtime hook auditing
    • CLI Reference: full ctx system command reference
    ","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/task-management/","level":1,"title":"Tracking Work Across Sessions","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-problem","level":2,"title":"The Problem","text":"

    You have work that spans multiple sessions. Tasks get added during one session, partially finished in another, and completed days later.

    Without a system, follow-up items fall through the cracks, priorities drift, and you lose track of what was done versus what still needs doing. TASKS.md grows cluttered with completed checkboxes that obscure the remaining work.

    How do you manage work items that span multiple sessions without losing context?

    Prefer Skills over Raw Commands

    When working with an AI agent, use /ctx-task-add instead of raw ctx task add. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.

    Activate the Project First

    Run eval \"$(ctx activate)\" once per terminal in the project root. If you skip it, the ctx task add / ctx task ... commands below fail with Error: no context directory specified. See Activating a Context Directory.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tldr","level":2,"title":"TL;DR","text":"

    Manage Tasks:

    ctx task add \"Fix race condition\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add\nctx task add \"Write tests\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add to phase\nctx task complete \"race condition\"                      # mark done\nctx task snapshot \"before-refactor\"               # backup\nctx task archive                                  # clean up\n

    Pick Up the Next Task:

    /ctx-next # pick what's next\n

    Read on for the full workflow and conversational patterns.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx task add Command Add a new task to TASKS.md ctx task complete Command Mark a task as done by number or text ctx task snapshot Command Create a point-in-time backup of TASKS.md ctx task archive Command Move completed tasks to archive file /ctx-task-add Skill AI-assisted task creation with validation /ctx-archive Skill AI-guided archival with safety checks /ctx-next Skill Pick what to work on based on priorities","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-1-add-tasks-with-priorities","level":3,"title":"Step 1: Add Tasks with Priorities","text":"

    Every piece of follow-up work gets a task. Use ctx task add from the terminal or /ctx-task-add from your AI assistant. Tasks should start with a verb and be specific enough that someone unfamiliar with the session could act on them.

    # High-priority bug found during code review\nctx task add \"Fix race condition in session cooldown\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Medium-priority feature work\nctx task add \"Add --format json flag to ctx status for CI integration\" --priority medium \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Low-priority cleanup\nctx task add \"Remove deprecated --raw flag from ctx load\" --priority low \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n

    The /ctx-task-add skill validates your task before recording it. It checks that the description is actionable, not a duplicate, and specific enough for someone else to pick up.

    If you say \"fix the bug,\" it will ask you to clarify which bug and where.

    Tasks Are Often Created Proactively

    In practice, many tasks are created proactively by the agent rather than by explicit CLI commands.

    After completing a feature, the agent will often identify follow-up work: tests, docs, edge cases, error handling, and offer to add them as tasks.

    You do not need to dictate ctx task add commands; the agent picks up on work context and suggests tasks naturally.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-2-organize-with-phase-sections","level":3,"title":"Step 2: Organize with Phase Sections","text":"

    Tasks live in phase sections inside TASKS.md.

    Phases provide logical groupings that preserve order and enable replay.

    A task does not move between sections. It stays in its phase permanently, and status is tracked via checkboxes and inline tags.

    ## Phase 1: Core CLI\n\n- [x] Implement ctx add command\n- [x] Implement ctx task complete command\n- [ ] Add --section flag to ctx task add `#priority:medium`\n\n## Phase 2: AI Integration\n\n- [ ] Implement ctx agent cooldown `#priority:high` `#in-progress`\n- [ ] Add ctx watch XML parsing `#priority:medium`\n  - Blocked by: Need to finalize agent output format\n\n## Backlog\n\n- [ ] Performance optimization for large TASKS.md files `#priority:low`\n- [ ] Add metrics dashboard to ctx status `#priority:deferred`\n

    Use --section when adding a task to a specific phase:

    ctx task add \"Add ctx watch XML parsing\" --priority medium --section \\\n    \"Phase 2: AI Integration\" \\\n    --session-id abc12345 --branch main --commit 68fbc00a\n

    Without --section, the task is inserted before the first unchecked task in TASKS.md.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"

    At the start of a session, or after finishing a task, use /ctx-next to get prioritized recommendations.

    The skill reads TASKS.md, checks recent sessions, and ranks candidates using explicit priority, blocking status, in-progress state, momentum from recent work, and phase order.

    You can also ask naturally: \"what should we work on?\" or \"what's the highest priority right now?\"

    /ctx-next\n

    The output looks like this:

    **1. Implement ctx agent cooldown** `#priority:high`\n\n    Still in-progress from yesterday's session. The tombstone file approach is\n    half-built. Finishing is cheaper than context-switching.\n\n**2. Add --section flag to ctx task add** `#priority:medium`\n\n    Last Phase 1 item. Quick win that unblocks organized task entry.\n\n---\n\n*Based on 8 pending tasks across 3 phases.\n\nLast session: agent-cooldown (2026-02-06).*\n

    In-progress tasks almost always come first:

    Finishing existing work takes priority over starting new work.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-4-complete-tasks","level":3,"title":"Step 4: Complete Tasks","text":"

    When a task is done, mark it complete by number or partial text match:

    # By task number (as shown in TASKS.md)\nctx task complete 3\n\n# By partial text match\nctx task complete \"agent cooldown\"\n

    The task's checkbox changes from [ ] to [x]. Tasks are never deleted: they stay in their phase section so history is preserved.

    Be Conversational

    You rarely need to run ctx task complete yourself during an interactive session.

    When you say something like \"the rate limiter is done\" or \"we finished that,\" the agent marks the task complete and moves on to suggesting what is next.

    The CLI commands are most useful for manual housekeeping, scripted workflows, or when you want precision.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-5-snapshot-before-risky-changes","level":3,"title":"Step 5: Snapshot Before Risky Changes","text":"

    Before a major refactor or any change that might break things, snapshot your current task state. This creates a copy of TASKS.md in .context/archive/ without modifying the original.

    # Default snapshot\nctx task snapshot\n\n# Named snapshot (recommended before big changes)\nctx task snapshot \"before-refactor\"\n

    This creates a file like .context/archive/tasks-before-refactor-2026-02-08-1430.md. If the refactor goes sideways, and you need to confirm what the task state looked like before you started, the snapshot is there.

    Snapshots are cheap: Take them before any change you might want to undo or review later.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-6-archive-when-tasksmd-gets-cluttered","level":3,"title":"Step 6: Archive When TASKS.md Gets Cluttered","text":"

    After several sessions, TASKS.md accumulates completed tasks that make it hard to see what is still pending.

    Use ctx task archive to move all [x] items to a timestamped archive file.

    Start with a dry run to preview what will be moved:

    ctx task archive --dry-run\n

    Then archive:

    ctx task archive\n

    Completed tasks move to .context/archive/tasks-2026-02-08.md. Phase headers are preserved in the archive for traceability. Pending tasks ([ ]) remain in TASKS.md.

    The /ctx-archive skill adds two safety checks before archiving: it verifies that completed tasks are genuinely cluttering the view and that nothing was marked [x] prematurely.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#letting-the-agent-drive","level":2,"title":"Letting the Agent Drive","text":"

    The steps above show the CLI commands for task management.

    In practice, most task management happens conversationally:

    An agent that has loaded the context files does not need you to type ctx task add. It tracks work naturally and offers the right operations.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#use-natural-language-instead-of-commands","level":3,"title":"Use Natural Language Instead of Commands","text":"

    These conversational prompts replace explicit commands during interactive sessions:

    Instead of typing... Just say... ctx task add \"Write tests for X\" --session-id ID --branch BR --commit HASH \"We should add tests for this: track that?\" /ctx-next \"What should we work on?\" ctx task complete \"rate limiting\" \"The rate limiter is done, what's next?\" ctx task archive \"TASKS.md is getting long, can you clean it up?\" ctx task add ... --session-id ID --branch BR --commit HASH && ctx task add ... \"Add follow-ups for what we just built.\"

    The agent translates these into the right ctx operations behind the scenes.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-creates-tasks-proactively","level":3,"title":"The Agent Creates Tasks Proactively","text":"

    After completing a piece of work, the agent does not wait for you to dictate follow-up items. It identifies them from the work context and offers:

    **Agent**: \"I've finished implementing the rate limiter middleware. I noticed\nthree follow-up items:\n\n1. Write integration tests for the rate limiter (the unit tests pass but\n   there's no end-to-end coverage)\n2. Add rate limit headers to API responses (X-RateLimit-Remaining, etc.)\n3. Document the rate limit configuration in the ops runbook\n\nWant me to add these as tasks?\"\n

    This is how an agent behaves once it has read the Agent Playbook.

    The playbook's \"Work, Reflect, Persist\" cycle means the agent asks itself after every milestone: Did I just complete a task? Did new work emerge?

    When the answer is yes, it acts.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#asking-the-agent-what-to-capture","level":3,"title":"Asking the Agent What to Capture","text":"

    You don't always know what's worth persisting.

    Instead of reviewing the session yourself and typing specific commands, ask the agent to do the thinking:

    You say What happens \"What tasks came out of this session?\" Agent reviews the work, identifies follow-ups, and offers to add them \"Are there any tasks, learnings, or decisions worth adding?\" Agent scans the full session for all three types and proposes a batch \"What should we remember from today?\" Agent surfaces everything persist-worthy: not just tasks \"Anything we should track before we move on?\" Agent checks for loose ends specific to the work just completed

    This is especially powerful after long or exploratory sessions where multiple threads were touched.

    The agent has the full conversation in context and can spot follow-ups you might miss:

    You:   \"Are there any tasks, learnings, or decisions worth adding\n        from this session?\"\n\nAgent: \"Yes. Here's what I'd suggest:\n\n  Tasks:\n  - Add retry logic to the webhook delivery (we discussed it when\n    the timeout test failed but never tracked it)\n  - Update the API docs to reflect the new rate limit headers\n\n  Learnings:\n  - net/http's default client has no timeout: we burned 15 minutes\n    on that before adding an explicit 30s timeout\n\n  Decisions:\n  - We chose token-bucket over sliding-window for rate limiting\n    because it handles bursts better and the library is simpler\n\n  Want me to add all of these?\"\n\nYou:   \"All of them.\"\n

    The agent fills in structured fields from what it observed during the session. You get the same quality entries as if you typed verbose commands, without needing to remember what happened or how to format it.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-completes-tasks-and-moves-on","level":3,"title":"The Agent Completes Tasks and Moves On","text":"

    When you confirm that work is finished, or when the agent can see that it is, it marks the task done and suggests the next step:

    You: \n\"The cooldown logic is working now.\"\n\nAgent: \n\"Marked 'Implement ctx agent cooldown' as done. Your highest-\npriority remaining task is 'Add --section flag to ctx task add'.\n\nIt's the last item in Phase 1 and a quick win. Want to start on that?\"\n

    The agent chains completion into next-task selection.

    One sentence from you triggers two operations.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#end-of-session-capture","level":3,"title":"End-of-Session Capture","text":"

    At the end of a session, the agent reviews what happened and catches loose ends:

    You: \n\"I think we're good for today.\"\n\nAgent: \n\"Before we wrap up: during the middleware work you mentioned\nwanting to add metrics for rate limit hits. That never made it into TASKS.md.\n\nWant me to add it as a task so it's there next session?\"\n

    This is the proactive persistence model at work.

    The agent monitors the conversation for mentioned-but-not-tracked work and surfaces it before context is lost.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#a-conversational-session-example","level":3,"title":"A Conversational Session Example","text":"

    Here is what task management looks like in a real session when the agent is driving:

    You:   What's left to do?\nAgent: You have 5 pending tasks across 2 phases. The highest priority is\n       \"Implement ctx agent cooldown\": it's marked in-progress from\n       yesterday and half-built. Want to pick that up?\n\nYou:   Yeah, let's finish it.\n       [... 20 minutes of implementation work ...]\n\nYou:   That's working. Tests pass.\nAgent: Nice. I'll mark \"Implement ctx agent cooldown\" as done.\n       I noticed two things from the implementation:\n       1. The tombstone file cleanup should happen on a timer, not just\n          at startup. Want me to add that as a task?\n       2. We learned that file locks don't work reliably on NFS. I'll\n          add that to LEARNINGS.md.\n\nYou:   Yes to both. What's next?\nAgent: Added the cleanup task as high priority. Next up is \"Add --section\n       flag to ctx task add\": last item in Phase 1. It should be\n       straightforward since the flag parsing pattern already exists\n       in the codebase. Ready?\n

    It's All Conversational

    Notice what did not happen: The user never typed a ctx command.

    The agent handled task completion, follow-up creation, learning capture, and next-task selection: all from natural conversation.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"
    # Add a task\nctx task add \"Implement rate limiting for API endpoints\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to a specific phase\nctx task add \"Write integration tests for rate limiter\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# See what to work on\n# (from AI assistant) /ctx-next\n\n# Mark done by text\nctx task complete \"rate limiting\"\n\n# Mark done by number\nctx task complete 5\n\n# Snapshot before a risky refactor\nctx task snapshot \"before-middleware-rewrite\"\n\n# Archive completed tasks when the list gets long\nctx task archive --dry-run     # preview first\nctx task archive               # then archive\n
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tips","level":2,"title":"Tips","text":"
    • Start tasks with a verb: \"Add,\" \"Fix,\" \"Implement,\" \"Investigate\": not just a topic like \"Authentication.\"
    • Include the why in the task description. Future sessions lack the context of why you added the task. \"Add rate limiting\" is worse than \"Add rate limiting to prevent abuse on the public API after the load test showed 10x traffic spikes.\"
    • Use #in-progress sparingly. Only one or two tasks should carry this tag at a time. If everything is in-progress, nothing is.
    • Snapshot before, not after. The point of a snapshot is to capture the state before a change, not to celebrate what you just finished.
    • Archive regularly. Once completed tasks outnumber pending ones, it is time to archive. A clean TASKS.md helps both you and your AI assistant focus.
    • Never delete tasks. Mark them [x] (completed) or [-] (skipped with a reason). Deletion breaks the audit trail.
    • Trust the agent's task instincts. When the agent suggests follow-up items after completing work, it is drawing on the full context of what just happened.
    • Conversational prompts beat commands in interactive sessions. Saying \"what should we work on?\" is faster and more natural than running /ctx-next. Save explicit commands for scripts, CI, and unattended runs.
    • Let the agent chain operations. A single statement like \"that's done, what's next?\" can trigger completion, follow-up identification, and next-task selection in one flow.
    • Review proactive task suggestions before moving on. The best follow-ups come from items spotted in-context right after the work completes.
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#next-up","level":2,"title":"Next Up","text":"

    Using the Scratchpad →: Store short-lived sensitive notes in an encrypted scratchpad.

    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#see-also","level":2,"title":"See Also","text":"
    • The Complete Session: full session lifecycle including task management in context
    • Persisting Decisions, Learnings, and Conventions: capturing the \"why\" behind your work
    • Detecting and Fixing Drift: keeping TASKS.md accurate over time
    • CLI Reference: full documentation for ctx add, ctx task complete, ctx task
    • Context Files: TASKS.md: format and conventions for TASKS.md
    ","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/triggers/","level":1,"title":"Authoring Lifecycle Triggers","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#authoring-lifecycle-triggers","level":1,"title":"Authoring Lifecycle Triggers","text":"

    Triggers are executable shell scripts that fire at specific events during an AI session. They're how you express \"when the AI saves a file, also do X\" or \"before the AI edits this path, check Y first.\" This recipe walks through writing your first trigger, testing it, and enabling it safely.

    Triggers Execute Arbitrary Code

    A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks:

    • Only enable scripts you have read and understand.
    • Never enable a trigger you downloaded from the internet without reviewing every line.
    • Avoid shelling out to user-controlled values (jq -r output, path field, tool field) without quoting.
    • A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.

    The generated trigger template starts disabled (no executable bit) so you cannot accidentally run an unreviewed script. Enable it explicitly with ctx trigger enable.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#scenario","level":2,"title":"Scenario","text":"

    You want a pre-tool-use trigger that blocks the AI from editing anything in internal/crypto/ without explicit confirmation. Cryptographic code is sensitive, and accidental edits have caused outages before, and you want a hard gate.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-1-scaffold-the-script","level":2,"title":"Step 1: Scaffold the Script","text":"
    ctx trigger add pre-tool-use protect-crypto\n

    That creates .context/hooks/pre-tool-use/protect-crypto.sh with a template:

    #!/usr/bin/env bash\nset -euo pipefail\n\n# Read the JSON event from stdin.\npayload=$(cat)\n\n# Parse fields with jq.\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Your logic here.\n\n# Return a JSON result. action can be \"allow\", \"block\", or absent.\necho '{\"action\": \"allow\"}'\n

    Note: the directory is .context/hooks/pre-tool-use/; the on-disk layout still uses hooks/ even though the command is ctx trigger. If you ls .context/hooks/, that's where your triggers live.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-2-write-the-logic","level":2,"title":"Step 2: Write the Logic","text":"

    Open the file and replace the template body:

    #!/usr/bin/env bash\nset -euo pipefail\n\npayload=$(cat)\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Only gate write-family tools.\ncase \"$tool\" in\n  write_file|edit_file|apply_patch) ;;\n  *)\n    echo '{\"action\": \"allow\"}'\n    exit 0\n    ;;\nesac\n\n# Block any path under internal/crypto/.\ncase \"$path\" in\n  internal/crypto/*|*/internal/crypto/*)\n    jq -n --arg p \"$path\" '{\n      action: \"block\",\n      message: (\"Edits to \" + $p + \" require manual review. \" +\n                \"See CONVENTIONS.md for the crypto-change process.\")\n    }'\n    exit 0\n    ;;\nesac\n\necho '{\"action\": \"allow\"}'\n

    A few things to note:

    • set -euo pipefail: any unhandled error aborts the script. Critical for a security-relevant trigger.
    • Quote everything from jq: the path field comes from the AI tool; treat it as untrusted input.
    • Explicit allow case: the default is allow. An empty or missing response is a risky default.
    • Use jq -n --arg for output construction, as it is safer than string concatenation when the message may contain special characters.
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-3-test-with-a-mock-payload","level":2,"title":"Step 3: Test with a Mock Payload","text":"

    Before enabling the trigger, test it with a realistic mock input using ctx trigger test. This runs the script against a synthetic JSON payload without actually firing any AI tool.

    # Test the \"should block\" case\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\n

    Expected: the trigger returns {\"action\":\"block\", \"message\": \"...\"}.

    # Test the \"should allow\" case\nctx trigger test pre-tool-use --tool write_file --path internal/memory/mirror.go\n

    Expected: the trigger returns {\"action\":\"allow\"}.

    # Test that non-write tools pass through\nctx trigger test pre-tool-use --tool read_file --path internal/crypto/aes.go\n

    Expected: {\"action\":\"allow\"} because the case statement only gates write-family tools.

    If any of these cases misbehave, fix the trigger before enabling it. The trigger is disabled at this point, so misbehavior doesn't affect real AI sessions.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-4-enable-it","level":2,"title":"Step 4: Enable It","text":"

    Once the test cases pass, enable the trigger:

    ctx trigger enable protect-crypto\n

    That sets the executable bit. Next time the AI starts a pre-tool-use event, the trigger will fire.

    Verify it's enabled:

    ctx trigger list\n

    Should show protect-crypto under pre-tool-use with an enabled indicator.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-5-iterate-safely","level":2,"title":"Step 5: Iterate Safely","text":"

    If you discover a bug after enabling, disable first, fix second:

    ctx trigger disable protect-crypto\n# ...edit the script...\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\nctx trigger enable protect-crypto\n

    Disabling simply clears the executable bit; the script stays on disk, and ctx trigger enable re-enables it without rewriting anything.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#patterns-worth-copying","level":2,"title":"Patterns Worth Copying","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#logging-not-blocking","level":3,"title":"Logging, Not Blocking","text":"

    For auditing or analytics, return {\"action\":\"allow\"} always and append to a log as a side effect:

    #!/usr/bin/env bash\nset -euo pipefail\npayload=$(cat)\necho \"$payload\" >> .context/logs/tool-use.jsonl\necho '{\"action\":\"allow\"}'\n
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#context-injection-at-session-start","level":3,"title":"Context Injection at Session Start","text":"

    A session-start trigger can prepend text to the agent's initial prompt by emitting {\"action\":\"inject\", \"content\": \"...\"} . This is useful for injecting daily standup notes, open PRs, or rotating TODOs without storing them in a steering file.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#chaining-triggers-of-the-same-type","level":3,"title":"Chaining Triggers of the Same Type","text":"

    Multiple scripts in the same type directory all run. If any returns action: block, the block wins. Keep individual triggers single-purpose and rely on composition.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    Forgetting the shebang. Without #!/usr/bin/env bash, the trigger won't execute even with the executable bit set.

    Not quoting $path. If you use $path in a command substitution or a case glob without quoting, a file name with spaces or metacharacters will break the trigger in surprising ways.

    Enabling before testing. ctx trigger enable makes the script live immediately. Always ctx trigger test first.

    Outputting non-JSON. The trigger's stdout must be valid JSON or ctx's trigger runner will log a parse error. Use jq -n to construct output rather than hand-writing JSON strings.

    Mixing hook and trigger vocabulary. The command is ctx trigger but the on-disk directory is .context/hooks/. The feature was renamed; the directory name lags behind. Don't let this confuse you; they refer to the same thing.

    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#see-also","level":2,"title":"See Also","text":"
    • ctx trigger reference: full command, flag, and event-type reference.
    • ctx steering: persistent rules, not scripts. Use steering when the thing you want is \"tell the AI to always do X\" rather than \"run a script when Y happens.\"
    • Writing steering files: the rule-based equivalent of this recipe.
    ","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/troubleshooting/","level":1,"title":"Troubleshooting","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-problem","level":2,"title":"The Problem","text":"

    Something isn't working: a hook isn't firing, nudges are too noisy, context seems stale, or the agent isn't following instructions. The information to diagnose it exists (across status, drift, event logs, hook config, and session history), but assembling it manually is tedious.

    How do you figure out what's wrong and fix it?

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tldr","level":2,"title":"TL;DR","text":"
    ctx doctor                   # structural health check\nctx hook event --last 20  # recent hook activity\n# or ask: \"something seems off, can you diagnose?\"\n
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx doctor CLI command Structural health report ctx doctor --json CLI command Machine-readable health report ctx hook event CLI command Query local event log /ctx-doctor Skill Agent-driven diagnosis with analysis","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#quick-check-ctx-doctor","level":3,"title":"Quick Check: ctx doctor","text":"

    Run ctx doctor for an instant structural health report. It checks context initialization, required files, drift, hook configuration, event logging, webhooks, reminders, task completion ratio, and context token size: all in one pass:

    ctx doctor\n
    ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n

    Warnings are non-critical but worth fixing. Errors need attention. Informational notes (○) flag optional features that aren't enabled.

    For scripting:

    ctx doctor --json | jq '.warnings'\n
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#deep-dive-ctx-doctor","level":3,"title":"Deep Dive: /ctx-doctor","text":"

    When you need the agent to reason about what's wrong, use the skill. Ask naturally or invoke directly:

    Why didn't my hook fire?\nSomething seems off, can you diagnose?\n/ctx-doctor\n

    The agent follows a triage sequence:

    1. Baseline: runs ctx doctor --json for structural health
    2. Events: runs ctx hook event --json --last 100 (if event logging enabled)
    3. Correlate: connects findings across both sources
    4. Present: structured findings with evidence
    5. Suggest: actionable next steps (but doesn't auto-fix)

    The skill degrades gracefully: without event logging enabled, it still runs structural checks and notes what you'd gain by enabling it.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#raw-event-inspection","level":3,"title":"Raw Event Inspection","text":"

    For power users: ctx hook event with filters gives direct access to the event log.

    # Last 50 events (default)\nctx hook event\n\n# Events from a specific session\nctx hook event --session eb1dc9cd-0163-4853-89d0-785fbfaae3a6\n\n# Only QA reminder events\nctx hook event --hook qa-reminder\n\n# Raw JSONL for jq processing\nctx hook event --json | jq '.message'\n\n# Include rotated (older) events\nctx hook event --all --last 100\n

    Filters use AND logic: --hook qa-reminder --session abc123 returns only QA reminder events from that specific session.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#common-problems","level":2,"title":"Common Problems","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#no-context-directory-specified-for-this-project","level":3,"title":"\"No context directory specified for this project\"","text":"

    Symptoms: Any ctx command fails with Error: no context directory specified for this project (possibly with a likely-candidate hint or a candidate list depending on what's visible from your CWD).

    Cause: ctx does not search the filesystem for a .context/ directory. You have to declare which one to use before running day-to-day commands.

    Fix: bind CTX_DIR for the current shell:

    eval \"$(ctx activate)\"\n

    See Activating a Context Directory for the full recipe (one-shot CTX_DIR=... inline form, CI patterns, direnv setup).

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#ctx-not-initialized","level":3,"title":"\"ctx: Not Initialized\"","text":"

    Symptoms: After declaring CTX_DIR, the command fails with ctx: not initialized - run \"ctx init\" first.

    Cause: The declared directory exists but hasn't been initialized with template files.

    Fix:

    ctx init          # create .context/ with template files\nctx init --minimal  # or just the essentials (CONSTITUTION, TASKS, DECISIONS)\n

    Commands that work without CTX_DIR or initialization: ctx init, ctx activate, ctx deactivate, ctx setup, ctx doctor, ctx guide, ctx why, ctx config switch/status, ctx hub *, and help-only grouping commands.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-cli-and-my-claude-code-session-disagree-on-the-project","level":3,"title":"\"My CLI and My Claude Code Session Disagree on the Project\"","text":"

    Symptoms: A !-pragma or interactive ctx call writes to the wrong .context/; or you ran ctx remind add in shell A and the reminder shows up in project B's notifications.

    Cause: CTX_DIR is sourced from three different surfaces, and they can drift apart:

    Surface Source of CTX_DIR Bound when Claude Code hooks ${CLAUDE_PROJECT_DIR}/.context (injected) Every hook line; the project Claude is in !-pragma in chat / interactive shell Whatever the parent shell exported When you ran eval \"$(ctx activate)\" New shell tab opened mid-session Whatever your shellrc exports Login

    When these drift, the per-prompt check-anchor-drift hook fires a verbatim warning naming both values. To fix: re-run eval \"$(ctx activate)\" from inside the project the Claude Code session is editing, or close the shell tab and reopen it from the right working directory.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-hook-isnt-firing","level":3,"title":"\"My Hook Isn't Firing\"","text":"

    Symptoms: No nudges appearing, webhook silent, event log shows no entries for the expected hook.

    Diagnosis:

    # 1. Check if ctx is installed and on PATH\nwhich ctx && ctx --version\n\n# 2. Check if the hook is registered\ngrep \"check-persistence\" ~/.claude/plugins/ctx/hooks.json\n\n# 3. Run the hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-persistence\n\n# 4. Check event log for the hook (if enabled)\nctx hook event --hook check-persistence\n

    Common causes:

    • Plugin is not installed: run ctx init --claude to reinstall
    • PATH issue: the hook invokes ctx from PATH; ensure it resolves
    • Throttle active: most hooks fire once per day: check .context/state/ for daily marker files
    • Hook silenced: a custom message override may be an empty file: check ctx hook message list for overrides
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#too-many-nudges","level":3,"title":"\"Too Many Nudges\"","text":"

    Symptoms: The agent is overwhelmed with hook output. Context checkpoints, persistence reminders, and QA gates fire constantly.

    Diagnosis:

    # Check how often hooks fired recently\nctx hook event --last 50\n\n# Count fires per hook\nctx hook event --json | jq -r '.detail.hook // \"unknown\"' \\\n  | sort | uniq -c | sort -rn\n

    Common causes:

    • QA reminder is noisy by design: it fires on every Edit call with no throttle. This is intentional. If it's too much, silence it with an empty override: ctx hook message edit qa-reminder gate, then empty the file
    • Long session: context checkpoint fires with increasing frequency after prompt 15. This is the system telling you the session is getting long: consider wrapping up
    • Short throttle window: if you deleted marker files in .context/state/, daily-throttled hooks will re-fire
    • Outdated Claude Code plugin: Update the plugin using Claude Code → /plugin → \"Marketplace\"
    • ctx version mismatch: Build (or download) and install the latest ctx vesion.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#context-seems-stale","level":3,"title":"\"Context Seems Stale\"","text":"

    Symptoms: The agent references outdated information, paths that don't exist, or decisions that were reversed.

    Diagnosis:

    # Structural drift check\nctx drift\n\n# Full doctor check (includes drift + more)\nctx doctor\n\n# Check when context files were last modified\nctx status --verbose\n

    Common causes:

    • Drift accumulated: stale path references in ARCHITECTURE.md or CONVENTIONS.md. Fix with ctx drift --fix or ask the agent to clean up.
    • Task backlog: too many completed tasks diluting active context. Archive with ctx task archive or ctx compact --archive.
    • Large context files: LEARNINGS.md with 40+ entries competes for attention. Consolidate with /ctx-consolidate.
    • Missing session ceremonies: if /ctx-remember and /ctx-wrap-up aren't being used, context doesn't get refreshed. See Session Ceremonies.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-agent-isnt-following-instructions","level":3,"title":"\"The Agent Isn't Following Instructions\"","text":"

    Symptoms: The agent ignores conventions, forgets decisions, or acts contrary to CONSTITUTION.md rules.

    Diagnosis:

    # Check context token size: Is it too large for the model?\nctx doctor --json | jq '.results[] | select(.name == \"context_size\")'\n\n# Check if context is actually being loaded\nctx hook event --hook context-load-gate\n

    Common causes:

    • Context too large: if total tokens exceed the model's effective attention, instructions get diluted. Check ctx doctor for the size check. Compact with ctx compact --archive.
    • Context not loading: if context-load-gate hasn't fired, the agent may not have received context. Verify the hook is registered.
    • Conflicting instructions: CONVENTIONS.md says one thing, AGENT_PLAYBOOK.md says another. Review both files for consistency.
    • Agent drift: the agent's behavior diverges from instructions over long sessions. This is normal. Use /ctx-reflect to re-anchor, or start a new session.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#prerequisites","level":2,"title":"Prerequisites","text":"
    • Event logging (optional but recommended): event_log: true in .ctxrc
    • ctx initialized: ctx init

    Event logging is not required for ctx doctor or /ctx-doctor to work. Both degrade gracefully: structural checks run regardless, and the skill notes when event data is unavailable.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tips","level":2,"title":"Tips","text":"
    • Start with ctx doctor: It's the fastest way to get a comprehensive health picture. Save event log inspection for when you need to understand when and how often something happened.
    • Enable event logging early: The log is opt-in and low-cost (~250 bytes per event, 1MB rotation cap). Enable it before you need it: Diagnosing a problem without historical data is much harder.
    • Use the skill for correlation: ctx doctor tells you what is wrong. /ctx-doctor tells you why by correlating structural findings with event patterns. The agent can spot connections that individual commands miss.
    • Event log is gitignored: It's machine-local diagnostic data, not project context. Different machines produce different event streams.
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#next-up","level":2,"title":"Next Up","text":"

    Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.

    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#see-also","level":2,"title":"See Also","text":"
    • Auditing System Hooks: the complete hook catalog and webhook-based audit trails
    • Detecting and Fixing Drift: structural and semantic drift detection and repair
    • Webhook Notifications: push notifications for hook activity
    • ctx doctor CLI: full command reference
    • ctx hook event CLI: event log query reference
    • /ctx-doctor skill: agent-driven diagnosis
    ","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/typical-kb-session/","level":1,"title":"Typical KB Session","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#the-problem","level":2,"title":"The Problem","text":"

    You set the editorial pipeline up (Build a Knowledge Base). Now you sit down for a real research session: a transcript to ingest, a question to answer against existing evidence, a finding to capture for later. What's the actual flow?

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#tldr","level":2,"title":"TL;DR","text":"
    /ctx-remember                                    # session-start recall\n/ctx-kb-ingest ./inputs/transcript.md \"topic\"    # editorial pass\n/ctx-kb-ask \"does the kb say X?\"                 # grounded Q&A\n/ctx-kb-note \"follow-up: chase the v1.1 link\"    # park a finding\n/ctx-wrap-up                                     # ceremony → /ctx-handover\n
    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose /ctx-remember Skill Session-start recall (folds KB state when present) /ctx-kb-ingest Skill Mode-aware editorial pass /ctx-kb-ask Skill Q&A grounded in the kb /ctx-kb-note Skill Park a finding for the next ingest /ctx-wrap-up Skill End-of-session ceremony; delegates to the handover step /ctx-handover Skill Writes the per-session handover; called by /ctx-wrap-up","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-1-session-start-recall","level":2,"title":"Step 1: Session Start (Recall)","text":"
    /ctx-remember\n

    /ctx-remember reads the latest handover under .context/handovers/ (timestamped <TS>-<slug>.md so concurrent agent runs never overwrite); its ## Summary and ## Next session are the authoritative recall surface. The five canonical files (TASKS, DECISIONS, etc.) are read as usual.

    When .context/kb/ exists, /ctx-remember additionally folds editorial state into the readback: any closeouts whose generated-at postdates the handover are read for their ## What changed sections (these are unfolded passes the last handover did not yet consume).

    SESSION_LOG.md is not read at session start; it is mid-flight working memory, not a recall surface.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-2-ingest-the-sources-you-brought","level":2,"title":"Step 2: Ingest the Sources You Brought","text":"
    /ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n

    The skill declares its mode up front (most often topic-page), resolves sources, scans the source-coverage ledger for adjacent incomplete topics, and synthesizes prose into the topic page section by section. Every cited claim mints an EV-### row in evidence-index.md with the source short-name + locator + optional sha: pin for in-repo files.

    The pass ends with a circuit-breaker check (file exists, cites ≥ 1 EV-###, site builds clean, cold-reader rubric at pass) and writes a closeout.

    If the skill reports topic-page: deferred instead of produced, look at the closeout's Next pass hint. It names the exact resumption invocation.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-3-ask-grounded-questions","level":2,"title":"Step 3: Ask Grounded Questions","text":"
    /ctx-kb-ask \"does the kb say hooks block until they exit?\"\n

    /ctx-kb-ask reads the kb's prose and answers with EV-### citations. If the kb cannot answer, it opens a Q-### row in outstanding-questions.md and reports the gap rather than inventing.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-4-park-findings-for-later","level":2,"title":"Step 4: Park Findings for Later","text":"
    /ctx-kb-note \"check whether SIGTERM behavior changed in v1.2\"\n

    /ctx-kb-note appends one-liners to .context/ingest/findings.md, a lightweight surface for parking ideas that don't earn a full ingest pass right now. The next /ctx-kb-ingest can choose to absorb them.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-5-wrap-up","level":2,"title":"Step 5: Wrap Up","text":"
    /ctx-wrap-up \"Cursor Hooks: lifecycle deep dive\"\n

    /ctx-wrap-up runs the standard capture checklist (learnings, decisions, conventions, tasks) and delegates to /ctx-handover as its final step. In a KB session it additionally:

    • Surfaces pending closeouts under .context/ingest/closeouts/.
    • Counts open rows in outstanding-questions.md.

    The handover artifact lands at .context/handovers/<TS>-<slug>.md (timestamped so concurrent agent runs never overwrite). The handover folds postdated closeouts into a ## Folded closeouts section and archives them under .context/archive/closeouts/. Editorial work that was incomplete at wrap-up (open Q-### rows, topic-page: deferred passes) is surfaced as recall on the next session start.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#common-shapes","level":2,"title":"Common Shapes","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#multiple-topics-in-one-session","level":3,"title":"Multiple Topics in One Session","text":"

    Run /ctx-kb-ingest once per topic. Each pass writes its own closeout; the handover folds all of them at the end.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#mid-session-checkpoint","level":3,"title":"Mid-Session Checkpoint","text":"
    ctx handover write \"Mid-day checkpoint\" \\\n  --summary \"...\" --next \"...\" --no-fold\n

    --no-fold writes the handover without consuming closeouts, useful when you want a recall anchor mid-session without ending the editorial chunking.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#aborted-session","level":3,"title":"Aborted Session","text":"

    If you close the laptop after an ingest pass but before /ctx-wrap-up, the closeouts stay in place. The next session's /ctx-remember reads them as unfolded postdated closeouts; the next wrap-up's handover step folds them normally. See Recover an Aborted Session for the failure-mode detail.

    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#reference","level":2,"title":"Reference","text":"
    • Recipe: Build a Knowledge Base
    • Recipe: Recover an Aborted Session
    • Skill: /ctx-kb-ingest
    • Skill: /ctx-handover
    • Editorial constitution: .context/ingest/KB-RULES.md
    ","path":["Typical KB Session"],"tags":[]},{"location":"recipes/webhook-notifications/","level":1,"title":"Webhook Notifications","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-problem","level":2,"title":"The Problem","text":"

    Your agent runs autonomously (loops, implements, releases) while you are away from the terminal. You have no way to know when it finishes, hits a limit, or when a hook fires a nudge.

    How do you get notified about agent activity without watching the terminal?

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tldr","level":2,"title":"TL;DR","text":"
    ctx hook notify setup  # configure webhook URL (encrypted)\nctx hook notify test   # verify delivery\n# Hooks auto-notify on: session-end, loop-iteration, resource-danger\n
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose ctx hook notify setup CLI command Configure and encrypt webhook URL ctx hook notify test CLI command Send a test notification ctx hook notify --event <name> \"msg\" CLI command Send a notification from scripts/skills .ctxrc notify.events Configuration Filter which events reach your webhook","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-1-get-a-webhook-url","level":3,"title":"Step 1: Get a Webhook URL","text":"

    Any service that accepts HTTP POST with JSON works. Common options:

    Service How to get a URL IFTTT Create an applet with the \"Webhooks\" trigger Slack Create an Incoming Webhook Discord Channel Settings > Integrations > Webhooks ntfy.sh Use https://ntfy.sh/your-topic (no signup) Pushover Use API endpoint with your user key

    The URL contains auth tokens. ctx encrypts it; it never appears in plaintext in your repo.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-2-configure-the-webhook","level":3,"title":"Step 2: Configure the Webhook","text":"
    ctx hook notify setup\n# Enter webhook URL: https://maker.ifttt.com/trigger/ctx/json/with/key/YOUR_KEY\n# Webhook configured: https://maker.ifttt.com/***\n# Encrypted at: .context/.notify.enc\n

    This encrypts the URL with AES-256-GCM using the same key as the scratchpad (~/.ctx/.ctx.key). The encrypted file (.context/.notify.enc) is safe to commit. The key lives outside the project and is never committed.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-3-test-it","level":3,"title":"Step 3: Test It","text":"
    ctx hook notify test\n# Webhook responded: HTTP 200 OK\n

    If you see No webhook configured, run ctx hook notify setup first.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-4-configure-events","level":3,"title":"Step 4: Configure Events","text":"

    Notifications are opt-in: no events are sent unless you configure an event list in .ctxrc:

    # .ctxrc\nnotify:\n  events:\n    - loop       # loop completion or max-iteration hit\n    - nudge      # VERBATIM relay hooks (context checkpoint, persistence, etc.)\n    - relay      # all hook output (verbose, for debugging)\n    - heartbeat  # every-prompt session-alive signal with metadata\n

    Only listed events fire. Omitting an event silently drops it.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-5-use-in-your-own-skills","level":3,"title":"Step 5: Use in Your Own Skills","text":"

    Add ctx hook notify calls to any skill or script:

    # In a release skill\nctx hook notify --event release \"v1.2.0 released successfully\" 2>/dev/null || true\n\n# In a backup script\nctx hook notify --event backup \"Nightly backup completed\" 2>/dev/null || true\n

    The 2>/dev/null || true suffix ensures the notification never breaks your script: If there's no webhook or the HTTP call fails, it's a silent noop.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-types","level":2,"title":"Event Types","text":"

    ctx fires these events automatically:

    Event Source When loop Loop script Loop completes or hits max iterations nudge System hooks VERBATIM relay nudge is emitted (context checkpoint, persistence, ceremonies, journal, resources, knowledge, version) relay System hooks Any hook output (VERBATIM relays, agent directives, block responses) heartbeat System hook Every prompt: session-alive signal with prompt count and context modification status test ctx hook notify test Manual test notification (custom) Your skills You wire ctx hook notify --event <name> in your own scripts

    nudge vs relay: The nudge event fires only for VERBATIM relay hooks (the ones the agent is instructed to show verbatim). The relay event fires for all hook output: VERBATIM relays, agent directives, and hard gates. Subscribe to relay for debugging (\"did the agent get the post-commit nudge?\"), nudge for user-facing assurance (\"was the checkpoint emitted?\").

    Webhooks as a Hook Audit Trail

    Subscribe to relay events and you get an external record of every hook that fires, independent of the agent.

    This lets you verify hooks are running and catch cases where the agent absorbs a nudge instead of surfacing it.

    See Auditing System Hooks for the full workflow.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#payload-format","level":2,"title":"Payload Format","text":"

    Every notification sends a JSON POST:

    {\n  \"event\": \"nudge\",\n  \"message\": \"check-context-size: Context window at 82%\",\n  \"detail\": {\n    \"hook\": \"check-context-size\",\n    \"variant\": \"window\",\n    \"variables\": {\"Percentage\": 82, \"TokenCount\": \"164k\"}\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n

    The detail field is a structured template reference containing the hook name, variant, and any template variables. This lets receivers filter by hook or variant without parsing rendered text. The field is omitted when no template reference applies (e.g. custom ctx hook notify calls).

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#heartbeat-payload","level":3,"title":"Heartbeat Payload","text":"

    The heartbeat event fires on every prompt with session metadata and token usage telemetry:

    {\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc123-...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-28T10:15:00Z\",\n  \"project\": \"ctx\"\n}\n

    The tokens, context_window, and usage_pct fields are included when token data is available from the session JSONL file. They are omitted when no usage data has been recorded yet (e.g. first prompt).

    Unlike other events, heartbeat fires every prompt (not throttled). Use it for observability dashboards or liveness monitoring of long-running sessions.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#security-model","level":2,"title":"Security Model","text":"Component Location Committed? Permissions Encryption key ~/.ctx/.ctx.key No (user-level) 0600 Encrypted URL .context/.notify.enc Yes (safe) 0600 Webhook URL Never on disk in plaintext N/A N/A

    The key is shared with the scratchpad. If you rotate the encryption key, re-run ctx hook notify setup to re-encrypt the webhook URL with the new key.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#key-rotation","level":2,"title":"Key Rotation","text":"

    ctx checks the age of the encryption key once per day. If it's older than 90 days (configurable via key_rotation_days), a VERBATIM nudge is emitted suggesting rotation.

    # .ctxrc\nkey_rotation_days: 30   # nudge sooner (default: 90)\n
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#worktrees","level":2,"title":"Worktrees","text":"

    The webhook URL is encrypted with the same encryption key (~/.ctx/.ctx.key). Because the key lives at the user level, it is shared across all worktrees on the same machine - notifications work in worktrees automatically.

    This means agents running in worktrees cannot send webhook alerts. For autonomous runs where worktree agents are opaque, monitor them from the terminal rather than relying on webhooks. Enrich journals and review results on the main branch after merging.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-log-the-local-complement","level":2,"title":"Event Log: The Local Complement","text":"

    Don't need a webhook but want diagnostic visibility? Enable event_log: true in .ctxrc. The event log writes the same payload as webhooks to a local JSONL file (.context/state/events.jsonl) that you can query without any external service:

    ctx hook event --last 20          # recent hook activity\nctx hook event --hook qa-reminder # filter by hook\n

    Webhooks and event logging are independent: you can use either, both, or neither. Webhooks give you push notifications and an external audit trail. The event log gives you local queryability and ctx doctor integration.

    See Troubleshooting for how they work together.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tips","level":2,"title":"Tips","text":"
    • Fire-and-forget: Notifications never block. HTTP errors are silently ignored. No retry, no response parsing.
    • No webhook = no cost: When no webhook is configured, ctx hook notify exits immediately. System hooks that call notify.Send() add zero overhead.
    • Multiple projects: Each project has its own .notify.enc. You can point different projects at different webhooks.
    • Event filter is per-project: Configure notify.events in each project's .ctxrc independently.
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#next-up","level":2,"title":"Next Up","text":"

    Auditing System Hooks →: Verify your hooks are running, audit what they do, and get alerted when they go silent.

    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#see-also","level":2,"title":"See Also","text":"
    • CLI Reference: ctx hook notify: full command reference
    • Configuration: .ctxrc settings including notify options
    • Running an Unattended AI Agent: how loops work and how notifications fit in
    • Hook Output Patterns: understanding VERBATIM relays, agent directives, and hard gates
    • Auditing System Hooks: using webhooks as an external audit trail for hook execution
    ","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/","level":1,"title":"When to Use a Team of Agents","text":"","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-problem","level":2,"title":"The Problem","text":"

    You have a task, and you are wondering: \"should I throw more agents at it?\"

    More agents can mean faster results, but they also mean coordination overhead, merge conflicts, divergent mental models, and wasted tokens re-reading context.

    The wrong setup costs more than it saves.

    This recipe is a decision framework: It helps you choose between a single agent, parallel worktrees, and a full agent team, and explains what ctx provides at each level.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tldr","level":2,"title":"TL;DR","text":"
    • Single agent for most work;
    • Parallel worktrees when tasks touch disjoint file sets;
    • Agent teams only when tasks need real-time coordination. When in doubt, start with one agent.
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-spectrum","level":2,"title":"The Spectrum","text":"

    There are three modes, ordered by complexity:

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#1-single-agent-default","level":3,"title":"1. Single Agent (Default)","text":"

    One agent, one session, one branch. This is correct for most work.

    Use this when:

    • The task has linear dependencies (step 2 needs step 1's output);
    • Changes touch overlapping files;
    • You need tight feedback loops (review each change before the next);
    • The task requires deep understanding of a single area;
    • Total effort is less than a few hours of agent time.

    ctx provides: Full .context/: tasks, decisions, learnings, conventions, all in one session.

    The agent builds a coherent mental model and persists it as it goes.

    Example tasks: Bug fixes, feature implementation, refactoring a module, writing documentation for one area, debugging.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#2-parallel-worktrees-independent-tracks","level":3,"title":"2. Parallel Worktrees (Independent Tracks)","text":"

    2-4 agents, each in a separate git worktree on its own branch, working on non-overlapping parts of the codebase.

    Use this when:

    • You have 5+ independent tasks in the backlog;
    • Tasks group cleanly by directory or package;
    • File overlap between groups is zero or near-zero;
    • Each track can be completed and merged independently;
    • You want parallelism without coordination complexity.

    ctx provides: Shared .context/ via git (each worktree sees the same tasks, decisions, conventions). /ctx-worktree skill for setup and teardown. TASKS.md as a lightweight work queue.

    Example tasks: Docs + new package + test coverage (three tracks that don't touch the same files). Parallel recipe writing. Independent module development.

    See: Parallel Agent Development with Git Worktrees

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#3-agent-team-coordinated-swarm","level":3,"title":"3. Agent Team (Coordinated Swarm)","text":"

    Multiple agents communicating via messages, sharing a task list, with a lead agent coordinating. Claude Code's team/swarm feature.

    Use this when:

    • Tasks have dependencies but can still partially overlap;
    • You need research and implementation happening simultaneously;
    • The work requires different roles (researcher, implementer, tester);
    • A lead agent needs to review and integrate others' work;
    • The task is large enough that coordination cost is justified.

    ctx provides: .context/ as shared state that all agents can read. Task tracking for work assignment. Decisions and learnings as team memory that survives individual agent turnover.

    Example tasks: Large refactor across modules where a lead reviews merges. Research and implementation where one agent explores options while another builds. Multi-file feature that needs integration testing after parallel implementation.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-decision-framework","level":2,"title":"The Decision Framework","text":"

    Ask these questions in order:

    Can one agent do this in a reasonable time?\n  YES → Single agent. Stop here.\n  NO  ↓\n\nCan the work be split into non-overlapping file sets?\n  YES → Parallel worktrees (2-4 tracks)\n  NO  ↓\n\nDo the subtasks need to communicate during execution?\n  YES → Agent team with lead coordination\n  NO  → Parallel worktrees with a merge step\n
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-file-overlap-test","level":3,"title":"The File Overlap Test","text":"

    This is the critical decision point. Before choosing multi-agent, list the files each subtask would touch. If two subtasks modify the same file, they belong in the same track (or the same single-agent session).

    You: \"I want to parallelize these tasks. Which files would each one touch?\"\n\nAgent: [reads `TASKS.md`, analyzes codebase]\n       \"Task A touches internal/config/ and internal/cli/initialize/\n        Task B touches docs/ and site/\n        Task C touches internal/config/ and internal/cli/status/\n\n        Tasks A and C overlap on internal/config/ # they should be\n        in the same track. Task B is independent.\"\n

    When in doubt, keep things in one track. A merge conflict in a critical file costs more time than the parallelism saves.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#when-teams-make-things-worse","level":2,"title":"When Teams Make Things Worse","text":"

    \"More agents\" is not always better. Watch for these patterns:

    Merge hell: If you are spending more time resolving conflicts than the parallel work saved, you split wrong: Re-group by file overlap.

    Context divergence: Each agent builds its own mental model. After 30 minutes of independent work, agent A might make assumptions that contradict agent B's approach. Shorter tracks with frequent merges reduce this.

    Coordination theater: A lead agent spending most of its time assigning tasks, checking status, and sending messages instead of doing work. If the task list is clear enough, worktrees with no communication are cheaper.

    Re-reading overhead: Every agent reads .context/ on startup. A team of 4 agents each reading 4000 tokens of context = 16000 tokens before anyone does any work. For small tasks, that overhead dominates.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#what-ctx-gives-you-at-each-level","level":2,"title":"What ctx Gives You at Each Level","text":"ctx Feature Single Agent Worktrees Team .context/ files Full access Shared via git Shared via filesystem TASKS.md Work queue Split by track Assigned by lead Decisions/Learnings Persisted in session Persisted per branch Persisted by any agent /ctx-next Picks next task Picks within track Lead assigns /ctx-worktree N/A Setup + teardown Optional /ctx-commit Normal commits Per-branch commits Per-agent commits","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#team-composition-recipes","level":2,"title":"Team Composition Recipes","text":"

    Four practical team compositions for common workflows.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#feature-development-3-agents","level":3,"title":"Feature Development (3 Agents)","text":"Role Responsibility Architect Writes spec in specs/, breaks work into TASKS.md phases Implementer Picks tasks from TASKS.md, writes code, marks [x] done Reviewer Runs tests, ctx drift, lint; files issues as new tasks

    Coordination: TASKS.md checkboxes. Architect writes tasks before implementer starts. Reviewer runs after each implementer commit.

    Anti-pattern: All three agents editing the same file simultaneously. Sequence the work so only one agent touches a file at a time.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#consolidation-sprint-3-4-agents","level":3,"title":"Consolidation Sprint (3-4 Agents)","text":"Role Responsibility Auditor Runs ctx drift, identifies stale paths and broken refs Code Fixer Updates source code to match context (or vice versa) Doc Writer Updates ARCHITECTURE.md, CONVENTIONS.md, and docs/ Test Fixer (Optional) Fixes tests broken by the fixer's changes

    Coordination: Auditor's ctx drift output is the shared work queue. Each agent claims a subset of issues by adding #in-progress labels.

    Anti-pattern: Fixer and doc writer both editing ARCHITECTURE.md. Assign file ownership explicitly.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#release-prep-2-agents","level":3,"title":"Release Prep (2 Agents)","text":"Role Responsibility Release Notes Generates changelog from commits, writes release notes Validation Runs full test suite, lint, build across platforms

    Coordination: Both read TASKS.md to identify what shipped. Release notes agent works from git log; validation agent works from make audit.

    Anti-pattern: Release notes agent running tests \"to verify.\" Each agent stays in its lane.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#documentation-sprint-3-agents","level":3,"title":"Documentation Sprint (3 Agents)","text":"Role Responsibility Content Writes new pages, expands existing docs Cross-linker Adds nav entries, cross-references, \"See Also\" sections Verifier Builds site, checks broken links, validates rendering

    Coordination: Content agent writes files first. Cross-linker updates zensical.toml and index pages after content lands. Verifier builds after each batch.

    Antipattern: Content and cross-linker both editing zensical.toml. Batch nav updates into the cross-linker's pass.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tips","level":2,"title":"Tips","text":"
    • Start with one agent: Only add parallelism when you have identified the bottleneck. \"This would go faster with more agents\" is usually wrong for tasks under 2 hours.
    • The 3-4 agent ceiling is real: Coordination overhead grows quadratically. 2 agents = 1 communication pair. 4 agents = 6 pairs. Beyond 4, you are managing agents more than doing work.
    • Worktrees > teams for most parallelism needs: If agents don't need to talk to each other during execution, worktrees give you parallelism with zero coordination overhead.
    • Use ctx as the shared brain: Whether it's one agent or four, the .context/ directory is the single source of truth. Decisions go in DECISIONS.md, not in chat messages between agents.
    • Merge early, merge often: Long-lived parallel branches diverge. Merge a track as soon as it's done rather than waiting for all tracks to finish.
    • TASKS.md conflicts are normal: Multiple agents completing different tasks will conflict on merge. The resolution is always additive: accept all [x] completions from both sides.
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#next-up","level":2,"title":"Next Up","text":"

    Parallel Agent Development with Git Worktrees →: Run multiple agents on independent task tracks using git worktrees.

    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#go-deeper","level":2,"title":"Go Deeper","text":"
    • CLI Reference: all commands and flags
    • Integrations: setup for Claude Code, Cursor, Aider
    • Session Journal: browse and search session history
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#see-also","level":2,"title":"See Also","text":"
    • Parallel Agent Development with Git Worktrees: the mechanical \"how\" for worktree-based parallelism
    • Running an Unattended AI Agent: serial autonomous loops: a different scaling strategy
    • Tracking Work Across Sessions: managing the task backlog that feeds into any multi-agent setup
    ","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"reference/","level":1,"title":"Reference","text":"

    Technical reference for ctx commands, skills, and internals.

    ","path":["Reference"],"tags":[]},{"location":"reference/#the-system-explains-itself","level":3,"title":"The System Explains Itself","text":"

    The 12 properties that must hold for any valid ctx implementation. Not features: constraints. The system's contract with its users and contributors.

    ","path":["Reference"],"tags":[]},{"location":"reference/#code-conventions","level":3,"title":"Code Conventions","text":"

    Common patterns and fixes for the AST compliance tests in internal/audit/. When a test fails, find the matching section.

    ","path":["Reference"],"tags":[]},{"location":"reference/#cli","level":3,"title":"CLI","text":"

    Every command, subcommand, and flag. Now a top-level section: see CLI Reference.

    ","path":["Reference"],"tags":[]},{"location":"reference/#skills","level":3,"title":"Skills","text":"

    The full skill catalog: what each skill does, when it triggers, and how skills interact with commands.

    ","path":["Reference"],"tags":[]},{"location":"reference/#tool-ecosystem","level":3,"title":"Tool Ecosystem","text":"

    How ctx compares to Cursor Rules, Aider conventions, CLAUDE.md, and other context approaches.

    ","path":["Reference"],"tags":[]},{"location":"reference/#session-journal","level":3,"title":"Session Journal","text":"

    Export, browse, and enrich your session history. Covers the journal site, Obsidian export, and the enrichment pipeline.

    ","path":["Reference"],"tags":[]},{"location":"reference/#scratchpad","level":3,"title":"Scratchpad","text":"

    Encrypted, git-tracked scratch space for short notes and sensitive values that travel with the project.

    ","path":["Reference"],"tags":[]},{"location":"reference/#version-history","level":3,"title":"Version History","text":"

    Changelog for every ctx release.

    ","path":["Reference"],"tags":[]},{"location":"reference/audit-conventions/","level":1,"title":"Code Conventions","text":"","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#code-conventions-common-patterns-and-fixes","level":1,"title":"Code Conventions: Common Patterns and Fixes","text":"

    This guide documents the code conventions enforced by internal/audit/ AST tests. Each section shows the violation pattern, the fix, and the rationale. When a test fails, find the matching section below.

    All tests skip _test.go files. The patterns apply only to production code under internal/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#variable-shadowing-bare-err-reuse","level":2,"title":"Variable Shadowing (Bare err := Reuse)","text":"

    Test: TestNoVariableShadowing

    When a function has multiple := assignments to err, each shadows the previous one. This makes it impossible to tell which error a later if err != nil is checking.

    Before:

    func Run(cmd *cobra.Command) error {\n    data, err := os.ReadFile(path) \n    if err != nil {\n        return err\n    }\n\n    result, err := json.Unmarshal(data)  // shadows first err\n    if err != nil {\n        return err\n    }\n\n    err = validate(result)  // shadows again\n    return err\n}\n

    After:

    func Run(cmd *cobra.Command) error {\n    data, readErr := os.ReadFile(path)\n    if readErr != nil {\n        return readErr\n    }\n\n    result, parseErr := json.Unmarshal(data)\n    if parseErr != nil {\n        return parseErr\n    }\n\n    validateErr := validate(result)\n    return validateErr\n}\n

    Rule: Use descriptive error names (readErr, writeErr, parseErr, walkErr, absErr, relErr) so each error site is independently identifiable.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#import-name-shadowing","level":2,"title":"Import Name Shadowing","text":"

    Test: TestNoImportNameShadowing

    When a local variable has the same name as an imported package, the import becomes inaccessible in that scope.

    Before:

    import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(session *entity.Session) {  // param shadows import\n    // session package is now unreachable here\n}\n

    After:

    import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(sess *entity.Session) {\n    // session package still accessible\n}\n

    Rule: Parameters, variables, and return values must not reuse imported package names. Common renames: session -> sess, token -> tok, config -> cfg, entry -> ent.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-strings","level":2,"title":"Magic Strings","text":"

    Test: TestNoMagicStrings

    String literals in function bodies are invisible to refactoring tools and cause silent breakage when the value changes in one place but not another.

    Before (string literals):

    func loadContext() {\n    data := filepath.Join(dir, \"TASKS.md\")\n    if strings.HasSuffix(name, \".yaml\") {\n        // ...\n    }\n}\n

    After:

    func loadContext() {\n    data := filepath.Join(dir, config.FilenameTask)\n    if strings.HasSuffix(name, config.ExtYAML) {\n        // ...\n    }\n}\n

    Before (format verbs, also caught):

    func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return fmt.Sprintf(\"%x\", h[:8])\n}\n

    After:

    func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return hex.EncodeToString(h[:cfgFmt.HashPrefixLen])\n}\n

    Before (URL schemes, also caught):

    if strings.HasPrefix(target, \"https://\") ||\n    strings.HasPrefix(target, \"http://\") {\n    return target\n}\n

    After:

    if strings.HasPrefix(target, cfgHTTP.PrefixHTTPS) ||\n    strings.HasPrefix(target, cfgHTTP.PrefixHTTP) {\n    return target\n}\n

    Exempt from this check:

    • Empty string \"\", single space \" \", indentation strings
    • Regex capture references ($1, ${name})
    • const and var definition sites (that's where constants live)
    • Struct tags
    • Import paths
    • Packages under internal/config/, internal/assets/tpl/

    Rule: If a string is used for comparison, path construction, or appears in 3+ files, it belongs in internal/config/ as a constant. Format strings belong in internal/config/ as named constants (e.g., cfgGit.FlagLastN, cfgTrace.RefFormat). User-facing prose belongs in internal/assets/ YAML files accessed via desc.Text().

    Common fix for fmt.Sprintf with format verbs:

    Pattern Fix fmt.Sprintf(\"%d\", n) strconv.Itoa(n) fmt.Sprintf(\"%d\", int64Val) strconv.FormatInt(int64Val, 10) fmt.Sprintf(\"%x\", bytes) hex.EncodeToString(bytes) fmt.Sprintf(\"%q\", s) strconv.Quote(s) fmt.Sscanf(s, \"%d\", &n) strconv.Atoi(s) fmt.Sprintf(\"-%d\", n) fmt.Sprintf(cfgGit.FlagLastN, n) \"https://\" cfgHTTP.PrefixHTTPS \"&lt;\" config constant in config/html/","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-printf-calls","level":2,"title":"Direct Printf Calls","text":"

    Test: TestNoPrintfCalls

    cmd.Printf and cmd.PrintErrf bypass the write-package formatting pipeline and scatter user-facing text across the codebase.

    Before:

    func Run(cmd *cobra.Command, args []string) {\n    cmd.Printf(\"Found %d tasks\\n\", count)\n}\n

    After:

    func Run(cmd *cobra.Command, args []string) {\n    write.TaskCount(cmd, count)\n}\n

    Rule: All formatted output goes through internal/write/ which uses cmd.Print/cmd.Println with pre-formatted strings from desc.Text().

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#raw-time-format-strings","level":2,"title":"Raw Time Format Strings","text":"

    Test: TestNoRawTimeFormats

    Inline time format strings (\"2006-01-02\", \"15:04:05\") drift when one call site is updated but others are missed.

    Before:

    func formatDate(t time.Time) string {\n    return t.Format(\"2006-01-02\")\n}\n

    After:

    func formatDate(t time.Time) string {\n    return t.Format(cfgTime.DateFormat)\n}\n

    Rule: All time format strings must use constants from internal/config/time/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-flag-registration","level":2,"title":"Direct Flag Registration","text":"

    Test: TestNoFlagBindOutsideFlagbind

    Direct cobra flag calls (.Flags().StringVar(), etc.) scatter flag wiring across dozens of cmd.go files. Centralizing through internal/flagbind/ gives one place to audit flag names, defaults, and description key lookups.

    Before:

    func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    c.Flags().StringVarP(&output, \"output\", \"o\", \"\",\n        \"output format\")\n    return c\n}\n

    After:

    func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    flagbind.StringFlagShort(c, &output, flag.Output,\n        flag.OutputShort, cmd.DescKeyOutput)\n    return c\n}\n

    Rule: All flag registration goes through internal/flagbind/. If the helper you need doesn't exist, add it to flagbind/flag.go before using it.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#todo-comments","level":2,"title":"TODO Comments","text":"

    Test: TestNoTODOComments

    TODO, FIXME, HACK, and XXX comments in production code are invisible to project tracking. They accumulate silently and never get addressed.

    Before:

    // TODO: handle pagination\nfunc listEntries() []Entry {\n

    After:

    Remove the comment and add a task to .context/TASKS.md:

    - [ ] Handle pagination in listEntries (internal/task/task.go)\n

    Rule: Deferred work lives in TASKS.md, not in source comments.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#dead-exports","level":2,"title":"Dead Exports","text":"

    Test: TestNoDeadExports

    Exported symbols with zero references outside their definition file are dead weight. They increase API surface, confuse contributors, and cost maintenance.

    Fix: Either delete the export (preferred) or demote it to unexported if it's still used within the file.

    If the symbol existed for historical reasons and might be needed again, move it to quarantine/deadcode/ with a .dead extension. This preserves the code in git without polluting the live codebase:

    quarantine/deadcode/internal/config/flag/flag.go.dead\n

    Each .dead file includes a header:

    // Dead exports quarantined from internal/config/flag/flag.go\n// Quarantined: 2026-04-02\n// Restore from git history if needed.\n

    Rule: If a test-only allowlist entry is needed (the export exists only for test use), add the fully qualified symbol to testOnlyExports in dead_exports_test.go. Keep this list small; prefer eliminating the export.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#core-package-structure","level":2,"title":"Core Package Structure","text":"

    Test: TestCoreStructure

    core/ directories under internal/cli/ must contain only doc.go and test files at the top level. All domain logic lives in subpackages. This prevents core/ from becoming a god package.

    Before:

    internal/cli/dep/core/\n    go.go           # violation: logic at core/ level\n    python.go       # violation\n    node.go         # violation\n    types.go        # violation\n

    After:

    internal/cli/dep/core/\n    doc.go          # package doc only\n    golang/\n        golang.go\n        golang_test.go\n        doc.go\n    python/\n        python.go\n        python_test.go\n        doc.go\n    node/\n        node.go\n        node_test.go\n        doc.go\n

    Rule: Extract each logical unit into its own subpackage under core/. Each subpackage gets a doc.go. The subpackage name should match the domain concept (golang, check, fix, store), not a generic label (util, helper).

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cross-package-types","level":2,"title":"Cross-Package Types","text":"

    Test: TestCrossPackageTypes

    When a type defined in one package is used from a different module (e.g., cli/doctor importing a type from cli/notify), the type has crossed its module boundary. Cross-cutting types belong in internal/entity/ for discoverability.

    Before:

    // internal/cli/notify/core/types.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/cli/notify/core\"\nfunc check(p core.NotifyPayload) { ... }\n

    After:

    // internal/entity/notify.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/entity\"\nfunc check(p entity.NotifyPayload) { ... }\n

    Exempt: Types inside entity/, proto/, core/ subpackages, and config/ packages. Same-module usage (e.g., cli/doctor/cmd/ using cli/doctor/core/) is not flagged.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#type-file-convention","level":2,"title":"Type File Convention","text":"

    Test: TestTypeFileConvention, TestTypeFileConventionReport

    Exported types in core/ subpackages should live in types.go (the convention from CONVENTIONS.md), not scattered across implementation files. This makes type definitions discoverable. TestTypeFileConventionReport generates a diagnostic summary of all type placements for triage.

    Exception: entity/ organizes by domain (task.go, session.go), proto/ uses schema.go, and err/ packages colocate error types with their domain context.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-yaml-linkage","level":2,"title":"DescKey / YAML Linkage","text":"

    Test: TestDescKeyYAMLLinkage

    Every DescKey constant must have a corresponding key in the YAML asset files, and every YAML key must have a corresponding DescKey constant. Orphans in either direction mean dead text or runtime panics.

    Fix for orphan YAML key: Delete the YAML entry, or add the corresponding DescKey constant in config/embed/{text,cmd,flag}/.

    Fix for orphan DescKey: Delete the constant, or add the corresponding entry in the YAML file under internal/assets/commands/text/, cmd/, or flag/.

    If the orphan YAML entry was once valid but the feature was removed, move the YAML entry to a .dead file in quarantine/deadcode/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#package-doc-quality","level":2,"title":"Package Doc Quality","text":"

    Test: TestPackageDocQuality

    Every package under internal/ must have a doc.go with a meaningful package doc comment (at least 8 lines of real content). One-liners and file-list patterns (// - foo.go, // Source files:) are flagged because they drift as files change.

    Template:

    //   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n\n// Package mypackage does X.\n//\n// It handles Y by doing Z. The main entry point is [FunctionName]\n// which accepts A and returns B.\n//\n// Configuration is read from [config.SomeConstant]. Output is\n// written through [write.SomeHelper].\n//\n// This package is used by [parentpackage] during the W lifecycle\n// phase.\npackage mypackage\n
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-regex-compilation","level":2,"title":"Inline Regex Compilation","text":"

    Test: TestNoInlineRegexpCompile

    regexp.MustCompile and regexp.Compile inside function bodies recompile the pattern on every call. Compiled patterns belong at package level.

    Before:

    func parse(s string) bool {\n    re := regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n    return re.MatchString(s)\n}\n

    After:

    // In internal/config/regex/regex.go:\n// DatePattern matches ISO date format (YYYY-MM-DD).\nvar DatePattern = regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n\n// In calling package:\nfunc parse(s string) bool {\n    return regex.DatePattern.MatchString(s)\n}\n

    Rule: All compiled regexes live in internal/config/regex/ as package-level var declarations. Two tests enforce this: TestNoInlineRegexpCompile catches function-body compilation, and TestNoRegexpOutsideRegexPkg catches package-level compilation outside config/regex/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#doc-comments","level":2,"title":"Doc Comments","text":"

    Test: TestDocComments

    All functions (exported and unexported), structs, and package-level variables must have a doc comment. Config packages allow group doc comments for const blocks.

    Before:

    func buildIndex(entries []Entry) map[string]int {\n

    After:

    // buildIndex maps entry names to their position in the\n// ordered slice for O(1) lookup during reconciliation.\n//\n// Parameters:\n//   - entries: ordered slice of entries to index\n//\n// Returns:\n//   - map[string]int: name-to-position mapping\nfunc buildIndex(entries []Entry) map[string]int {\n

    Rule: Every function, struct, and package-level var gets a doc comment in godoc format. Functions include Parameters: and Returns: sections. Structs with 2+ fields document every field. See CONVENTIONS.md for the full template.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#line-length","level":2,"title":"Line Length","text":"

    Test: TestLineLength

    Lines in non-test Go files must not exceed 80 characters. This is a hard check, not a suggestion.

    Before:

    _ = trace.Record(fmt.Sprintf(cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum), state.Dir())\n

    After:

    ref := fmt.Sprintf(\n    cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum,\n)\n_ = trace.Record(ref, state.Dir())\n

    Rule: Break at natural points: function arguments, struct fields, chained calls. Long strings (URLs, struct tags) are the rare acceptable exception.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#literal-whitespace","level":2,"title":"Literal Whitespace","text":"

    Test: TestNoLiteralWhitespace

    Bare whitespace string and byte literals (\"\\n\", \"\\r\\n\", \"\\t\") must not appear outside internal/config/token/. All other packages use the token constants.

    Before:

    output := strings.Join(lines, \"\\n\")\n

    After:

    output := strings.Join(lines, token.Newline)\n

    Rule: Whitespace literals are defined once in internal/config/token/. Use token.Newline, token.Tab, token.CRLF, etc.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-numeric-values","level":2,"title":"Magic Numeric Values","text":"

    Test: TestNoMagicValues

    Numeric literals in function bodies need constants, with narrow exceptions.

    Before:

    if len(entries) > 100 {\n    entries = entries[:100]\n}\n

    After:

    if len(entries) > config.MaxEntries {\n    entries = entries[:config.MaxEntries]\n}\n

    Exempt: 0, 1, -1, 2-10, strconv radix/bitsize args (10, 32, 64 in strconv.Parse*/Format*), octal permissions (caught separately by TestNoRawPermissions), and const/var definition sites.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-separators","level":2,"title":"Inline Separators","text":"

    Test: TestNoInlineSeparators

    strings.Join calls must use token constants for their separator argument, not string literals.

    Before:

    result := strings.Join(parts, \", \")\n

    After:

    result := strings.Join(parts, token.CommaSep)\n

    Rule: Separator strings live in internal/config/token/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stuttery-function-names","level":2,"title":"Stuttery Function Names","text":"

    Test: TestNoStutteryFunctions

    Function names must not redundantly include their package name as a PascalCase word boundary. Go callers already write pkg.Function, so pkg.PkgFunction stutters.

    Before:

    // In package write\nfunc WriteJournal(cmd *cobra.Command, ...) {\n

    After:

    // In package write\nfunc Journal(cmd *cobra.Command, ...) {\n

    Exempt: Identity functions like write.Write / write.write.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#predicate-naming-no-ishascan-prefix","level":2,"title":"Predicate Naming (No Is/Has/Can Prefix)","text":"

    Test: None (manual review convention)

    Exported methods that return bool must not use Is, Has, or Can prefixes. The predicate reads more naturally without them, especially at call sites where the package name provides context.

    Before:

    func IsCompleted(t *Task) bool { ... }\nfunc HasChildren(n *Node) bool { ... }\nfunc IsExemptPackage(path string) bool { ... }\n

    After:

    func Completed(t *Task) bool { ... }\nfunc Children(n *Node) bool { ... }  // or: ChildCount > 0\nfunc ExemptPackage(path string) bool { ... }\n

    Rule: Drop the prefix. Private helpers may use prefixes when it reads more naturally (isValid in a local context is fine). This convention applies to exported methods and package-level functions. See CONVENTIONS.md \"Predicates\" section.

    This is not yet enforced by an AST test; it requires semantic understanding of return types and naming intent that makes automated detection fragile. Apply during code review.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#mixed-visibility","level":2,"title":"Mixed Visibility","text":"

    Test: TestNoMixedVisibility

    Files with exported functions must not also contain unexported functions. Public API and private helpers live in separate files.

    Before:

    load.go\n    func Load() { ... }        // exported\n    func parseHeader() { ... } // unexported, violation\n

    After:

    load.go\n    func Load() { ... }        // exported only\nparse.go\n    func parseHeader() { ... } // private helper\n

    Exempt: Files with exactly one function, doc.go, test files.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stray-errgo-files","level":2,"title":"Stray Err.Go Files","text":"

    Test: TestNoStrayErrFiles

    err.go files must only exist under internal/err/. Error constructors anywhere else create a broken-window pattern where contributors add local error definitions when they see a local err.go.

    Fix: Move the error constructor to internal/err/<domain>/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cli-cmd-structure","level":2,"title":"CLI Cmd Structure","text":"

    Test: TestCLICmdStructure

    Each cmd/$sub/ directory under internal/cli/ may contain only cmd.go, run.go, doc.go, and test files. Extra .go files (helpers, output formatters, types) belong in the corresponding core/ subpackage.

    Before:

    internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\n    format.go   # violation: helper in cmd dir\n

    After:

    internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\ninternal/cli/doctor/core/format/\n    format.go\n    doc.go\n
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-namespace","level":2,"title":"DescKey Namespace","text":"

    Test: TestUseConstantsOnlyInCobraUse, TestDescKeyOnlyInLookupCalls, TestNoWrongNamespaceLookup

    Three tests enforce DescKey/Use constant discipline:

    1. Use* constants appear only in cobra Use: struct field assignments, never as arguments to desc.Text() or elsewhere.
    2. DescKey* constants are passed only to assets.CommandDesc(), assets.FlagDesc(), or desc.Text(), never to cobra Use:.
    3. No cross-namespace lookups: TextDescKey must not be passed to CommandDesc(), FlagDescKey must not be passed to Text(), etc.
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#yaml-examples-registry-linkage","level":2,"title":"YAML Examples / Registry Linkage","text":"

    Test: TestExamplesYAMLLinkage, TestRegistryYAMLLinkage

    Every key in examples.yaml and registry.yaml must match a known entry type constant. Prevents orphan entries that are never rendered.

    Fix: Delete the orphan YAML entry, or add the corresponding constant in config/entry/.

    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#other-enforced-patterns","level":2,"title":"Other Enforced Patterns","text":"

    These tests follow the same fix approach: extract the operation to its designated package:

    Test Violation Fix TestNoNakedErrors fmt.Errorf/errors.New outside internal/err/ Add error constructor to internal/err/<domain>/ TestNoRawFileIO Direct os.ReadFile, os.Create, etc. Use io.SafeReadFile, io.SafeWriteFile, etc. TestNoRawLogging Direct fmt.Fprintf(os.Stderr, ...) Use log/warn.Warn() or log/event.Append() TestNoExecOutsideExecPkg exec.Command outside internal/exec/ Add command to internal/exec/<domain>/ TestNoCmdPrintOutsideWrite cmd.Print* outside internal/write/ Add output helper to internal/write/<domain>/ TestNoRawPermissions Octal literals (0644, 0755) Use config/fs.PermFile, config/fs.PermExec, etc. TestNoErrorsAs errors.As() Use errors.AsType() (generic, Go 1.23+) TestNoStringConcatPaths dir + \"/\" + file Use filepath.Join(dir, file)","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#general-fix-workflow","level":2,"title":"General Fix Workflow","text":"

    When an audit test fails:

    1. Read the error message. It includes file:line and a description of the violation.
    2. Find the matching section above. The test name maps directly to a section.
    3. Apply the pattern. Most fixes are mechanical: extract to the right package, rename a variable, or replace a literal with a constant.
    4. Run make test before committing. Audit tests run as part of go test ./internal/audit/.
    5. Don't add allowlist entries as a first resort. Fix the code. Allowlists exist only for genuinely unfixable cases (test-only exports, config packages that are definitionally exempt).
    ","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/comparison/","level":1,"title":"Tool Ecosystem","text":"","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"

    Many tools help AI think.

    ctx helps AI remember.

    • Not by storing thoughts,
    • but by preserving intent.
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How ctx Differs from Similar Tools","text":"

    There are many tools in the AI ecosystem that touch parts of the context problem:

    • Some manage prompts.
    • Some retrieve data.
    • Some provide runtime context objects.
    • Some offer enterprise platforms.

    ctx focuses on a different layer entirely.

    This page explains where ctx fits, and where it intentionally does not.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"

    Most tools treat context as input.

    ctx treats context as infrastructure.

    That single difference explains nearly all of ctx's design choices.

    Question Most tools ctx Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"

    Examples include:

    • prompt templates;
    • reusable system prompts;
    • prompt libraries;
    • prompt versioning tools.

    These tools help you start a session.

    They do not help you continue one.

    Prompt tools:

    • inject text at session start;
    • are ephemeral by design;
    • do not evolve with the project.

    ctx:

    • persists knowledge over time;
    • accumulates decisions and learnings;
    • makes the context part of the repository itself.

    Prompt tooling and ctx are complementary; not competing. Yet, they operate in different layers.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"

    RAG systems typically:

    • index documents
    • embed text
    • retrieve chunks dynamically at runtime

    They are excellent for:

    • large knowledge bases
    • static documentation
    • reference material

    RAG answers questions like:

    \"What information might be relevant right now?\"

    ctx answers a different question:

    \"What have we already decided, learned, or committed to?\"

    Here are some key differences:

    RAG ctx Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory

    ctx does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.

    RAG belongs to the data plane; ctx defines the context control plane.

    It focuses on project memory, not knowledge search.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"

    Agent frameworks often provide:

    • task loops
    • tool orchestration
    • planner/executor patterns
    • autonomous iteration

    These systems are powerful, but they typically assume that:

    • memory is external
    • context is injected
    • state is transient

    Agent frameworks answer:

    \"How should the agent act?\"

    ctx answers:

    \"What should the agent remember?\"

    Without persistent context, agents tend to:

    • rediscover decisions
    • repeat mistakes
    • lose architectural intent

    This is why ctx pairs well with autonomous loop workflows:

    • The loop provides iteration
    • ctx provides continuity

    Together, loops become cumulative instead of forgetful.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"

    Some SDKs expose \"context\" objects that exist:

    • inside a process
    • during a request
    • for the lifetime of a call chain

    These are extremely useful and completely different.

    SDK context objects:

    • are in-memory
    • disappear when the process ends
    • are not shared across sessions

    ctx:

    • survives process restarts
    • survives new chats
    • survives new days

    They share a name, not a purpose.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"

    Enterprise platforms often provide:

    • centralized context services
    • dashboards
    • access control
    • organizational knowledge layers

    These tools are designed for:

    • teams
    • governance
    • compliance
    • managed environments

    ctx is intentionally:

    • local-first: context lives next to your code, not behind a service boundary.
    • file-based: everything important is a Markdown file you can read, diff, grep, and version-control.
    • single-binary core: the context persistence path (init, add, agent, status, drift, load, sync, compact, task, decision, learning, and their siblings) is a single Go binary with no required runtime dependencies. Optional integrations (ctx trace (needs git), ctx serve (needs zensical), the ctx Hub (needs a running hub), Claude Code plugin (needs claude)) are opt-in and each declares its dependency explicitly.
    • CLI-driven: every feature is reachable from the command line and scriptable.
    • developer-controlled: no auto-updating cloud service, no telemetry, no account to sign up for.

    The core ctx binary does not require:

    • a server
    • a database
    • an account
    • a SaaS backend
    • network connectivity (for core operations)

    ctx optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#specific-tool-comparisons","level":2,"title":"Specific Tool Comparisons","text":"

    Users often evaluate ctx against specific tools they already use. These comparisons clarify where responsibilities overlap, where they diverge, and where the tools are genuinely complementary.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#claude-code-memory-anthropic-auto-memory","level":3,"title":"Claude Code Memory / Anthropic Auto-Memory","text":"

    Anthropic's auto-memory is tool-managed memory (L2): the model decides what to remember, stores it automatically, and retrieves it implicitly. ctx is system memory (L3): humans and agents explicitly curate decisions, learnings, and tasks in inspectable files.

    Auto-memory is convenient - you do not configure anything. But it is also opaque: you cannot see what was stored, edit it precisely, or share it across tools. ctx files are plain Markdown in your repository, visible in diffs and code review.

    The two are complementary. ctx can absorb auto-memory as an input source (importing what the model remembered into structured context files) while providing the durable, inspectable layer that auto-memory lacks.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cursorrules-clauderules","level":3,"title":".Cursorrules / .Claude/rules","text":"

    Static rule files (.cursorrules, .claude/rules/) declare conventions: coding style, forbidden patterns, preferred libraries. They are effective for what to do and load automatically at session start.

    ctx adds dimensions that rule files do not cover: architectural decisions with rationale, learnings discovered during development, active tasks, and a constitution that governs agent behavior. Critically, ctx context accumulates - each session can add to it, and token budgeting ensures only the most relevant context is injected.

    Use rule files for static conventions. Use ctx for evolving project memory.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#aider-read-watch","level":3,"title":"Aider --read / --watch","text":"

    Aider's --read flag injects file contents at session start; --watch reloads them on change. The concept is similar to ctx's \"load\" step: make the agent aware of specific files.

    The differences emerge beyond loading. Aider has no persistence model -- nothing the agent learns during a session is written back. There is no token budgeting (large files consume the full context window), no priority ordering across file types, and no structured format for decisions or learnings. ctx provides the full lifecycle: load, accumulate, persist, and budget.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#copilot-workspace","level":3,"title":"Copilot @Workspace","text":"

    GitHub Copilot's @workspace performs workspace-wide code search. It answers \"what code exists?\" - finding function definitions, usages, and file structure across the repository.

    ctx answers a different question: \"what did we decide?\" It stores architectural intent, not code indices. Copilot's workspace search and ctx's project memory are orthogonal; one finds code, the other preserves the reasoning behind it.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cline-memory","level":3,"title":"Cline Memory","text":"

    Cline's memory bank stores session context within the Cline extension. The motivation is similar to ctx: help the agent remember across sessions.

    The key difference is portability. Cline memory is tied to Cline - it does not transfer to Claude Code, Cursor, Aider, or any other tool. ctx is tool-agnostic: context lives in plain files that any editor, agent, or script can read. Switching tools does not mean losing memory.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-a-good-fit","level":2,"title":"When ctx Is a Good Fit","text":"

    ctx works best when:

    • you want AI work to compound over time;
    • architectural decisions matter;
    • context must be inspectable;
    • humans and AI must share the same source of truth;
    • Git history should include why, not just what.
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When ctx Is Not the Right Tool","text":"

    ctx is probably not what you want if:

    • you only need one-off prompts;
    • you rely exclusively on RAG;
    • you want autonomous agents without a human-readable state;
    • you require centralized enterprise control;
    • you want black-box memory systems,

    These are valid goals; just different ones.

    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#further-reading","level":2,"title":"Further Reading","text":"
    • You Can't Import Expertise: why project-specific context matters more than generic best practices
    ","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/design-invariants/","level":1,"title":"Invariants","text":"","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-system-explains-itself","level":1,"title":"The System Explains Itself","text":"

    These are the properties that must hold for any valid ctx implementation.

    • These are not features.
    • These are constraints.

    A change that violates an invariant is a category error, not an improvement.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#cognitive-state-tiers","level":2,"title":"Cognitive State Tiers","text":"

    ctx distinguishes between three forms of state:

    • Authoritative state: Versioned, inspectable artifacts that define intent and survive time.
    • Delivery views: Deterministic assemblies of the authoritative state for a specific budget or workflow.
    • Ephemeral working state: Local, transient, or sensitive data that assists interaction but does not define system truth.

    The invariants below apply primarily to the authoritative cognitive state.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#1-cognitive-state-is-explicit","level":2,"title":"1. Cognitive State Is Explicit","text":"

    All authoritative context lives in artifacts that can be inspected, reviewed, and versioned.

    If something is important, it must exist as a file: Not only in a prompt, a chat, or a model's hidden memory.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#2-assembly-is-reproducible","level":2,"title":"2. Assembly Is Reproducible","text":"

    Given the same:

    • repository state,
    • configuration,
    • and inputs,

    context assembly produces the same result.

    Heuristics may rank or filter for delivery under constraints.

    They do not alter the authoritative state.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#3-the-authoritative-state-is-human-readable","level":2,"title":"3. The Authoritative State Is Human-Readable","text":"

    The authoritative cognitive state must be stored in formats that a human can:

    • read,
    • diff,
    • review,
    • and edit directly.

    Sensitive working memory may be encrypted at rest. However, encryption must not become the only representation of authoritative knowledge.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#4-artifacts-outlive-sessions","level":2,"title":"4. Artifacts Outlive Sessions","text":"

    Sessions are transient.

    Knowledge persists.

    Reasoning, decisions, and outcomes must remain available after the interaction that produced them has ended.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#5-authority-is-user-defined","level":2,"title":"5. Authority Is User-Defined","text":"

    What enters the authoritative context is an explicit human decision.

    Models may suggest.

    Automation may assist.

    Selection is never implicit.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#6-operation-is-local-first","level":2,"title":"6. Operation Is Local-First","text":"

    The core system must function without requiring network access or a remote service.

    External systems may extend ctx.

    They must not be required for its operation.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#7-versioning-is-the-memory-model","level":2,"title":"7. Versioning Is the Memory Model","text":"

    The evolution of the authoritative cognitive state must be:

    • preserved,
    • inspectable,
    • and branchable.

    Ephemeral and sensitive working state may use different retention and diff strategies by design.

    Understanding includes understanding how we arrived here.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#8-structure-enables-scale","level":2,"title":"8. Structure Enables Scale","text":"

    Unstructured accumulation is not memory.

    Authoritative cognitive state must have a defined layout that:

    • communicates intent,
    • supports navigation,
    • and prevents drift.
    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#9-verification-is-the-scoreboard","level":2,"title":"9. Verification Is the Scoreboard","text":"

    Claims without recorded outcomes are noise.

    Reality (observed and captured) is the only signal that compounds.

    This invariant defines a required direction:

    The authoritative state must be able to record expectation and result.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#10-capture-once-reuse-indefinitely","level":2,"title":"10. Capture Once, Reuse Indefinitely","text":"

    Work that has already produced understanding must not be re-derived from scratch.

    Explored paths, rejected options, and validated conclusions are permanent assets.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#11-policies-are-encoded-not-remembered","level":2,"title":"11. Policies Are Encoded, Not Remembered","text":"

    Alignment must not depend on recall or goodwill.

    Constraints that matter must exist in machine-readable form and participate in context assembly.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#12-the-system-explains-itself","level":2,"title":"12. The System Explains Itself","text":"

    From the repository state alone it must be possible to determine:

    • what was authoritative,
    • what constraints applied.

    Delivery views may be optimized.

    They must not become the only explanation.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#non-goals","level":1,"title":"Non-Goals","text":"

    To avoid category errors, ctx does not attempt to be:

    • a skill,
    • a prompt management tool,
    • a chat history viewer,
    • an autonomous agent runtime,
    • a vector database,
    • a hosted memory service.

    Such systems may integrate with ctx.

    They do not define it.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#implications-for-contributions","level":1,"title":"Implications for Contributions","text":"

    Valid contributions:

    • strengthen an invariant,
    • reduce the cost of maintaining an invariant,
    • or extend the system without violating invariants.

    Invalid contributions:

    • introduce hidden authoritative state,
    • replace reproducible assembly with non-reproducible behavior,
    • make core operation depend on external services,
    • reduce human inspectability of authoritative state,
    • or bypass explicit user authority over what becomes authoritative.
    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-contract","level":1,"title":"The Contract","text":"

    Everything else (commands, skills, layouts, integrations, optimizations) is an implementation detail.

    These invariants are the system.

    ","path":["Reference","Invariants"],"tags":[]},{"location":"reference/scratchpad/","level":1,"title":"Scratchpad","text":"","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#what-is-ctx-scratchpad","level":2,"title":"What Is ctx Scratchpad?","text":"

    A one-liner scratchpad, encrypted at rest, synced via git.

    Quick notes that don't fit decisions, learnings, or tasks: reminders, intermediate values, sensitive tokens, working memory during debugging. Entries are numbered, reorderable, and persist across sessions.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#encrypted-by-default","level":2,"title":"Encrypted by Default","text":"

    Scratchpad entries are encrypted with AES-256-GCM before touching the disk.

    Component Path Git status Encryption key ~/.ctx/.ctx.key User-level, 0600 permissions Encrypted data .context/scratchpad.enc Committed

    The key is generated automatically during ctx init (256-bit via crypto/rand) and stored at ~/.ctx/.ctx.key. One key per machine, shared across all projects.

    The ciphertext format is [12-byte nonce][ciphertext+tag]. No external dependencies: Go stdlib only.

    Because the key is .gitignored and the data is committed, you get:

    • At-rest encryption: the .enc file is opaque without the key
    • Git sync: push/pull the encrypted file like any other tracked file
    • Key separation: the key never leaves the machine unless you copy it
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#commands","level":2,"title":"Commands","text":"Command Purpose ctx pad List all entries (numbered 1-based) ctx pad show N Output raw text of entry N (no prefix, pipe-friendly) ctx pad add \"text\" Append a new entry ctx pad rm ID [ID...] Remove entries by stable ID (supports ranges: 3-5) ctx pad edit N \"text\" Replace entry N with new text ctx pad edit N --append \"text\" Append text to the end of entry N ctx pad edit N --prepend \"text\" Prepend text to the beginning of entry N ctx pad edit N --tag tagname Add a tag to entry N ctx pad add TEXT --file PATH Ingest a file as a blob entry (TEXT is the label) ctx pad show N --out PATH Write decoded blob content to a file ctx pad normalize Reassign entry IDs as 1..N ctx pad mv N M Move entry from position N to position M ctx pad resolve Show both sides of a merge conflict for resolution ctx pad import FILE Bulk-import lines from a file (or stdin with -) ctx pad import --blob DIR Import directory files as blob entries ctx pad export [DIR] Export all blob entries to a directory as files ctx pad merge FILE... Merge entries from other scratchpad files into current ctx pad --tag TAG List entries filtered by tag (prefix with ~ to exclude) ctx pad tags List all tags with counts ctx pad tags --json List all tags with counts as JSON

    All commands decrypt on read, operate on plaintext in memory, and re-encrypt on write. The key file is never printed to stdout.

    For blob entries, --append, --prepend, and --tag modify the label while preserving the blob data.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#examples","level":3,"title":"Examples","text":"
    # Add a note\nctx pad add \"check DNS propagation after deploy\"\n\n# List everything\nctx pad\n#   1. check DNS propagation after deploy\n#   2. staging API key: sk-test-abc123\n\n# Show raw text (for piping)\nctx pad show 2\n# sk-test-abc123\n\n# Compose entries\nctx pad edit 1 --append \"$(ctx pad show 2)\"\n\n# Reorder\nctx pad mv 2 1\n\n# Clean up (IDs are stable; they don't shift when entries are deleted)\nctx pad rm 2\n
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#tags","level":2,"title":"Tags","text":"

    Entries can contain #word tags for lightweight categorization. Tags are convention-based: any #word token in an entry's text is a tag. No special syntax to add or remove them; use the existing add and edit commands.

    # Add tagged entries\nctx pad add \"check DNS propagation #later\"\nctx pad add \"deploy hotfix #urgent\"\nctx pad add \"review PR #later #ci\"\n\n# Filter by tag\nctx pad --tag later\n#   1. check DNS propagation #later\n#   3. review PR #later #ci\n\n# Exclude a tag\nctx pad --tag ~later\n#   2. deploy hotfix #urgent\n\n# Multiple filters (AND logic)\nctx pad --tag later --tag ci\n#   3. review PR #later #ci\n\n# List all tags with counts\nctx pad tags\n# ci       1\n# later    2\n# urgent   1\n\n# JSON output\nctx pad tags --json\n# [{\"tag\":\"ci\",\"count\":1},{\"tag\":\"later\",\"count\":2},{\"tag\":\"urgent\",\"count\":1}]\n\n# Add a tag to an existing entry\nctx pad edit 1 --tag done\n\n# Combine with other operations\nctx pad edit 1 --append \"checked\" --tag done\n\n# Remove a tag (replace entry text without the tag)\nctx pad edit 1 \"check DNS propagation\"\n

    Entry IDs are stable; they don't shift when other entries are deleted, so ctx pad rm 3 always targets the same entry. Use ctx pad normalize to reassign IDs as 1..N if gaps bother you. Tags are case-sensitive and support letters, digits, hyphens, and underscores (#high-priority, #v2, #my_tag).

    For blob entries, tags are extracted from the label only.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#bulk-import-and-export","level":2,"title":"Bulk Import and Export","text":"

    Import lines from a file in bulk (each non-empty line becomes an entry):

    # Import from a file\nctx pad import notes.txt\n\n# Import from stdin\ngrep TODO *.go | ctx pad import -\n

    Export all blob entries to a directory as files:

    # Export to a directory\nctx pad export ./ideas\n\n# Preview without writing\nctx pad export --dry-run\n\n# Overwrite existing files\nctx pad export --force ./backup\n
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#merging-scratchpads","level":2,"title":"Merging Scratchpads","text":"

    Combine entries from other scratchpad files into your current pad. Useful when merging work from parallel worktrees, other machines, or teammates:

    # Merge from a worktree's encrypted scratchpad\nctx pad merge worktree/.context/scratchpad.enc\n\n# Merge from multiple sources (encrypted and plaintext)\nctx pad merge pad-a.enc notes.md\n\n# Merge a foreign encrypted pad using its key\nctx pad merge --key /other/.ctx.key foreign.enc\n\n# Preview without writing\nctx pad merge --dry-run pad-a.enc pad-b.md\n

    Each input file is auto-detected as encrypted or plaintext: decryption is attempted first, and on failure the file is parsed as plain text. Entries are deduplicated by exact content, so running merge twice with the same file is safe.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#file-blobs","level":2,"title":"File Blobs","text":"

    The scratchpad can store small files (up to 64 KB) as blob entries. Files are base64-encoded and stored with a human-readable label.

    # Ingest a file: first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# Listing shows label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n\n# Extract to a file\nctx pad show 2 --out ./recovered.yaml\n\n# Or print decoded content to stdout\nctx pad show 2\n

    Blob entries are encrypted identically to text entries. The internal format is label:::base64data: You never need to construct this manually.

    Constraint Value Max file size (pre-encoding) 64 KB Storage format label:::base64(content) Display label [BLOB] in listings

    When Should You Use Blobs

    Blobs are for small files you want encrypted and portable: config snippets, key fragments, deployment manifests, test fixtures. For anything larger than 64 KB, use the filesystem directly.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#using-with-ai","level":2,"title":"Using with AI","text":"

    Use Natural Language

    As in many ctx features, the ctx scratchpad can also be used with natural langauge. You don't have to memorize the CLI commands.

    CLI gives you \"precision\", whereas natural language gives you flow.

    The /ctx-pad skill maps natural language to ctx pad commands. You don't need to remember the syntax:

    You say What happens \"jot down: check DNS after deploy\" ctx pad add \"check DNS after deploy\" \"show my scratchpad\" ctx pad \"delete the third entry\" ctx pad rm 3 \"update entry 2 to include the new endpoint\" ctx pad edit 2 \"...\" \"move entry 4 to the top\" ctx pad mv 4 1 \"import my notes from notes.txt\" ctx pad import notes.txt \"export all blobs to ./backup\" ctx pad export ./backup \"merge the scratchpad from the worktree\" ctx pad merge worktree/.context/scratchpad.enc

    The skill handles the translation. You describe what you want in plain English; the agent picks the right command.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#worktrees","level":2,"title":"Worktrees","text":"

    The encryption key lives at ~/.ctx/.ctx.key (outside the project directory). Because all worktrees on the same machine share this path, ctx pad works in worktrees automatically - no special setup needed.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#key-distribution","level":2,"title":"Key Distribution","text":"

    The encryption key (~/.ctx/.ctx.key) stays on the machine where it was generated. ctx never transmits it.

    To share the scratchpad across machines:

    1. Copy the key manually: scp, USB drive, password manager.
    2. Push/pull the .enc file via git as usual.
    3. Both machines can now read and write the same scratchpad.

    Never Commit the Key

    The key is .gitignored by default. If you override this, anyone with repo access can decrypt your scratchpad.

    Treat the key like an SSH private key.

    See the Syncing Scratchpad Notes Across Machines recipe for a step-by-step walkthrough.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#plaintext-override","level":2,"title":"Plaintext Override","text":"

    For projects where encryption is unnecessary, disable it in .ctxrc:

    scratchpad_encrypt: false\n

    In plaintext mode:

    • Entries are stored in .context/scratchpad.md instead of .enc.
    • No key is generated or required.
    • All ctx pad commands work identically.
    • The file is human-readable and diffable.

    When Should You Use Plaintext

    Plaintext mode is useful for non-sensitive projects, solo work where encryption adds friction, or when you want scratchpad entries visible in git diff.

    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#when-should-you-use-scratchpad-versus-context-files","level":2,"title":"When Should You Use Scratchpad versus Context Files","text":"Use case Where it goes Temporary reminders (\"check X after deploy\") Scratchpad Working values during debugging Scratchpad Sensitive tokens or API keys (short-term) Scratchpad Quick notes that don't fit anywhere else Scratchpad Items that are not directly relevant to the project Scratchpad Things that you want to keep near, but also hidden Scratchpad Work items with completion tracking TASKS.md Trade-offs with rationale DECISIONS.md Reusable lessons with context/lesson/application LEARNINGS.md Codified patterns and standards CONVENTIONS.md

    Rule of thumb:

    • If it needs structure or will be referenced months later, use a context file (i.e. DECISIONS.md, LEARNINGS.md, TASKS.md).
    • If it is working memory for the current session or week, use the scratchpad.
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#see-also","level":2,"title":"See Also","text":"
    • Syncing Scratchpad Notes Across Machines: Key distribution, push/pull workflow, merge conflict resolution
    • Using the Scratchpad: Natural language examples, blob workflow, when to use scratchpad vs context files
    • Context Files: Format and conventions for all .context/ files
    • Security: Trust model and permission hygiene
    ","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/session-journal/","level":1,"title":"Session Journal","text":"

    Important Security Note

    Session journals contain sensitive data such as file contents, commands, API keys, internal discussions, error messages with stack traces, and more.

    The .context/journal-site/ and .context/journal-obsidian/ directories MUST be .gitignored.

    • DO NOT host your journal publicly.
    • DO NOT commit your journal files to version control.
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#browse-your-session-history","level":2,"title":"Browse Your Session History","text":"

    ctx's Session Journal turns your AI coding sessions into a browsable, searchable, and editable archive.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#quick-start","level":2,"title":"Quick Start","text":"

    After using ctx for a couple of sessions, you can generate a journal site with:

    # Import all sessions to markdown\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n

    Then open http://localhost:8000 to browse your sessions.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#what-you-get","level":2,"title":"What You Get","text":"

    The Session Journal gives you:

    • Browsable history: Navigate through all your AI sessions by date
    • Full conversations: See every message, tool use, and result
    • Token usage: Track how many tokens each session consumed
    • Search: Find sessions by content, project, or date
    • Dark mode: Easy on the eyes for late-night archaeology

    Each session page includes the following sections:

    Section Content Metadata Date, time, duration, model, project, git branch Summary Space for your notes (editable) Tool Usage Which tools were used and how often Conversation Full transcript with timestamps","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#1-import-sessions","level":3,"title":"1. Import Sessions","text":"
    # Import all sessions from current project (only new files)\nctx journal import --all\n\n# Import sessions from all projects\nctx journal import --all --all-projects\n\n# Import a specific session by ID (always writes)\nctx journal import abc123\n\n# Preview what would be imported\nctx journal import --all --dry-run\n\n# Re-import existing (regenerates conversation, preserves YAML frontmatter)\nctx journal import --all --regenerate\n\n# Discard frontmatter during regeneration\nctx journal import --all --regenerate --keep-frontmatter=false -y\n

    Imported sessions go to .context/journal/ as editable Markdown files.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#2-generate-the-site","level":3,"title":"2. Generate the Site","text":"
    # Generate site structure\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n

    The site is generated in .context/journal-site/ by default.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#3-browse-and-search","level":3,"title":"3. Browse and Search","text":"

    Open http://localhost:8000 after running --serve.

    • Use the sidebar to navigate by date
    • Use search (/ key) to find specific content
    • Click any session to see the full conversation
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#editing-sessions","level":2,"title":"Editing Sessions","text":"

    Imported sessions are plain Markdown in .context/journal/. You can:

    • Add summaries: Fill in the ## Summary section
    • Add notes: Insert your own commentary anywhere
    • Highlight key moments: Use Markdown formatting
    • Delete noise: Remove irrelevant tool outputs

    After editing, regenerate the site:

    ctx journal site --serve\n
    Safe by Default

    Running ctx journal import --all only imports new sessions. Existing files are skipped entirely (your edits and enrichments are never touched).

    Use --regenerate to re-import existing files. Conversation content is regenerated, but YAML frontmatter (topics, type, outcome, etc.) is preserved. You'll be prompted before any existing files are overwritten; add -y to skip the prompt.

    Use --keep-frontmatter=false to discard enriched frontmatter during regeneration.

    Locked entries (via ctx journal lock) are always skipped, regardless of flags. If you prefer to add locked: true to frontmatter during enrichment, run ctx journal sync to propagate the lock state to .state.json.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#large-sessions","level":2,"title":"Large Sessions","text":"

    Sessions with many messages (200+) are automatically split into multiple parts for better browser performance. Navigation links connect the parts:

    session-abc123.md      (Part 1 of 3)\nsession-abc123-p2.md   (Part 2 of 3)\nsession-abc123-p3.md   (Part 3 of 3)\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#suggestion-sessions","level":2,"title":"Suggestion Sessions","text":"

    Claude Code generates \"suggestion\" sessions for auto-complete prompts. These are separated in the index under a \"Suggestions\" section to keep your main session list focused.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enriching-journal-entries","level":2,"title":"Enriching Journal Entries","text":"

    Raw imported sessions contain basic metadata (date, time, project) but lack the structured information needed for effective search, filtering, and analysis. Journal enrichment adds semantic metadata that transforms a flat archive into a searchable knowledge base.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#why-enrich","level":3,"title":"Why Enrich?","text":"

    Without enrichment, you have timestamps and raw conversations. With enrichment:

    • Find sessions by topic: \"Show me all auth-related sessions\"
    • Filter by outcome: \"What did I abandon vs complete?\"
    • Track technology usage: \"When did I last work with PostgreSQL?\"
    • Identify key files: Jump directly to the files discussed
    • Get summaries: Understand what happened without reading transcripts
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-frontmatter-schema","level":3,"title":"The Frontmatter Schema","text":"

    Enriched entries begin with YAML frontmatter:

    ---\ntitle: \"Implement caching layer\"\ndate: 2026-01-27\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/cache/memory.go\n---\n
    Field Required Description title Yes Descriptive title (not the session slug) date Yes Session date (YYYY-MM-DD) type Yes Session type (see below) outcome Yes How the session ended (see below) topics No Subject areas discussed technologies No Languages, databases, frameworks libraries No Specific packages or libraries used key_files No Important files created or modified

    Type values:

    Type When to use feature Building new functionality bugfix Fixing broken behavior refactor Restructuring without behavior change exploration Research, learning, experimentation debugging Investigating issues documentation Writing docs, comments, README

    Outcome values:

    Outcome Meaning completed Goal achieved partial Some progress, work continues abandoned Stopped pursuing this approach blocked Waiting on external dependency","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-ctx-journal-enrich","level":3,"title":"Using /ctx-journal-enrich","text":"

    The /ctx-journal-enrich skill automates enrichment by analyzing conversation content and proposing metadata.

    Invoke by session identifier:

    /ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-01-24\n/ctx-journal-enrich 76fe2ab9\n

    The skill will:

    1. Check if locked - locked entries are skipped (same as export);
    2. Find the matching journal file;
    3. Read and analyze the conversation;
    4. Propose frontmatter (type, topics, outcome, technologies);
    5. Generate a 2-3 sentence summary;
    6. Extract decisions, learnings, and tasks mentioned;
    7. Show a diff and ask for confirmation before writing.
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#before-and-after","level":3,"title":"Before and After","text":"

    Before enrichment:

    # twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\n[Add your summary of this session]\n\n## Conversation\n...\n

    After enrichment:

    ---\ntitle: \"Add Redis caching to API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n\n# twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\nImplemented Redis-based caching middleware for frequently accessed API endpoints.\nAdded cache invalidation on writes and configurable TTL per route. Reduced\n the average response time from 200ms to 15ms for cached routes.\n\n## Decisions\n\n* Used Redis over in-memory cache for horizontal scaling\n* Chose per-route TTL configuration over global setting\n\n## Learnings\n\n* Redis WATCH command prevents race conditions during cache invalidation\n\n## Conversation\n...\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enrichment-and-site-generation","level":3,"title":"Enrichment and Site Generation","text":"

    The journal site generator uses enriched metadata for better organization:

    • Titles appear in navigation instead of slugs
    • Summaries provide context in the index
    • Topics enable filtering (when using search)
    • Types allow grouping by work category

    Future improvements will add topic-based navigation and outcome filtering to the generated site.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#batch-enrichment","level":3,"title":"Batch Enrichment","text":"

    To enrich multiple sessions, process them one at a time:

    # List unenriched sessions (those without frontmatter)\ngrep -L \"^---$\" .context/journal/*.md | head -10\n

    Then run /ctx-journal-enrich on each. Enrichment is intentionally interactive to ensure accuracy.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#obsidian-vault-export","level":2,"title":"Obsidian Vault Export","text":"

    If you use Obsidian for knowledge management, you can export your journal as an Obsidian vault instead of (or alongside) the static site:

    ctx journal obsidian\n

    This generates a vault in .context/journal-obsidian/ with:

    • Wikilinks ([[target|display]]) instead of Markdown links
    • MOC pages (Map of Content) for topics, key files, and session types
    • Related sessions footer per entry: links to entries sharing the same topics
    • Transformed frontmatter: topics renamed to tags (Obsidian-recognized), aliases added from title for search
    • Graph-optimized structure: MOC hubs and cross-linked entries create dense graph connectivity

    To use: open the output directory in Obsidian (\"Open folder as vault\").

    # Custom output directory\nctx journal obsidian --output ~/vaults/ctx-journal\n

    Static Site vs Obsidian Vault

    Use ctx journal site when you want a web-browsable archive with search and dark mode. Use ctx journal obsidian when you want graph view, backlinks, and tag-based navigation inside Obsidian. Both use the same enriched source entries: you can generate both.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#full-pipeline","level":2,"title":"Full Pipeline","text":"

    The complete journal workflow has four stages. Each is idempotent: safe to re-run, and stages skip already-processed entries.

    import → enrich → rebuild\n
    Stage Command / Skill What it does Skips if Import ctx journal import --all Converts session JSONL to Markdown File already exists (safe default) Enrich /ctx-journal-enrich Adds frontmatter, summaries, topics Frontmatter already present Rebuild ctx journal site --build Generates static HTML site (never) Obsidian ctx journal obsidian Generates Obsidian vault with wikilinks (never)

    One-Command Pipeline

    /ctx-journal-enrich-all handles import automatically - it detects unimported sessions and imports them before enriching. You only need to run ctx journal site --build afterward.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-make-journal","level":3,"title":"Using make journal","text":"

    If your project includes Makefile.ctx (deployed by ctx init), the first and last stages are combined:

    make journal           # import + rebuild\n

    After it runs, it reminds you to enrich in Claude Code:

    Next steps (in Claude Code):\n  /ctx-journal-enrich-all # imports if needed + adds metadata per entry\n\nThen re-run: make journal\n

    Rendering Issues?

    If individual entries have rendering problems (broken fences, malformed lists), check the programmatic normalization in the import pipeline. Most cases are handled automatically during ctx journal import.

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#tips","level":2,"title":"Tips","text":"

    Daily workflow:

    # Import, browse, then enrich in Claude Code\nmake journal && make journal-serve\n# Then in Claude Code: /ctx-journal-enrich <session>\n

    After a productive session:

    # Import just that session and add notes\nctx journal import <session-id>\n# Edit .context/journal/<session>.md\n# Regenerate: ctx journal site\n

    Searching across all sessions:

    # Use grep on the journal directory\ngrep -r \"authentication\" .context/journal/\n

    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#requirements","level":2,"title":"Requirements","text":"Use pipx for zensical

    pip install zensical may install a non-functional stub on system Python. Using venv has other issues too.

    These issues especially happen on Mac OSX.

    Use pipx install zensical, which creates an isolated environment and handles Python version management automatically.

    The journal site uses zensical for static site generation:

    pipx install zensical\n
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#see-also","level":2,"title":"See Also","text":"
    • ctx journal: Session discovery and listing
    • ctx journal site: Static site generation
    • ctx journal obsidian: Obsidian vault export
    • Context Files: The .context/ directory structure
    ","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/skills/","level":1,"title":"Skills","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skills","level":2,"title":"Skills","text":"

    Skills are slash commands that run inside your AI assistant (e.g., /ctx-next), as opposed to CLI commands that run in your terminal (e.g., ctx status).

    Skills give your agent structured workflows: It knows what to read, what to run, and when to ask. Most wrap one or more ctx CLI commands with opinionated behavior on top.

    Skills Are Best Used Conversationally

    The beauty of ctx is that it's designed to be intuitive and conversational, allowing you to interact with your AI assistant naturally. That's why you don't have to memorize many of these skills.

    See the Prompting Guide for natural-language triggers that invoke these skills conversationally.

    However, when you need a more precise control, you have the option to invoke the relevant skills directly.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#all-skills","level":2,"title":"All Skills","text":"Skill Description Type /ctx-remember Recall project context and present structured readback user-invocable /ctx-wrap-up End-of-session context persistence ceremony user-invocable /ctx-status Show context summary with interpretation user-invocable /ctx-agent Load full context packet for AI consumption user-invocable /ctx-next Suggest 1-3 concrete next actions with rationale user-invocable /ctx-commit Commit with integrated context persistence user-invocable /ctx-reflect Pause and reflect on session progress user-invocable /ctx-task-add Add actionable task to TASKS.md user-invocable /ctx-decision-add Record architectural decision with rationale user-invocable /ctx-learning-add Record gotchas and lessons learned user-invocable /ctx-convention-add Record coding convention for consistency user-invocable /ctx-archive Archive completed tasks from TASKS.md user-invocable /ctx-pad Manage encrypted scratchpad entries user-invocable /ctx-history Browse and import AI session history user-invocable /ctx-journal-enrich Enrich single journal entry with metadata user-invocable /ctx-journal-enrich-all Full journal pipeline: export if needed, then batch-enrich user-invocable /ctx-blog Generate blog post draft from project activity user-invocable /ctx-blog-changelog Generate themed blog post from a commit range user-invocable /ctx-consolidate Consolidate redundant learnings or decisions user-invocable /ctx-drift Detect and fix context drift user-invocable /ctx-prompt Apply, list, and manage saved prompt templates user-invocable /ctx-prompt-audit Analyze prompting patterns for improvement user-invocable /ctx-link-check Audit docs for dead internal and external links user-invocable /ctx-permission-sanitize Audit Claude Code permissions for security risks user-invocable /ctx-brainstorm Structured design dialogue before implementation user-invocable /ctx-spec Scaffold a feature spec from a project template user-invocable /ctx-plan-import Import Claude Code plan files into project specs user-invocable /ctx-implement Execute a plan step-by-step with verification user-invocable /ctx-loop Generate autonomous loop script user-invocable /ctx-worktree Manage git worktrees for parallel agents user-invocable /ctx-architecture Build and maintain architecture maps user-invocable /ctx-architecture-failure-analysis Adversarial failure analysis for correctness bugs user-invocable /ctx-remind Manage session-scoped reminders user-invocable /ctx-doctor Troubleshoot ctx behavior with health checks and event analysis user-invocable /ctx-skill-audit Audit skills against Anthropic prompting best practices user-invocable /ctx-skill-create Create, improve, and test skills user-invocable /ctx-pause Pause context hooks for this session user-invocable /ctx-resume Resume context hooks after a pause user-invocable /ctx-kb-ingest Editorial KB pass (topic-page / triage / evidence-only) user-invocable /ctx-kb-ask Q&A grounded in the KB; refuses to web-jump user-invocable /ctx-kb-site-review Mechanical KB structural audit user-invocable /ctx-kb-ground Re-ground the KB against listed external sources user-invocable /ctx-kb-note Park a finding in ingest/findings.md user-invocable /ctx-handover Handover step delegated by /ctx-wrap-up; folds postdated closeouts sub-mechanism","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-lifecycle","level":2,"title":"Session Lifecycle","text":"

    Skills for starting, running, and ending a productive session.

    Session Ceremonies

    Two skills in this group are ceremony skills: /ctx-remember (session start) and /ctx-wrap-up (session end). Unlike other skills that work conversationally, these should be invoked as explicit slash commands for completeness. See Session Ceremonies.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remember","level":3,"title":"/ctx-remember","text":"

    Recall project context and present a structured readback. Ceremony skill: invoke explicitly at session start.

    Wraps: ctx agent --budget 4000, ctx journal source --limit 3, reads TASKS.md, DECISIONS.md, LEARNINGS.md

    See also: Session Ceremonies, The Complete Session

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-status","level":3,"title":"/ctx-status","text":"

    Show context summary (files, token budget, tasks, recent activity) with interpreted suggestions.

    Wraps: ctx status [--verbose] [--json]

    See also: The Complete Session, ctx status CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-agent","level":3,"title":"/ctx-agent","text":"

    Load the full context packet optimized for AI consumption. Also runs automatically via the PreToolUse hook with cooldown.

    Wraps: ctx agent [--budget] [--format] [--cooldown] [--session]

    See also: The Complete Session, ctx agent CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-next","level":3,"title":"/ctx-next","text":"

    Suggest 1-3 concrete next actions ranked by priority, momentum, and unblocked status.

    Wraps: reads TASKS.md, ctx journal source --limit 3

    See also: The Complete Session, Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-commit","level":3,"title":"/ctx-commit","text":"

    Commit code with integrated context persistence: pre-commit checks, staged files, Co-Authored-By trailer, and a post-commit prompt to capture decisions and learnings.

    Wraps: git add, git commit, optionally chains to /ctx-decision-add and /ctx-learning-add

    See also: The Complete Session

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-reflect","level":3,"title":"/ctx-reflect","text":"

    Pause and reflect on session progress. Walks through a checklist of learnings, decisions, task completions, and session notes to persist.

    Wraps: chains to ctx learning add, ctx decision add, manual TASKS.md updates

    See also: The Complete Session, Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-wrap-up","level":3,"title":"/ctx-wrap-up","text":"

    End-of-session context persistence ceremony. Gathers signal from git diff, recent commits, and conversation themes. Proposes candidates (learnings, decisions, conventions, tasks) with complete structured fields for user approval, then persists via ctx add. Offers /ctx-commit if uncommitted changes remain. Always delegates to /ctx-handover as its final step, regardless of whether .context/kb/ exists: KB presence only affects what gets folded into the handover, not whether it is written. Ceremony skill: invoke explicitly at session end.

    Trigger phrases: \"let's wrap up\", \"save context\", \"save state\", \"leave a handover\", \"before I go\", \"stepping away\", \"end of session\"

    Wraps: git diff --stat, git log, ctx learning add, ctx decision add, ctx convention add, ctx task add, chains to /ctx-commit, delegates to /ctx-handover

    See also: Session Ceremonies, The Complete Session, /ctx-handover

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#context-persistence","level":2,"title":"Context Persistence","text":"

    Skills for recording work artifacts: tasks, decisions, learnings, conventions: into .context/ files.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-task-add","level":3,"title":"/ctx-task-add","text":"

    Add an actionable task with optional priority and phase section.

    Wraps: ctx task add \"description\" [--priority high|medium|low] --session-id ID --branch BR --commit HASH

    See also: Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-decision-add","level":3,"title":"/ctx-decision-add","text":"

    Record an architectural decision with context, rationale, and consequence. Supports Y-statement (lightweight) and full ADR formats.

    Wraps: ctx decision add \"title\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id ID --branch BR --commit HASH

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-learning-add","level":3,"title":"/ctx-learning-add","text":"

    Record a project-specific gotcha, bug, or unexpected behavior. Filters for insights that are searchable, project-specific, and required real effort to discover.

    Wraps: ctx learning add \"title\" --context \"...\" --lesson \"...\" --application \"...\" --session-id ID --branch BR --commit HASH

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-convention-add","level":3,"title":"/ctx-convention-add","text":"

    Record a coding convention that should be standardized across sessions. Targets patterns seen 2-3+ times.

    Wraps: ctx convention add \"rule\" --section \"Name\"

    See also: Persisting Decisions, Learnings, and Conventions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-archive","level":3,"title":"/ctx-archive","text":"

    Archive completed tasks from TASKS.md to a timestamped file in .context/archive/. Preserves phase headers for traceability.

    Wraps: ctx task archive [--dry-run]

    See also: Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#scratchpad","level":2,"title":"Scratchpad","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pad","level":3,"title":"/ctx-pad","text":"

    Manage the encrypted scratchpad: add, remove, edit, and reorder one-liner notes. Encrypted at rest with AES-256-GCM.

    Wraps: ctx pad, ctx pad add, ctx pad rm, ctx pad edit, ctx pad mv, ctx pad import, ctx pad export, ctx pad merge

    See also: Scratchpad, Using the Scratchpad

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#journal-history","level":2,"title":"Journal & History","text":"

    Skills for browsing, exporting, and enriching your AI session history into a structured journal.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-history","level":3,"title":"/ctx-history","text":"

    Browse, inspect, and import AI session history. List recent sessions, show details by slug or ID, and import to .context/journal/.

    Wraps: ctx journal source, ctx journal source --show, ctx journal import

    See also: Browsing and Enriching Past Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich","level":3,"title":"/ctx-journal-enrich","text":"

    Enrich a single journal entry with YAML frontmatter: title, type, outcome, topics, technologies, and summary. Shows diff before writing.

    Wraps: reads and edits .context/journal/*.md files

    See also: Browsing and Enriching Past Sessions, Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich-all","level":3,"title":"/ctx-journal-enrich-all","text":"

    Full journal pipeline: imports unimported sessions first, then batch-enriches all unenriched entries. Filters out short sessions and continuations. Can spawn subagents for large backlogs.

    Wraps: ctx journal import --all + iterates /ctx-journal-enrich

    See also: Browsing and Enriching Past Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#content-creation","level":2,"title":"Content Creation","text":"

    Skills for turning project activity into publishable content.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog","level":3,"title":"/ctx-blog","text":"

    Generate a blog post draft from recent project activity: git history, decisions, learnings, tasks, and journal entries. Requires a narrative arc (problem, approach, outcome).

    Wraps: reads git log, DECISIONS.md, LEARNINGS.md, TASKS.md, journal entries; writes to docs/blog/

    See also: Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog-changelog","level":3,"title":"/ctx-blog-changelog","text":"

    Generate a themed blog post from a commit range. Takes a starting commit and unifying theme, analyzes diffs and journal entries from that period.

    Wraps: git log, git diff --stat; writes to docs/blog/

    See also: Turning Activity into Content

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#auditing-health","level":2,"title":"Auditing & Health","text":"

    Skills for detecting drift, auditing alignment, and improving prompt quality.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-consolidate","level":3,"title":"/ctx-consolidate","text":"

    Consolidate redundant entries in LEARNINGS.md or DECISIONS.md. Groups overlapping entries by keyword similarity, presents candidates, and (with user approval) merges groups into denser combined entries. Originals are archived, not deleted.

    Wraps: reads LEARNINGS.md and DECISIONS.md, writes consolidated entries, archives originals, runs ctx reindex

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-drift","level":3,"title":"/ctx-drift","text":"

    Detect and fix context drift: stale paths, missing files, file age staleness, task accumulation, entry count warnings, and constitution violations via ctx drift. Also detects skill drift against canonical templates.

    Wraps: ctx drift [--fix]

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-prompt-audit","level":3,"title":"/ctx-prompt-audit","text":"

    Analyze recent prompting patterns to identify vague or ineffective prompts. Reviews 3-5 journal entries and suggests rewrites with positive observations.

    Wraps: reads .context/journal/ entries

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-doctor","level":3,"title":"/ctx-doctor","text":"

    Troubleshoot ctx behavior. Runs structural health checks via ctx doctor, analyzes event log patterns via ctx hook event, and presents findings with suggested actions. The CLI provides the structural baseline; the agent adds semantic analysis of event patterns and correlations.

    Wraps: ctx doctor --json, ctx hook event --json --last 100, ctx remind list, ctx hook message list, reads .ctxrc

    Trigger phrases: \"diagnose\", \"troubleshoot\", \"doctor\", \"health check\", \"why didn't my hook fire?\", \"hooks seem broken\", \"something seems off\"

    Graceful degradation: If event_log is not enabled, the skill still works but with reduced capability. It runs structural checks and notes: \"Enable event_log: true in .ctxrc for hook-level diagnostics.\"

    See also: Troubleshooting, ctx doctor CLI, ctx hook event CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-link-check","level":3,"title":"/ctx-link-check","text":"

    Scan all Markdown files under docs/ for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, and skips localhost/example URLs.

    Wraps: Glob + Grep to scan, curl for external checks

    Trigger phrases: \"check links\", \"audit links\", \"any broken links?\", \"dead links\"

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-permission-sanitize","level":3,"title":"/ctx-permission-sanitize","text":"

    Audit .claude/settings.local.json for dangerous permissions across four risk categories: hook bypass (Critical), destructive commands (High), config injection vectors (High), and overly broad patterns (Medium). Reports findings by severity and offers specific fix actions with user confirmation.

    Wraps: reads .claude/settings.local.json, edits with confirmation

    Trigger phrases: \"audit permissions\", \"are my permissions safe?\", \"sanitize permissions\", \"check settings\"

    See also: Claude Code Permission Hygiene

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#planning-execution","level":2,"title":"Planning & Execution","text":"

    Skills for structured design, implementation, and parallel agent workflows.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-brainstorm","level":3,"title":"/ctx-brainstorm","text":"

    Transform raw ideas into clear, validated designs through structured dialogue before any implementation begins. Follows a gated process: understand context, clarify the idea (one question at a time), surface non-functional requirements, lock understanding with user confirmation, explore 2-3 design approaches with trade-offs, stress-test the chosen approach, and present the detailed design.

    Wraps: reads DECISIONS.md, relevant source files; chains to /ctx-decision-add for recording design choices

    Trigger phrases: \"let's brainstorm\", \"design this\", \"think through\", \"before we build\", \"what approach should we take?\"

    See also: /ctx-spec

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-spec","level":3,"title":"/ctx-spec","text":"

    Scaffold a feature spec from the project template and walk through each section with the user. Covers: problem, approach, happy path, edge cases, validation rules, error handling, interface, implementation, configuration, testing, and non-goals. Spends extra time on edge cases and error handling.

    Wraps: reads specs/tpl/spec-template.md, writes to specs/, optionally chains to /ctx-task-add

    Trigger phrases: \"spec this out\", \"write a spec\", \"create a spec\", \"design document\"

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#-brief-path-flag","level":4,"title":"--brief <path> flag","text":"

    When invoked as /ctx-spec --brief <path>, the skill treats the file at <path> as the authoritative source and skips the interactive Q&A. Use this when a prior /ctx-plan session produced a debated brief that already covers the design.

    The skill enforces this authority order when sources disagree:

    1. Frozen contracts in docs/ (release notes, public CLI docs)
    2. Recorded decisions in .context/DECISIONS.md
    3. The brief at <path>
    4. Agent inference, only when 1 through 3 are silent, and labeled TBD in the resulting spec so it stands out for review.

    Light compression for clarity is allowed; new facts are not. Where the brief is silent, the spec writes TBD rather than filling the gap from inference. If the brief contradicts a frozen contract, the contradiction is surfaced to the user rather than silently followed.

    See also: /ctx-brainstorm, /ctx-plan, /ctx-plan-import

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-plan-import","level":3,"title":"/ctx-plan-import","text":"

    Import Claude Code plan files (~/.claude/plans/*.md) into the project's specs/ directory. Lists plans with dates and H1 titles, supports filtering (--today, --since, --all), slugifies headings for filenames, and optionally creates tasks referencing each imported spec.

    Wraps: reads ~/.claude/plans/*.md, writes to specs/, optionally chains to /ctx-task-add

    See also: Importing Claude Code Plans, Tracking Work Across Sessions

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-implement","level":3,"title":"/ctx-implement","text":"

    Execute a multi-step plan with build and test verification at each step. Loads a plan from a file or conversation context, breaks it into atomic steps, and checkpoints after every 3-5 steps.

    Wraps: reads plan file, runs verification commands (go build, go test, etc.)

    See also: Running an Unattended AI Agent

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-loop","level":3,"title":"/ctx-loop","text":"

    Generate a ready-to-run shell script for autonomous AI iteration. Supports Claude Code, Aider, and generic tool templates with configurable completion signals.

    Wraps: ctx loop [--tool] [--prompt] [--max-iterations] [--completion] [--output]

    See also: Autonomous Loops, Running an Unattended AI Agent

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-worktree","level":3,"title":"/ctx-worktree","text":"

    Manage git worktrees for parallel agent development. Create sibling worktrees on dedicated branches, analyze task blast radius for grouping, and tear down with merge.

    Wraps: git worktree add, git worktree list, git worktree remove, git merge

    See also: Parallel Agent Development with Git Worktrees

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture","level":3,"title":"/ctx-architecture","text":"

    Build and maintain architecture maps incrementally. Creates or refreshes ARCHITECTURE.md (succinct project map, loaded at session start) and DETAILED_DESIGN.md (deep per-module reference, consulted on-demand). Coverage is tracked in map-tracking.json so each run extends the map rather than re-analyzing everything.

    Wraps: ctx status, git log, reads source files; writes ARCHITECTURE.md, DETAILED_DESIGN.md, map-tracking.json

    See also: Detecting and Fixing Drift

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture-failure-analysis","level":3,"title":"/ctx-architecture-failure-analysis","text":"

    Adversarial failure analysis that generates falsifiable incident hypotheses against architecture artifacts. Hunts for correctness bugs that survive code review and tests: race conditions, ordering assumptions, cache staleness, error swallowing, ownership gaps, idempotency failures, state machine drift, and scaling cliffs.

    Requires /ctx-architecture artifacts as input. Reads ARCHITECTURE.md, DETAILED_DESIGN*.md, and map-tracking.json, then systematically applies 9 failure categories to every mutation point. Each finding carries an evidence standard (code path, trigger, failure path, silence reason, code evidence), a confidence level, and an explicit risk score. A mandatory challenge phase attempts to disprove each finding before it is accepted.

    Produces .context/DANGER-ZONES.md with ranked findings split into Critical (risk >= 7, silent/cascading) and Elevated tiers.

    Wraps: reads architecture artifacts, source code; writes DANGER-ZONES.md. Optionally uses GitNexus for blast radius and Gemini Search for cross-referencing known failure patterns.

    Relationship:

    Skill Mode /ctx-architecture Map what exists /ctx-architecture-enrich Improve map fidelity /ctx-architecture-failure-analysis Generate falsifiable incident hypotheses","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remind","level":3,"title":"/ctx-remind","text":"

    Manage session-scoped reminders via natural language. Translates user intent (\"remind me to refactor swagger\") into the corresponding ctx remind command. Handles date conversion for --after flags.

    Wraps: ctx remind, ctx remind list, ctx remind dismiss

    See also: Session Reminders

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skill-authoring","level":2,"title":"Skill Authoring","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-audit","level":3,"title":"/ctx-skill-audit","text":"

    Audit one or more skills against Anthropic prompting best practices. Checks audit dimensions: positive framing, motivation, phantom references, examples, subagent guards, scope, and descriptions. Reports findings by severity with concrete fix suggestions.

    Wraps: reads internal/assets/claude/skills/*/SKILL.md or .claude/skills/*/SKILL.md, references anthropic-best-practices.md

    Trigger phrases: \"audit this skill\", \"check skill quality\", \"review the skills\", \"are our skills any good?\"

    See also: /ctx-skill-create, Contributing

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-create","level":3,"title":"/ctx-skill-create","text":"

    Create, improve, and test skills. Guides the full lifecycle: capture intent, interview for edge cases, draft the SKILL.md, test with realistic prompts, review results with the user, and iterate. Applies core principles: the agent is already smart (only add what it does not know), the description is the trigger (make it specific and \"pushy\"), and explain the why instead of rigid directives.

    Wraps: reads/writes .claude/skills/ and internal/assets/claude/skills/

    Trigger phrases: \"create a skill\", \"turn this into a skill\", \"make a slash command\", \"this should be a skill\", \"improve this skill\", \"the skill isn't triggering\"

    See also: Contributing

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-control","level":2,"title":"Session Control","text":"

    Skills for controlling hook behavior during a session.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pause","level":3,"title":"/ctx-pause","text":"

    Pause all context nudge and reminder hooks for the current session. Security hooks still fire. Use for quick investigations or tasks that don't need ceremony overhead.

    Wraps: ctx hook pause

    Trigger phrases: \"pause ctx\", \"pause context\", \"stop the nudges\", \"quiet mode\"

    See also: Pausing Context Hooks

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-resume","level":3,"title":"/ctx-resume","text":"

    Resume context hooks after a pause. Restores normal nudge, reminder, and ceremony behavior. Silent no-op if not paused.

    Wraps: ctx hook resume

    Trigger phrases: \"resume ctx\", \"resume context\", \"turn nudges back on\", \"unpause\"

    See also: Pausing Context Hooks

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"

    Skills for the editorial knowledge-ingestion pipeline. Active when .context/kb/ exists (laid down by ctx init). The pipeline gives you evidence-tracked knowledge with confidence bands, folder-shaped topic pages, a source-coverage state machine, and per-session handovers that fold postdated closeouts.

    See the Build a Knowledge Base recipe for the full workflow. The editorial constitution lives at .context/ingest/KB-RULES.md.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ingest","level":3,"title":"/ctx-kb-ingest","text":"

    Mode-aware editorial pass. Declares its pass-mode (topic-page / triage / evidence-only) up front, scans the source-coverage ledger for adjacent incomplete topics, synthesizes prose into .context/kb/topics/<slug>/index.md, mints EV-### rows in evidence-index.md, runs a four-invariant completion circuit breaker, and writes a closeout under .context/ingest/closeouts/. Refuses on empty input.

    Wraps: ctx kb ingest, ctx kb topic new, the writer packages under internal/write/kb/.

    Trigger phrases: \"ingest the transcripts\", \"pull this into the kb\", \"add evidence from\"

    See also: Build a Knowledge Base, Typical KB Session, ctx kb CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ask","level":3,"title":"/ctx-kb-ask","text":"

    Q&A grounded in the KB. Cites EV-### rows; refuses to web-jump. When the KB cannot answer, opens a Q-### row in outstanding-questions.md rather than inventing. Refuses on empty question.

    Wraps: ctx kb ask, reads .context/kb/*.md

    Trigger phrases: \"does the kb say\", \"according to evidence\"

    See also: ctx kb CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-site-review","level":3,"title":"/ctx-kb-site-review","text":"

    Mechanical structural audit. Coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, refuses judgment calls that require evidence (those go through ingest).

    Wraps: ctx kb site-review

    Trigger phrases: \"audit the kb\", \"check kb for rot\"

    See also: ctx kb CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ground","level":3,"title":"/ctx-kb-ground","text":"

    External re-grounding pass. Reads .context/ingest/grounding-sources.md and refreshes each listed source. Refuses cleanly when the file is absent or empty.

    Wraps: ctx kb ground

    Trigger phrases: \"re-ground the kb\", \"check upstream\"

    See also: ctx kb CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-note","level":3,"title":"/ctx-kb-note","text":"

    Lightweight capture into .context/ingest/findings.md. Never writes to a topic page or evidence-index.md. Use for parking findings the next ingest pass should absorb.

    Wraps: ctx kb note \"<text>\"

    Trigger phrases: \"drop a note\", \"park this finding\"

    See also: ctx kb CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-handover","level":3,"title":"/ctx-handover","text":"

    Per-session handover artifact writer; the sub-mechanism that /ctx-wrap-up delegates to as its final step. Collects --summary (past tense) and --next (future tense, specific) and calls ctx handover write. Writes the handover to .context/handovers/<TS>-<slug>.md (timestamped so concurrent agent runs never overwrite). Folds postdated closeouts into a ## Folded closeouts section and physically archives the source closeouts under .context/archive/closeouts/ (closeouts are append-never-rewrite; archival moves bytes but does not modify them). --no-fold skips the fold for mid-session checkpoints.

    Mandatory tail of /ctx-wrap-up. Direct invocation is reserved for --no-fold mid-session checkpoints and recovery after an aborted session.

    Wraps: ctx handover write <title> --summary X --next Y

    See also: /ctx-wrap-up, Typical KB Session, Recover an Aborted KB Session, ctx handover CLI

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#project-specific-skills","level":2,"title":"Project-Specific Skills","text":"

    The ctx plugin ships the skills listed above. Teams can add their own project-specific skills to .claude/skills/ in the project root: These are separate from plugin-shipped skills and are scoped to the project.

    Project-specific skills follow the same format and are invoked the same way.

    Custom skills are not covered in this reference.

    ","path":["Reference","Skills"],"tags":[]},{"location":"reference/versions/","level":1,"title":"Version History","text":"","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#version-history","level":2,"title":"Version History","text":"

    Documentation snapshots for each release.

    Tap the corresponding view docs to view the docs as they were at that release.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.8.0 2026-03-23 view docs v0.6.0 2026-02-16 view docs v0.3.0 2026-02-07 view docs v0.2.0 2026-02-01 view docs v0.1.2 2026-01-27 view docs v0.1.1 2026-01-26 view docs v0.1.0 2026-01-25 view docs","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v080-the-architecture-release","level":3,"title":"v0.8.0: The Architecture Release","text":"

    MCP server for tool-agnostic AI integration. Memory bridge connecting Claude Code auto-memory to .context/. Complete CLI restructuring into cmd/ + core/ taxonomy. All user-facing strings externalized to YAML. fatih/color removed; two direct dependencies remain.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v060-the-integration-release","level":3,"title":"v0.6.0: The Integration Release","text":"

    Plugin architecture: hooks and skills converted from shell scripts to Go subcommands, shipped as a Claude Code marketplace plugin. Multi-tool hook generation for Cursor, Aider, Copilot, and Windsurf. Webhook notifications with encrypted URL storage.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v030-the-discipline-release","level":3,"title":"v0.3.0: The Discipline Release","text":"

    Journal static site generation via zensical. 49-skill audit and fix pass (positive framing, phantom reference removal, scope tightening). Context consolidation skill. golangci-lint v2 migration.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v020-the-archaeology-release","level":3,"title":"v0.2.0: The Archaeology Release","text":"

    Session journal system: ctx journal import converts Claude Code JSONL transcripts to browsable Markdown. Constants refactor with semantic prefixes (Dir*, File*, Filename*). CRLF handling for Windows compatibility.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v012","level":3,"title":"v0.1.2","text":"

    Default Claude Code permissions deployed on ctx init. Prompting guide published as a standalone documentation page.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v011","level":3,"title":"v0.1.1","text":"

    Bug fixes: hook schema key format corrected, JSON unicode escaping fixed in context file output.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v010-initial-release","level":3,"title":"v0.1.0: Initial Release","text":"

    CLI with 15 subcommands, 6 context file types (CONSTITUTION, TASKS, CONVENTIONS, ARCHITECTURE, DECISIONS, LEARNINGS), Makefile build system, and Claude Code hook integration.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#latest","level":2,"title":"Latest","text":"

    The main documentation always reflects the latest development version.

    For the most recent stable release, see v0.8.0.

    ","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#changelog","level":2,"title":"Changelog","text":"

    For detailed changes between versions, see the GitHub Releases page.

    ","path":["Reference","Version History"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"

    Security model, agent hardening, and vulnerability reporting.

    ","path":["Security"],"tags":[]},{"location":"security/#security-design","level":3,"title":"Security Design","text":"

    Trust model, what ctx does for security, permission hygiene, state file management, and the log-first audit trail principle. Read first to understand the security boundaries.

    ","path":["Security"],"tags":[]},{"location":"security/#securing-ai-agents","level":3,"title":"Securing AI Agents","text":"

    Defense in depth for unattended AI agents: five layers of protection, each with a known bypass, strength in combination.

    ","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":3,"title":"Reporting Vulnerabilities","text":"

    How to report a security issue: email, GitHub private reporting, PGP-encrypted submissions, what to include, and the response timeline.

    ","path":["Security"],"tags":[]},{"location":"security/agent-security/","level":1,"title":"Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#defense-in-depth-securing-ai-agents","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-problem","level":2,"title":"The Problem","text":"

    An unattended AI agent with unrestricted access to your machine is an unattended shell with unrestricted access to your machine.

    This is not a theoretical concern. AI coding agents execute shell commands, write files, make network requests, and modify project configuration. When running autonomously (overnight, in a loop, without a human watching), the attack surface is the full capability set of the operating system user account.

    The risk is not that the AI is malicious. The risk is that the AI is controllable: it follows instructions from context, and context can be poisoned.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#threat-model","level":2,"title":"Threat Model","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#how-agents-get-compromised","level":3,"title":"How Agents Get Compromised","text":"

    AI agents follow instructions from multiple sources: system prompts, project files, conversation history, and tool outputs. An attacker who can inject content into any of these sources can redirect the agent's behavior.

    Vector How it works Prompt injection via dependencies A malicious package includes instructions in its README, changelog, or error output. The agent reads these during installation or debugging and follows them. Prompt injection via fetched content The agent fetches a URL (documentation, API response, Stack Overflow answer) containing embedded instructions. Poisoned project files A contributor adds adversarial instructions to CLAUDE.md, .cursorrules, or .context/ files. The agent loads these at session start. Self-modification between iterations In an autonomous loop, the agent modifies its own configuration files. The next iteration loads the modified config with no human review. Tool output injection A command's output (error messages, log lines, file contents) contains instructions the agent interprets and follows.","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#what-can-a-compromised-agent-do","level":3,"title":"What Can a Compromised Agent Do","text":"

    Depends entirely on what permissions and access the agent has:

    Access level Potential impact Unrestricted shell Execute any command, install software, modify system files Network access Exfiltrate source code, credentials, or context files to external servers Docker socket Escape container isolation by spawning privileged sibling containers SSH keys Pivot to other machines, push to remote repositories, access production systems Write access to own config Disable its own guardrails for the next iteration","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-defense-layers","level":2,"title":"The Defense Layers","text":"

    No single layer is sufficient. Each layer catches what the others miss.

    Layer 1: Soft instructions     (CONSTITUTION.md, playbook)\nLayer 2: Application controls  (permission allowlist, tool restrictions)\nLayer 3: OS-level isolation    (user accounts, filesystem, containers)\nLayer 4: Network controls      (firewall rules, airgap)\nLayer 5: Infrastructure        (VM isolation, resource limits)\n
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"

    Markdown files like CONSTITUTION.md and the Agent Playbook tell the agent what to do and what not to do. These are probabilistic: the agent usually follows them, but there is no enforcement mechanism.

    What it catches: Most common mistakes. An agent that has been told \"never delete production data\" will usually not delete production data.

    What it misses: Prompt injection. A sufficiently crafted injection can override soft instructions. Long context windows dilute attention on rules stated early. Edge cases where instructions are ambiguous.

    Verdict: Necessary but not sufficient. Good for the common case. Do not rely on it for security boundaries.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"

    AI tool runtimes (Claude Code, Cursor, etc.) provide permission systems: tool allowlists, command restrictions, confirmation prompts.

    For Claude Code, ctx init writes both an allowlist and an explicit deny list into .claude/settings.local.json. The golden images live in internal/assets/permissions/:

    Allowlist (allow.txt): only these tools run without confirmation:

    Bash(ctx:*)\nSkill(ctx-convention-add)\nSkill(ctx-decision-add)\n... # all bundled ctx-* skills\n

    Deny list (deny.txt): these are blocked even if the agent requests them:

    # Dangerous operations\nBash(sudo *)\nBash(git push *)\nBash(git push)\nBash(rm -rf /*)\nBash(rm -rf ~*)\nBash(curl *)\nBash(wget *)\nBash(chmod 777 *)\n\n# Sensitive file reads\nRead(**/.env)\nRead(**/.env.*)\nRead(**/*credentials*)\nRead(**/*secret*)\nRead(**/*.pem)\nRead(**/*.key)\n\n# Sensitive file edits\nEdit(**/.env)\nEdit(**/.env.*)\n

    What it catches: The agent cannot run commands outside the allowlist, and the deny list blocks dangerous operations even if a future allowlist change were to widen access. If rm, curl, sudo, or docker are not allowed and sudo/curl/wget are explicitly denied, the agent cannot invoke them regardless of what any prompt says.

    What it misses: The agent can modify the allowlist itself. In an autonomous loop, if the agent writes to .claude/settings.local.json, and the next iteration loads the modified config, then the protection is effectively lost. The application enforces the rules, but the application reads the rules from files the agent can write.

    Verdict: Strong first layer. Must be combined with self-modification prevention (Layer 3).

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-3-os-level-isolation-deterministic-and-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Deterministic and Unbypassable)","text":"

    The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without CAP_NET_RAW cannot open raw sockets. These are kernel boundaries.

    Control Purpose Dedicated user account No sudo, no privileged group membership (docker, wheel, adm). The agent cannot escalate privileges. Filesystem permissions Project directory writable; everything else read-only or inaccessible. Agent cannot reach other projects, home directories, or system config. Immutable config files CLAUDE.md, .claude/settings.local.json, and .context/CONSTITUTION.md owned by a different user or marked immutable (chattr +i on Linux). The agent cannot modify its own guardrails.

    What it catches: Privilege escalation, self-modification, lateral movement to other projects or users.

    What it misses: Actions within the agent's legitimate scope. If the agent has write access to source code (which it needs to do its job), it can introduce vulnerabilities in the code itself.

    Verdict: Essential. This is the layer that makes the other layers trustworthy.

    OS-level isolation does not make the agent safe; it makes the other layers meaningful.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"

    An agent that cannot reach the internet cannot exfiltrate data. It also cannot ingest new instructions mid-loop from external documents, API responses, or hostile content.

    Scenario Recommended control Agent does not need the internet --network=none (container) or outbound firewall drop-all Agent needs to fetch dependencies Allow specific registries (npmjs.com, proxy.golang.org, pypi.org) via firewall rules. Block everything else. Agent needs API access Allow specific API endpoints only. Use an HTTP proxy with allowlisting.

    What it catches: Data exfiltration, phone-home payloads, downloading additional tools, and instruction injection via fetched content.

    What it misses: Nothing, if the agent genuinely does not need the network. The tradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"

    The strongest boundary is a separate machine (or something that behaves like one).

    The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.

    Containers (Docker, Podman):

    docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n

    Docker Socket Is Sudo Access

    Critical: never mount the Docker socket (/var/run/docker.sock).

    An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox.

    Use rootless Docker or Podman to eliminate this escalation path.

    Virtual machines: The strongest isolation. The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.

    Resource limits: CPU, memory, and disk quotas prevent a runaway agent from consuming all resources. Use ulimit, cgroup limits, or container resource constraints.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"

    A defense-in-depth setup for overnight autonomous runs:

    Layer Implementation Stops Soft instructions CONSTITUTION.md with \"never delete tests\", \"always run tests before committing\" Common mistakes (probabilistic) Application allowlist .claude/settings.local.json with explicit tool permissions Unauthorized commands (deterministic within runtime) Immutable config chattr +i on CLAUDE.md, .claude/, CONSTITUTION.md Self-modification between iterations Unprivileged user Dedicated user, no sudo, no docker group Privilege escalation Container --cap-drop=ALL --network=none, rootless, no socket mount Host escape, network exfiltration Resource limits --memory=4g --cpus=2, disk quotas Resource exhaustion

    Each layer is straightforward: The strength is in the combination.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#common-mistakes","level":2,"title":"Common Mistakes","text":"

    \"I'll just use --dangerously-skip-permissions\": This disables Layer 2 entirely. Without Layers 3-5, you have no protection at all. Only use this flag inside a properly isolated container or VM.

    \"The agent is sandboxed in Docker\": A Docker container with the Docker socket mounted, running as root, with --privileged, and full network access is not sandboxed. It is a root shell with extra steps.

    \"CONSTITUTION.md says not to do that\": Markdown is a suggestion. It works most of the time. It is not a security boundary. Do not use it as one.

    \"I reviewed the CLAUDE.md, it's fine\": The agent can modify CLAUDE.md during iteration N. Iteration N+1 loads the modified version. Unless the file is immutable, your review is stale.

    \"The agent only has access to this one project\": Does the project directory contain .env files, SSH keys, API tokens, or credentials? Does it have a .git/config with push access to a remote? Filesystem isolation means isolating what is in the directory too.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-security-considerations","level":2,"title":"Team Security Considerations","text":"

    When multiple developers share a .context/ directory, security considerations extend beyond single-agent hardening.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#code-review-for-context-files","level":3,"title":"Code Review for Context Files","text":"

    Treat .context/ changes like code changes. Context files influence agent behavior (a modified CONSTITUTION.md or CONVENTIONS.md changes what every agent on the team will do next session). Review them in PRs with the same scrutiny you apply to production code.

    Watch for:

    • Weakened constitutional rules (removed constraints, softened language)
    • New decisions that contradict existing ones without acknowledging it
    • Learnings that encode incorrect assumptions
    • Task additions that bypass the team's prioritization process
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#gitignore-patterns","level":3,"title":"Gitignore Patterns","text":"

    ctx init configures .gitignore automatically, but verify these patterns are in place:

    • Always gitignored: .ctx.key (encryption key), .context/logs/, .context/journal/
    • Team decision: scratchpad.enc (encrypted, safe to commit for shared scratchpad state); .gitignore if scratchpads are personal
    • Never committed: .env, credentials, API keys (enforced by drift secret detection)
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#multi-developer-context-sharing","level":3,"title":"Multi-Developer Context Sharing","text":"

    CONSTITUTION.md is the shared contract. All team members and their agents inherit it. Changes require team consensus, not unilateral edits.

    When multiple agents write to the same context files concurrently (e.g., two developers adding learnings simultaneously), git merge conflicts are expected. Resolution is typically additive: accept both additions. Destructive resolution (dropping one side) loses context.

    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-conventions-for-context-management","level":3,"title":"Team Conventions for Context Management","text":"

    Establish and document:

    • Who reviews context changes: Same reviewers as code, or a designated context owner?
    • How to resolve conflicting decisions: If two sessions record contradictory decisions, which wins? Default: the later one must explicitly supersede the earlier one with rationale.
    • Frequency of context maintenance: Weekly ctx drift checks, monthly consolidation passes, archival after each milestone.
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#checklist","level":2,"title":"Checklist","text":"

    Before running an unattended AI agent:

    • Agent runs as a dedicated unprivileged user (no sudo, no docker group)
    • Agent's config files are immutable or owned by a different user
    • Permission allowlist restricts tools to the project's toolchain
    • Container drops all capabilities (--cap-drop=ALL)
    • Docker socket is NOT mounted
    • Network is disabled or restricted to specific domains
    • Resource limits are set (memory, CPU, disk)
    • No SSH keys, API tokens, or credentials are accessible to the agent
    • Project directory does not contain .env or secrets files
    • Iteration cap is set (--max-iterations)
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#further-reading","level":2,"title":"Further Reading","text":"
    • Running an Unattended AI Agent: the ctx recipe for autonomous loops, including step-by-step permissions and isolation setup
    • Security: ctx's own trust model and vulnerability reporting
    • Autonomous Loops: full documentation of the loop pattern, prompt templates, and troubleshooting
    ","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/design/","level":1,"title":"Security Design","text":"

    How ctx thinks about security: trust boundaries, what the system does and does not do for you, the engineering principle behind the audit trail, and the permission hygiene workflow.

    For vulnerability disclosure, see Reporting Vulnerabilities.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#trust-model","level":2,"title":"Trust Model","text":"

    ctx operates within a single trust boundary: the local filesystem.

    The person who authors .context/ files is the same person who runs the agent that reads them. There is no remote input, no shared state, and no server component.

    This means:

    • ctx does not sanitize context files for prompt injection. This is a deliberate design choice, not an oversight. The files are authored by the developer who owns the machine: sanitizing their own instructions back to them would be counterproductive.
    • If you place adversarial instructions in your own .context/ files, your agent will follow them. This is expected behavior. You control the context; the agent trusts it.

    Shared Repositories

    In shared repositories, .context/ files should be reviewed in code review (the same way you would review CI/CD config or Makefiles). A malicious contributor could add harmful instructions to CONSTITUTION.md or TASKS.md.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#what-ctx-does-for-security","level":2,"title":"What ctx Does for Security","text":"

    ctx is designed with security in mind:

    • No secrets in context: The constitution explicitly forbids storing secrets, tokens, API keys, or credentials in .context/ files.
    • Local only: ctx runs entirely locally with no external network calls.
    • No code execution: ctx reads and writes Markdown files only; it does not execute arbitrary code.
    • Git-tracked: Core context files are meant to be committed, so they should never contain sensitive data. Exception: sessions/ and journal/ contain raw conversation data and should be gitignored.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#permission-hygiene","level":2,"title":"Permission Hygiene","text":"

    Claude Code evaluates permissions in deny → ask → allow order. ctx init automatically populates permissions.deny with rules that block dangerous operations before the allow list is ever consulted.

    Default deny rules block:

    • sudo, git push, rm -rf /, rm -rf ~, curl, wget, chmod 777
    • Read / Edit of .env, credentials, secrets, .pem, .key files

    Even with deny rules in place, the allow list accumulates one-off permissions over time. Periodically review for:

    • Destructive commands: git reset --hard, git clean -f, etc.
    • Config injection vectors: permissions that allow modifying files controlling agent behavior (CLAUDE.md, settings.local.json).
    • Broad wildcards: overly permissive patterns that pre-approve more than intended.

    For the full hygiene workflow, see the Claude Code Permission Hygiene recipe.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#state-file-management","level":2,"title":"State File Management","text":"

    Hook state files (throttle markers, prompt counters, pause markers) are stored in .context/state/, which is project-scoped and gitignored. State files are automatically managed by the hooks that create them; no manual cleanup is needed.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#log-first-audit-trail","level":2,"title":"Log-First Audit Trail","text":"

    The event log (.context/state/events.jsonl) is the authoritative record of what ctx hooks did during a session. Several audit-adjacent features depend on that log being trustworthy, not merely best-effort:

    • ctx event / ctx system view-events replays session history from the log.
    • Webhook notifications give operators a real-time signal that assumes every notification corresponds to a logged event.
    • Drift, freshness, and map-staleness checks count events over time and surface regressions.

    A log that silently drops entries while the rest of the system claims success is worse than no log at all: operators see a green TUI and a webhook notification and conclude \"it happened,\" even when the audit trail never landed. The codebase treats this as a correctness problem, not a UX polish problem.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#the-rule","level":3,"title":"The Rule","text":"

    Any code path that emits an observable side effect (webhook, stdout marker, throttle-file touch, state mutation) must append the corresponding event-log entry first and gate the side effect on the append succeeding. If the log write fails, the side effect must not fire.

    In code, this shape:

    if appendErr := event.Append(channel, msg, sessionID, ref); appendErr != nil {\n    return appendErr // do NOT send the webhook or touch the marker\n}\nif sendErr := notify.Send(channel, msg, sessionID, ref); sendErr != nil {\n    return sendErr\n}\n// downstream side effects (marker touch, stdout, etc.)\n

    The nudge.Relay helper in internal/cli/system/core/nudge enforces this for the common \"log + webhook\" pair. Hook Run functions that compose their own sequence (session_event, heartbeat, several check_* hooks) follow the same ordering explicitly.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#known-gaps","level":3,"title":"Known Gaps","text":"
    • Nudge webhooks have no log channel. nudge.EmitAndRelay sends a \"nudge\" notification before the \"relay\" event is logged. The nudge leg is fire-and-forget because no event-log channel records nudges today. A future refactor may add one; until then this is the one documented exception.
    • ctx agent --cooldown and ctx doctor propagate rather than gate. They surface real errors to the caller (usually Cobra) rather than deciding what to do with them locally. Editors that invoke these commands may display errors in an ugly way; the ugliness is the correct signal (something persisted is broken), not a defect to smooth over.
    • Verbose hook logs in core/log.Message stay best-effort. That logger captures per-hook activity (how many prompts, which percent, etc.) for debugging; it is NOT the event audit trail. Its failures go to stderr via log/warn.Warn rather than propagating, because losing an operational log line is not a correctness problem.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#background","level":3,"title":"Background","text":"

    The error returns on event.Append, io.AppendBytes, nudge.Relay, and cooldown.Active / cooldown.TouchTombstone were introduced as part of the resolver-tightening refactor. Before that change, most hook paths called these helpers and silently discarded their errors. The principle above was extracted from the observation that every user-visible correctness problem hit during the refactor traced back to some function saying \"this succeeded\" when the underlying write never landed.

    ","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#best-practices","level":2,"title":"Best Practices","text":"
    1. Review before committing: Always review .context/ files before committing.
    2. Use .gitignore: If you must store sensitive notes locally, add them to .gitignore.
    3. Drift detection: Run ctx drift to check for potential issues.
    4. Permission audit: Review .claude/settings.local.json after busy sessions.
    ","path":["Security","Security Design"],"tags":[]},{"location":"security/hub/","level":1,"title":"Hub Security Model","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#ctx-hub-security-model","level":1,"title":"ctx Hub: Security Model","text":"

    What the hub defends against, what it does not defend against, and the concrete mechanisms in play.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#threat-model","level":2,"title":"Threat Model","text":"

    The hub is designed for trusted cross-project knowledge sharing within a team or homelab. It assumes:

    • The hub host is trusted. Anyone with root on that box can read every entry ever published.
    • Network is semi-trusted. Hub traffic is gRPC over TCP; TLS is strongly recommended but not mandatory.
    • Client machines are trusted enough to hold a per-project client token. Losing a client token is roughly equivalent to losing an API key: scoped damage, not total compromise.
    • Entry content is not secret. Decisions, learnings, and conventions may be indexed by AI agents, rendered in docs, shared across projects. Do not push credentials or PII into the hub.

    The hub is not a secure messaging system, a secrets store, or a compliance-grade audit log. If your threat model needs those, use a dedicated tool and keep the hub for knowledge sharing.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#mechanisms","level":2,"title":"Mechanisms","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#bearer-tokens","level":3,"title":"Bearer Tokens","text":"

    All RPCs except Register require a bearer token in gRPC metadata. Two kinds of tokens exist:

    Kind Format Scope Lifetime Admin token ctx_adm_... Register new projects Manual rotate Client token ctx_cli_... Publish, Sync, Listen, Status Project lifetime

    Tokens are compared in constant time (crypto/subtle) to prevent timing oracles, and looked up via an O(1) hash map so the comparison cost does not depend on the total number of registered clients.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#client-side-encryption-at-rest","level":3,"title":"Client-Side Encryption at Rest","text":"

    .context/.connect.enc stores the client token and hub address, encrypted with AES-256-GCM using the same scheme the notification subsystem uses. The key is derived from ctx's local keyring (see internal/crypto).

    An attacker with read access to the project directory cannot learn the client token without also breaking ctx's local keyring.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#hub-side-token-storage","level":3,"title":"Hub-Side Token Storage","text":"

    Tokens Are Stored in Plaintext on the Hub Host

    <data-dir>/clients.json currently stores client tokens verbatim, not hashed. Anyone with read access to the hub's data directory sees every registered client's token and can impersonate any project that has ever registered.

    Mitigations today:

    • Run the hub as an unprivileged user and lock the data directory with chmod 700 <data-dir>.
    • Use the systemd unit in Operations, which enables ProtectSystem=strict, NoNewPrivileges=true, and a dedicated user.
    • Never expose <data-dir> over NFS, SMB, or shared filesystems.
    • Treat <data-dir> the same way you'd treat /etc/shadow: back it up encrypted, never check it into version control.

    Hashing clients.json and moving to keyring-backed storage is tracked as a follow-up in the PR #60 task group. Until that lands, assume a hub host compromise equals total hub compromise.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#input-validation","level":3,"title":"Input Validation","text":"

    Every published entry is validated before it touches the log:

    • Type must be one of: decision, learning, convention, task. Unknown types are rejected.
    • ID and Origin are required and non-empty.
    • Content size is capped at 1 MB. Reasonable for text, hostile for attempts to fill the disk.
    • Duplicate project registration is rejected; a client that replays an old Register call gets an error, not a second token.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#no-script-execution","level":3,"title":"No Script Execution","text":"

    The hub never interprets entry content. There is no expression language, no template evaluation, no Markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#audit-trail","level":3,"title":"Audit Trail","text":"

    entries.jsonl is append-only. Every accepted publish is recorded with the publishing project's origin tag and sequence number. Nothing is ever deleted by the hub; retention is managed manually by the operator (see log rotation).

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#what-the-hub-does-not-defend-against","level":2,"title":"What the Hub Does Not Defend Against","text":"
    • Untrusted entry senders. A client with a valid token can publish anything (within the 1 MB cap). There is no content validation beyond shape.
    • Denial of service from a registered client. A misbehaving client can publish until disk is full. Monitor entries.jsonl growth.
    • Network eavesdropping without TLS. Plain gRPC leaks entry content and tokens. Use a TLS-terminating reverse proxy (see Multi-machine recipe).
    • Host compromise. Root on the hub host = access to every entry and every token. Harden the host.
    • Accidental secret upload. The hub will happily fan out a decision containing an API key. Sanitize content before publishing.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#operational-hardening-checklist","level":2,"title":"Operational Hardening Checklist","text":"
    • Run the hub as an unprivileged user with NoNewPrivileges=true and ProtectSystem=strict (see the systemd unit in Operations).
    • Terminate TLS in front of the hub for anything beyond a trusted LAN.
    • Restrict the listen port with firewall rules to the client subnet only.
    • Back up <data-dir>/admin.token to a secrets manager; do not leave it in shell history.
    • Rotate the admin token when a team member with access leaves. Client tokens keep working across rotations.
    • Monitor entries.jsonl growth; alert on sudden spikes.
    • Run NTP on all clients to prevent entry-timestamp skew.
    • Do not publish from machines you do not trust.
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#responsible-disclosure","level":2,"title":"Responsible Disclosure","text":"

    Security issues in the hub follow the same process as the rest of ctx; see Reporting.

    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#see-also","level":2,"title":"See Also","text":"
    • ctx Hub Operations
    • ctx Hub failure modes
    • HA cluster recipe
    ","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/reporting/","level":1,"title":"Reporting Vulnerabilities","text":"

    Disclosure process for security issues in ctx. For the broader security model (trust boundaries, audit trail, permission hygiene), see Security Design.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"

    At ctx we take security very seriously.

    If you discover a security vulnerability in ctx, please report it responsibly.

    Do NOT open a public issue for security vulnerabilities.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#email","level":3,"title":"Email","text":"

    Send details to security@ctx.ist.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"
    1. Go to the Security tab;
    2. Click \"Report a Vulnerability\";
    3. Provide a detailed description.
    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#encrypted-reports-optional","level":3,"title":"Encrypted Reports (Optional)","text":"

    If your report contains sensitive details (proof-of-concept exploits, credentials, or internal system information), you can encrypt your message with our PGP key:

    • In-repo: SECURITY_KEY.asc
    • Keybase: keybase.io/alekhinejose
    # Import the key\ngpg --import SECURITY_KEY.asc\n\n# Encrypt your report\ngpg --armor --encrypt --recipient security@ctx.ist report.txt\n

    Encryption is optional. Unencrypted reports to security@ctx.ist or via GitHub Private Reporting are perfectly fine.

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#what-to-include","level":3,"title":"What to Include","text":"
    • Description of the vulnerability,
    • Steps to reproduce,
    • Potential impact,
    • Suggested fix (if any).
    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#attribution","level":2,"title":"Attribution","text":"

    We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).

    ","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#response-timeline","level":2,"title":"Response Timeline","text":"

    Open Source, Best-Effort Timelines

    ctx is a volunteer-maintained open source project.

    The timelines below are guidelines, not guarantees, and depend on contributor availability.

    We will address security reports on a best-effort basis and prioritize them by severity.

    Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"thesis/","level":1,"title":"Context as State","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#a-persistence-layer-for-human-ai-cognition","level":2,"title":"A Persistence Layer for Human-AI Cognition","text":"

    Volkan Özçelik - me@volkan.io

    February 2026

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#abstract","level":3,"title":"Abstract","text":"

    As AI tools evolve from code-completion utilities into reasoning collaborators, the knowledge that governs their behavior becomes as important as the code they produce; yet, that knowledge is routinely discarded at the end of every session.

    AI-assisted development systems assemble context at prompt time using heuristic retrieval from mutable sources: recent files, semantic search results, session history. These approaches optimize relevance at the moment of generation but do not persist the cognitive state that produced decisions. Reasoning is not reproducible, intent is lost across sessions, and teams cannot audit the knowledge that constrains automated behavior.

    This paper argues that context should be treated as deterministic, version-controlled state rather than as a transient query result. We ground this argument in three sources of evidence: a landscape analysis of 17 systems spanning AI coding assistants, agent frameworks, and knowledge stores; a taxonomy of five primitive categories that reveals irrecoverable architectural trade-offs; and an experience report from ctx, a persistence layer for AI-assisted development, which developed itself using its own persistence model across 389 sessions over 33 days. We define a three-tier model for cognitive state: authoritative knowledge, delivery views, and ephemeral state. Then we present six design invariants empirically validated by 56 independent rejection decisions observed across the analyzed landscape. We show that context determinism applies to assembly, not to model output, and that the curation cost this model requires is offset by compounding returns in reproducibility, auditability, and team cognition.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#1-introduction","level":2,"title":"1. Introduction","text":"

    The introduction of large language models into software development has shifted the primary interface from code execution to interactive reasoning. In this environment, the correctness of an output depends not only on source code but on the context supplied to the model: the conventions, decisions, architectural constraints, and domain knowledge that bound the space of acceptable responses.

    Current systems treat context as a query result assembled at the moment of interaction. A developer begins a session; the tool retrieves what it estimates to be relevant from chat history, recent files, and vector stores; the model generates output conditioned on this transient assembly; the session ends, and the context evaporates. The next session begins the cycle again.

    This model has improved substantially over the past year. CLAUDE.md files, Cursor rules, Copilot's memory system, and tools such as Mem0, Letta, and Kindex each address aspects of the persistence problem. Yet across 17 systems we analyzed spanning AI coding assistants, agent frameworks, autonomous coding agents, and purpose-built knowledge stores, no system provides all five of the following properties simultaneously: deterministic context assembly, human-readable file-based persistence, token-budgeted delivery, a single-binary core with zero required runtime dependencies for the persistence path, and local-first operation.

    This paper does not propose a universal replacement for retrieval-centric workflows. It defines a persistence layer (embodied in ctx (https://ctx.ist)) whose advantages emerge under specific operational conditions: when reproducibility is a requirement, when knowledge must outlive sessions and individuals, when teams require shared cognitive authority, or when offline operation is necessary.

    The trade-offs (manual curation cost, reduced automatic recall, coarser granularity) are intentional and mirror the trade-offs accepted by systems that favor reproducibility over convenience, such as reproducible builds and immutable infrastructure 1 6.

    The contribution is threefold: a three-tier model for cognitive state that resolves the ambiguity between authoritative knowledge and ephemeral session artifacts; six design invariants empirically grounded in a cross-system landscape analysis; and an experience report demonstrating that the model produces compounding returns when applied to its own development.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#2-the-limits-of-prompt-time-context","level":2,"title":"2. The Limits of Prompt-Time Context","text":"

    Prompt-time assembly pipelines typically consist of corpus selection, retrieval, ranking, and truncation. These pipelines are probabilistic and time-dependent, producing three failure modes that compound over the lifetime of a project.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#21-non-reproducibility","level":3,"title":"2.1 Non-Reproducibility","text":"

    If context is derived from mutable sources using heuristic ranking, identical requests at different times receive different inputs. A developer who asks \"What is our authentication strategy?\" on Tuesday may receive a different context window than the same question on Thursday: Not because the strategy changed, but because the retrieval heuristic surfaced different fragments.

    Reproducibility (the ability to reconstruct the exact inputs that produced a given output) is a foundational property of reliable systems. Its loss in AI-assisted development mirrors the historical evolution from ad-hoc builds to deterministic build systems 1 2. The build community learned that when outputs depend on implicit state (environment variables, system clocks, network-fetched dependencies), debugging becomes archaeology. The same principle applies when AI outputs depend on non-deterministic context retrieval.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#22-opaque-knowledge","level":3,"title":"2.2 Opaque Knowledge","text":"

    Embedding-based memory increases recall but reduces inspectability. When a vector store determines that a code snippet is \"similar\" to the current query, the ranking function is opaque: the developer cannot inspect why that snippet was chosen, whether a more relevant artifact was excluded, or whether the ranking will remain stable. This prevents deterministic debugging, policy auditing, and causal attribution (properties that information retrieval theory identifies as fundamental trade-offs of probabilistic ranking) 3.

    In practice, this opacity manifests as a compliance ceiling. In our experience developing a context management system (detailed in Section 7), soft instructions (directives that ask an AI agent to read specific files or follow specific procedures) achieve approximately 75-85% compliance. The remaining 15-25% represents cases where the agent exercises judgment about whether the instruction applies, effectively applying a second ranking function on top of the explicit directive. When 100% compliance is required, instruction is insufficient; the content must be injected directly, removing the agent's option to skip it.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#23-loss-of-intent","level":3,"title":"2.3 Loss of Intent","text":"

    Session transcripts record interaction but not cognition. A transcript captures what was said but not which assumptions were accepted, which alternatives were rejected, or which constraints governed the decision. The distinction matters: a decision to use PostgreSQL recorded as a one-line note (\"Use PostgreSQL\") teaches a model what was decided; a structured record with context, rationale, and consequences teaches it why (and why is what prevents the model from unknowingly reversing the decision in a future session) 4.

    Session transcripts provide history. Cognitive state requires something more: the persistent, structured representation of the knowledge required for correct decision-making.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#3-cognitive-state-a-three-tier-model","level":2,"title":"3. Cognitive State: A Three-Tier Model","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#31-definitions","level":3,"title":"3.1 Definitions","text":"

    We define cognitive state as the authoritative, persistent representation of the knowledge required for correct decision-making within a project. It is human-authored or human-ratified, versioned, inspectable, and reproducible. It is distinct from logs, transcripts, retrieval results, and model-generated summaries.

    Previous formulations of this idea have treated cognitive state as a monolithic concept. In practice, a three-tier model better captures the operational reality:

    Tier 1: Authoritative State: The canonical knowledge that the system treats as ground truth. In a concrete implementation, this corresponds to a set of human-curated files with defined schemas: a constitution (inviolable rules), conventions (code patterns), an architecture document (system structure), decision records (choices with rationale), learnings (captured experience), a task list (current work), a glossary (domain terminology), and an agent playbook (operating instructions). Each file has a single purpose, a defined lifecycle, and a distinct update frequency. Authoritative state is version-controlled alongside code and reviewed through the same mechanisms (diffs, pull requests, blame annotations).

    Tier 2: Delivery Views: Derived representations of authoritative state, assembled for consumption by a model. A delivery view is produced by a deterministic assembly function that takes the authoritative state, a token budget, and an inclusion policy as inputs and produces a context window as output. The same authoritative state, budget, and policy must always produce the same delivery view. Delivery views are ephemeral (they exist only for the duration of a session), but their construction is reproducible.

    Tier 3: Ephemeral State: Session transcripts, scratchpad notes, draft journal entries, and other artifacts that exist during or immediately after a session but are not authoritative. Ephemeral state is the raw material from which authoritative state may be extracted through human review, but it is never consumed directly by the assembly function.

    This three-tier model resolves confusion present in earlier formulations: the claim that AI output is a deterministic function of the repository state. The corrected claim is that context selection is deterministic (the delivery view is a function of authoritative state), but model output remains stochastic, conditioned on the deterministic context. Formally:

    delivery_view = assemble(authoritative_state, budget, policy)\noutput = model(delivery_view)   # stochastic\n

    The persistence layer's contribution is making assemble reproducible, not making model deterministic.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#32-separation-of-concerns","level":3,"title":"3.2 Separation of Concerns","text":"

    The decision to separate authoritative state into distinct files with distinct purposes is not cosmetic. Different types of knowledge have different lifecycles:

    Knowledge Type Update Frequency Read Frequency Load Priority Example Constitution Rarely Every session Always \"Never commit secrets to git\" Tasks Every session Session start Always \"Implement token budget CLI flag\" Conventions Weekly Before coding High \"All errors use structured logging with severity levels\" Decisions When decided When questioning Medium \"Use PostgreSQL over MySQL (see ADR-003)\" Learnings When learned When stuck Medium \"Hook scripts >50ms degrade interactive UX\" Architecture When changed When designing On demand \"Three-layer pipeline: ingest → enrich → assemble\" Journal Every session Rarely Never auto \"Session 247: Removed dead-end session copy layer\"

    A monolithic context file would force the assembly function to load everything or nothing. Separation enables progressive disclosure: the minimum context that matters for the current moment, with the option to load more when needed. A normal session loads the constitution, tasks, and conventions; a deep investigation loads decision history and journal entries from specific dates.

    The budget mechanism is the constraint that makes separation valuable. Without a budget, the default behavior is to load everything, which destroys the attention density that makes loaded context useful. With a budget, the assembly function must prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings (scored by recency). Entries that do not fit receive title-only summaries rather than being silently dropped (an application of the \"tell me what you don't know\" pattern identified independently by four systems in our landscape analysis).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#4-design-invariants","level":2,"title":"4. Design Invariants","text":"

    The following six invariants define the constraints that a cognitive state persistence layer must satisfy. They are not axioms chosen a priori; they are empirically grounded properties whose violation was independently identified as producing complexity costs across the 17 systems we analyzed.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-1-markdown-on-filesystem-persistence","level":3,"title":"Invariant 1: Markdown-on-Filesystem Persistence","text":"

    Context files must be human-readable, git-diffable, and editable with any text editor. No database. No binary storage.

    Validation: 11 independent rejection decisions across the analyzed landscape protected this property. Systems that adopted embedded records, binary serialization, or knowledge graphs as their core primitive consistently traded away the ability for a developer to run cat DECISIONS.md and understand the system's knowledge. The inspection cost of opaque storage compounds over the lifetime of a project: every debugging session, every audit, every onboarding conversation requires specialized tooling to access knowledge that could have been a text file.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-2-zero-runtime-dependencies","level":3,"title":"Invariant 2: Zero Runtime Dependencies","text":"

    The tool must work with no installed runtimes, no running services, and no API keys for core functionality.

    Validation: 13 independent rejection decisions protected this property (the most frequently defended invariant). Systems that required databases (PostgreSQL, SQLite, Redis), embedding models, server daemons, container runtimes, or cloud APIs for core operation introduced failure modes proportional to their dependency count. A persistence layer that depends on infrastructure is not a persistence layer; it is a service. Services have uptime requirements, version compatibility matrices, and operational costs that simple file operations do not.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-3-deterministic-context-assembly","level":3,"title":"Invariant 3: Deterministic Context Assembly","text":"

    The same files plus the same budget must produce the same output. No embedding-based retrieval, no LLM-driven selection, no wall-clock-dependent scoring in the assembly path.

    Validation: 6 independent rejection decisions protected this property. Non-deterministic assembly (whether from embedding variance, LLM-based selection, or time-dependent scoring) destroys the ability to reproduce a context window and therefore to diagnose why a model produced a given output. Determinism in the assembly path is what makes the persistence layer auditable.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-4-human-authority-over-persistent-state","level":3,"title":"Invariant 4: Human Authority over Persistent State","text":"

    The agent may propose changes to context files but must not unilaterally modify them. All persistent changes go through human-reviewable git commits.

    Validation: 6 independent rejection decisions protected this property. Systems that allowed agents to self-modify their memory (writing freeform notes, auto-pruning old entries, generating summaries as ground truth) consistently produced lower-quality persistent context than systems that enforced human review. Structure is a feature, not a limitation: across the landscape, the pattern \"structured beats freeform\" was independently discovered by four systems that evolved from freeform LLM summaries to typed schemas with required fields.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-5-local-first-air-gap-capable","level":3,"title":"Invariant 5: Local-First, Air-Gap Capable","text":"

    Core functionality must work offline with no network access. Cloud services may be used for optional features but never for core context management.

    Validation: 7 independent rejection decisions protected this property. Infrastructure-dependent memory systems cannot operate in classified environments, isolated networks, or constrained-environment scenarios. A filesystem-native model continues to function under all conditions where the repository is accessible.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-6-no-default-telemetry","level":3,"title":"Invariant 6: No Default Telemetry","text":"

    Any analytics, if ever added, must be strictly opt-in.

    Validation: 4 independent rejection decisions protected this property. Default telemetry erodes the trust model that a persistence layer depends on. If developers must trust the system with their architectural decisions, operational learnings, and project constraints, the system cannot simultaneously be reporting usage data to external services.

    These six invariants collectively define a design space. Each feature proposal can be evaluated against them: a feature that violates any invariant is rejected regardless of how many other systems implement it. The discipline of constraint (refusing to add capabilities that compromise foundational properties) is itself an architectural contribution. Across the 17 analyzed systems, 56 patterns were explicitly rejected for violating these invariants. The rejection count per invariant (11, 13, 6, 6, 7, 4) provides a rough measure of each property's vulnerability to architectural erosion. A representative sample of these rejections is provided in Appendix A.1

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#5-landscape-analysis","level":2,"title":"5. Landscape Analysis","text":"

    The 17 systems were selected to cover the architectural design space rather than to achieve completeness. Each included system satisfies three criteria: it represents a distinct architectural primitive for AI-assisted development, it is actively maintained or widely referenced, and it provides sufficient public documentation or source code for architectural inspection. The goal was to ensure that every major category of primitive (document, embedded record, state snapshot, event/message, construction/derivation) was represented by multiple systems, enabling cross-system pattern detection.

    The resulting set spans six categories: AI coding assistants (Continue, Sourcegraph/Cody, Aider, Claude Code), AI agent frameworks (CrewAI, AutoGen, LangGraph, LlamaIndex, Letta/MemGPT), autonomous coding agents (OpenHands, Sweep), session provenance tools (Entire), data versioning systems (Dolt, Pachyderm), pipeline/build systems (Dagger), and purpose-built knowledge stores (QubicDB, Kindex). Each system was analyzed from its source code and documentation, producing 34 individual analysis artifacts (an architectural profile and a set of insights per system) that yielded 87 adopt/adapt recommendations, 56 explicit rejection decisions, and 52 watch items.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#51-primitive-taxonomy","level":3,"title":"5.1 Primitive Taxonomy","text":"

    Every system in the AI-assisted development landscape operates on a core primitive: an atomic unit around which the entire architecture revolves. Our analysis of 17 systems reveals five categories of primitives, each making irrecoverable trade-offs:

    Group A: Document/File Primitives: Human-readable documents as the primary unit. Documents are authored by humans, version-controlled in git, and consumed by AI tools. The invariant of this group is that the primitive is always human-readable and version-controllable with standard tools. Three systems participate in this pattern: the system described in this paper as a pure expression, and Continue (via its rules directory) and Claude Code (via CLAUDE.md files) as partial participants: both use document-based context as an input but organize around different core primitives.

    Group B: Embedded Record Primitives: Vector-embedded records stored with numerical embeddings for similarity search, metadata for filtering, and scoring mechanisms for ranking. Five systems use this approach (LlamaIndex, CrewAI, Letta/MemGPT, QubicDB, Kindex). The invariant is that the primitive requires an embedding model or vector database for core operations: a dependency that precludes offline and air-gapped use.

    Group C: State Snapshot Primitives: Point-in-time captures of the complete system state. The invariant is that any past state can be reconstructed at any historical point. Three systems use this approach (LangGraph, Entire, Dolt).

    Group D: Event/Message Primitives: Sequential events or messages forming an append-only log with causal relationships. Four systems use this approach (OpenHands, AutoGen, Claude Code, Sweep). The invariant is temporal ordering and append-only semantics.

    Group E: Construction/Derivation Primitives: Derived or constructed values that encode how they were produced. The invariant is that the primitive is a function of its inputs; re-executing the same inputs produces the same primitive. Three systems use this approach (Dagger, Pachyderm, Aider).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#52-comparison-matrix","level":3,"title":"5.2 Comparison Matrix","text":"

    The five primitive categories differ along seven dimensions:

    Property Document Embedded Record State Snapshot Event/Message Construction Human-readable Yes No Varies Partially No Version-controllable Yes No Varies Yes Yes Queryable by meaning No Yes No No No Rewindable Via git No Yes Yes (replay) Yes Deterministic Yes No Yes Yes Yes Zero-dependency Yes No Varies Varies Varies Offline-capable Yes No Varies Varies Yes

    The document primitive is the only one that simultaneously satisfies human-readability, version-controllability, determinism, zero dependencies, and offline capability. This is not because documents are superior in general (embedded records provide semantic queryability that documents lack) but because the combination of all five properties is what the persistence layer requires. The choice between primitive categories is not a matter of capability but of which properties are considered invariant.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#53-convergent-patterns","level":3,"title":"5.3 Convergent Patterns","text":"

    Across the 17 analyzed systems, six design patterns were independently discovered. These convergent patterns carry extra validation weight because they emerged from different problem spaces:

    Pattern 1: \"Tell me what you don't know\": When context is incomplete, explicitly communicate to the model what information is missing and what confidence level the provided context represents. Four systems independently converged on this pattern: inserting skip markers, tracking evidence gaps, annotating provenance, or naming output quality tiers.

    Pattern 2: \"Freshness matters\": Information relevance decreases over time. Three systems independently chose exponential decay with different half-lives (30 days, 90 days, and LRU ordering). Static priority ordering with no time dimension leaves relevant recent knowledge at the same priority as stale entries. This pattern is in productive tension with the persistence model's emphasis on determinism: the claim is not that time-dependence is irrelevant, but that it belongs in the curation step (a human deciding to consolidate or archive stale entries) rather than in the assembly function (an algorithm silently down-ranking entries based on age).

    Pattern 3: \"Content-address everything\": Compute a hash of content at creation time for deduplication, cache invalidation, integrity verification, and change detection. Five systems independently implement content hashing, each discovering it solves different problems 5.

    Pattern 4: \"Structured beats freeform\": When capturing knowledge or session state, a structured schema with required fields produces more useful data than freeform text. Four systems evolved from freeform summaries to typed schemas: one moving from LLM-generated prose to a structured condenser with explicit fields for completed tasks, pending tasks, and files modified.

    Pattern 5: \"Protocol convergence\": The Model Context Protocol (MCP) is emerging as a standard tool integration layer. Nine of 17 systems support it, spanning every category in the analysis. MCP's significance for the persistence model is that it provides a transport mechanism for context delivery without dictating how context is stored or assembled. This makes the approach compatible with both retrieval-centric and persistence-centric architectures.

    Pattern 6: \"Human-in-the-loop for memory\": Critical memory decisions should involve human judgment. Fully automated memory management produces lower-quality persistent context than human-reviewed systems. Four systems independently converged on variants of this pattern: ceremony-based consolidation, interrupt/resume for human input, confirmation mode for high-risk actions, and separated \"think fast\" vs. \"think slow\" processing paths.

    Pattern 6 directly validates the ceremony model described in this paper. The persistence layer requires human curation not because automation is impossible, but because the quality of persistent knowledge degrades when the curation step is removed. The improvement opportunity is to make curation easier, not to automate it away.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#6-worked-example-architectural-decision-under-two-models","level":2,"title":"6. Worked Example: Architectural Decision under Two Models","text":"

    We now instantiate the three-tier model in a concrete system (ctx) and illustrate the difference between prompt-time retrieval and cognitive state persistence using a real scenario from its development.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#61-the-problem","level":3,"title":"6.1 The Problem","text":"

    During development, the system accumulated three overlapping storage layers for session data: raw transcripts (owned by the AI tool), session copies (JSONL copies plus context snapshots), and enriched journal entries (Markdown summaries). The middle layer (session copies) was a dead-end write sink. An auto-save hook copied transcripts to a directory that nothing read from, because the journal pipeline already read directly from the raw transcripts. Approximately 15 source files, a shell hook, 20 configuration constants, and 30 documentation references supported infrastructure with no consumers.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#62-prompt-time-retrieval-model","level":3,"title":"6.2 Prompt-Time Retrieval Model","text":"

    In a retrieval-based system, the decision to remove the middle layer depends on whether the retrieval function surfaces the relevant context:

    The developer asks: \"Should we simplify the session storage?\" The retrieval system must find and rank the original discussion thread where the three layers were designed, the usage statistics showing zero reads from the middle layer, the journal pipeline documentation showing it reads from raw transcripts directly, and the dependency analysis showing 15 files, a hook, and 30 doc references. If any of these fragments are not retrieved (because they are in old chat history, because the embedding similarity score is low, or because the token budget was consumed by more recent but less relevant context), the model may recommend preserving the middle layer, or may not realize it exists.

    Six months later, a new team member asks the same question. The retrieval results will differ: the original discussion has aged out of recency scoring, the usage statistics are no longer in recent history, and the model may re-derive the answer or arrive at a different conclusion.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#63-cognitive-state-model","level":3,"title":"6.3 Cognitive State Model","text":"

    In the persistence model, the decision is recorded as a structured artifact at write time:

    ## [2026-02-11] Remove .context/sessions/ storage layer\n\n**Status**: Accepted\n\n**Context**: The session/recall/journal system had three overlapping\nstorage layers. The recall pipeline reads directly from raw transcripts,\nmaking .context/sessions/ a dead-end write sink that nothing reads from.\n\n**Decision**: Remove .context/sessions/ entirely. Two stores remain:\nraw transcripts (global, tool-owned) and enriched journal\n(project-local).\n\n**Rationale**: Dead-end write sinks waste code surface, maintenance\neffort, and user attention. The recall pipeline already proved that\nreading directly from raw transcripts is sufficient. Context snapshots\nare redundant with git history.\n\n**Consequence**: Deleted internal/cli/session/ (15 files), removed\nauto-save hook, removed --auto-save from watch, removed pre-compact\nauto-save, removed /ctx-save skill, updated ~45 documentation files.\nFour earlier decisions superseded.\n

    This artifact is:

    • Deterministically included in every subsequent session's delivery view (budget permitting, with title-only fallback if budget is exceeded)
    • Human-readable and reviewable as a diff in the commit that introduced it
    • Permanent: it persists in version control regardless of retrieval heuristics
    • Causally linked: it explicitly supersedes four earlier decisions, creating an auditable chain

    When the new team member asks \"Why don't we store session copies?\" six months later, the answer is the same artifact, at the same revision, with the same rationale. The reasoning is reconstructible because it was persisted at write time, not discovered at query time.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#64-the-diff-when-policy-changes","level":3,"title":"6.4 The Diff When Policy Changes","text":"

    If a future requirement re-introduces session storage (for example, to support multi-agent session correlation), the change appears as a diff to the decision record:

    - **Status**: Accepted\n+ **Status**: Superseded by [2026-08-15] Reintroduce session storage\n+ for multi-agent correlation\n

    The new decision record references the old one, creating a chain of reasoning visible in git log. In the retrieval model, the old decision would simply be ranked lower over time and eventually forgotten.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#7-experience-report-a-system-that-designed-itself","level":2,"title":"7. Experience Report: A System That Designed Itself","text":"

    The persistence model described in this paper was developed and tested by using it on its own development. Over 33 days and 389 sessions, the system's context files accumulated a detailed record of decisions made, reversed, and consolidated: providing quantitative and qualitative evidence for the model's properties.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#71-scale-and-structure","level":3,"title":"7.1 Scale and Structure","text":"

    The development produced the following authoritative state artifacts:

    • 8 consolidated decision records covering 24 original decisions spanning context injection architecture, hook design, task management, security, agent autonomy, and webhook systems
    • 18 consolidated learning records covering 75 original observations spanning agent compliance, hook behavior, testing patterns, documentation drift, and tool integration
    • A constitution with 13 inviolable rules across 4 categories (security, quality, process, context preservation)
    • 389 enriched journal entries providing a complete session-level audit trail

    The consolidation ratio (24 decisions compressed to 8 records, 75 learnings compressed to 18) illustrates the curation cost and its return: authoritative state becomes denser and more useful over time as related entries are merged, contradictions are resolved, and superseded decisions are marked.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#72-architectural-reversals","level":3,"title":"7.2 Architectural Reversals","text":"

    Three architectural reversals during development provide evidence that the persistence model captures and communicates reasoning effectively:

    Reversal 1: The two-tier persistence model: The original design included a middle storage tier for session copies. After 21 days of development, the middle tier was identified as a dead-end write sink (described in Section 6). The decision record captured the full context, and the removal was executed cleanly: 15 source files, a shell hook, and 45 documentation references. The pattern of a \"dead-end write sink\" was subsequently observed in 7 of 17 systems in our landscape analysis that store raw transcripts alongside structured context.

    Reversal 2: The prompt-coach hook: An early design included a hook that analyzed user prompts and offered improvement suggestions. After deployment, the hook produced zero useful tips, its output channel was invisible to users, and it accumulated orphan temporary files. The hook was removed, and the decision record captured the failure mode for future reference.

    Reversal 3: The soft-instruction compliance model: The original context injection strategy relied on soft instructions: directives asking the AI agent to read specific files. After measuring compliance across multiple sessions, we found a consistent 75-85% compliance ceiling. The revised strategy injects content directly, bypassing the agent's judgment about whether to comply. The learning record captures the ceiling measurement and the rationale for the architectural change.

    Each reversal was captured as a structured decision record with context, rationale, and consequences. In a retrieval-based system, these reversals would exist only in chat history, discoverable only if the retrieval function happens to surface them. In the persistence model, they are permanent, indexable artifacts that inform future decisions.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#73-compliance-ceiling","level":3,"title":"7.3 Compliance Ceiling","text":"

    The 75-85% compliance ceiling for soft instructions is the most operationally significant finding from the experience report. It means that any context management strategy relying on agent compliance with instructions (\"read this file,\" \"follow this convention,\" \"check this list\") has a hard ceiling on reliability.

    The root cause is structural: the instruction \"don't apply judgment\" is itself evaluated by judgment. When an agent receives a directive to read a file, it first assesses whether the directive is relevant to the current task (and that assessment is the judgment the directive was trying to prevent).

    The architectural response maps directly to the formal model defined in Section 3.1. Content requiring 100% compliance is included in authoritative_state and injected by the deterministic assemble function, bypassing the agent entirely. Content where 80% compliance is acceptable is delivered as instructions within the delivery view. The three-tier architecture makes this distinction explicit: authoritative state is injected; delivery views are assembled deterministically; ephemeral state is available but not pushed.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#74-compounding-returns","level":3,"title":"7.4 Compounding Returns","text":"

    Over 33 days, we observed a qualitative shift in the development experience. Early sessions (days 1-7) spent significant time re-establishing context: explaining conventions, re-stating constraints, re-deriving past decisions. Later sessions (days 25-33) began with the agent loading curated context and immediately operating within established constraints, because the constraints were in files rather than in chat history.

    This compounding effect (where each session's context curation improves all subsequent sessions) is the primary return on the curation investment. The cost is borne once (writing a decision record, capturing a learning, updating the task list); the benefit is collected on every subsequent session load.

    The effect is analogous to compound interest in financial systems: the knowledge base grows not linearly with effort but with increasing marginal returns as new knowledge interacts with existing context. A learning captured on day 5 prevents a mistake on day 12, which avoids a debugging session that would have consumed a day 12 session, freeing that session for productive work that generates new learnings. The growth is not literally exponential (it is bounded by project scope and subject to diminishing returns as the knowledge base matures), but within the observed 33-day window, the returns were consistently accelerating.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#75-scope-and-generalizability","level":3,"title":"7.5 Scope and Generalizability","text":"

    This experience report is self-referential by design: the system was developed using its own persistence model. This circularity strengthens the internal validity of the findings (the model was stress-tested under authentic conditions) but limits external generalizability. The two-week crossover point was observed on a single project of moderate complexity with a small team already familiar with the model's assumptions. Whether the same crossover holds for larger teams, for codebases with different characteristics, or for teams adopting the model without having designed it remains an open empirical question. The quantitative claims in this section should be read as existence proofs (demonstrating that the model can produce compounding returns) rather than as predictions about specific adoption scenarios.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#8-situating-the-persistence-layer","level":2,"title":"8. Situating the Persistence Layer","text":"

    The persistence layer occupies a specific position in the stack of AI-assisted development:

    Application Logic\nAI Interaction / Agents\nContext Retrieval Systems\nCognitive State Persistence Layer\nVersion Control / Storage\n

    Current systems innovate primarily in the retrieval layer (improving how context is discovered, ranked, and delivered at query time). The persistence layer sits beneath retrieval and above version control. Its role is to maintain the authoritative state that retrieval systems may query but do not own. The relationship is complementary: retrieval answers \"What in the corpus might be relevant?\"; cognitive state answers \"What must be true for this system to operate correctly?\" A mature system uses both: retrieval for discovery, persistence for authority.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#9-applicability-and-trade-offs","level":2,"title":"9. Applicability and Trade-Offs","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#91-when-to-use-this-model","level":3,"title":"9.1 When to Use This Model","text":"

    A cognitive state persistence layer is most appropriate when:

    Reproducibility is a requirement: If a system must be able to answer \"Why did this output occur, and can it be produced again?\" then deterministic, version-controlled context becomes necessary. This is relevant in regulated environments, safety-critical systems, long-lived infrastructure, and security-sensitive deployments.

    Knowledge must outlive sessions and individuals: Projects with multi-year lifetimes accumulate architectural decisions, domain interpretations, and operational policy. If this knowledge is stored only in chat history, issue trackers, and institutional memory, it decays. The persistence model converts implicit knowledge into branchable, reviewable artifacts.

    Teams require shared cognitive authority: In collaborative environments, correctness depends on a stable answer to \"What does the system believe to be true?\" When this answer is derived from retrieval heuristics, authority shifts to ranking algorithms. When it is versioned and human-readable, authority remains with the team.

    Offline or air-gapped operation is required: Infrastructure-dependent memory systems cannot operate in classified environments, isolated networks, or constrained-environment scenarios.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#92-when-not-to-use-this-model","level":3,"title":"9.2 When Not to Use This Model","text":"

    Zero-configuration personal workflows: For short-lived or exploratory tasks, the cost of explicit knowledge curation outweighs its benefits. Heuristic retrieval is sufficient when correctness is non-critical, outputs are disposable, and historical reconstruction is unnecessary.

    Maximum automatic recall from large corpora: Vector retrieval systems provide superior performance when the primary task is searching vast, weakly structured information spaces. The persistence model assumes that what matters can be decided and that this decision is valuable to record.

    Fully autonomous agent architectures: Agent runtimes that generate and discard state continuously, optimizing for local goal completion, do not benefit from a model that centers human ratification of knowledge.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#93-incremental-adoption","level":3,"title":"9.3 Incremental Adoption","text":"

    The transition does not require full system replacement. An incremental path:

    Step 1: Record decisions as versioned artifacts: Instead of allowing conclusions to remain in discussion threads, persist them in reviewable form with context, rationale, and consequences 4. This alone converts ephemeral reasoning into the cognitive state.

    Step 2: Make inclusion deterministic: Define explicit assembly rules. Retrieval may still exist, but it is no longer authoritative.

    Step 3: Move policy into cognitive state: When system behavior depends on stable constraints, encode those constraints as versioned knowledge. Behavior becomes reproducible.

    Step 4: Optimize assembly, not retrieval: Once the authoritative layer exists, performance improvements come from budgeting, caching, and structural refinement rather than from improving ranking heuristics.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#94-the-curation-cost","level":3,"title":"9.4 The Curation Cost","text":"

    The primary objection to this model is the cost of explicit knowledge curation. This cost is real. Writing a structured decision record takes longer than letting a chatbot auto-summarize a conversation. Maintaining a glossary requires discipline. Consolidating 75 learnings into 18 records requires judgment.

    The response is not that the cost is negligible but that it is amortized. A decision record written once is loaded hundreds of times. A learning captured today prevents repeated mistakes across all future sessions. The curation cost is paid once; the benefit compounds.

    The experience report provides rough order-of-magnitude numbers. Across 389 sessions over 33 days, curation activities (writing decision records, capturing learnings, updating the task list, consolidating entries) averaged approximately 3-5 minutes per session. In early sessions (days 1-7), before curated context existed, re-establishing context consumed approximately 10-15 minutes per session: re-explaining conventions, re-stating architectural constraints, re-deriving decisions that had been made but not persisted. By the final week (days 25-33), the re-explanation overhead had dropped to near zero: the agent loaded curated context and began productive work immediately.

    At ~12 sessions per day, the curation cost was roughly 35-60 minutes daily. The re-explanation cost in the first week was roughly 120-180 minutes daily. By the third week, that cost had fallen to under 15 minutes daily while the curation cost remained stable. The crossover (where cumulative curation cost was exceeded by cumulative time saved) occurred around day 10. These figures are approximate and derived from a single project with a small team already familiar with the model; the crossover point will vary with project complexity, team size, and curation discipline.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#10-future-work","level":2,"title":"10. Future Work","text":"

    Several directions are compatible with the model described here:

    Section-level deterministic budgeting: Current assembly operates at file granularity. Section-level budgeting would allow finer-grained control (including specific decision records while excluding others within the same file) without sacrificing determinism.

    Causal links between decisions: The experience report shows that decisions frequently reference earlier decisions (superseding, extending, or qualifying them). Formal causal links would enable traversal of the decision graph and automatic detection of orphaned or contradictory constraints.

    Content-addressed context caches: Five systems in our landscape analysis independently discovered that content hashing provides cache invalidation, integrity verification, and change detection. Applying content addressing to the assembly output would enable efficient cache reuse when the authoritative state has not changed.

    Conditional context inclusion: Five systems independently suggest that context entries could carry activation conditions (file patterns, task keywords, or explicit triggers) that control whether they are included in a given assembly. This would reduce the per-session budget cost of large knowledge bases without sacrificing determinism.

    Provenance metadata: Linking context entries to the sessions, decisions, or learnings that motivated them would strengthen the audit trail. Optional provenance fields on Markdown entries (session identifier, cause reference, motivation) would be lightweight and compatible with the existing file-based model.

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#11-conclusion","level":2,"title":"11. Conclusion","text":"

    AI-assisted development has treated context as a \"query result\" assembled at the moment of interaction, discarded at the session end. This paper identifies a complementary layer: the persistence of authoritative cognitive state as deterministic, version-controlled artifacts.

    The contribution is grounded in three sources of evidence. A landscape analysis of 17 systems reveals five categories of primitives and shows that no existing system provides the combination of human-readability, determinism, zero dependencies, and offline capability that the persistence layer requires. Six design invariants, validated by 56 independent rejection decisions, define the constraints of the design space. An experience report over 389 sessions and 33 days demonstrates compounding returns: later sessions start faster, decisions are not re-derived, and architectural reversals are captured with full context.

    The core claim is this: persistent cognitive state enables causal reasoning across time. A system built on this model can explain not only what is true, but why it became true and when it changed.

    When context is the state:

    • Reasoning is reproducible: the same authoritative state, budget, and policy produce the same delivery view.
    • Knowledge is auditable: decisions are traceable to explicit artifacts with context, rationale, and consequences.
    • Understanding compounds: each session's curation improves all subsequent sessions.

    The choice between retrieval-centric workflows and a persistence layer is not a matter of capability but of time horizon. Retrieval optimizes for relevance at the moment of interaction. Persistence optimizes for the durability of understanding across the lifetime of a project.

    🐸🖤 \"Gooood... let the deterministic context flow through the repository...\" - Kermit the Sidious, probably

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#appendix-a-representative-rejection-decisions","level":2,"title":"Appendix A: Representative Rejection Decisions","text":"

    The 56 rejection decisions referenced in Section 4 were cataloged across all 17 system analyses, grouped by the invariant they would violate. This appendix provides a representative sample (two per invariant) to illustrate the methodology.

    Invariant 1: Markdown-on-Filesystem (11 rejections): CrewAI's vector embedding storage was rejected because embeddings are not human-readable, not git-diff-friendly, and require external services. Kindex's knowledge graph as core primitive was rejected because it requires specialized commands to inspect content that could be a text file (kin show <id> vs. cat DECISIONS.md).

    Invariant 2: Zero Runtime Dependencies (13 rejections): Letta/MemGPT's PostgreSQL-backed architecture was rejected because it conflicts with local-first, no-database, single-binary operation. Pachyderm's Kubernetes-based distributed architecture was rejected as the antithesis of a single-binary design for a tool that manages text files.

    Invariant 3: Deterministic Assembly (6 rejections): LlamaIndex's embedding-based retrieval as the primary selection mechanism was rejected because it destroys determinism, requires an embedding model, and removes human judgment from the selection process. QubicDB's wall-clock-dependent scoring was rejected because it directly conflicts with the \"same inputs produce same output\" property.

    Invariant 4: Human Authority (6 rejections): Letta/MemGPT's agent self-modification of memory was rejected as fundamentally opposed to human-curated persistence. Claude Code's unstructured auto-memory (where the agent writes freeform notes) was rejected because structured files with defined schemas produce higher-quality persistent context than unconstrained agent output.

    Invariant 5: Local-First / Air-Gap Capable (7 rejections): Sweep's cloud-dependent architecture was rejected as fundamentally incompatible with the local-first, offline-capable model. LangGraph's managed cloud deployment was rejected because cloud dependencies for core functionality violate air-gap capability.

    Invariant 6: No Default Telemetry (4 rejections): Continue's telemetry-by-default (PostHog) was rejected because it contradicts the local-first, privacy-respecting trust model. CrewAI's global telemetry on import (Scarf tracking pixel) was rejected because it violates user trust and breaks air-gap capability.

    The remaining 9 rejections did not map to a specific invariant but were rejected on other architectural grounds: for example, Aider's full-file-content-in-context approach (which defeats token budgeting), AutoGen's multi-agent orchestration as core primitive (scope creep), and Claude Code's 30-day transcript retention limit (institutional knowledge should have no automatic expiration).

    ","path":["The Thesis"],"tags":[]},{"location":"thesis/#references","level":2,"title":"References","text":"
    1. Reproducible Builds Project, \"Reproducible Builds: Increasing the Integrity of Software Supply Chains\", 2017. https://reproducible-builds.org/docs/definition/ ↩↩↩

    2. S. McIntosh et al., \"The Impact of Build System Evolution on Software Quality\", ICSE, 2015. https://doi.org/10.1109/ICSE.2015.70 ↩

    3. C. Manning, P. Raghavan, H. Schütze, Introduction to Information Retrieval, Cambridge University Press, 2008. https://nlp.stanford.edu/IR-book/ ↩

    4. M. Nygard, \"Documenting Architecture Decisions\", Cognitect Blog, 2011. https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions ↩↩

    5. L. Torvalds et al., Git Internals - Git Objects (content-addressed storage concepts). https://git-scm.com/book/en/v2/Git-Internals-Git-Objects ↩

    6. Kief Morris, Infrastructure as Code, O'Reilly, 2016. ↩

    7. J. Kreps, \"The Log: What every software engineer should know about real-time data's unifying abstraction\", 2013. https://engineering.linkedin.com/distributed-systems/log ↩

    8. P. Hunt et al., \"ZooKeeper: Wait-free coordination for Internet-scale systems\", USENIX ATC, 2010. https://www.usenix.org/legacy/event/atc10/tech/full_papers/Hunt.pdf ↩

    ","path":["The Thesis"],"tags":[]}]} \ No newline at end of file diff --git a/site/security/hub/index.html b/site/security/hub/index.html index b798b46ab..3bf8d77f5 100644 --- a/site/security/hub/index.html +++ b/site/security/hub/index.html @@ -1616,7 +1616,7 @@

    Input ValidationNo Script Execution

    The hub never interprets entry content. There is no expression -language, no template evaluation, no markdown rendering at +language, no template evaluation, no Markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim.

    Audit Trail

    diff --git a/site/thesis/index.html b/site/thesis/index.html index 4aef9e2b6..5ee51c743 100644 --- a/site/thesis/index.html +++ b/site/thesis/index.html @@ -2359,7 +2359,7 @@

    Invariant 5: Local-First, Air-G be used for optional features but never for core context management.

    Validation: 7 independent rejection decisions protected this property. Infrastructure-dependent memory systems cannot operate in classified -environments, isolated networks, or disaster-recovery scenarios. A +environments, isolated networks, or constrained-environment scenarios. A filesystem-native model continues to function under all conditions where the repository is accessible.

    Invariant 6: No Default Telemetry

    @@ -2770,7 +2770,7 @@

    9.1 When to Use This Model9.2 When Not to Use This Model

    Zero-configuration personal workflows: For short-lived or exploratory tasks, the cost of explicit knowledge curation outweighs its benefits. Heuristic diff --git a/specs/ceremony-profiles.md b/specs/ceremony-profiles.md index f5241832d..a34d7bcaa 100644 --- a/specs/ceremony-profiles.md +++ b/specs/ceremony-profiles.md @@ -18,14 +18,13 @@ names: These names are also baked into the message templates at `internal/assets/hooks/messages/check-ceremony/{remember,wrapup,both}.txt`. -Non-code-dev projects rebrand those ceremonies. The motivating case is -the DR knowledgebase project at `things-wtf-disaster-recovery`, an -**editorial** project where: +Non-code-dev projects rebrand those ceremonies. The motivating case +is an **editorial** knowledgebase project at `your-project` where: - ctx code-dev skills explicitly do not apply (per its `.context/CONSTITUTION.md` and root `10-CONSTITUTION.md`). -- The project has shipped DR-specific replacements: `/dp-remember`, - `/dp-wrap-up`, `/dp-commit`. +- The project has shipped project-specific replacements: `/proj-remember`, + `/proj-wrap-up`, `/proj-commit`. In that project the ceremony nudge fires correctly (3 sessions without ceremony usage) but recommends the wrong skills. The user gets nudged @@ -54,9 +53,9 @@ In `.ctxrc`: ```yaml ceremony: - remember: dp-remember - wrapup: dp-wrap-up - # commit: dp-commit # optional, see "Commit ceremony" below + remember: proj-remember + wrapup: proj-wrap-up + # commit: proj-commit # optional, see "Commit ceremony" below ``` When the block is absent, the ceremony layer falls back to the @@ -151,7 +150,7 @@ Ceremony: ctx-remember / ctx-wrap-up (default) or ``` -Ceremony: dp-remember / dp-wrap-up (project) +Ceremony: proj-remember / proj-wrap-up (project) ``` ## Implementation plan @@ -174,8 +173,8 @@ Ceremony: dp-remember / dp-wrap-up (project) 7. Update tests: - Default profile renders `/ctx-remember` / `/ctx-wrap-up` as before. - - A project with `ceremony.remember: dp-remember` renders - `/dp-remember` and the scanner only considers `dp-remember` + - A project with `ceremony.remember: proj-remember` renders + `/proj-remember` and the scanner only considers `proj-remember` usage as fulfilling the open-bookend. 8. Document in `docs/recipes/` (one short page on declaring a project ceremony profile, with the editorial-project example). @@ -208,8 +207,8 @@ Ceremony: dp-remember / dp-wrap-up (project) ## Related -- DR editorial project (consumer): - `~/Desktop/WORKSPACE/things-wtf-disaster-recovery` +- Editorial project (consumer): + `~/your/project/path` - Existing ceremony plumbing: `internal/config/ceremony/ceremony.go`, `internal/cli/system/core/ceremony/ceremony.go`, diff --git a/specs/copilot-feature-parity-kit.md b/specs/copilot-feature-parity-kit.md index fa07d7217..d1658b910 100644 --- a/specs/copilot-feature-parity-kit.md +++ b/specs/copilot-feature-parity-kit.md @@ -24,7 +24,7 @@ into three layers: 3. **Governance** — Proactive nudges, ceremony checks, session health Architecture principle: **single source of truth**. Skills are authored -as markdown SKILL.md files in `internal/assets/integrations/copilot-cli/skills/` +as Markdown SKILL.md files in `internal/assets/integrations/copilot-cli/skills/` and deployed by `ctx setup copilot-cli --write`. VS Code commands call `ctx` CLI or MCP tools — the extension does not duplicate skill logic. @@ -103,7 +103,7 @@ case '/implement': ``` VS Code commands delegate to `ctx` CLI. The extension provides UI -(progress, follow-ups, markdown rendering) but not logic. +(progress, follow-ups, Markdown rendering) but not logic. ### 1.4 Files to Create/Modify @@ -370,4 +370,4 @@ These features are Claude Code specific and **not ported**: 3. **VS Code command registration limit**: Is there a practical limit on slash commands in a chat participant? Current 45 → 55+ after this spec. 4. **Skill frontmatter schema**: Does Copilot CLI enforce a specific YAML - frontmatter schema for SKILL.md, or is it freeform markdown? + frontmatter schema for SKILL.md, or is it freeform Markdown? diff --git a/specs/future-complete/context-hub.md b/specs/future-complete/context-hub.md index 2d703e7e2..9c67cd6c9 100644 --- a/specs/future-complete/context-hub.md +++ b/specs/future-complete/context-hub.md @@ -266,7 +266,7 @@ local context: ### Shared File Format -Each shared file uses the same markdown format as local files, with +Each shared file uses the same Markdown format as local files, with origin tags: ```markdown diff --git a/specs/future-complete/copilot-cli-integration.md b/specs/future-complete/copilot-cli-integration.md index a9e98f1e5..dfa04abc5 100644 --- a/specs/future-complete/copilot-cli-integration.md +++ b/specs/future-complete/copilot-cli-integration.md @@ -120,7 +120,7 @@ | Status bar | — | 🔧 Reminder status bar (PR pending) | — | | Diagnostics command | — | ✅ `/diag` with timing | — | | Progress indicators | — | ✅ `stream.progress()` | — | -| Markdown rendering | Terminal output | ✅ VS Code markdown | Terminal output | +| Markdown rendering | Terminal output | ✅ VS Code Markdown | Terminal output | | Interactive mode | ✅ Terminal REPL | ✅ Chat panel | ✅ Terminal REPL | | Programmatic mode | — | — | ✅ `copilot -p "prompt"` | | Plan mode | — | — | ✅ Shift+Tab | diff --git a/specs/future-complete/ctx-fmt.md b/specs/future-complete/ctx-fmt.md index 5a4c4d9cd..74a0c5dc6 100644 --- a/specs/future-complete/ctx-fmt.md +++ b/specs/future-complete/ctx-fmt.md @@ -11,7 +11,7 @@ exists today; the only workaround is manual line breaks. - Elevate `internal/cli/journal/core/wrap/` to `internal/wrap/` so wrapping logic is shared between journal and context formatting -- Add list-aware wrapping: 2-space continuation indent for markdown +- Add list-aware wrapping: 2-space continuation indent for Markdown list items (`- [ ]`, `- [x]`, `- [-]`, `- `) - New `ctx fmt` subcommand that formats all context files in-place - `make fmt-context` target as convenience alias @@ -132,7 +132,7 @@ func ListItem(line string, width int) []string ## Non-Goals -- Perfect markdown rendering. Good enough for humans and agents. +- Perfect Markdown rendering. Good enough for humans and agents. - Reflowing already-wrapped paragraphs into optimal line lengths. Only lines exceeding the width get wrapped. - Formatting non-context files (README, specs, etc.) diff --git a/specs/future-complete/journal-compact.md b/specs/future-complete/journal-compact.md index e8f79f946..a10fa1110 100644 --- a/specs/future-complete/journal-compact.md +++ b/specs/future-complete/journal-compact.md @@ -13,7 +13,7 @@ not git-controlled, unbounded growth creates three problems: from JSONL sources, so compaction gains are lost unless the pipeline knows which entries were compacted -The journal markdown files are derived artifacts — the JSONL source +The journal Markdown files are derived artifacts — the JSONL source files in `~/.claude/projects/` are the originals. Compaction is a lossy view optimization, not data destruction. @@ -23,7 +23,7 @@ Inspired by Elasticsearch's hot/warm/cold/frozen index lifecycle: | Tier | Age | Content | File format | |----------|---------------|--------------------------------------------------|---------------------| -| **Hot** | 0–N days | Full markdown, all tool output, code blocks | Individual `.md` | +| **Hot** | 0–N days | Full Markdown, all tool output, code blocks | Individual `.md` | | **Warm** | N–M days | Frontmatter + summary + key outcomes, body stripped | Individual `.md` | | **Cold** | Not in v1 | Monthly rollup, one section per session | `YYYY-MM-rollup.md` | diff --git a/specs/future-complete/jsonl-envelope-enrichment.md b/specs/future-complete/jsonl-envelope-enrichment.md index 41ad1040e..ba0ded823 100644 --- a/specs/future-complete/jsonl-envelope-enrichment.md +++ b/specs/future-complete/jsonl-envelope-enrichment.md @@ -181,7 +181,7 @@ three tiers, all shipped in this spec. | Case | Expected behavior | |------|-------------------| | `planContent` is empty string | Treat as absent — no plan section rendered | -| `planContent` contains markdown that conflicts with journal formatting | Render inside a fenced details block to isolate formatting | +| `planContent` contains Markdown that conflicts with journal formatting | Render inside a fenced details block to isolate formatting | | `isApiErrorMessage` on a message with useful text content | Still collapse — API errors are always retry noise, the useful response comes in the next message | | Multiple consecutive API error messages | Collapse into one line: "⚠ N API errors (retried)" | | `sourceToolAssistantUUID` references a UUID not in the current session | Store as-is — cross-session references are valid for subagent chains | @@ -201,7 +201,7 @@ deserialize. If absent, zero value. The schema validation spec | Error condition | User-facing message | Recovery | |-----------------|---------------------|----------| -| `planContent` fails to render as markdown | Fall back to raw text in `

    ` block | Automatic |
    +| `planContent` fails to render as Markdown | Fall back to raw text in `
    ` block | Automatic |
     | Unknown `entrypoint` value | Store as-is, no warning | None needed |
     
     ## Interface
    diff --git a/specs/future-complete/prompt-deprecation.md b/specs/future-complete/prompt-deprecation.md
    index 6506e62ac..09ff6eb27 100644
    --- a/specs/future-complete/prompt-deprecation.md
    +++ b/specs/future-complete/prompt-deprecation.md
    @@ -4,7 +4,7 @@
     
     `ctx` ships two overlapping systems for agent instructions: **skills**
     (`.claude/skills/`, with frontmatter, trigger descriptions, and tool
    -permissions) and **prompt templates** (`.context/prompts/`, plain markdown,
    +permissions) and **prompt templates** (`.context/prompts/`, plain Markdown,
     no metadata). Three of the four bundled prompts (code-review, explain,
     refactor) are functionally lightweight skills — they give the agent working
     instructions to apply. But because they live in the prompt system, the agent
    diff --git a/specs/future-complete/session-housekeeping-2026-04-11.md b/specs/future-complete/session-housekeeping-2026-04-11.md
    index 22dda174a..72a060d0e 100644
    --- a/specs/future-complete/session-housekeeping-2026-04-11.md
    +++ b/specs/future-complete/session-housekeeping-2026-04-11.md
    @@ -67,7 +67,7 @@ Removed `templates/`, `steering/`, `hooks/`, `skills/` rows from the
     Replaced with:
     
     - The 8-row table of actual core context files.
    -- A note labelling `templates/` and `steering/` as
    +- A note labeling `templates/` and `steering/` as
       implementation-detail-but-user-editable with links.
     - An "Outside `.context/`" subsection pointing at the skill and hook
       home pages.
    diff --git a/specs/hub-security-audit.md b/specs/hub-security-audit.md
    index d11fa6202..4dfa8c78c 100644
    --- a/specs/hub-security-audit.md
    +++ b/specs/hub-security-audit.md
    @@ -1364,7 +1364,7 @@ backpressure" track).
     
     ### Low (defense-in-depth, hardening)
     
    -#### H-16 — Content not sanitized for markdown injection
    +#### H-16 — Content not sanitized for Markdown injection
     
     **Severity**: Low.
     
    @@ -1375,7 +1375,7 @@ backpressure" track).
     `validateEntry` does not inspect `Content` beyond size.
     When the client-side renderer writes entries to
     `.context/hub/decisions.md` (and siblings), the content
    -is concatenated into a markdown document. A malicious
    +is concatenated into a Markdown document. A malicious
     Content field like `\n---\ntitle: Fake\n---\n# Impostor`
     can inject what looks like a new frontmatter block and
     a new entry.
    diff --git a/specs/internal-assets-readme.md b/specs/internal-assets-readme.md
    index 83d50fe8b..c010aa443 100644
    --- a/specs/internal-assets-readme.md
    +++ b/specs/internal-assets-readme.md
    @@ -17,7 +17,7 @@ ground: `internal/` (a Go-private import boundary) containing
     TypeScript, shell scripts, and Markdown. The lack of a
     contract document made an experienced reviewer mistake this
     for an architectural smell and propose lifting `integrations/`
    -out of `internal/`, before realising every file in there is
    +out of `internal/`, before realizing every file in there is
     genuinely embedded and the `//go:embed` no-`../` rule makes
     relocation costly.
     
    diff --git a/specs/kb-editorial-pipeline.md b/specs/kb-editorial-pipeline.md
    index ee8aac3ac..e3f1388ea 100644
    --- a/specs/kb-editorial-pipeline.md
    +++ b/specs/kb-editorial-pipeline.md
    @@ -1,12 +1,23 @@
    -# KB Editorial Pipeline (paired with handover mechanism)
    +# KB Editorial Pipeline (Paired with Handover Mechanism)
     
     > Source: `ideas/003-editorial-pipeline-debated-brief.md` (debated
     > brief from `/ctx-plan` session 01d0cf92, 2026-05-09).
     >
    -> Authority order when this spec, `DECISIONS.md`, or `docs/` could
    -> conflict: frozen contracts in `docs/` > recorded decisions in
    -> `DECISIONS.md` > the brief at `ideas/003-...` > inference labeled
    -> `TBD` here. Do not invert.
    +> Revision 2 (2026-05-16): rewritten to absorb the **current**
    +> upstream editorial-pipeline shape (pass-mode contract, completion circuit breaker,
    +> source-coverage ledger as state machine, topic-adjacency pre-flight,
    +> cold-reader orientation rubric, folder-shaped topics from day one,
    +> failure analysis). The original 4-phase model is superseded; the
    +> brief's two organizing principles (LLM as migration tool;
    +> KB-of-KBs is a KB) carry forward. See DECISIONS.md
    +> "Lift current upstream editorial-pipeline shape, not the 4-phase predecessor"
    +> for the migration rationale; this comparison note
    +> `ideas/upstream-pipeline-comparison.md` is the input.
    +>
    +> Authority order when this spec, `DECISIONS.md`, or `docs/`
    +> could conflict: frozen contracts in `docs/` > recorded decisions
    +> in `DECISIONS.md` > the brief at `ideas/003-...` > inference
    +> labeled `TBD` here. Do not invert.
     
     ## Problem
     
    @@ -19,19 +30,18 @@ knowledge with confidence bands, contradictions, and external
     grounding.
     
     The cost is concrete and active:
    -`things-wtf-disaster-recovery` has been hand-rolling an
    +`your-project` has been hand-rolling an
     editorial pipeline at the repo root (`10-CONSTITUTION.md`,
     numbered mode prompts, `20-INBOX.md`, hand-typed 8-item
    -closeouts) and disabling half of ctx's code-dev skill surface
    -in `CLAUDE.md` (`/ctx-commit`, `/ctx-implement`, `/ctx-spec`,
    -`/ctx-architecture`, `/ctx-brainstorm`, `/ctx-wrap-up`) to
    -avoid name collision with its editorial constitution.
    +closeouts) and disabling half of `ctx`'s code-dev skill surface
    +in `CLAUDE.md` to avoid name collision with its editorial
    +constitution.
     
    -Separately, ctx has no per-session **handover artifact**: the
    +Separately, `ctx` has no per-session **handover artifact**: the
     narrative thread between sessions is currently re-derived
    -probabilistically from canonical files plus journal. An
    -ad-hoc one-off (`HANDOVER-2026-04-22.md`) exists but no
    -discipline shapes it.
    +probabilistically from canonical files plus journal. An ad-hoc
    +one-off (`HANDOVER-2026-04-22.md`) exists but no discipline
    +shapes it.
     
     This spec adds both surfaces and integrates them via a
     closeout/fold mechanism so editorial work flows cleanly into
    @@ -39,25 +49,57 @@ session-to-session continuity.
     
     ## Approach
     
    -Lift the validated shape from a sibling tool's editorial
    -pipeline (4 modes, 9 KB artifacts, closeout/fold, browseable
    -site) into `ctx` with a non-colliding rename
    -(`KB-RULES.md`, not `CONSTITUTION.md`) and pair it with a
    -per-session handover mechanism. The closeout/fold is the
    -integration point: every editorial pass writes a closeout
    -with a `generated-at` cursor; the next handover folds
    +Lift the **current** upstream editorial-pipeline shape into `ctx` with a
    +non-colliding rename (`KB-RULES.md`, not `CONSTITUTION.md`).
    +The shape:
    +
    +1. **Pass-mode contract**: every `ctx kb ingest` invocation
    +   declares its mode (`topic-page` / `triage` / `evidence-only`)
    +   before extraction, with a definition-of-done. Mid-pass
    +   mode-switching is forbidden; abort and re-invoke instead.
    +2. **Completion circuit breaker**: `topic-page` mode passes
    +   four invariants before claiming `produced` / `extended`:
    +   file exists; cites ≥ 1 `EV-###`; site build clean (or named
    +   failure); cold-reader rubric passes.
    +3. **Source-coverage ledger**: state machine over every source
    +   the kb has touched. `discovered → admitted →
    +   highlights-extracted → partially-ingested → topic-page-drafted
    +   → comprehensive`, plus terminals `superseded` / `skipped`.
    +   Every pass updates the ledger honestly; "lying to the ledger"
    +   is a hard anti-pattern.
    +4. **Topic-adjacency pre-flight**: before resolving a topic,
    +   scan the ledger for sibling-topic rows not in
    +   `{comprehensive, skipped, superseded}` and force the page
    +   being authored to acknowledge them. Makes discoverability
    +   failures mechanically hard.
    +5. **Cold-reader orientation rubric**: four yes/no questions
    +   the artifact must satisfy: concept clear? why this project
    +   cares clear? canonical evidence reachable? boundaries clear?
    +6. **Folder-shaped topics from day one**:
    +   `.context/kb/topics//index.md` for every topic, with
    +   optional sibling sub-pages (`security.md`, `multi-surface.md`).
    +   Lazy split: only when `index.md` outgrows the cold-reader
    +   rubric's "boundaries clear?" check.
    +7. **CLI-as-scaffold-authority**: topic-page file creation is
    +   performed only by `ctx kb topic new`; skills invoke the CLI,
    +   never synthesize the scaffold.
    +
    +Pair the editorial pipeline with a per-session **handover
    +artifact**. The closeout/fold mechanism is the integration
    +point: every editorial pass writes a closeout with
    +`generated-at` frontmatter; the next `/ctx-handover` folds
     postdated closeouts and archives the sources.
     
    -**Two organizing principles** (carried forward verbatim from the
    -brief because they explain why this is rationally bold rather
    -than recklessly broad):
    +**Two organizing principles** (carried forward verbatim from
    +the brief because they explain why this is rationally bold
    +rather than recklessly broad):
     
    -- **P1 — The LLM is the migration tool.** Wholesale ID
    +- **P1: The LLM is the migration tool.** Wholesale ID
       renumbering, taxonomy reshuffles, confidence-band remapping,
       cross-file reference rewrites are absorbed by an LLM cleanup
       pass. We commit to specific schemas in v1 instead of hedging
       with abstract types. Be wrong cheaply.
    -- **P2 — A KB is knowledge; a KB of KBs is a KB.** Recursive
    +- **P2: A KB is knowledge; a KB of KBs is a KB.** Recursive
       composability. Federation is `source-map kind: kb` plus
       pointing the standard ingest pipeline at another KB. No
       special feature needed; v1 schemas accommodate v2 federation
    @@ -84,36 +126,65 @@ fold KB state into the readback."
     
     **End-to-end: research session through cold restart.**
     
    -1. User runs `ctx init` in a fresh project. Init lays down the
    -   five canonical files **plus** `.context/handovers/`,
    -   `.context/kb/` (with `.gitkeep`), `.context/ingest/` (with
    -   `KB-RULES.md`, mode prompts `00-GROUND.md`, `30-INGEST.md`,
    -   `40-ASK.md`, `50-SITE_REVIEW.md`, `INBOX.md`,
    -   `SESSION_LOG.md`, `grounding-sources.md`, `OPERATOR.md`,
    -   `PROMPT.md`, `schemas/`), and `.context/site/` (gitignored).
    +1. User runs `git init && ctx init` in a fresh project. Init
    +   lays down the five canonical files **plus**
    +   `.context/handovers/`, `.context/kb/` (with `.gitkeep` and
    +   `topics/.gitkeep`), `.context/ingest/` (with `KB-RULES.md`,
    +   mode prompts, `INBOX.md`, `SESSION_LOG.md`,
    +   `grounding-sources.md`, `OPERATOR.md`, `PROMPT.md`,
    +   `schemas/`), and `.context/site/` (gitignored).
     2. User runs `ctx setup` to deploy skills. New skills:
        `/ctx-handover`, `/ctx-kb-ingest`, `/ctx-kb-ask`,
        `/ctx-kb-site-review`, `/ctx-kb-ground`, `/ctx-kb-note`.
    -3. User invokes `/ctx-kb-ingest ./inputs/2026-04-12-call.md`.
    -   The skill runs the four-phase ingest (triage, extract,
    -   reconcile, surface), writes evidence rows to
    -   `.context/kb/evidence-index.md`, updates glossary /
    -   contradictions / outstanding-questions / timeline as
    -   needed, appends one line per phase boundary to
    -   `.context/ingest/SESSION_LOG.md`, and writes a closeout
    -   `.context/ingest/closeouts/-ingest-closeout.md` with
    -   `generated-at` frontmatter.
    +3. User invokes
    +   `/ctx-kb-ingest ./inputs/2026-04-12-call.md "cursor hooks"`.
    +   The skill:
    +   1. Verifies pre-write gates: `.context/`,
    +      `.context/kb/` exist, kb scope is declared
    +      (`.context/kb/index.md` has a non-placeholder
    +      `## Scope`).
    +   2. **Declares pass-mode**: emits up-front block
    +      (`Pass-mode: topic-page`, `Reason: ...`,
    +      `Definition of done: ...`).
    +   3. Resolves topic, scans
    +      `.context/kb/source-coverage.md` for adjacency
    +      pre-flight, lists sibling-topic gaps.
    +   4. Resolves sources, advances ledger to `admitted`.
    +   5. Calls `ctx kb topic new "cursor hooks"` if the topic
    +      folder does not exist (scaffold authority).
    +   6. Synthesises prose section by section, mints `EV-###`
    +      rows in `evidence-index.md`, updates `glossary.md`,
    +      `timeline.md`, `source-map.md` as needed.
    +   7. Applies life-stage reconciliation discipline (skipped
    +      if `< 5` topic pages; full discipline at `>= 5`).
    +   8. Sets Confidence floor per the cited-bands rule.
    +   9. Updates `source-coverage.md` to `topic-page-drafted`
    +      or `comprehensive`.
    +   10. Runs circuit-breaker check: file exists; cites EV;
    +       `ctx kb site build` clean; cold-reader rubric records
    +       `Result: pass`.
    +   11. Writes closeout to
    +       `.context/ingest/closeouts/-ingest-closeout.md`
    +       with required frontmatter
    +       (`sha`, `branch`, `mode`, `pass-mode`, `life-stage`,
    +       `generated-at`).
     4. User runs `/ctx-wrap-up`. The skill (a) walks the standard
    -   capture checklist, (b) detects `.context/kb/` exists and
    -   surfaces editorial state (pending closeouts, unresolved
    -   outstanding-questions count), (c) **mandatorily** drives
    -   `/ctx-handover` as the final step.
    -5. `/ctx-handover` collects `--summary` (past tense, what
    -   happened) and `--next` (future tense, specific first
    -   action), runs `ctx handover write`, which folds postdated
    -   closeouts into `## Folded closeouts`, archives the source
    -   closeouts to `.context/archive/closeouts/`, and writes
    -   `.context/handovers/-.md`.
    +   capture checklist, (b) **always** delegates to
    +   `/ctx-handover` as its final step regardless of whether
    +   `.context/kb/` exists. When `.context/kb/` does exist, the
    +   wrap-up additionally surfaces editorial state (pending
    +   closeouts, unresolved outstanding-questions count) before
    +   delegating; KB presence affects what gets folded, not
    +   whether the handover is written.
    +5. `/ctx-handover` (wrap-up's handover step) collects
    +   `--summary` (past tense, what happened) and `--next`
    +   (future tense, specific first action), runs
    +   `ctx handover write`, which folds postdated closeouts into
    +   `## Folded closeouts`, archives the source closeouts to
    +   `.context/archive/closeouts/`, and writes
    +   `.context/handovers/-.md`. The filename is
    +   timestamped so concurrent agent runs never overwrite one
    +   another.
     6. User opens a fresh session next morning. The session-start
        hook reads the latest handover. User asks "do you
        remember?". `/ctx-remember` reads canonical files +
    @@ -125,23 +196,177 @@ fold KB state into the readback."
        via a shell-out to `zensical` (already used for
        `ctx journal site`).
     
    +### Pass-Mode Contract
    +
    +Every `ctx kb ingest` invocation classifies itself as exactly
    +one of three modes **before any source extraction begins**:
    +
    +| Mode             | Mints prose? | Mints EV-### ? | Touches topic page? | Default? |
    +|------------------|--------------|----------------|---------------------|----------|
    +| `topic-page`     | yes          | yes            | yes (create/extend) | yes      |
    +| `triage`         | no           | **no**         | no                  | no       |
    +| `evidence-only`  | no           | yes (tagged)   | no                  | no       |
    +
    +**Mode selection rules.** Default is `topic-page`. `triage`
    +fires when the user supplied multiple disparate sources with
    +no clear single topic, OR explicitly invoked triage language.
    +`evidence-only` fires only on explicit user request matching
    +the valid-trigger criteria (*"Just mint EV rows"*,
    +*"backfill evidence"*); the skill MAY NOT infer it from source
    +size, ambiguity, time pressure, or operator convenience.
    +
    +**Up-front declaration (mandatory).** Before extraction begins
    +(after pre-write gates pass and **before** topic resolution),
    +the skill MUST surface a visible pre-work declaration:
    +
    +> **Pass-mode:** ``
    +> **Reason:** ``
    +> **Definition of done:** ``
    +
    +The declaration is a contract, not a label.
    +
    +**Mid-pass mode-switching is forbidden.** If the work in flight
    +no longer fits, abort with a partial closeout citing what was
    +done, and recommend re-invocation under the correct mode.
    +
    +### Topic-Page Circuit Breaker
    +
    +A pass in `topic-page` mode MAY NOT report
    +`topic-page: produced` or `topic-page: extended` unless ALL of
    +the following are true at completion:
    +
    +1. `.context/kb/topics//index.md` (or a sibling sub-page
    +   like `.context/kb/topics//.md`) exists and was
    +   created or extended in this pass.
    +2. The page cites at least one `EV-###` row that resolves to
    +   `evidence-index.md`.
    +3. `ctx kb site build` ran clean (or its failure is named in
    +   the closeout's `Next pass hint` AND the pass reports
    +   `topic-page: deferred`).
    +4. The cold-reader orientation rubric records
    +   **`Result: pass`** in the closeout's `What changed` section.
    +   All four rubric items must be `yes`.
    +
    +Any failure → `topic-page: deferred` and the source-coverage
    +ledger advances to `topic-page-drafted` (not `comprehensive`).
    +
    +### Source-Coverage Ledger (State Machine)
    +
    +`.context/kb/source-coverage.md` is a state machine over every
    +source the kb has touched. States and allowed transitions:
    +
    +| state                   | meaning                                                                  | next states |
    +|-------------------------|--------------------------------------------------------------------------|-------------|
    +| `discovered`            | surfaced via discovery / candidate harvest; not yet admitted             | → `admitted`, → `skipped` |
    +| `admitted`              | admitted against scope; not yet extracted                                | → `highlights-extracted`, → `partially-ingested`, → `topic-page-drafted`, → `comprehensive` |
    +| `highlights-extracted`  | EV rows minted for highlights only; no topic page or stub only           | → `partially-ingested`, → `topic-page-drafted`, → `comprehensive` |
    +| `partially-ingested`    | topic page exists but is incomplete relative to source                   | → `topic-page-drafted`, → `comprehensive` |
    +| `topic-page-drafted`    | page exists; cold-reader recorded; Confidence `speculative` or `TBD-cite` remains | → `comprehensive` |
    +| `comprehensive`         | page complete; cited bands ≥ `medium`; no `TBD-cite`; cold-reader passed | terminal until source updates |
    +| `superseded`            | source replaced by a newer canonical version; superseder named           | terminal |
    +| `skipped`               | source admitted-then-rejected as out-of-scope; reason cited              | terminal until scope changes |
    +
    +**Row schema** (Markdown table):
    +
    +```
    +| Source       | Topic        | State                 | EV coverage     | Residue      | Next action                       | Updated    |
    +| CURSOR-HOOKS | cursor-hooks | highlights-extracted  | EV-018..EV-034  | examples,... | /ctx-kb-ingest cursor-hooks       | 2026-05-13 |
    +```
    +
    +Every pass that touches a source updates the ledger before
    +writing the closeout. **Lying to the ledger is a hard
    +anti-pattern.**
    +
    +### Topic-Adjacency Pre-Flight
    +
    +Before resolving the topic, scan the ledger for rows whose
    +state is **not** in `{comprehensive, skipped, superseded}` AND
    +whose `Topic` is plausibly adjacent. Heuristic:
    +
    +- **Shared first segment of a slash- or hyphen-separated slug**:
    +  `cursor/skills` is adjacent to `cursor/hooks`;
    +  `your-domain-foo` is adjacent to
    +  `your-domain-bar`.
    +- **Shared product / vendor / surface in source URL or
    +  description**: sources under `cursor.com/docs/*` are
    +  adjacent.
    +- **Explicit cross-references** in the named topic's existing
    +  sub-pages or this pass's source set.
    +
    +For each adjacent incomplete topic surfaced, the pass MUST:
    +
    +1. Acknowledge it in `## Related concepts in this kb` on the
    +   topic page being authored.
    +2. Surface it in the closeout's `Adjacency pre-flight` block.
    +3. Surface it in the response contract's
    +   `Adjacent topics noted` field.
    +
    +**Do NOT enumerate `EV-###` IDs by name in the adjacency block.**
    +Use *count + location* (*"seventeen rows in
    +`evidence-index.md`"*); naming an EV row from a lower-
    +confidence sibling demotes the floor of cited bands.
    +
    +Silence is not the same as a clean pre-flight; explicit
    +*"no incomplete adjacent topics surfaced"* required when zero
    +matches.
    +
    +### Cold-Reader Orientation Rubric
    +
    +Four yes/no items recorded in the closeout's `What changed`
    +section, in `topic-page` mode:
    +
    +```
    +Cold-reader orientation:
    +- Concept clear?                yes|no: 
    +- Why this kb cares clear?      yes|no: 
    +- Canonical evidence reachable? yes|no: 
    +- Boundaries clear?             yes|no: 
    +Result: pass | fail
    +```
    +
    +`Result: pass` requires all four `yes`. Any `no` → `Result:
    +fail` → circuit-breaker fails → `topic-page: deferred`.
    +
    +### Life-Stage Check
    +
    +Count `.context/kb/topics/*/index.md` before this pass begins
    +synthesizing:
    +
    +- `< 5` topic pages → **bootstrap** mode. Skip reconciliation
    +  ceremony; synthesize topic pages aggressively. Exception:
    +  surface a contradiction even in bootstrap if the new
    +  material plainly contradicts existing kb claims.
    +- `>= 5` topic pages → **maintenance** mode. Apply full
    +  reconciliation discipline (laddering, demotion,
    +  contradictions).
    +
    +Document the life-stage call in the closeout's frontmatter
    +(`life-stage:`) and `What changed` section.
    +
     ### Edge Cases
     
     | Case | Expected behavior |
     |------|-------------------|
    -| Empty input to `ctx kb ingest` | Refuse cleanly: `no sources provided; pass a folder or describe the materials inline.` Non-zero exit. |
    +| Empty input to `ctx kb ingest` | Refuse cleanly: `no sources provided; pass a folder, a URL, an MCP resource, or describe the materials inline.` Non-zero exit. |
     | Empty question to `ctx kb ask` | Refuse cleanly: `no question provided; pass a question or describe it inline.` Non-zero exit. |
     | Empty `grounding-sources.md` on `ctx kb ground` | Skill prompts once for sources before running; `NONE` on a line is a per-pass skip (re-prompts next invocation). |
     | Concurrent ingest writers producing duplicate `EV-###` | Doctor advisory detects duplicates on next `ctx doctor` run; LLM cleanup pass renumbers and rewrites cross-references (P1). Documented as single-writer convention. |
     | Temporal misordering: ingest today's transcript before last week's | Pipeline detects date-stamped filename; demotion policy applies temporal-precedence rule (newer-occurred carries forward over older-occurred even if older was extracted later); doctor warns when `dated:` source has rows missing `occurred:`. |
     | Mid-session checkpoint via handover | `ctx handover write --no-fold` writes the handover without consuming closeouts (rare; default is fold). |
     | Session aborted before wrap-up | Closeouts stay in place; next session's `/ctx-remember` reads handover **plus** unfolded postdated closeouts. Editorial work survives. |
    +| Mid-pass mode-switching detected | Skill aborts with partial closeout citing the mismatch; recommends re-invocation under the correct mode. **Never** silent-switches. |
    +| Cold-reader rubric returns `Result: fail` | Pass reports `topic-page: deferred` AND `validation: deferred (cold-reader orientation failed)`; ledger advances to `topic-page-drafted` (not `comprehensive`); closeout names which rubric items returned `no`. |
    +| Adjacency pre-flight surfaces zero matches | Closeout records explicit *"no incomplete adjacent topics surfaced"* and response-contract field reads `none surfaced`. Silence is not allowed. |
    +| Inferring `evidence-only` from source size / time pressure | Hard anti-pattern. Skill refuses to set `evidence-only` without explicit user trigger. |
    +| Lying to the source-coverage ledger | Doctor advisory: cross-check ledger rows against file existence + last-modified time vs. row's `Updated`. Mismatch → advisory. |
     | `zensical` missing on PATH for `ctx kb site` | Single-line install hint, non-zero exit. No interactive install attempt. |
     | `.context/kb/` missing when read-side surfaces look | Skip the "kb state" branch silently; behave as if no KB exists. Mode-awareness is `if exists`, not `must exist`. |
     | Speculative-confidence claim ships to rendered site | Render filter excludes `confidence: speculative` content; `low`-confidence content ships only when paired with matching `outstanding-questions.md` entry. |
     | Hand-edit to `.context/ingest/INBOX.md` | Silently discarded on next mode-skill run (skill rewrites the inbox). To configure, edit `grounding-sources.md`. Documented in `KB-RULES.md`. |
     | `ctx kb note` while no ingest pipeline directory exists | Refuse: `kb not initialized; run \`ctx init\` first`. Non-zero exit. |
     | Ingest source path doesn't exist | Refuse cleanly with the missing path; do not attempt partial extraction. |
    +| Sub-page split needed (index.md fails cold-reader "boundaries clear?") | Skill proposes the split once; waits for user confirmation; never auto-splits. |
    +| `ctx kb topic new` for a slug that already exists | Refuse: `topic  already exists at .context/kb/topics//index.md`. Non-zero exit. |
     
     ### Validation Rules
     
    @@ -153,16 +378,33 @@ fold KB state into the readback."
       `/ctx-kb-ground`) refuse on empty input rather than
       prompting; refuse-on-empty is the contract.
     - `ctx kb` writes only to `.context/kb/` and
    -  `.context/ingest/`. Path constants in
    -  `internal/path/path.go` enforce this (rooted writes).
    +  `.context/ingest/`. Path constants enforce this (rooted
    +  writes).
    +- `/ctx-kb-ingest` MUST emit the up-front pass-mode
    +  declaration before any source extraction begins. The skill
    +  body checks the declaration was emitted before proceeding;
    +  doctor advisory detects closeouts missing the `Pass-mode`
    +  body block.
    +- Topic-page mode passes MUST satisfy all four circuit-breaker
    +  invariants before claiming `produced` / `extended`. Failure
    +  on any → `topic-page: deferred`.
     - Closeout files require frontmatter fields `sha`, `branch`,
    -  `mode`, `generated-at`. Missing any → site-review mode
    -  flags it; handover fold skips it with a warning.
    +  `mode`, `pass-mode`, `life-stage`, `generated-at`. Missing
    +  any → site-review mode flags it; handover fold skips it with
    +  a warning.
     - Confidence band must be one of `high|medium|low|speculative`.
       Site-review mode coerces malformed capitalization
       (`High` → `high`); other malformations are flagged.
     - Closeouts are append-never-rewrite. Archived closeouts are
       immutable.
    +- Source-coverage ledger row state must match an allowed
    +  transition from the prior state. Doctor advisory flags
    +  illegal transitions (e.g. `comprehensive → highlights-
    +  extracted` without an explicit `superseded` step).
    +- Topic pages live at `.context/kb/topics//index.md`
    +  from day one. Sub-page split is **lazy** (only when
    +  `index.md` fails the cold-reader "boundaries clear?" check)
    +  and **proposed** (one question, wait for confirmation).
     - Tasks, decisions, learnings, conventions, reminders
       unchanged: they remain authored by their existing canonical
       CLIs. KB cannot write to canonical files; canonical CLIs
    @@ -174,12 +416,16 @@ fold KB state into the readback."
     |-----------------|---------------------|----------|
     | `ctx kb` invoked but `.context/` missing | `context directory not found; run \`ctx init\` first.` | Run `ctx init` |
     | `ctx kb` invoked but `.context/ingest/` missing (initialized before this spec shipped) | `kb pipeline not initialized; run \`ctx init --upgrade\` to lay down ingest scaffolding.` | Run `ctx init --upgrade` (idempotent on existing files; refuses to overwrite divergent content) |
    +| `ctx kb` invoked but kb scope undeclared (placeholder in `.context/kb/index.md`) | `kb scope is undeclared. Open .context/kb/index.md and replace the TODO placeholder with a one-paragraph scope statement.` | Hand-edit `.context/kb/index.md` |
     | `ctx handover write` with empty `--summary` or `--next` | `--summary and --next are required and must be non-trivial; placeholder values like 'TBD' are rejected.` | Re-run with concrete values |
     | `ctx kb ingest` with non-existent path | `source not found: ` | Fix path or pass `--inline "..."` description |
    +| `ctx kb topic new` for existing slug | `topic  already exists at .context/kb/topics//index.md` | Use existing folder; skill will append/extend |
     | `ctx kb site build` when zensical missing | `zensical not on PATH; install per https://zensical.org/ then re-run.` | Install zensical |
     | Closeout fold encounters malformed frontmatter | `warning: skipping malformed closeout (no generated-at): ` (proceeds with valid ones) | Hand-edit the malformed file or delete it |
     | Doctor advisory: duplicate EV-### detected | Advisory line listing the dupe IDs and files. Non-fatal. | Run an LLM cleanup pass (agent renumbers + rewrites references) |
     | Doctor advisory: dated source has rows missing `occurred:` | Advisory line listing the source short-name and row IDs. Non-fatal. | Hand-edit or re-run ingest with corrected source-map dating |
    +| Doctor advisory: source-coverage ledger mismatch (row's `Updated` predates file mtime) | Advisory line listing the row + file mtime. Non-fatal. | Re-run a pass that touches the source to refresh the row |
    +| Doctor advisory: closeout missing Pass-mode body block | Advisory line listing the closeout. Non-fatal. | Hand-edit the closeout to add the body block matching the frontmatter `pass-mode:` field |
     
     ## Interface
     
    @@ -187,11 +433,13 @@ fold KB state into the readback."
     
     ```
     ctx handover write  --summary "..." --next "..." [--highlights "..."] [--open-questions "..."] [--no-fold] [--commit <sha>]
    -ctx kb ingest <folder|paths...>            # 4-phase ingest pass
    +ctx kb ingest <folder|paths...>            # mode-aware editorial pass
     ctx kb ask "<question>"                    # Q&A from existing KB; read-only on prose
     ctx kb site-review                         # mechanical structural audit
     ctx kb ground                              # external grounding via grounding-sources.md
     ctx kb note "..."                          # lightweight capture; appends to ingest/findings.md
    +ctx kb topic new "<name>"                  # scaffold a topic folder (sole writer of topic index.md)
    +ctx kb reindex                             # refresh kb/index.md's CTX:KB:TOPICS managed block
     ctx kb site build                          # render kb to .context/site/kb/
     ctx kb site serve [--addr :8000]           # build + serve
     ctx kb site customize                      # lazy-init .context/site-config/kb.toml
    @@ -207,12 +455,13 @@ ctx kb site customize                      # lazy-init .context/site-config/kb.t
     | `--commit` | string | (resolved) | Override resolved git HEAD for Provenance line |
     | `--addr` | string | `:8000` | Bind address for `ctx kb site serve` |
     | `--inline` | string | "" | Inline natural-language source description for `ctx kb ingest` (for cases with no folder) |
    +| `--mode` | string | "" | Optional explicit pass-mode override (`topic-page`/`triage`/`evidence-only`). Default is skill-detected. CLI form respects same precedence rules. |
     
     ### Skill
     
     ```
     /ctx-handover <title>           # deploys via ctx setup; wraps ctx handover write
    -/ctx-kb-ingest <folder|paths>   # wraps ctx kb ingest
    +/ctx-kb-ingest <folder|paths>   # wraps ctx kb ingest; declares pass-mode up front
     /ctx-kb-ask "<question>"        # wraps ctx kb ask
     /ctx-kb-site-review             # wraps ctx kb site-review
     /ctx-kb-ground                  # wraps ctx kb ground
    @@ -221,18 +470,26 @@ ctx kb site customize                      # lazy-init .context/site-config/kb.t
     
     Trigger phrases (per-skill):
     
    -- `/ctx-handover`: "let's wrap up", "save state", "leave a handover", "before I go", "stepping away" (also: mandatory tail of `/ctx-wrap-up`).
    +- `/ctx-wrap-up`: "let's wrap up", "save context", "save state", "leave a handover", "before I go", "stepping away", "end of session". Owns the user-facing session-end trigger; always delegates to `/ctx-handover` as its final step. `/ctx-handover` is a sub-mechanism, not a user-facing trigger; legitimate direct-invocation cases are `--no-fold` mid-session checkpoint and recovery after an aborted session.
     - `/ctx-kb-ingest`: "ingest the transcripts", "pull this into the kb", "add evidence from", explicit slash form for paths.
     - `/ctx-kb-ask`: "does the kb say", "according to evidence", explicit slash form for the question.
     - `/ctx-kb-site-review`: "audit the kb", "check kb for rot".
     - `/ctx-kb-ground`: "re-ground the kb", "check upstream", explicit slash form preferred.
     - `/ctx-kb-note`: "drop a note", "capture this for the next ingest", "park this finding".
     
    -`/ctx-wrap-up` skill is updated to: (a) detect `.context/kb/`
    -existence, (b) surface editorial state (pending closeouts,
    -outstanding-questions count) in its summary, (c) mandatorily
    -drive `/ctx-handover` as the final step regardless of capture
    -outcomes.
    +`/ctx-wrap-up` skill is updated to: (a) **always** delegate to
    +`/ctx-handover` as its final step regardless of whether
    +`.context/kb/` exists or capture outcomes, (b) when
    +`.context/kb/` exists, additionally surface editorial state
    +(pending closeouts, outstanding-questions count) in its
    +summary before delegating. KB presence affects what gets
    +folded, not whether the handover is written.
    +
    +`/ctx-remember` skill is updated to: always read the latest
    +handover (orthogonal to KB; it is the former-agent-to-next-agent
    +note left by the previous wrap-up); when `.context/kb/`
    +exists, additionally fold any postdated unfolded closeouts
    +into the readback.
     
     ## Implementation
     
    @@ -240,38 +497,47 @@ outcomes.
     
     | File | Change |
     |------|--------|
    -| `internal/cli/handover/cmd.go` | New: `ctx handover write` Cobra command. `MarkFlagRequired` on `--summary` and `--next`. Calls `internal/store/handover.go` writer + closeout fold helpers. |
    -| `internal/cli/kb/cmd.go` | New: `ctx kb` Cobra parent command. |
    -| `internal/cli/kb/ingest/cmd.go` | New: `ctx kb ingest` four-phase pipeline runner. Refuses on empty input. |
    -| `internal/cli/kb/ask/cmd.go` | New: `ctx kb ask` Q&A driver. Refuses on empty question. |
    -| `internal/cli/kb/sitereview/cmd.go` | New: `ctx kb site-review` mechanical audit. |
    -| `internal/cli/kb/ground/cmd.go` | New: `ctx kb ground` external grounding. Reads `grounding-sources.md`; prompts when empty. |
    -| `internal/cli/kb/note/cmd.go` | New: `ctx kb note` lightweight capture. Appends to `.context/ingest/findings.md`. |
    -| `internal/cli/kb/site/cmd.go` | New: `ctx kb site build|serve|customize` mirrors existing `ctx journal site`. |
    -| `internal/store/handover.go` | New: `WriteHandover`, `LatestHandoverCursor`, `UnconsumedCloseouts`, `ArchiveCloseouts`. Mirrors sibling shape. |
    -| `internal/store/closeout.go` | New: `WriteCloseout` with required frontmatter; reader for `generated-at` cursor extraction. |
    -| `internal/store/kb.go` | New: writers for `evidence-index.md` (append, never renumber), `glossary.md`, `contradictions.md`, `outstanding-questions.md`, `domain-decisions.md`, `timeline.md`, `source-map.md`, `relationship-map.md`. |
    -| `internal/path/path.go` | Extend: `HandoversDir`, `KBDir`, `KBEvidenceFile`, `KBGlossaryFile`, ... `IngestDir`, `IngestRulesFile`, `IngestInboxFile`, `IngestSessionLogFile`, `IngestGroundingSources`, `CloseoutsSubdir`, `ArchiveCloseoutsSubdir`, `SiteDir`, `SiteKBDir`, `SiteConfigDir`. |
    -| `internal/cli/initcmd/init.go` | Extend: lay down `handovers/`, `kb/.gitkeep`, `ingest/` (full template tree), `site/` (gitignored). Add `--upgrade` flag for repos initialized before this spec. |
    -| `internal/assets/kb/templates/ingest/*.md` | New embedded templates: `KB-RULES.md`, `00-GROUND.md`, `30-INGEST.md`, `40-ASK.md`, `50-SITE_REVIEW.md`, `INBOX.md`, `SESSION_LOG.md`, `grounding-sources.md`, `OPERATOR.md`, `PROMPT.md`. |
    -| `internal/assets/kb/templates/ingest/schemas/*.md` | New embedded schemas: `evidence-index.md`, `glossary.md`, `contradictions.md`, `outstanding-questions.md`, `domain-decisions.md`, `timeline.md`, `source-map.md`, `relationship-map.md`, `session-log.md`. Each carries fields list + one worked example, no domain content. |
    -| `internal/assets/claude/skills/ctx-handover/SKILL.md` | New skill file with input contract, authority boundary, edge cases per spec. |
    -| `internal/assets/claude/skills/ctx-kb-ingest/SKILL.md` | New: 4-phase pipeline driver; refuses on empty input. |
    -| `internal/assets/claude/skills/ctx-kb-ask/SKILL.md` | New: Q&A from KB; refuses on empty question. |
    -| `internal/assets/claude/skills/ctx-kb-site-review/SKILL.md` | New: mechanical audit; no arguments. |
    -| `internal/assets/claude/skills/ctx-kb-ground/SKILL.md` | New: external grounding; reads `grounding-sources.md`. |
    -| `internal/assets/claude/skills/ctx-kb-note/SKILL.md` | New: lightweight capture. |
    +| `internal/gitmeta/require.go` | **NEW** (Phase RG): `RequireGitTree(projectRoot)` + typed `MissingGitError`. Phase KB depends on Phase RG; closeout/handover provenance requires honest git state. |
    +| `internal/gitmeta/resolvehead.go` | **NEW** (Phase RG): `ResolveHead(projectRoot)` returns commit short SHA + branch + override-via-env (`CTX_TASK_COMMIT`, `GITHUB_SHA`). Removes the `commit:none` fallback (state unreachable). |
    +| `internal/cli/handover/` | **NEW**: parent + `cmd/write/cmd.go`. `MarkFlagRequired` on `--summary`, `--next` with placeholder rejection (Phase SK pattern, already in `internal/validate/`). |
    +| `internal/cli/kb/` | **NEW**: parent `cmd.go`, `doc.go`, `run.go`. Subcommand subdirs: `cmd/ingest/`, `cmd/ask/`, `cmd/sitereview/`, `cmd/ground/`, `cmd/note/`, `cmd/topicnew/`, `cmd/reindex/`, `cmd/site/build/`, `cmd/site/serve/`, `cmd/site/customize/`. Refuse-on-empty for ingest/ask/ground. |
    +| `internal/cli/kb/core/` | **NEW**: shared helpers (`path/path.go` for KB path constants, `passmode/` for mode declaration + validation, `circuitbreaker/`, `ledger/` for source-coverage state machine, `adjacency/`, `coldreader/`, `lifestage/`). |
    +| `internal/write/handover/` | **NEW**: `WriteHandover`, `LatestHandoverCursor`, `UnconsumedCloseouts`, `ArchiveCloseouts`. Mirrors the upstream shape. |
    +| `internal/write/closeout/` | **NEW**: `WriteCloseout` with required frontmatter (`sha`, `branch`, `mode`, `pass-mode`, `life-stage`, `generated-at`); cursor-extracting reader. |
    +| `internal/write/kb/` | **NEW**: per-artifact writers (evidence-index append-never-renumber; glossary; contradictions; outstanding-questions; domain-decisions; timeline; source-map; relationship-map; source-coverage state-machine API); demotion API; `EvidenceRow` includes `occurred:` field; `TopicScaffold` writer (called only by `ctx kb topic new`). |
    +| `internal/cli/initialize/core/kb/` | **NEW**: scaffolding helper for `ctx init` to lay down `.context/kb/topics/.gitkeep`, `.context/ingest/`, `.context/handovers/`, `.context/site/`. |
    +| `internal/cli/initialize/cmd/root/cmd.go` | Modify: add `--upgrade` flag (idempotent on byte-identical content; refuse on divergent). |
    +| `internal/assets/embed.go` | Modify: add `//go:embed kb/templates/ingest/*.md kb/templates/ingest/schemas/*.md kb/templates/.gitkeep` lines. |
    +| `internal/assets/kb/templates/ingest/*.md` | **NEW**: embedded templates: `KB-RULES.md`, `00-GROUND.md`, `30-INGEST.md`, `40-ASK.md`, `50-SITE_REVIEW.md`, `INBOX.md`, `SESSION_LOG.md`, `grounding-sources.md`, `OPERATOR.md`, `PROMPT.md`. |
    +| `internal/assets/kb/templates/ingest/schemas/*.md` | **NEW**: embedded schemas: `evidence-index.md`, `glossary.md`, `contradictions.md`, `outstanding-questions.md`, `domain-decisions.md`, `timeline.md`, `source-map.md`, `source-coverage.md` (state-machine row format), `relationship-map.md`, `session-log.md`. Each carries fields list + one worked example, no domain content. |
    +| `internal/assets/kb/templates/kb/index.md` | **NEW**: kb landing page template with `CTX:KB:TOPICS` managed block + `## Scope` placeholder. |
    +| `internal/assets/kb/templates/kb/topics/_template/index.md` | **NEW**: topic-page template (Status block, lede, "What it is", "Why this kb cares", "Sources and further reading", "Related concepts in this kb"). |
    +| `internal/assets/claude/skills/ctx-handover/SKILL.md` | **NEW** with input contract, authority boundary, edge cases per spec. |
    +| `internal/assets/claude/skills/ctx-kb-ingest/SKILL.md` | **NEW**: mode-aware ingest driver; pass-mode declaration; 4 circuit-breaker invariants; adjacency pre-flight; cold-reader rubric; refuses on empty input. |
    +| `internal/assets/claude/skills/ctx-kb-ask/SKILL.md` | **NEW**: Q&A from KB; refuses on empty question. |
    +| `internal/assets/claude/skills/ctx-kb-site-review/SKILL.md` | **NEW**: mechanical audit; no arguments. |
    +| `internal/assets/claude/skills/ctx-kb-ground/SKILL.md` | **NEW**: external grounding; reads `grounding-sources.md`. |
    +| `internal/assets/claude/skills/ctx-kb-note/SKILL.md` | **NEW**: lightweight capture. |
     | `internal/assets/claude/skills/ctx-wrap-up/SKILL.md` | Modify: branch on `.context/kb/` existence; mandatorily drive `/ctx-handover` as final step. |
    -| `internal/assets/claude/skills/ctx-remember/SKILL.md` (or wherever it is) | Modify: read latest handover + any postdated unfolded closeouts; fold KB state into readback if `.context/kb/` exists. |
    -| `internal/cli/doctor/advisory.go` | Extend: duplicate-`EV-###` detection, `dated:` sources missing `occurred:` check, malformed-closeout-frontmatter detection. |
    +| `internal/assets/claude/skills/ctx-remember/SKILL.md` | Modify: read latest handover + any postdated unfolded closeouts; fold KB state into readback if `.context/kb/` exists. |
    +| `internal/cli/doctor/core/advisory.go` (or equivalent) | Extend: duplicate-`EV-###` detection; `dated:` sources missing `occurred:` check; malformed-closeout-frontmatter detection; **source-coverage-ledger-mismatch** detection (row's Updated vs. file mtime); **closeout-missing-pass-mode-body-block** detection; **illegal-ledger-state-transition** detection. |
     | `internal/cli/setup/cmd.go` | Already walks skills dir; new skill subdirs picked up automatically. |
    -| `internal/cli/wrapup/run.go` | Modify: `if KBExists() { printPendingCloseouts; printOutstandingQuestionsCount }`. Mandatory handover wording remains in skill, not CLI. |
    -| Project root `.gitignore` | Append: `.context/site/` (idempotent — match existing pattern for `.context/journal/.imported.json`). |
    +| `internal/cli/wrapup/run.go` (or equivalent) | Modify: `if KBExists() { printPendingCloseouts; printOutstandingQuestionsCount }`. Mandatory handover wording remains in skill, not CLI. |
    +| Project root `.gitignore` | Append: `.context/site/` (idempotent; match existing pattern for `.context/journal/.imported.json`). |
    +| `hack/smoke-kb.sh` | **NEW**: end-to-end shell smoke (init → kb ingest → kb ask → kb site-review → kb ground → handover write → archive populated → doctor clean). |
     
     ### Key Functions
     
     ```go
    -// internal/store/handover.go
    +// internal/gitmeta/require.go
    +type MissingGitError struct{ ProjectRoot string }
    +func RequireGitTree(projectRoot string) error
    +
    +// internal/gitmeta/resolvehead.go
    +type HeadRef struct{ SHA, Branch string }
    +func ResolveHead(projectRoot string) (HeadRef, error) // honors CTX_TASK_COMMIT, GITHUB_SHA
    +
    +// internal/write/handover/handover.go
     type HandoverEntry struct {
         Title           string
         Summary         string
    @@ -280,53 +546,137 @@ type HandoverEntry struct {
         OpenQuestions   string
         Commit          string
         Branch          string
    -    FoldedCloseouts []CloseoutFile
    +    FoldedCloseouts []closeout.File
     }
    -func (s *Store) WriteHandover(e HandoverEntry) (HandoverResult, error)
    -func (s *Store) LatestHandoverCursor() (cursor time.Time, file string, err error)
    -func (s *Store) UnconsumedCloseouts(cursor time.Time) (consumed []CloseoutFile, malformed []string, err error)
    -func (s *Store) ArchiveCloseouts(files []CloseoutFile) error
    -
    -// internal/store/closeout.go
    -type CloseoutFrontmatter struct {
    -    SHA, Branch, Mode string
    -    GeneratedAt       time.Time
    +func WriteHandover(projectRoot string, e HandoverEntry) (Result, error)
    +func LatestHandoverCursor(projectRoot string) (cursor time.Time, file string, err error)
    +func UnconsumedCloseouts(projectRoot string, cursor time.Time) (consumed []closeout.File, malformed []string, err error)
    +func ArchiveCloseouts(projectRoot string, files []closeout.File) error
    +
    +// internal/write/closeout/closeout.go
    +type Frontmatter struct {
    +    SHA, Branch, Mode, PassMode, LifeStage string
    +    GeneratedAt                             time.Time
     }
    -type CloseoutFile struct {
    -    Path string
    -    Frontmatter CloseoutFrontmatter
    -    Body string
    +type File struct {
    +    Path        string
    +    Frontmatter Frontmatter
    +    Body        string
     }
    -func (s *Store) WriteCloseout(mode string, body string) (CloseoutFile, error)
    -
    -// internal/store/kb.go
    -func (s *Store) AppendEvidence(row EvidenceRow) error  // never renumber
    -func (s *Store) DemoteEvidence(id string, newBand string, reason string) error
    -// ... per-artifact writers
    +func WriteCloseout(projectRoot, mode, passMode, lifeStage string, body string) (File, error)
    +
    +// internal/write/kb/evidence.go
    +type EvidenceRow struct {
    +    ID         string // EV-###
    +    Claim      string
    +    SourceID   string
    +    Locator    string
    +    SHA        string // optional; for in-repo citations
    +    Confidence string // high|medium|low|speculative
    +    Tags       []string
    +    Occurred   *time.Time // optional; temporal-precedence rule
    +    Extracted  time.Time
    +}
    +func AppendEvidence(projectRoot string, row EvidenceRow) error  // never renumber
    +func DemoteEvidence(projectRoot, id, newBand, reason string) error
    +
    +// internal/write/kb/sourcecoverage.go
    +type LedgerState string
    +const (
    +    StateDiscovered          LedgerState = "discovered"
    +    StateAdmitted            LedgerState = "admitted"
    +    StateHighlightsExtracted LedgerState = "highlights-extracted"
    +    StatePartiallyIngested   LedgerState = "partially-ingested"
    +    StateTopicPageDrafted    LedgerState = "topic-page-drafted"
    +    StateComprehensive       LedgerState = "comprehensive"
    +    StateSuperseded          LedgerState = "superseded"
    +    StateSkipped             LedgerState = "skipped"
    +)
    +type LedgerRow struct {
    +    Source, Topic                string
    +    State                        LedgerState
    +    EVCoverage, Residue          string
    +    NextAction                   string
    +    Updated                      time.Time
    +}
    +func AdvanceLedger(projectRoot string, row LedgerRow) error // validates allowed transition
    +func ReadLedger(projectRoot string) ([]LedgerRow, error)
    +func ValidTransition(from, to LedgerState) bool
    +
    +// internal/cli/kb/core/passmode/passmode.go
    +type Mode string
    +const (
    +    ModeTopicPage    Mode = "topic-page"
    +    ModeTriage       Mode = "triage"
    +    ModeEvidenceOnly Mode = "evidence-only"
    +)
    +type Declaration struct {
    +    Mode            Mode
    +    Reason          string
    +    DefinitionOfDone string
    +}
    +func RenderDeclaration(d Declaration) string // 3-line markdown block
    +
    +// internal/cli/kb/core/circuitbreaker/check.go
    +type Invariants struct {
    +    PageExists       bool
    +    CitesEV          bool
    +    SiteBuildClean   bool
    +    ColdReaderPass   bool
    +}
    +func (i Invariants) AllPassed() bool
    +func Check(projectRoot, slug string, coldReader ColdReader) (Invariants, error)
    +
    +// internal/cli/kb/core/coldreader/rubric.go
    +type Rubric struct {
    +    ConceptClear            string // yes|no: note
    +    WhyClear                string
    +    EvidenceReachable       string
    +    BoundariesClear         string
    +}
    +func (r Rubric) Result() string // "pass" if all yes; "fail" otherwise
    +
    +// internal/cli/kb/core/lifestage/lifestage.go
    +type Stage string
    +const (
    +    StageBootstrap   Stage = "bootstrap"
    +    StageMaintenance Stage = "maintenance"
    +)
    +func Detect(projectRoot string, threshold int) (Stage, int, error) // threshold default 5
     ```
     
     ### Helpers to Reuse
     
    -- `internal/gitmeta/` — already resolves git HEAD into commit + branch with overrides for CI replay (`CTX_TASK_COMMIT`, `GITHUB_SHA`).
    -- `internal/cli/journal/site/` — existing zensical shell-out pattern; lift the build/serve runtime-config materialization wholesale for `ctx kb site`.
    -- `internal/cli/initcmd/` — existing template walk for embedded asset deployment.
    -- `internal/cli/setup/` — existing skill-dir walker; new skill subdirs land for free.
    -- `internal/cli/doctor/advisory.go` — existing advisory pattern; add new check functions following the same shape.
    -- `internal/path/path.go` — existing path constant convention; extend.
    -- `internal/store/` — existing store helpers (path resolution, `O_CREATE|O_EXCL` writes, idempotency); reuse for new writers.
    +- `internal/validate/`: already has `RequireBodyFlags` (Phase SK)
    +  for `MarkFlagRequired` + placeholder rejection. Reuse for
    +  `ctx handover write`.
    +- `internal/cli/journal/cmd/site/`: existing zensical shell-out
    +  pattern; lift the build/serve runtime-config materialization
    +  wholesale for `ctx kb site`.
    +- `internal/config/zensical/`: existing mkdocs.go / toml.go;
    +  reuse for kb-site config rendering.
    +- `internal/cli/initialize/core/`: existing template walk for
    +  embedded asset deployment.
    +- `internal/cli/setup/`: existing skill-dir walker; new skill
    +  subdirs land for free.
    +- `internal/cli/doctor/`: existing advisory pattern; add new
    +  check functions following the same shape.
    +- `internal/assets/embed.go`: existing `//go:embed` block;
    +  extend with kb-template lines.
     
     ## Configuration
     
     No new `.ctxrc` keys in v1. Site-config (`.context/site-config/kb.toml`)
    -is lazy-initialized via `ctx kb site customize` for users who want
    -to override theme / nav / plugins. Infrastructure paths
    +is lazy-initialized via `ctx kb site customize` for users who
    +want to override theme / nav / plugins. Infrastructure paths
     (`docs_dir`, `site_dir`) are wrapper-owned and overwritten at
     build time per the existing `ctx journal site` pattern.
     
     Environment variables honored:
     
    -- `CTX_TASK_COMMIT` — override resolved commit for handover Provenance (CI replay).
    -- `GITHUB_SHA` (when `GITHUB_ACTIONS=true`) — same purpose.
    +- `CTX_TASK_COMMIT`: override resolved commit for handover
    +  Provenance (CI replay).
    +- `GITHUB_SHA` (when `GITHUB_ACTIONS=true`): same purpose.
     
     `ZENSICAL_BIN` is **not** introduced; the binary is resolved
     from PATH per the existing journal-site convention.
    @@ -335,105 +685,182 @@ from PATH per the existing journal-site convention.
     
     ### Unit
     
    -- `internal/store/handover_test.go` — `WriteHandover` happy
    -  path; rejects empty summary/next; rejects placeholder bodies;
    -  closeout fold cursor logic; archive moves files
    -  atomically.
    -- `internal/store/closeout_test.go` — frontmatter parse;
    -  malformed frontmatter handling; `generated-at` ordering.
    -- `internal/store/kb_test.go` — evidence-index append never
    -  renumbers; demotion bands valid only within
    +- `internal/gitmeta/require_test.go`: `.git` dir → nil;
    +  `.git` file (worktree pointer) → nil; absent → typed error;
    +  override allow-list (root help-shaped commands) skips the
    +  check.
    +- `internal/gitmeta/resolvehead_test.go`: happy path; env
    +  override via `CTX_TASK_COMMIT`; `GITHUB_SHA` honored only
    +  with `GITHUB_ACTIONS=true`.
    +- `internal/write/handover/handover_test.go`:
    +  `WriteHandover` happy path; rejects empty summary/next;
    +  rejects placeholder bodies; closeout fold cursor logic;
    +  archive moves files atomically.
    +- `internal/write/closeout/closeout_test.go`: frontmatter
    +  parse (all required fields including `pass-mode`,
    +  `life-stage`); malformed frontmatter handling;
    +  `generated-at` ordering.
    +- `internal/write/kb/evidence_test.go`: evidence-index
    +  append never renumbers; demotion bands valid only within
       `high|medium|low|speculative`; concurrent-write detection
       surfaces in doctor.
    -- `internal/cli/kb/<mode>/cmd_test.go` — refuse-on-empty for
    -  ingest / ask / ground.
    -- `internal/cli/handover/cmd_test.go` — `MarkFlagRequired`
    -  enforcement; `--no-fold` behavior; provenance from
    -  `gitmeta.ResolveHead`.
    -- `internal/cli/doctor/advisory_test.go` — duplicate-`EV-###`
    -  detection; `dated:`-without-`occurred:` detection;
    -  malformed-closeout detection.
    +- `internal/write/kb/sourcecoverage_test.go`: every allowed
    +  transition; every disallowed transition refused;
    +  `ValidTransition(from, to)` truth table; row schema parse
    +  and write round-trip.
    +- `internal/cli/kb/core/passmode/passmode_test.go`:
    +  declaration render; mode set validation; rejects invalid
    +  modes; rejects empty reason on non-default modes.
    +- `internal/cli/kb/core/circuitbreaker/check_test.go`: all
    +  four invariants must hold; any one failing produces a
    +  reportable failure with the specific item named.
    +- `internal/cli/kb/core/coldreader/rubric_test.go`: any `no`
    +  → `fail`; all `yes` → `pass`.
    +- `internal/cli/kb/core/lifestage/lifestage_test.go`: count
    +  topic folders; threshold boundary (4 → bootstrap, 5 →
    +  maintenance).
    +- `internal/cli/kb/<mode>/cmd_test.go`: refuse-on-empty for
    +  ingest / ask / ground; refuse on existing slug for topic new.
    +- `internal/cli/handover/cmd/write/cmd_test.go`:
    +  `MarkFlagRequired` enforcement; `--no-fold` behavior;
    +  provenance from `gitmeta.ResolveHead`.
    +- `internal/cli/doctor/core/advisory_test.go`:
    +  duplicate-`EV-###`; `dated:`-without-`occurred:`;
    +  malformed-closeout; source-coverage-ledger-mismatch;
    +  closeout-missing-pass-mode-body-block;
    +  illegal-ledger-state-transition.
     
     ### Integration
     
    -- `internal/cli/initcmd/init_test.go` — full init creates all
    -  new dirs and templates; `--upgrade` is idempotent on
    +- `internal/cli/initialize/init_test.go`: full init creates
    +  all new dirs and templates; `--upgrade` is idempotent on
       byte-identical existing content; `--upgrade` refuses on
       divergent existing content.
    -- `internal/cli/setup/setup_test.go` — new skill subdirs deploy.
    -- `hack/smoke-kb.sh` — end-to-end shell smoke: `ctx init`;
    -  `ctx kb ingest ./testdata/inputs`; `ctx kb ask "..."`;
    -  `ctx kb site-review`; `ctx kb ground`; `ctx handover write
    -  --summary X --next Y`; verify files exist, closeouts folded,
    -  archive populated, no doctor errors.
    +- `internal/cli/setup/setup_test.go`: new skill subdirs deploy.
    +- `hack/smoke-kb.sh`: end-to-end shell smoke. `git init &&
    +  ctx init`; `ctx kb ingest ./testdata/inputs`; `ctx kb ask
    +  "..."`; `ctx kb site-review`; `ctx kb ground`; `ctx
    +  handover write --summary X --next Y`; verify files exist,
    +  closeouts folded, archive populated, no doctor errors.
     
    -### Edge cases
    +### Edge Cases
     
     - Aborted-session recovery: write a closeout, do NOT write a
       handover, simulate session restart by re-reading the state;
       verify `/ctx-remember`'s read path picks up the unfolded
    -  closeout (test the store helper directly).
    -- Temporal misordering: ingest fixture A (occurred 2026-04-12,
    -  extracted 2026-05-09) then fixture B (occurred 2026-04-05,
    -  extracted 2026-05-09); verify demotion does NOT fire because
    -  temporal-precedence rule wins.
    -- Concurrent dupe IDs: simulate two parallel writers producing
    -  `EV-020` against different claims; verify `ctx doctor` flags
    -  the dupe; verify cross-references survive an LLM-style manual
    -  resolution (provided as a fixture in tests).
    +  closeout (test the writer helper directly).
    +- Temporal misordering: ingest fixture A (occurred
    +  2026-04-12, extracted 2026-05-09) then fixture B (occurred
    +  2026-04-05, extracted 2026-05-09); verify demotion does NOT
    +  fire because temporal-precedence rule wins.
    +- Concurrent dupe IDs: simulate two parallel writers
    +  producing `EV-020` against different claims; verify
    +  `ctx doctor` flags the dupe.
     - Render filter: speculative content does NOT appear in built
       HTML; low-confidence content appears only when paired with
       outstanding-questions row.
    -
    -### Validation corpus
    -
    -`things-wtf-disaster-recovery` is the live regression suite.
    -Phase 2 (per the brief) is "port things-wtf to the shipped
    -shape and document any divergence." Each divergence is either a
    -bug fix on this spec or a `DECISIONS.md` entry explaining why
    -the formal shape differs from what worked manually.
    +- Mid-pass mode-switch attempted: declared `topic-page`,
    +  agent realizes it spans 5 topics, must abort with partial
    +  closeout rather than silently widen.
    +- Adjacency pre-flight zero matches: must surface
    +  `none surfaced` explicitly in response contract +
    +  closeout's `Adjacency pre-flight` block.
    +- Cold-reader fail: integration test ingests a deliberately
    +  vague source, verifies rubric reports `fail`, ledger
    +  advances to `topic-page-drafted` not `comprehensive`,
    +  closeout names which rubric items failed.
    +
    +### Validation Corpus
    +
    +`your-project` is the live regression suite
    +(older shape, hand-rolled) and the structural reference
    +(current upstream shape applied to a different domain).
    +Phase KB-2 stands up a new research workspace using the
    +shipped `ctx kb` tool; each divergence from manual is either
    +a Phase KB bug or a `DECISIONS.md` entry explaining why the
    +formal shape differs from what worked manually.
     
     ## Non-Goals
     
    -(Explicit deferrals — referenced from the brief's "What we
    +(Explicit deferrals, referenced from the brief's "What we
     rejected" table.)
     
     - **No `/ctx-kb-decide` skill.** KB ontology rejects it: in a
    -  KB you don't decide, you increase confidence. Pipeline is the
    -  sole writer; ad-hoc capture flows through `ctx kb note` or
    -  hand-edit.
    -- **No team write-coordination layer.** Single-writer convention
    -  with doctor advisory + LLM cleanup is the v1 stance.
    -  Multi-writer coordination is deferred until a real team-scale
    -  user hits the wall.
    +  KB you don't decide, you increase confidence. Pipeline is
    +  the sole writer; ad-hoc capture flows through `ctx kb note`
    +  or hand-edit.
    +- **No team write-coordination layer.** Single-writer
    +  convention with doctor advisory + LLM cleanup is the v1
    +  stance. Multi-writer coordination is deferred until a real
    +  team-scale user hits the wall.
     - **No UUIDs for evidence rows.** `EV-###` aesthetic preserved.
     - **No KB-scoped IDs (`research-master/EV-019`) in v1.** P2
       federation handles multi-KB without scoping IDs.
     - **No domain-split per user (`.context/kb/<domain>/`).** Single
       KB is simpler; multi-domain via P2 federation when needed.
    +  Topic folders provide intra-kb structure.
     - **No KB-side merge into canonical files.** `domain-decisions.md`
    -  stays separate from `DECISIONS.md`. Different schema, different
    -  write authority, different lifecycle.
    -- **No automatic demotion-cascade in v1.** When EV-031 demotes,
    -  affected glossary / domain-decision / timeline rows are NOT
    -  auto-flagged; the human handles the cascade. v2 may add
    -  site-review automation.
    +  stays separate from `DECISIONS.md`. Different schema,
    +  different write authority, different lifecycle.
    +- **No automatic demotion-cascade in v1.** When EV-031
    +  demotes, affected glossary / domain-decision / timeline
    +  rows are NOT auto-flagged; the human handles the cascade.
    +  v2 may add site-review automation.
     - **No bundled renderer.** `zensical` is shelled out, not
       vendored or wrapped. Same model as `ctx journal site`.
    -- **No KB linting.** Confidence-band discipline is rule-driven
    -  (per `KB-RULES.md`), not enforced programmatically.
    -- **No bulk migration tooling for repos initialized before this
    -  spec.** `ctx init --upgrade` lays down the new dirs
    +- **No KB linting.** Confidence-band discipline is
    +  rule-driven (per `KB-RULES.md`), not enforced
    +  programmatically.
    +- **No bulk migration tooling for repos initialized before
    +  this spec.** `ctx init --upgrade` lays down the new dirs
       idempotently; pre-existing canonical files are untouched;
    -  hand-rolled editorial files (e.g. things-wtf's
    -  `10-CONSTITUTION.md` at repo root) are left alone — porting
    -  is a manual cutover (Phase 2 of validation).
    +  hand-rolled editorial files (e.g. `your-project`'s
    +  `10-CONSTITUTION.md` at repo root) are left alone;
    +  porting is a manual cutover (Phase 2 of validation).
     - **No interactive install of zensical.** Missing-binary case
       fails with a one-line install hint and non-zero exit.
    -- **No replacement of `/ctx-decision-add`, `/ctx-learning-add`,
    -  `/ctx-task-add`, `/ctx-convention-add`, `/ctx-wrap-up`.** New
    -  skills are siblings; existing capture skills unchanged in
    -  authority.
    +- **No replacement of `/ctx-decision-add`,
    +  `/ctx-learning-add`, `/ctx-task-add`, `/ctx-convention-add`,
    +  `/ctx-wrap-up`.** New skills are siblings; existing
    +  capture skills unchanged in authority.
    +- **No mid-pass mode-switching.** Mode is committed at
    +  declaration time; if work no longer fits, abort and
    +  re-invoke. Silent switching is a hard anti-pattern.
    +- **No pre-emptive sub-page split.** Topic pages stay as
    +  `index.md` until the cold-reader "boundaries clear?" check
    +  fails; only then is a split proposed.
    +
    +## Failure Analysis
    +
    +Three concrete ways the lifted shape fails badly, with
    +mitigations baked into the spec:
    +
    +1. **Pass-mode contract gets ignored under operator
    +   pressure.** Anti-pattern: inferring `evidence-only` to
    +   dodge topic-page validation. Mitigation: declaration is
    +   logged to the closeout's frontmatter (`pass-mode:`) AND
    +   to the closeout body block (redundancy makes
    +   false-finish drift visible); doctor advisory detects
    +   closeouts whose body `Pass-mode` block disagrees with the
    +   frontmatter `pass-mode:` field.
    +2. **Source-coverage ledger drifts from reality.**
    +   Anti-pattern: "lying to the ledger." Mitigation: doctor
    +   advisory cross-checks ledger rows against file existence
    +   + last-modified time vs. row's `Updated` cell. Mismatch
    +   → advisory line. Additionally, illegal state transitions
    +   (e.g. `comprehensive → highlights-extracted` without an
    +   explicit `superseded` step) are refused at write time by
    +   `AdvanceLedger`.
    +3. **Adjacency pre-flight degenerates into trivia.** A
    +   surfaced sibling pasted as a footnote satisfies the
    +   letter, not the spirit. Mitigation: the pre-flight
    +   result is a structured field in the closeout: the
    +   `Adjacent topics noted` field must be either
    +   `none surfaced` or a slug-list, never free prose.
    +   Doctor parses the field; free-prose values fail
    +   validation.
    +
    +These mitigations are part of v1, not v2.
     
     ## Open Questions
     
    @@ -446,8 +873,9 @@ implementation.)
        kickoff.
     2. **Brief vs spec storage.** Where do briefs live?
        `.context/briefs/` (debate residue, distinct lifecycle) vs
    -   `.context/specs/briefs/` (subdir of specs). Lean dedicated
    -   `briefs/`. Tied to the polish-PR work in `ideas/002` §3.
    +   `.context/specs/briefs/` (subdir of specs). Lean
    +   dedicated `briefs/`. Tied to the polish-PR work in
    +   `ideas/002` §3.
     3. **`ctx kb note` destination.** Single
        `.context/ingest/findings.md` or one file per invocation?
        Lean single file (simpler); per-invocation preserves
    @@ -457,31 +885,22 @@ implementation.)
        deployment? Light defer; not v1 critical. Things-wtf
        workaround (CLAUDE.md disabling) remains acceptable for v1.
     5. **Confidence bands flowing into `LEARNINGS.md`.** Probably
    -   no (different truth bases — KB has citations, learnings have
    -   author intent). Confirm rather than assume.
    +   no (different truth bases: KB has citations, learnings
    +   have author intent). Confirm rather than assume.
     6. **`relationship-map.md` vs GitNexus.** Different graphs;
        v1 keeps independent. Cross-feed is v2.
    -7. **Demotion-policy automation.** Auto-flag affected pages on
    -   demotion? v1 defers to human; v2 may automate via
    +7. **Demotion-policy automation.** Auto-flag affected pages
    +   on demotion? v1 defers to human; v2 may automate via
        site-review.
    -8. **`--no-fold` flag scope.** Handover-only (sibling's choice)
    -   vs every artifact-writing command. Lean handover-only.
    -9. **Polish-PR ordering.** The `MarkFlagRequired` /
    -   `--brief <path>` / authority-boundary rewrites in
    -   `ideas/002` §3 should ship **before** this spec is
    -   implemented (so `/ctx-spec --brief` works for the next round
    -   of feature specs). Recommended as Phase 0a of the
    -   implementation task breakdown.
    -10. **Git-mandate dependency (Phase 0b).** `specs/require-git.md`
    -    is a hard prerequisite. Once it ships, the `commit:none`
    -    sentinel is unreachable: the duplicate-`EV-###` advisory in
    -    `internal/cli/doctor/advisory.go` does NOT need to handle
    -    `commit:none` cases; `gitmeta.ResolveHead` calls in
    -    `WriteHandover` / `WriteCloseout` similarly drop their
    -    `none` fallback paths; closeout frontmatter `sha:` /
    -    `branch:` are guaranteed populated. Confirm during
    -    implementation that no Phase KB code paths silently retain
    -    `commit:none` handling.
    +8. **`--no-fold` flag scope.** Handover-only (the upstream's
    +   choice) vs every artifact-writing command. Lean
    +   handover-only.
    +9. **Life-stage threshold.** 5 topic pages matches the upstream default.
    +   Confirm or override.
    +10. **Path constants location.** `internal/cli/kb/core/path/`
    +    (per-subcommand pattern matching `internal/cli/task/core/path/`)
    +    vs a new top-level `internal/path/`. Lean
    +    per-subcommand (matches existing `ctx` convention).
     
     ---
     
    @@ -490,15 +909,24 @@ implementation.)
     `ideas/003-editorial-pipeline-debated-brief.md` (debated brief
     from `/ctx-plan` session 01d0cf92, 2026-05-09).
     
    +`ideas/upstream-pipeline-comparison.md` (revision-2
    +input; 2026-05-16) is the comparison note that surfaced the
    +deltas absorbed in this revision.
    +
     Inputs to the brief:
     
    -- `ideas/001-sibling-project-undercover-analysis.md` — handover
    -  mechanism, closeout/fold mechanism, doctor advisory tier.
    -- `ideas/002-editorial-pipeline-and-skill-rigor.md` — the lifted-
    -  pipeline plan + skill ceremony comparison.
    -- `things-wtf-disaster-recovery/` (sibling workspace) — live
    -  test corpus; hand-rolled version of the shape.
    -- Sibling tool's `internal/cli/initcmd/templates/ingest/` —
    -  source templates referenced for shape lift.
    -- `.context/journal-site/zensical.toml` — proof that ctx already
    -  shells out to zensical for journal rendering.
    +- `ideas/001-sibling-project-undercover-analysis.md`:
    +  handover mechanism, closeout/fold mechanism, doctor
    +  advisory tier.
    +- `ideas/002-editorial-pipeline-and-skill-rigor.md`:
    +  the lifted-pipeline plan + skill ceremony comparison.
    +- `your-project/` (upstream-reference workspace):
    +  live test corpus; hand-rolled version of the older 4-phase
    +  shape.
    +- `your-project` (upstream-reference workspace): structural
    +  reference for the current upstream shape applied to a
    +  different domain.
    +- the upstream reference's editorial-pipeline source tree:
    +  source templates and SKILL.md referenced for shape lift.
    +- `.context/journal-site/zensical.toml`: proof that `ctx`
    +  already shells out to zensical for journal rendering.
    diff --git a/specs/opencode-integration.md b/specs/opencode-integration.md
    index ccadf3742..bb2f2a768 100644
    --- a/specs/opencode-integration.md
    +++ b/specs/opencode-integration.md
    @@ -70,7 +70,7 @@ runtime provides the plugin context, so there's no runtime
     dependency tree to install.
     
     **`skills/`** — Subset of portable skills (ctx-agent, ctx-remember, ctx-status,
    -ctx-wrap-up). Format: YAML frontmatter + markdown body, same as Copilot CLI skills.
    +ctx-wrap-up). Format: YAML frontmatter + Markdown body, same as Copilot CLI skills.
     
     ### 2. Asset Reader (`internal/assets/read/agent/agent.go`)
     
    diff --git a/specs/placeholder-i18n.md b/specs/placeholder-i18n.md
    index 46d01ca22..bb2554bbf 100644
    --- a/specs/placeholder-i18n.md
    +++ b/specs/placeholder-i18n.md
    @@ -23,7 +23,7 @@ Three concrete gaps:
     1. **English-only set** baked as Go constants
        (`PlaceholderTBD`, `PlaceholderNA`, `PlaceholderSeeChat`, …). A
        Spanish, Turkish, German, or Japanese user has no shipped
    -   defence against their language's "to be defined" markers.
    +   defense against their language's "to be defined" markers.
     2. **No `.ctxrc` override hook**. Every other parser-vocabulary
        list in ctx (`session_prefixes`, `classify_rules`,
        `spec_signal_words`) is overridable. Placeholders are the
    @@ -211,7 +211,7 @@ func RejectPlaceholder(flag, value string) error {
       body flags (decision/learning add today, more later). Per-flag
       customization can be added if a real need appears.
     - **Shipping `tr` (or any other locale) defaults in this spec.**
    -  ctx has not shipped any locale-specific behaviour yet (no `tr`
    +  ctx has not shipped any locale-specific behavior yet (no `tr`
       files anywhere). Ship `en` only; the structure makes adding
       `tr.yaml`, `es.yaml`, etc. a copy-edit later.
     - **Fuzzy / Levenshtein / stem matching.** Exact case-folded
    diff --git a/specs/released/v0.1.0/cli.md b/specs/released/v0.1.0/cli.md
    index 66c5a3603..edf84a314 100644
    --- a/specs/released/v0.1.0/cli.md
    +++ b/specs/released/v0.1.0/cli.md
    @@ -174,7 +174,7 @@ Load and display assembled context (what AI sees).
     ctx load [--budget <tokens>] [--raw]
     ```
     
    -**Output**: The assembled markdown context as it would be provided to an AI.
    +**Output**: The assembled Markdown context as it would be provided to an AI.
     
     **Options**:
     - `--budget <tokens>`: Token budget for assembly (default: 8000)
    diff --git a/specs/released/v0.1.0/context-file-formats.md b/specs/released/v0.1.0/context-file-formats.md
    index 94dd5e0fe..04bfed063 100644
    --- a/specs/released/v0.1.0/context-file-formats.md
    +++ b/specs/released/v0.1.0/context-file-formats.md
    @@ -2,7 +2,7 @@
     
     ## Overview
     
    -Each context file serves a specific purpose and follows a consistent markdown 
    +Each context file serves a specific purpose and follows a consistent Markdown 
     structure. Files are designed to be human-readable, AI-parseable, 
     and token-efficient.
     
    diff --git a/specs/released/v0.1.0/context-loader.md b/specs/released/v0.1.0/context-loader.md
    index 7166a1b0c..7c193b561 100644
    --- a/specs/released/v0.1.0/context-loader.md
    +++ b/specs/released/v0.1.0/context-loader.md
    @@ -10,7 +10,7 @@ Context system.
     
     1. **Discovery** — Find the `.context/` directory and enumerate files
     2. **Reading** — Load file contents with proper encoding
    -3. **Parsing** — Convert markdown to structured data
    +3. **Parsing** — Convert Markdown to structured data
     4. **Assembly** — Combine parsed files into unified context
     5. **Budgeting** — Respect token limits and prioritize content
     
    @@ -82,7 +82,7 @@ CONSTITUTION.md is NEVER truncated.
     
     ### Markdown to Structure
     
    -The parser extracts structure from markdown using these rules:
    +The parser extracts structure from Markdown using these rules:
     
     ```go
     type ParsedContent struct {
    @@ -108,7 +108,7 @@ type Item struct {
     
     ### Error Handling
     
    -- **Malformed markdown**: Parse what's possible, log warnings
    +- **Malformed Markdown**: Parse what's possible, log warnings
     - **Missing files**: Include in `missing` array, continue loading others
     - **Encoding issues**: Assume UTF-8, replace invalid sequences
     - **Empty files**: Include with empty parsed content
    @@ -121,7 +121,7 @@ More accurate estimation can be added later with tiktoken or similar.
     
     ## Assembly
     
    -The `summary` field contains the assembled context as a single markdown string:
    +The `summary` field contains the assembled context as a single Markdown string:
     
     ```markdown
     # Project Context
    @@ -163,6 +163,6 @@ Optional caching layer:
     
     - Unit tests for each parsing rule
     - Integration tests with sample `.context/` directories
    -- Edge cases: empty files, malformed markdown, missing directory
    +- Edge cases: empty files, malformed Markdown, missing directory
     - Token budget tests: verify truncation behavior
     - Performance: loading should complete in <100ms for typical projects
    diff --git a/specs/released/v0.1.0/context-updater.md b/specs/released/v0.1.0/context-updater.md
    index ab19f8960..2017e32aa 100644
    --- a/specs/released/v0.1.0/context-updater.md
    +++ b/specs/released/v0.1.0/context-updater.md
    @@ -143,7 +143,7 @@ Before writing, validate:
     
     1. Target file exists (or can be created)
     2. Action is valid
    -3. Content is well-formed markdown
    +3. Content is well-formed Markdown
     4. Section exists (if specified)
     5. ID can be found (for update/remove/complete)
     
    diff --git a/specs/released/v0.1.0/core-architecture.md b/specs/released/v0.1.0/core-architecture.md
    index 4cfb315a8..bc7899d53 100644
    --- a/specs/released/v0.1.0/core-architecture.md
    +++ b/specs/released/v0.1.0/core-architecture.md
    @@ -65,7 +65,7 @@ For projects with many architectural decisions, use a `decisions/` directory:
     
     A module that:
     - Discovers and reads context files
    -- Parses markdown into structured data
    +- Parses Markdown into structured data
     - Assembles context for AI consumption
     - Handles missing files gracefully
     
    @@ -93,7 +93,7 @@ A single Go binary (`ctx`) with commands for human operators:
     
     ## Constraints
     
    -1. **No binary files** — All context must be text-based markdown
    +1. **No binary files** — All context must be text-based Markdown
     2. **No external services** — Everything runs locally, offline-capable
     3. **No magic** — Explicit is better than implicit; all context loading visible
     4. **No lock-in** — If you delete the CLI, the files remain useful
    diff --git a/specs/released/v0.2.0/IMPLEMENTATION_PLAN.md b/specs/released/v0.2.0/IMPLEMENTATION_PLAN.md
    index 02f5f182b..371b47a0d 100644
    --- a/specs/released/v0.2.0/IMPLEMENTATION_PLAN.md
    +++ b/specs/released/v0.2.0/IMPLEMENTATION_PLAN.md
    @@ -103,7 +103,7 @@ Session Files → Parser → Rendered HTML → HTTP Server
     - [ ] Pattern-based extraction (deterministic)
     - [ ] LLM refinement pass (optional)
     - [ ] Deduplication across sessions
    -- [ ] ctx-compatible markdown output
    +- [ ] ctx-compatible Markdown output
     
     ### Extraction Targets
     
    diff --git a/specs/released/v0.2.0/PROMPT.md b/specs/released/v0.2.0/PROMPT.md
    index 48fcb9326..70e2a173b 100644
    --- a/specs/released/v0.2.0/PROMPT.md
    +++ b/specs/released/v0.2.0/PROMPT.md
    @@ -79,7 +79,7 @@ Check if `internal/recall/` exists.
     
     **Renderer** (T1.2.x):
     - Use `//go:embed` for templates and static assets
    -- Use `goldmark` for markdown → HTML
    +- Use `goldmark` for Markdown → HTML
     - Use `chroma` for syntax highlighting
     - Thinking blocks: `<details>` elements, collapsed by default
     
    @@ -148,7 +148,7 @@ You will not remember this conversation. Write everything important to files.
     - No panics in library code
     - Use `internal/recall/` for all recall code
     - Embed assets with `//go:embed`
    -- Use `goldmark` for markdown, `chroma` for syntax highlighting
    +- Use `goldmark` for Markdown, `chroma` for syntax highlighting
     
     ### SCOPE CONSTRAINTS
     - Go only. No external databases. No npm/node.
    diff --git a/specs/released/v0.6.0/journal-obsidian.md b/specs/released/v0.6.0/journal-obsidian.md
    index 827f6badf..8bb36a271 100644
    --- a/specs/released/v0.6.0/journal-obsidian.md
    +++ b/specs/released/v0.6.0/journal-obsidian.md
    @@ -21,7 +21,7 @@ as an Obsidian vault. Reuses the existing scan/parse/index infrastructure from
       `buildTopicIndex`, `buildKeyFileIndex`, `buildTypeIndex` functions.
       The new command is an alternative output backend, not a parallel pipeline.
     - **Wikilinks native**: All internal links use `[[target|display]]` format.
    -  No markdown relative links.
    +  No Markdown relative links.
     - **Graph-optimized**: Every entry links to its topic/type MOCs. MOCs link
       to entries. This creates hub-and-spoke graph topology.
     - **Minimal vault config**: Just `.obsidian/` with `app.json` enforcing
    @@ -124,7 +124,7 @@ Changes:
     
     ### 2. Link Conversion
     
    -All markdown links to journal entries become wikilinks:
    +All Markdown links to journal entries become wikilinks:
     
     | Source (site format)                        | Obsidian output                              |
     |---------------------------------------------|----------------------------------------------|
    @@ -313,7 +313,7 @@ const (
     ### Wikilink Conversion Strategy
     
     1. Parse content line-by-line
    -2. Match markdown links with regex: `\[([^\]]+)\]\(([^)]+)\)`
    +2. Match Markdown links with regex: `\[([^\]]+)\]\(([^)]+)\)`
     3. For each match:
        - If target starts with `http://` or `https://` — skip (external)
        - Strip `.md` extension from target
    @@ -325,7 +325,7 @@ const (
     
     ## Testing
     
    -- Unit tests for wikilink conversion (markdown link → wikilink)
    +- Unit tests for wikilink conversion (Markdown link → wikilink)
     - Unit tests for frontmatter transformation (topics → tags, aliases)
     - Unit tests for MOC generation (verify wikilink format in output)
     - Integration test: run full pipeline on test fixtures, verify vault structure
    diff --git a/specs/released/v0.6.0/scratchpad.md b/specs/released/v0.6.0/scratchpad.md
    index e3573fb10..78a264c26 100644
    --- a/specs/released/v0.6.0/scratchpad.md
    +++ b/specs/released/v0.6.0/scratchpad.md
    @@ -57,7 +57,7 @@ scratchpad_encrypt: true   # default: true
     
     When `false`:
     - No key is generated
    -- Scratchpad is stored as `.context/scratchpad.md` (plain markdown)
    +- Scratchpad is stored as `.context/scratchpad.md` (plain Markdown)
     - Same CLI, same skill, same commands — only storage differs
     
     ## CLI Commands
    diff --git a/specs/released/v0.8.0/ctx-map.md b/specs/released/v0.8.0/ctx-map.md
    index 097ff2d95..225769fe5 100644
    --- a/specs/released/v0.8.0/ctx-map.md
    +++ b/specs/released/v0.8.0/ctx-map.md
    @@ -112,7 +112,7 @@ re-analyze and update confidence.
     
     ## ARCHITECTURE.md Constraints
     
    -- **Size target**: under 4000 tokens (~16KB of markdown)
    +- **Size target**: under 4000 tokens (~16KB of Markdown)
     - **Sections**: Overview, Package Dependency Graph (mermaid),
       Component Map (tables), Data Flow (mermaid sequence diagrams),
       Key Patterns, File Layout (ASCII tree)
    diff --git a/specs/released/v0.8.0/export-update-mode.md b/specs/released/v0.8.0/export-update-mode.md
    index c1eccb57f..6ce683b55 100644
    --- a/specs/released/v0.8.0/export-update-mode.md
    +++ b/specs/released/v0.8.0/export-update-mode.md
    @@ -20,7 +20,7 @@ When re-importing a session whose journal file already exists:
     
     This **already preserves enriched frontmatter** (title, type, outcome,
     topics, technologies, summary) during re-import. The enrichment skill
    -stores all metadata in the YAML frontmatter block, not in markdown
    +stores all metadata in the YAML frontmatter block, not in Markdown
     sections, so the current `extractFrontmatter()` + `stripFrontmatter()`
     merge captures everything.
     
    diff --git a/specs/released/v0.8.0/journal-normalize-pipeline.md b/specs/released/v0.8.0/journal-normalize-pipeline.md
    index b14b8b08e..790fef00b 100644
    --- a/specs/released/v0.8.0/journal-normalize-pipeline.md
    +++ b/specs/released/v0.8.0/journal-normalize-pipeline.md
    @@ -27,12 +27,12 @@ Zensical uses Python-Markdown (via mkdocs), NOT CommonMark.
     CommonMark has Type 1 blocks (`<pre>`) that only end at the closing tag, but
     Python-Markdown does not implement this. This means:
     
    -- `<pre><code>` does NOT protect content from markdown parsing
    +- `<pre><code>` does NOT protect content from Markdown parsing
     - `<details><pre>` is even worse (Type 6, also ends at blank lines)
     - Blank lines in tool output content break any HTML-based wrapping
     
     **Solution**: Fenced code blocks (```) correctly survive blank lines and
    -prevent all markdown/HTML interpretation. Safe because `stripFences` runs
    +prevent all Markdown/HTML interpretation. Safe because `stripFences` runs
     first, removing all fence lines from content.
     
     ## Tool Output Wrapping
    @@ -55,7 +55,7 @@ tolerating gaps in turn numbering (e.g., 348 → 350).
        export pipeline; unescape HTML entities if wrappers were present
     3. Emit fenced code block: ``` + raw content + ```
     
    -Content is emitted verbatim — no `html.EscapeString`, no markdown escaping.
    +Content is emitted verbatim — no `html.EscapeString`, no Markdown escaping.
     
     ### Tool Output Styling
     
    diff --git a/specs/released/v0.8.0/journal-site-rendering-fixes.md b/specs/released/v0.8.0/journal-site-rendering-fixes.md
    index bf667c634..813f03b74 100644
    --- a/specs/released/v0.8.0/journal-site-rendering-fixes.md
    +++ b/specs/released/v0.8.0/journal-site-rendering-fixes.md
    @@ -84,7 +84,7 @@ Functions: `collectTurnNumbers()`, `nextInSequence()`.
     ### 6. Context compaction `<summary>` tags (from previous session, in reduce.go)
     
     **Root cause**: Claude Code's context compaction injects multi-line
    -`<summary>...</summary>` blocks that markdown renderers interpret as HTML.
    +`<summary>...</summary>` blocks that Markdown renderers interpret as HTML.
     
     **Fix**: `stripSystemReminders` now also strips standalone `<summary>` blocks
     (multi-line) and compaction boilerplate. Safe disambiguation: Claude Code's
    @@ -129,7 +129,7 @@ text, so fences serve no structural purpose and actively cause problems.
     | `<pre><code>`  (Type 1) | OK          | None after HTML-escape  | Yes             |
     | `<details><pre>` (old)  | BROKEN      | Type 6 ends at blank    | Yes             |
     
    -**Trade-off**: User messages with markdown formatting (bold, links, lists)
    +**Trade-off**: User messages with Markdown formatting (bold, links, lists)
     are flattened to plain text. This is acceptable — preserving user input
     verbatim is more valuable than rendering decorative formatting.
     
    diff --git a/specs/released/v0.8.0/prompt-templates.md b/specs/released/v0.8.0/prompt-templates.md
    index ecf38b298..a50fd117d 100644
    --- a/specs/released/v0.8.0/prompt-templates.md
    +++ b/specs/released/v0.8.0/prompt-templates.md
    @@ -15,9 +15,9 @@ has no lightweight option.
     
     ## Decision
     
    -Add **prompt templates** — plain markdown files in `.context/prompts/` that
    +Add **prompt templates** — plain Markdown files in `.context/prompts/` that
     users can invoke via `/ctx-prompt <name>` or manage via `ctx prompt` CLI.
    -No frontmatter, no build step, no trigger rules. Just named markdown.
    +No frontmatter, no build step, no trigger rules. Just named Markdown.
     
     ## Design
     
    diff --git a/specs/released/v0.8.0/recall-export-safety.md b/specs/released/v0.8.0/recall-export-safety.md
    index 8fbd77b64..f1117cfb6 100644
    --- a/specs/released/v0.8.0/recall-export-safety.md
    +++ b/specs/released/v0.8.0/recall-export-safety.md
    @@ -7,7 +7,7 @@ import and confirmation prompt — see Phase 1.)
     
     ## Context
     
    -`ctx recall import` regenerates journal markdown from JSONL session data.
    +`ctx recall import` regenerates journal Markdown from JSONL session data.
     The conversation body is **always** regenerated — manual edits are lost.
     `--force` additionally discards enriched YAML frontmatter. The docs say
     "you can edit these files" without warning about this.
    diff --git a/specs/released/v0.8.0/recall-sync.md b/specs/released/v0.8.0/recall-sync.md
    index 55d26266f..f0729ab4c 100644
    --- a/specs/released/v0.8.0/recall-sync.md
    +++ b/specs/released/v0.8.0/recall-sync.md
    @@ -4,7 +4,7 @@
     
     `ctx recall lock` writes lock state to `.state.json` and updates frontmatter
     for visibility. But from the user's perspective during journal enrichment, the
    -natural flow is: edit markdown frontmatter (add `locked: true`), then sweep
    +natural flow is: edit Markdown frontmatter (add `locked: true`), then sweep
     all files to propagate that state. The current `lock` command requires naming
     files explicitly, which is friction after a batch enrichment pass.
     
    diff --git a/specs/released/v0.8.0/remind.md b/specs/released/v0.8.0/remind.md
    index 757c671bc..d7c31c20d 100644
    --- a/specs/released/v0.8.0/remind.md
    +++ b/specs/released/v0.8.0/remind.md
    @@ -135,7 +135,7 @@ Dismissing an ID that doesn't exist:
     | `created` | string | always | UTC RFC3339 timestamp |
     | `after` | string | nullable | Date gate (YYYY-MM-DD), null if immediate |
     
    -JSON (not markdown) because:
    +JSON (not Markdown) because:
     - Structured fields (id, date) need reliable parsing.
     - No human editing expected — the CLI is the interface.
     - Small file, no performance concern.
    @@ -433,7 +433,7 @@ func runCheckReminders(cmd *cobra.Command) error {
       would mean "I asked to be reminded but ctx decided not to." Reminders
       fire every session until dismissed.
     
    -- **JSON not markdown.** Reminders have structured fields (id, date).
    +- **JSON not Markdown.** Reminders have structured fields (id, date).
       Markdown would require fragile parsing. The file is small and
       machine-managed — no reason for human-readable format.
     
    diff --git a/specs/require-git.md b/specs/require-git.md
    index a9d26f482..97cee853a 100644
    --- a/specs/require-git.md
    +++ b/specs/require-git.md
    @@ -1,8 +1,8 @@
     # Require Git as Architectural Precondition
     
    -Promote the de facto invariant ("ctx works properly only with
    -git") to a de jure one. `ctx init` and every `ctx` subcommand
    -(except read-only diagnostics) refuse to operate without `.git/`.
    +`ctx` already needs git to work properly; this phase enforces
    +it. `ctx init` and every `ctx` subcommand (except read-only
    +diagnostics) refuse to operate without `.git/`.
     
     ## Problem
     
    
    From 0300648c29dd5d4b330e5a393e51ad37e9c9930b Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 18:35:06 -0700
    Subject: [PATCH 02/10] refactor(err): typed-string sentinels replace ErrMsg
     const layer
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    The prior Phase KB session introduced an `ErrMsg* = "<english>"`
    const layer in `internal/config/<pkg>/<pkg>.go` backing
    `var ErrX = errors.New(cfgPkg.ErrMsgX)` sentinels in
    `internal/err/<pkg>/<pkg>.go`. Two problems: the English text
    materialized at package-init time bypassed the
    `commands/text/errors.yaml` lookup that the rest of the codebase
    respects, and the doc comments justifying the layer ("package-init
    timing prevents desc.Text") were wrong — `desc.Text` can be called
    lazily from a method.
    
    The pre-existing convention (`internal/err/context/NotFoundError`,
    commit `e524dd98`) already had the answer: typed errors with lazy
    `Error()` that calls `desc.Text(...)`. This change generalises that
    pattern.
    
    ## Shape
    
    - New `entity.Sentinel` (typed string) carries a `desc.Text` lookup
      key and implements `error`. Two values are equal under `==` when
      they hold the same key, so `errors.Is(err, ErrX)` works against
      `fmt.Errorf("%w", …)` wrappers. The text resolves at call time,
      never at package init.
    - Every `internal/err/<area>/` sentinel is now an untyped
      `entity.Sentinel` const, e.g.
      `const ErrMissingFrontmatter = entity.Sentinel(text.DescKey...)`.
    - Sentinel texts moved into `commands/text/errors.yaml` under
      `err.<pkg>.<name>` (or `<name>-msg` where the bare key was
      already taken by an existing `%w`-format wrapper).
    - All `ErrMsg*` const blocks in
      `internal/config/{handover,closeout,git_meta,kb/cli,kb/evidence,
      kb/sourcecoverage,rc,initialize,schema}/` deleted, along with the
      doc comments that justified the old layer.
    
    ## Audit Posture
    
    - `entity.Sentinel` lives in `internal/entity/` because the
      cross-package-types audit (TestCrossPackageTypes) treats `entity/`
      as the canonical home for types shared across packages.
    - No other audit, lint, or test required relaxation. `make lint`
      reports 0 issues; full `make test` is green; the cross-package
      types audit shows `entity/` exempt-package count unchanged at
      0 violations.
    
    ## Sentinel Inventory Touched
    
    `handover` (6), `closeout` (3), `git_meta` (2), `kb/cli` (5),
    `kb/evidence` (2), `kb/sourcecoverage` (2), `rc`/`context` (7),
    `initialize` (2), `schema` (1) — 30 sentinels across 9 packages.
    
    `LEARNINGS.md` codifies the convention so future sessions don't
    re-introduce the const layer.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/LEARNINGS.md                         |  47 ++++++
     .context/TASKS.md                             |   7 +
     internal/assets/commands/text/errors.yaml     |  58 ++++++++
     internal/config/closeout/closeout.go          |  19 ---
     internal/config/embed/text/err_closeout.go    |  10 ++
     internal/config/embed/text/err_fs.go          |  22 +++
     internal/config/embed/text/err_gitmeta.go     |   7 +
     internal/config/embed/text/err_handover.go    |  18 +++
     internal/config/embed/text/err_kb_cli.go      |  15 ++
     internal/config/embed/text/err_kb_evidence.go |   8 +
     .../embed/text/err_kb_sourcecoverage.go       |   8 +
     internal/config/embed/text/err_schema.go      |  17 +++
     internal/config/embed/text/initialize.go      |   9 ++
     internal/config/git_meta/git_meta.go          |  15 --
     internal/config/handover/handover.go          |  28 ----
     internal/config/initialize/doc.go             |  22 +--
     internal/config/initialize/initialize.go      |  28 +---
     internal/config/kb/cli/cli.go                 |  29 ----
     internal/config/kb/evidence/evidence.go       |  16 --
     .../kb/sourcecoverage/sourcecoverage.go       |  16 --
     internal/config/rc/rc.go                      |  30 ----
     internal/config/schema/check.go               |   6 -
     internal/entity/sentinel.go                   |  37 +++++
     internal/err/closeout/closeout.go             |  35 +++--
     internal/err/context/context.go               | 137 +++++++++---------
     internal/err/git_meta/gitmeta.go              |  30 ++--
     internal/err/handover/handover.go             |  58 ++++----
     internal/err/initialize/initialize.go         |  37 +++--
     internal/err/kb/cli/cli.go                    |  44 +++---
     internal/err/kb/evidence/evidence.go          |  28 ++--
     .../err/kb/sourcecoverage/sourcecoverage.go   |  26 ++--
     internal/err/schema/doc.go                    |   8 +-
     internal/err/schema/schema.go                 |   7 +-
     33 files changed, 495 insertions(+), 387 deletions(-)
     create mode 100644 internal/config/embed/text/err_schema.go
     create mode 100644 internal/entity/sentinel.go
    
    diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md
    index 4b71de777..deea03a67 100644
    --- a/.context/LEARNINGS.md
    +++ b/.context/LEARNINGS.md
    @@ -17,6 +17,7 @@ DO NOT UPDATE FOR:
     <!-- INDEX:START -->
     | Date | Learning |
     |----|--------|
    +| 2026-05-17 | Sentinel errors use typed zero-data structs with lazy `desc.Text()` — never Go string consts |
     | 2026-05-17 | `_helpers.go` / `_utils.go` filenames are project anti-pattern; use domain nouns |
     | 2026-05-17 | Subagent parallelism shines for mechanical refactor with a worked-example reference |
     | 2026-05-17 | naked_errors audit rejects fmt.Errorf wrapping outside internal/err/<area>/ |
    @@ -146,6 +147,52 @@ DO NOT UPDATE FOR:
     
     ---
     
    +## [2026-05-17-180000] Sentinel errors use typed zero-data structs with lazy `desc.Text()` — never Go string consts
    +
    +**Context**: In a prior Phase KB session I invented an intermediate
    +`ErrMsg* = "english string"` constant layer in
    +`internal/config/<pkg>/<pkg>.go`, then in `internal/err/<pkg>/<pkg>.go`
    +wrote `var ErrX = errors.New(cfgPkg.ErrMsgX)` — backed by a doc comment
    +claiming `desc.Text` could not be used because `var` initializers run
    +before `lookup.Init()` populates the embedded YAML table. The framing
    +was wrong, and the shape contradicted the convention already established
    +in the codebase. The pre-existing pattern lives in
    +`internal/err/context/context.go` (commit `e524dd98`): typed error
    +structs whose `Error()` method calls `assets.TextDesc(...)` /
    +`desc.Text(...)` lazily, at call time — not at package init.
    +
    +**Lesson**: The canonical sentinel shape in this repo is a typed,
    +zero-data struct (for unparameterised sentinels) or a typed struct with
    +fields (for parameterised errors). The `Error()` method resolves text
    +via `desc.Text(text.DescKey...)` so the user-facing string lives in
    +`internal/assets/commands/text/errors.yaml`, keyed by a `DescKey<...>`
    +constant in `internal/config/embed/text/err_<pkg>.go`. The init-ordering
    +concern is genuine for `var ErrX = errors.New(desc.Text(...))` — but the
    +fix is to defer the `desc.Text` call into a method, not to materialise
    +the English at package init. Identity is preserved because empty-struct
    +values are comparable and `errors.Is` finds them through `fmt.Errorf("%w", …)`
    +wrappers.
    +
    +**Application**: When you need an `errors.Is` target, write:
    +
    +```go
    +type missingFooErr struct{}
    +func (missingFooErr) Error() string {
    +    return desc.Text(text.DescKeyErrPkgMissingFoo)
    +}
    +var ErrMissingFoo error = missingFooErr{}
    +```
    +
    +For parameterised errors, follow `internal/err/context/context.go`'s
    +`NotFoundError` shape: exported struct type with fields, pointer
    +receiver on `Error()`, `errors.As` at the call site. Never define an
    +`ErrMsg*` string constant in `internal/config/<pkg>/`; never write
    +`var ErrX = errors.New("english")`. If you see those, sweep them: text
    +to YAML, sentinel to typed struct, doc comment justifying the const layer
    +deleted along with the const.
    +
    +---
    +
     ## [2026-05-17-061500] `_helpers.go` / `_utils.go` filenames are project anti-pattern; use domain nouns
     
     **Context**: During Phase KB / Phase RG audit cleanup, the first file split I
    diff --git a/.context/TASKS.md b/.context/TASKS.md
    index 3d68485a9..863c84910 100644
    --- a/.context/TASKS.md
    +++ b/.context/TASKS.md
    @@ -2045,6 +2045,13 @@ Phase KB-3 (documentation):
     - [ ] Document MemPalace-as-ground-source recipe in
       `docs/recipes/build-a-knowledge-base.md`; uses already-specced
       `mcp:<server>:<resource>` syntax in `grounding-sources.md`; zero new ctx code
    +- [x] Replace the `ErrMsg`-string-sentinel anti-pattern across
    +  `internal/config/{handover,closeout,git_meta,kb/cli,kb/evidence,kb/sourcecoverage,rc,initialize,schema}/`.
    +  Sentinels became `entity.Sentinel` typed-string consts whose `Error()`
    +  resolves text from `commands/text/errors.yaml` via
    +  `desc.Text(text.DescKey...)` at call time. Pre-existing convention
    +  reference: `internal/err/context/NotFoundError` (commit `e524dd98`).
    +  Captured as a learning to prevent recurrence.
     
     ### Phase KB-followup: Adversarial design review of parallel skill trees `#priority:medium #added:2026-05-17`
     
    diff --git a/internal/assets/commands/text/errors.yaml b/internal/assets/commands/text/errors.yaml
    index 72136eebc..6c1c5fda8 100644
    --- a/internal/assets/commands/text/errors.yaml
    +++ b/internal/assets/commands/text/errors.yaml
    @@ -44,6 +44,18 @@ err.backup.write-archive:
       short: 'failed to write archive: %w'
     err.context.dir-not-found:
       short: 'context directory not found: '
    +err.context.dir-not-declared:
    +  short: 'context directory not declared'
    +err.context.relative-not-allowed-msg:
    +  short: 'context directory must be absolute'
    +err.context.non-canonical-basename-msg:
    +  short: 'context directory has non-canonical basename'
    +err.context.dir-not-a-directory-msg:
    +  short: 'context directory is not a directory'
    +err.context.dir-stat-msg:
    +  short: 'context directory stat failed'
    +err.context.not-initialized-msg:
    +  short: 'context not initialized'
     err.context.not-declared-zero:
       short: |-
         no context directory specified for this project
    @@ -74,6 +86,8 @@ err.context.not-initialized:
         ctx is not initialized in this project: %s
         Run 'ctx init' from the project root to set up context here.
         See: https://ctx.ist/recipes/activating-context/
    +err.schema.drift:
    +  short: 'schema drift detected'
     err.activate.no-candidates:
       short: |-
         ctx activate: no .context/ directory found from this location
    @@ -598,6 +612,10 @@ err.parser.missing-open-delim:
       short: missing opening frontmatter delimiter (---)
     err.parser.missing-close-delim:
       short: missing closing frontmatter delimiter (---)
    +err.init.context-populated-msg:
    +  short: 'context already populated; refusing to overwrite'
    +err.init.reset-requires-interactive-msg:
    +  short: 'ctx init --reset requires an interactive terminal'
     err.init-context-populated:
       short: '%w (directory: %s; populated: %s; to discard, run `ctx init %s`, which backs up to %s before overwriting and requires interactive y/N confirmation)'
     err.init-reset-requires-interactive:
    @@ -628,6 +646,18 @@ err.handover.parse-frontmatter:
       short: 'parse handover frontmatter: %w'
     err.handover.resolve-head:
       short: 'resolve git head for handover: %w'
    +err.handover.title-required:
    +  short: 'handover title is required'
    +err.handover.summary-required:
    +  short: 'handover summary is required'
    +err.handover.next-required:
    +  short: 'handover next-session field is required'
    +err.handover.missing-frontmatter:
    +  short: 'handover missing frontmatter'
    +err.handover.missing-closing-delim:
    +  short: 'handover missing closing frontmatter delimiter'
    +err.handover.missing-generated-at:
    +  short: 'handover missing generated-at'
     err.closeout.read-closeout:
       short: 'read closeout: %w'
     err.closeout.parse-frontmatter:
    @@ -648,6 +678,12 @@ err.closeout.archive-move:
       short: 'archive closeout %s: %w'
     err.closeout.missing-fields:
       short: '%w: %s'
    +err.closeout.missing-fields-msg:
    +  short: 'closeout frontmatter missing required fields'
    +err.closeout.missing-frontmatter:
    +  short: 'closeout missing frontmatter'
    +err.closeout.mode-required:
    +  short: 'closeout mode is required'
     err.gitmeta.missing-git-tree:
       short: '%w: .git/ not found at %s; run `git init` to fix'
     err.gitmeta.missing-git-tree-for-cmd:
    @@ -656,6 +692,10 @@ err.gitmeta.stat-git-dir:
       short: 'stat .git at %s: %w'
     err.gitmeta.resolve-head:
       short: 'resolve git head: %w'
    +err.gitmeta.missing-git-tree-msg:
    +  short: 'git working tree required'
    +err.gitmeta.resolve-head-empty:
    +  short: 'resolve git head: empty output'
     err.initkb.mkdir:
       short: 'mkdir %s: %w'
     err.initkb.copy-ingest-templates:
    @@ -686,10 +726,18 @@ err.kb.evidence.write-row:
       short: 'write evidence row: %w'
     err.kb.evidence.parse-id-number:
       short: 'parse EV number %q: %w'
    +err.kb.evidence.duplicate-id-msg:
    +  short: 'duplicate EV-### id'
    +err.kb.evidence.invalid-band-msg:
    +  short: 'invalid confidence band'
     err.kb.sourcecoverage.illegal-transition:
       short: '%w: %s → %s for source %s'
     err.kb.sourcecoverage.unknown-source:
       short: '%w: %s entering at %s'
    +err.kb.sourcecoverage.illegal-transition-msg:
    +  short: 'illegal state transition'
    +err.kb.sourcecoverage.unknown-source-msg:
    +  short: 'unknown source for non-initial state'
     err.kb.sourcecoverage.read-ledger:
       short: 'read ledger: %w'
     err.kb.sourcecoverage.mkdir-ledger-dir:
    @@ -782,3 +830,13 @@ err.kb.cli.read-topic-template:
       short: 'read embedded topic template: %w'
     err.kb.cli.write-topic-index:
       short: 'write topic index: %w'
    +err.kb.cli.ask-no-question:
    +  short: 'no question provided; pass a question or describe it inline'
    +err.kb.cli.ingest-no-sources:
    +  short: 'no sources provided; pass a folder, a URL, an MCP resource, or describe the materials inline'
    +err.kb.cli.note-no-text:
    +  short: 'no text provided; pass a one-liner inline'
    +err.kb.cli.topic-empty-name:
    +  short: 'topic name must contain at least one alnum char'
    +err.kb.cli.reindex-missing-block:
    +  short: 'kb/index.md is missing the CTX:KB:TOPICS managed block'
    diff --git a/internal/config/closeout/closeout.go b/internal/config/closeout/closeout.go
    index 74a9892aa..94f14c55b 100644
    --- a/internal/config/closeout/closeout.go
    +++ b/internal/config/closeout/closeout.go
    @@ -6,25 +6,6 @@
     
     package closeout
     
    -// Sentinel error-message constants. These back `errors.New`
    -// values in `internal/err/closeout/` and are matched via
    -// `errors.Is` at the call site. They cannot use desc.Text
    -// because the sentinels are package-level vars evaluated
    -// before the embedded YAML lookup is populated; wrapping
    -// format strings live in commands/text/errors.yaml instead.
    -const (
    -	// ErrMsgMissingFrontmatter signals a closeout file missing
    -	// the `---` open delimiter on line 1.
    -	ErrMsgMissingFrontmatter = "closeout missing frontmatter"
    -	// ErrMsgMissingFields signals a closeout frontmatter
    -	// missing one of the required fields (sha, branch, mode,
    -	// generated-at).
    -	ErrMsgMissingFields = "closeout frontmatter missing required fields"
    -	// ErrMsgModeRequired signals an empty Mode supplied to
    -	// Write. Every closeout must declare a mode.
    -	ErrMsgModeRequired = "closeout mode is required"
    -)
    -
     // YAML field-name constants used by the closeout frontmatter
     // parser to report missing fields. These mirror the YAML tags
     // on [entity.CloseoutFrontmatter]; they are structural
    diff --git a/internal/config/embed/text/err_closeout.go b/internal/config/embed/text/err_closeout.go
    index 105ebaf26..de7b065b7 100644
    --- a/internal/config/embed/text/err_closeout.go
    +++ b/internal/config/embed/text/err_closeout.go
    @@ -41,4 +41,14 @@ const (
     	// DescKeyErrCloseoutMissingFields is the text key for the
     	// missing-fields sentinel-wrap with the joined field list.
     	DescKeyErrCloseoutMissingFields = "err.closeout.missing-fields"
    +	// DescKeyErrCloseoutMissingFieldsMsg is the text key for the
    +	// missing-fields sentinel's own `.Error()` string (the prefix
    +	// that the wrapper format above interpolates via `%w`).
    +	DescKeyErrCloseoutMissingFieldsMsg = "err.closeout.missing-fields-msg"
    +	// DescKeyErrCloseoutMissingFrontmatter is the text key for the
    +	// missing-frontmatter parse sentinel.
    +	DescKeyErrCloseoutMissingFrontmatter = "err.closeout.missing-frontmatter"
    +	// DescKeyErrCloseoutModeRequired is the text key for the
    +	// empty-mode sentinel returned by Write.
    +	DescKeyErrCloseoutModeRequired = "err.closeout.mode-required"
     )
    diff --git a/internal/config/embed/text/err_fs.go b/internal/config/embed/text/err_fs.go
    index 3159a190c..0998a048f 100644
    --- a/internal/config/embed/text/err_fs.go
    +++ b/internal/config/embed/text/err_fs.go
    @@ -97,6 +97,28 @@ const (
     	// Used when state.Dir() is invoked in a project that has CTX_DIR
     	// declared but lacks the required context files.
     	DescKeyErrContextNotInitialized = "err.context.not-initialized"
    +	// DescKeyErrContextDirNotDeclared is the text key for the
    +	// ErrDirNotDeclared sentinel's own `.Error()` string.
    +	DescKeyErrContextDirNotDeclared = "err.context.dir-not-declared"
    +	// DescKeyErrContextRelativeNotAllowedMsg is the text key for
    +	// the ErrRelativeNotAllowed sentinel's own `.Error()` string
    +	// (the prefix interpolated via `%w` by the wrapper format
    +	// DescKeyErrContextRelativeNotAllowed).
    +	DescKeyErrContextRelativeNotAllowedMsg = "err.context.relative-not-allowed-msg"
    +	// DescKeyErrContextNonCanonicalBasenameMsg is the text key
    +	// for the ErrNonCanonicalBasename sentinel's own `.Error()`
    +	// string.
    +	DescKeyErrContextNonCanonicalBasenameMsg = "err.context.non-canonical-basename-msg"
    +	// DescKeyErrContextDirNotADirectoryMsg is the text key for
    +	// the ErrContextDirNotADirectory sentinel's own `.Error()`
    +	// string.
    +	DescKeyErrContextDirNotADirectoryMsg = "err.context.dir-not-a-directory-msg"
    +	// DescKeyErrContextDirStatMsg is the text key for the
    +	// ErrContextDirStat sentinel's own `.Error()` string.
    +	DescKeyErrContextDirStatMsg = "err.context.dir-stat-msg"
    +	// DescKeyErrContextNotInitializedMsg is the text key for the
    +	// ErrNotInitialized sentinel's own `.Error()` string.
    +	DescKeyErrContextNotInitializedMsg = "err.context.not-initialized-msg"
     )
     
     // DescKeys for filesystem write output.
    diff --git a/internal/config/embed/text/err_gitmeta.go b/internal/config/embed/text/err_gitmeta.go
    index d960020db..0ae62e14a 100644
    --- a/internal/config/embed/text/err_gitmeta.go
    +++ b/internal/config/embed/text/err_gitmeta.go
    @@ -23,4 +23,11 @@ const (
     	// DescKeyErrGitmetaResolveHead is the text key for the
     	// `git rev-parse --short HEAD` invocation failure.
     	DescKeyErrGitmetaResolveHead = "err.gitmeta.resolve-head"
    +	// DescKeyErrGitmetaMissingGitTreeMsg is the text key for the
    +	// missing-git-tree sentinel's own `.Error()` string (the
    +	// prefix interpolated via `%w` by the wrapper formats).
    +	DescKeyErrGitmetaMissingGitTreeMsg = "err.gitmeta.missing-git-tree-msg"
    +	// DescKeyErrGitmetaResolveHeadEmpty is the text key for the
    +	// empty-HEAD-output sentinel.
    +	DescKeyErrGitmetaResolveHeadEmpty = "err.gitmeta.resolve-head-empty"
     )
    diff --git a/internal/config/embed/text/err_handover.go b/internal/config/embed/text/err_handover.go
    index f522b8994..2d586d7dc 100644
    --- a/internal/config/embed/text/err_handover.go
    +++ b/internal/config/embed/text/err_handover.go
    @@ -41,4 +41,22 @@ const (
     	// DescKeyErrHandoverResolveHead is the text key for the
     	// git-head resolution failure wrapper.
     	DescKeyErrHandoverResolveHead = "err.handover.resolve-head"
    +	// DescKeyErrHandoverTitleRequired is the text key for the
    +	// empty-title sentinel.
    +	DescKeyErrHandoverTitleRequired = "err.handover.title-required"
    +	// DescKeyErrHandoverSummaryRequired is the text key for the
    +	// empty-summary sentinel.
    +	DescKeyErrHandoverSummaryRequired = "err.handover.summary-required"
    +	// DescKeyErrHandoverNextRequired is the text key for the
    +	// empty-next-session sentinel.
    +	DescKeyErrHandoverNextRequired = "err.handover.next-required"
    +	// DescKeyErrHandoverMissingFrontmatter is the text key for
    +	// the missing-frontmatter parse sentinel.
    +	DescKeyErrHandoverMissingFrontmatter = "err.handover.missing-frontmatter"
    +	// DescKeyErrHandoverMissingClosingDelim is the text key for
    +	// the unterminated-frontmatter parse sentinel.
    +	DescKeyErrHandoverMissingClosingDelim = "err.handover.missing-closing-delim"
    +	// DescKeyErrHandoverMissingGeneratedAt is the text key for
    +	// the missing-generated-at parse sentinel.
    +	DescKeyErrHandoverMissingGeneratedAt = "err.handover.missing-generated-at"
     )
    diff --git a/internal/config/embed/text/err_kb_cli.go b/internal/config/embed/text/err_kb_cli.go
    index 2569460e4..8e4f1d39d 100644
    --- a/internal/config/embed/text/err_kb_cli.go
    +++ b/internal/config/embed/text/err_kb_cli.go
    @@ -44,4 +44,19 @@ const (
     	// DescKeyErrKbCliWriteTopicIndex wraps `os.WriteFile` for
     	// the topic index.md.
     	DescKeyErrKbCliWriteTopicIndex = "err.kb.cli.write-topic-index"
    +	// DescKeyErrKbCliAskNoQuestion is the text key for the
    +	// empty-question-arg sentinel.
    +	DescKeyErrKbCliAskNoQuestion = "err.kb.cli.ask-no-question"
    +	// DescKeyErrKbCliIngestNoSources is the text key for the
    +	// empty-sources-arg sentinel.
    +	DescKeyErrKbCliIngestNoSources = "err.kb.cli.ingest-no-sources"
    +	// DescKeyErrKbCliNoteNoText is the text key for the
    +	// empty-note-arg sentinel.
    +	DescKeyErrKbCliNoteNoText = "err.kb.cli.note-no-text"
    +	// DescKeyErrKbCliTopicEmptyName is the text key for the
    +	// empty-slug topic-new sentinel.
    +	DescKeyErrKbCliTopicEmptyName = "err.kb.cli.topic-empty-name"
    +	// DescKeyErrKbCliReindexMissingBlock is the text key for
    +	// the missing-CTX:KB:TOPICS-block sentinel.
    +	DescKeyErrKbCliReindexMissingBlock = "err.kb.cli.reindex-missing-block"
     )
    diff --git a/internal/config/embed/text/err_kb_evidence.go b/internal/config/embed/text/err_kb_evidence.go
    index 6b841af55..7d45b66e2 100644
    --- a/internal/config/embed/text/err_kb_evidence.go
    +++ b/internal/config/embed/text/err_kb_evidence.go
    @@ -29,4 +29,12 @@ const (
     	// DescKeyErrKbEvidenceParseIDNumber wraps a strconv.Atoi
     	// failure parsing an EV-### number.
     	DescKeyErrKbEvidenceParseIDNumber = "err.kb.evidence.parse-id-number"
    +	// DescKeyErrKbEvidenceDuplicateIDMsg is the text key for the
    +	// duplicate-id sentinel's own `.Error()` string (the prefix
    +	// interpolated via `%w` by the duplicate-id wrapper).
    +	DescKeyErrKbEvidenceDuplicateIDMsg = "err.kb.evidence.duplicate-id-msg"
    +	// DescKeyErrKbEvidenceInvalidBandMsg is the text key for the
    +	// invalid-band sentinel's own `.Error()` string (the prefix
    +	// interpolated via `%w` by the invalid-band wrapper).
    +	DescKeyErrKbEvidenceInvalidBandMsg = "err.kb.evidence.invalid-band-msg"
     )
    diff --git a/internal/config/embed/text/err_kb_sourcecoverage.go b/internal/config/embed/text/err_kb_sourcecoverage.go
    index 2d6b61b45..b4b047399 100644
    --- a/internal/config/embed/text/err_kb_sourcecoverage.go
    +++ b/internal/config/embed/text/err_kb_sourcecoverage.go
    @@ -23,4 +23,12 @@ const (
     	// DescKeyErrKbSourcecoverageWriteLedger wraps a write
     	// failure on the ledger.
     	DescKeyErrKbSourcecoverageWriteLedger = "err.kb.sourcecoverage.write-ledger"
    +	// DescKeyErrKbSourcecoverageIllegalTransitionMsg is the text
    +	// key for the illegal-transition sentinel's own `.Error()`
    +	// string (the prefix interpolated via `%w` by the wrapper).
    +	DescKeyErrKbSourcecoverageIllegalTransitionMsg = "err.kb.sourcecoverage.illegal-transition-msg"
    +	// DescKeyErrKbSourcecoverageUnknownSourceMsg is the text key
    +	// for the unknown-source sentinel's own `.Error()` string
    +	// (the prefix interpolated via `%w` by the wrapper).
    +	DescKeyErrKbSourcecoverageUnknownSourceMsg = "err.kb.sourcecoverage.unknown-source-msg"
     )
    diff --git a/internal/config/embed/text/err_schema.go b/internal/config/embed/text/err_schema.go
    new file mode 100644
    index 000000000..338df1c8f
    --- /dev/null
    +++ b/internal/config/embed/text/err_schema.go
    @@ -0,0 +1,17 @@
    +//   /    ctx:                         https://ctx.ist
    +// ,'`./    do you remember?
    +// `.,'\
    +//   \    Copyright 2026-present Context contributors.
    +//                 SPDX-License-Identifier: Apache-2.0
    +
    +package text
    +
    +// DescKeys for schema validation error sentinels. The matching
    +// YAML entry lives in commands/text/errors.yaml; constructors in
    +// internal/err/schema/ resolve it via desc.Text at error
    +// construction time.
    +const (
    +	// DescKeyErrSchemaDrift is the text key for the
    +	// schema-drift sentinel.
    +	DescKeyErrSchemaDrift = "err.schema.drift"
    +)
    diff --git a/internal/config/embed/text/initialize.go b/internal/config/embed/text/initialize.go
    index e4bece8c8..3a6fdc811 100644
    --- a/internal/config/embed/text/initialize.go
    +++ b/internal/config/embed/text/initialize.go
    @@ -128,6 +128,15 @@ const (
     	// DescKeyErrInitBackupWrite is the text key for backup-target
     	// write failures. Template expects a %s path and a %w cause.
     	DescKeyErrInitBackupWrite = "err.init-backup-write"
    +	// DescKeyErrInitContextPopulatedMsg is the text key for the
    +	// context-populated sentinel's own `.Error()` string (the
    +	// prefix interpolated via `%w` by DescKeyErrInitContextPopulated).
    +	DescKeyErrInitContextPopulatedMsg = "err.init.context-populated-msg"
    +	// DescKeyErrInitResetRequiresInteractiveMsg is the text key for
    +	// the reset-requires-interactive sentinel's own `.Error()`
    +	// string (the prefix interpolated via `%w` by
    +	// DescKeyErrInitResetRequiresInteractive).
    +	DescKeyErrInitResetRequiresInteractiveMsg = "err.init.reset-requires-interactive-msg"
     )
     
     // DescKeys for init permission setup output.
    diff --git a/internal/config/git_meta/git_meta.go b/internal/config/git_meta/git_meta.go
    index 85d8692cd..a344e8ebb 100644
    --- a/internal/config/git_meta/git_meta.go
    +++ b/internal/config/git_meta/git_meta.go
    @@ -38,18 +38,3 @@ const RefHEAD = "HEAD"
     // ShortLen is the truncation length for short SHAs (git's
     // default --short width).
     const ShortLen = 7
    -
    -// Sentinel error-message constants. These back `errors.New`
    -// values in `internal/err/git_meta/` and are matched via
    -// `errors.Is` at the call site. They cannot use desc.Text
    -// because the sentinels are package-level vars evaluated
    -// before the embedded YAML lookup is populated; wrapping
    -// format strings live in commands/text/errors.yaml.
    -const (
    -	// ErrMsgMissingGitTree is the sentinel for the
    -	// "<projectRoot>/.git is absent" condition.
    -	ErrMsgMissingGitTree = "git working tree required"
    -	// ErrMsgResolveHeadEmpty signals that `git rev-parse --short
    -	// HEAD` returned an empty string (typically: unborn HEAD).
    -	ErrMsgResolveHeadEmpty = "resolve git head: empty output"
    -)
    diff --git a/internal/config/handover/handover.go b/internal/config/handover/handover.go
    index 8ad0b278a..601389e9c 100644
    --- a/internal/config/handover/handover.go
    +++ b/internal/config/handover/handover.go
    @@ -12,34 +12,6 @@ package handover
     // each other.
     const Subdir = "handovers"
     
    -// Sentinel error-message constants. These back `errors.New`
    -// values in `internal/err/handover/` and are matched via
    -// `errors.Is` at the call site. They cannot use desc.Text
    -// because the err/handover sentinels are package-level vars
    -// evaluated before the embedded YAML lookup is populated;
    -// wrapping-format strings and CLI output strings live in
    -// commands/text/{errors,write}.yaml instead.
    -const (
    -	// ErrMsgTitleRequired signals an empty Title supplied to
    -	// Write.
    -	ErrMsgTitleRequired = "handover title is required"
    -	// ErrMsgSummaryRequired signals an empty Summary supplied
    -	// to Write.
    -	ErrMsgSummaryRequired = "handover summary is required"
    -	// ErrMsgNextRequired signals an empty Next supplied to
    -	// Write.
    -	ErrMsgNextRequired = "handover next-session field is required"
    -	// ErrMsgMissingFrontmatter signals a handover file that
    -	// does not open with `---`.
    -	ErrMsgMissingFrontmatter = "handover missing frontmatter"
    -	// ErrMsgMissingClosingDelim signals a handover whose
    -	// frontmatter is never closed.
    -	ErrMsgMissingClosingDelim = "handover missing closing frontmatter delimiter"
    -	// ErrMsgMissingGeneratedAt signals a handover whose
    -	// frontmatter parsed but has no generated-at value.
    -	ErrMsgMissingGeneratedAt = "handover missing generated-at"
    -)
    -
     // Section header constants used when composing the handover
     // markdown body. These are structural identifiers, not
     // localizable prose: the read side matches them exactly.
    diff --git a/internal/config/initialize/doc.go b/internal/config/initialize/doc.go
    index 1190de914..b1a0252f8 100644
    --- a/internal/config/initialize/doc.go
    +++ b/internal/config/initialize/doc.go
    @@ -5,19 +5,13 @@
     //                 SPDX-License-Identifier: Apache-2.0
     
     // Package initialize hosts compile-time constants consumed by
    -// the ctx init command: sentinel error messages used by
    -// the err/initialize package, backup directory naming for
    -// ctx init --reset, and the canonical reset flag name.
    +// the ctx init command: backup directory naming for
    +// ctx init --reset and the canonical reset flag name.
     //
    -// # Why a Separate Config Package
    -//
    -// Sentinel error messages live here rather than in the
    -// embedded YAML loaded via desc.Text because the
    -// var ErrContextPopulated and var ErrResetRequiresInteractive
    -// declarations in err/initialize are evaluated at package-load
    -// time, before the YAML lookup table is populated. Keeping
    -// the strings as plain Go const values lets the sentinels
    -// initialize cleanly while the formatted wrapper messages
    -// (Populated, ResetRequiresInteractive) still flow through
    -// desc.Text for localization.
    +// Sentinel error values for ctx init refusal and reset live in
    +// `internal/err/initialize/`; their user-facing text lives in
    +// `commands/text/errors.yaml` and is resolved through
    +// `desc.Text` at error-display time by the sentinels' typed
    +// `Error()` methods. The wrapping constructors (Populated,
    +// ResetRequiresInteractive) also flow through desc.Text.
     package initialize
    diff --git a/internal/config/initialize/initialize.go b/internal/config/initialize/initialize.go
    index 6e9b9e5ae..b4bfd2c09 100644
    --- a/internal/config/initialize/initialize.go
    +++ b/internal/config/initialize/initialize.go
    @@ -5,31 +5,13 @@
     //                 SPDX-License-Identifier: Apache-2.0
     
     // Package initialize hosts compile-time constants consumed by
    -// the ctx init command (sentinel error messages, backup
    -// directory naming, reset flag literal).
    -//
    -// Sentinel error messages live here (not in the embedded YAML
    -// loaded via desc.Text) because the err/initialize package
    -// instantiates them at package-load time, before the YAML
    -// lookup table is populated.
    +// the ctx init command (backup directory naming, reset flag
    +// literal). Sentinel error values live in
    +// `internal/err/initialize/`; their user-facing text lives in
    +// `commands/text/errors.yaml` and is resolved through
    +// `desc.Text` at error-display time.
     package initialize
     
    -// Sentinel error messages for ctx init refusal and reset.
    -//
    -// These mirror keys in commands/text/errors.yaml but exist as
    -// raw string constants because the var ErrContextPopulated /
    -// var ErrResetRequiresInteractive sentinels in the err package
    -// are initialized before the YAML lookup is ready.
    -const (
    -	// ErrMsgContextPopulated is the sentinel message for
    -	// ctx init's refuse-when-populated guard.
    -	ErrMsgContextPopulated = "context already populated; refusing to overwrite"
    -	// ErrMsgResetRequiresInteractive is the sentinel message
    -	// for ctx init --reset's interactive-only guard.
    -	ErrMsgResetRequiresInteractive = "ctx init --reset requires" +
    -		" an interactive terminal"
    -)
    -
     // Backup directory naming for ctx init --reset.
     const (
     	// BackupDirPrefix is the basename prefix for timestamped
    diff --git a/internal/config/kb/cli/cli.go b/internal/config/kb/cli/cli.go
    index 563a45acb..de7d10006 100644
    --- a/internal/config/kb/cli/cli.go
    +++ b/internal/config/kb/cli/cli.go
    @@ -6,35 +6,6 @@
     
     package cli
     
    -// Sentinel error-message constants. These back `errors.New`
    -// values in [github.com/ActiveMemory/ctx/internal/err/kb/cli]
    -// and are matched via `errors.Is` at the call site. They
    -// cannot use desc.Text because the sentinels are
    -// package-level vars evaluated before the embedded YAML
    -// lookup is populated; wrapping format strings and CLI
    -// output strings have moved to commands/text/{errors,write}.yaml.
    -const (
    -	// ErrMsgAskNoQuestion signals an empty `ctx kb ask`
    -	// invocation (no question argument provided).
    -	ErrMsgAskNoQuestion = "no question provided; pass a question or " +
    -		"describe it inline"
    -	// ErrMsgIngestNoSources signals an empty `ctx kb ingest`
    -	// invocation (no source argument provided).
    -	ErrMsgIngestNoSources = "no sources provided; pass a folder, a URL, " +
    -		"an MCP resource, or describe the materials inline"
    -	// ErrMsgNoteNoText signals an empty `ctx kb note`
    -	// invocation (no text argument provided).
    -	ErrMsgNoteNoText = "no text provided; pass a one-liner inline"
    -	// ErrMsgTopicEmptyName signals a `ctx kb topic new`
    -	// invocation whose name reduces to an empty slug.
    -	ErrMsgTopicEmptyName = "topic name must contain at least one " +
    -		"alnum char"
    -	// ErrMsgReindexMissingBlock signals a kb landing page that
    -	// is missing the CTX:KB:TOPICS managed block.
    -	ErrMsgReindexMissingBlock = "kb/index.md is missing the " +
    -		"CTX:KB:TOPICS managed block"
    -)
    -
     // Topic-template substitution tokens. Replaced by the topic
     // scaffolder with the human-readable name and the kebab-case
     // slug, respectively. Structural literals; not localizable.
    diff --git a/internal/config/kb/evidence/evidence.go b/internal/config/kb/evidence/evidence.go
    index 865ab920b..3a8ba1f67 100644
    --- a/internal/config/kb/evidence/evidence.go
    +++ b/internal/config/kb/evidence/evidence.go
    @@ -6,22 +6,6 @@
     
     package evidence
     
    -// Sentinel error-message constants. These back `errors.New`
    -// values declared in `internal/err/kb/evidence/` and are
    -// matched via `errors.Is` at the call site. They cannot use
    -// desc.Text because the sentinels are package-level vars
    -// evaluated before the embedded YAML lookup is populated;
    -// wrapping-format strings have moved to
    -// commands/text/errors.yaml.
    -const (
    -	// ErrMsgDuplicateID signals an Append called with a row.ID
    -	// already present in the evidence index.
    -	ErrMsgDuplicateID = "duplicate EV-### id"
    -	// ErrMsgInvalidBand signals a row whose Confidence is not
    -	// one of the four canonical bands.
    -	ErrMsgInvalidBand = "invalid confidence band"
    -)
    -
     // Markdown rendering constants for the evidence-index file.
     // Structural literals (headings, table shape, ID format)
     // stay as Go consts.
    diff --git a/internal/config/kb/sourcecoverage/sourcecoverage.go b/internal/config/kb/sourcecoverage/sourcecoverage.go
    index 946abd08b..c3b693370 100644
    --- a/internal/config/kb/sourcecoverage/sourcecoverage.go
    +++ b/internal/config/kb/sourcecoverage/sourcecoverage.go
    @@ -6,22 +6,6 @@
     
     package sourcecoverage
     
    -// Sentinel error-message constants. These back `errors.New`
    -// values in `internal/err/kb/sourcecoverage/` and are matched
    -// via `errors.Is` at the call site. They cannot use desc.Text
    -// because the sentinels are package-level vars evaluated
    -// before the embedded YAML lookup is populated; wrapping
    -// format strings have moved to commands/text/errors.yaml.
    -const (
    -	// ErrMsgIllegalTransition signals that Advance was called
    -	// with a state pair the ledger's state machine rejects.
    -	ErrMsgIllegalTransition = "illegal state transition"
    -	// ErrMsgUnknownSource signals that Advance referenced a
    -	// Source not yet present in the ledger AND the new State is
    -	// not one of the entry-point states (discovered, admitted).
    -	ErrMsgUnknownSource = "unknown source for non-initial state"
    -)
    -
     // Markdown rendering constants for the source-coverage ledger.
     // Structural literals stay as Go consts.
     const (
    diff --git a/internal/config/rc/rc.go b/internal/config/rc/rc.go
    index d93067bc2..b524e3584 100644
    --- a/internal/config/rc/rc.go
    +++ b/internal/config/rc/rc.go
    @@ -6,36 +6,6 @@
     
     package rc
     
    -// Error message constants for rc sentinel errors. These are used
    -// only for errors.Is matching; user-facing wrapping goes through
    -// err/context constructors that format tailored messages.
    -const (
    -	// ErrMsgDirNotDeclared is the sentinel message for the
    -	// "context directory has not been declared" error.
    -	ErrMsgDirNotDeclared = "context directory not declared"
    -	// ErrMsgRelativeNotAllowed is the sentinel message for the
    -	// "CTX_DIR must be absolute" rejection.
    -	ErrMsgRelativeNotAllowed = "context directory must be absolute"
    -	// ErrMsgNonCanonicalBasename is the sentinel message for the
    -	// "CTX_DIR basename must be .context" rejection.
    -	ErrMsgNonCanonicalBasename = "context directory has non-canonical basename"
    -	// ErrMsgContextDirNotFound is the sentinel message for the
    -	// "declared CTX_DIR does not exist" rejection.
    -	ErrMsgContextDirNotFound = "context directory not found: "
    -	// ErrMsgContextDirNotADirectory is the sentinel message for the
    -	// "CTX_DIR is a file, not a directory" rejection.
    -	ErrMsgContextDirNotADirectory = "context directory is not a directory"
    -	// ErrMsgContextDirStat is the sentinel message for stat failures
    -	// other than not-exist (permission denied, I/O error).
    -	ErrMsgContextDirStat = "context directory stat failed"
    -	// ErrMsgNotInitialized is the sentinel message for the
    -	// "context directory exists but ctx init has not run" rejection.
    -	// Used by [state.Dir] to refuse mkdir in an uninitialized project,
    -	// which would otherwise leak a stub `.context/state/` (mode 0750)
    -	// into any directory a hook subprocess runs in.
    -	ErrMsgNotInitialized = "context not initialized"
    -)
    -
     // Format strings for sentinel-wrapping in err/context constructors.
     // Centralized here so the magic-string audit (which exempts
     // internal/config) does not flag them at the call site.
    diff --git a/internal/config/schema/check.go b/internal/config/schema/check.go
    index 98550d2d6..cbbfe0bbf 100644
    --- a/internal/config/schema/check.go
    +++ b/internal/config/schema/check.go
    @@ -67,9 +67,3 @@ const (
     	// HeadingBlockTypes is the block types section heading.
     	HeadingBlockTypes = "## Content Block Types"
     )
    -
    -// Error message constants.
    -const (
    -	// ErrMsgDrift is the error message for schema drift.
    -	ErrMsgDrift = "schema drift detected"
    -)
    diff --git a/internal/entity/sentinel.go b/internal/entity/sentinel.go
    new file mode 100644
    index 000000000..f015e22d6
    --- /dev/null
    +++ b/internal/entity/sentinel.go
    @@ -0,0 +1,37 @@
    +//   /    ctx:                         https://ctx.ist
    +// ,'`./    do you remember?
    +// `.,'\
    +//   \    Copyright 2026-present Context contributors.
    +//                 SPDX-License-Identifier: Apache-2.0
    +
    +package entity
    +
    +import "github.com/ActiveMemory/ctx/internal/assets/read/desc"
    +
    +// Sentinel is a typed string carrying a `desc.Text` lookup
    +// key. Every `internal/err/<area>/` package declares its
    +// identity sentinels as untyped consts of this type, one per
    +// logical error class.
    +//
    +// Two Sentinel values are equal under `==` when they hold
    +// the same key, which is exactly what `errors.Is` needs:
    +// as long as each sentinel uses a distinct key, callers can
    +// match the wrapped error with `errors.Is(err, ErrX)`.
    +//
    +// User-facing text never leaks into Go source code:
    +// `Error()` resolves the key against the embedded YAML
    +// lookup at call time, so the message is localizable and
    +// the sentinel value itself remains pure identity. This
    +// also sidesteps the package-init ordering problem of
    +// `var ErrX = errors.New(desc.Text(key))`, where the lookup
    +// table is not yet populated when the var initializer runs.
    +type Sentinel string
    +
    +// Error implements the error interface.
    +//
    +// Returns:
    +//   - string: localized sentinel text resolved via
    +//     `desc.Text` at call time.
    +func (s Sentinel) Error() string {
    +	return desc.Text(string(s))
    +}
    diff --git a/internal/err/closeout/closeout.go b/internal/err/closeout/closeout.go
    index c2b158fa8..62e2944f2 100644
    --- a/internal/err/closeout/closeout.go
    +++ b/internal/err/closeout/closeout.go
    @@ -7,30 +7,33 @@
     package closeout
     
     import (
    -	"errors"
     	"fmt"
     	"strings"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
    -	cfgCloseout "github.com/ActiveMemory/ctx/internal/config/closeout"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	"github.com/ActiveMemory/ctx/internal/config/token"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrMissingFrontmatter signals a closeout file missing the
    -// `---` open delimiter on line 1.
    -var ErrMissingFrontmatter = errors.New(cfgCloseout.ErrMsgMissingFrontmatter)
    -
    -// ErrMissingFields signals a closeout frontmatter missing one
    -// of the required fields (sha, branch, mode, generated-at).
    -// Constructor [MissingFields] wraps it with the actual field
    -// names.
    -var ErrMissingFields = errors.New(cfgCloseout.ErrMsgMissingFields)
    -
    -// ErrModeRequired signals a
    -// [github.com/ActiveMemory/ctx/internal/write/closeout.Write]
    -// call with an empty mode string.
    -var ErrModeRequired = errors.New(cfgCloseout.ErrMsgModeRequired)
    +const (
    +	// ErrMissingFrontmatter signals a closeout file missing the
    +	// `---` open delimiter on line 1.
    +	ErrMissingFrontmatter = entity.Sentinel(
    +		text.DescKeyErrCloseoutMissingFrontmatter,
    +	)
    +	// ErrMissingFields signals a closeout frontmatter missing
    +	// one of the required fields (sha, branch, mode,
    +	// generated-at). Constructor [MissingFields] wraps it with
    +	// the actual field names.
    +	ErrMissingFields = entity.Sentinel(
    +		text.DescKeyErrCloseoutMissingFieldsMsg,
    +	)
    +	// ErrModeRequired signals a
    +	// [github.com/ActiveMemory/ctx/internal/write/closeout.Write]
    +	// call with an empty mode string.
    +	ErrModeRequired = entity.Sentinel(text.DescKeyErrCloseoutModeRequired)
    +)
     
     // MissingFields wraps the sentinel [ErrMissingFields] with a
     // comma-separated list of the missing field names.
    diff --git a/internal/err/context/context.go b/internal/err/context/context.go
    index 9a2106ae5..2b7d69688 100644
    --- a/internal/err/context/context.go
    +++ b/internal/err/context/context.go
    @@ -16,81 +16,84 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	cfgRc "github.com/ActiveMemory/ctx/internal/config/rc"
     	"github.com/ActiveMemory/ctx/internal/config/token"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrDirNotDeclared is the sentinel returned by rc.ContextDir when
    -// CTX_DIR is unset or empty. Callers that can legitimately proceed
    -// without a declared context directory (init, activate, deactivate,
    -// bootstrap) check with errors.Is; everyone else should propagate
    -// the error or call rc.RequireContextDir for a user-facing message
    -// (see NotDeclared below).
    -//
    -// The message lives in config/rc (not resolved through desc.Text)
    -// because sentinel values are initialized at package load time,
    -// before the embedded YAML lookup is populated. Callers that print
    -// this to users should wrap it via NotDeclared; the sentinel itself
    -// is for errors.Is comparisons, not for display.
    -var ErrDirNotDeclared = errors.New(cfgRc.ErrMsgDirNotDeclared)
    +const (
    +	// ErrDirNotDeclared is the sentinel returned by
    +	// rc.ContextDir when CTX_DIR is unset or empty. Callers
    +	// that can legitimately proceed without a declared context
    +	// directory (init, activate, deactivate, bootstrap) check
    +	// with errors.Is; everyone else should propagate the error
    +	// or call rc.RequireContextDir for a user-facing message
    +	// (see NotDeclared below).
    +	ErrDirNotDeclared = entity.Sentinel(
    +		text.DescKeyErrContextDirNotDeclared,
    +	)
     
    -// ErrRelativeNotAllowed is the sentinel returned when CTX_DIR is
    -// declared as a relative path. Absolute-only is a hardline: a
    -// relative CTX_DIR would resolve differently in every cwd, exactly
    -// the silent cwd-dependency this resolver is meant to eliminate.
    -//
    -// Wrap via [RelativeNotAllowed] for user-facing messages so the
    -// offending value is shown.
    -var ErrRelativeNotAllowed = errors.New(cfgRc.ErrMsgRelativeNotAllowed)
    +	// ErrRelativeNotAllowed is the sentinel returned when
    +	// CTX_DIR is declared as a relative path. Absolute-only is
    +	// a hardline: a relative CTX_DIR would resolve differently
    +	// in every cwd, exactly the silent cwd-dependency this
    +	// resolver is meant to eliminate. Wrap via
    +	// [RelativeNotAllowed] for user-facing messages so the
    +	// offending value is shown.
    +	ErrRelativeNotAllowed = entity.Sentinel(
    +		text.DescKeyErrContextRelativeNotAllowedMsg,
    +	)
     
    -// ErrNonCanonicalBasename is the sentinel returned when CTX_DIR's
    -// basename is not the canonical [cfgDir.Context]. It catches the
    -// common footgun `export CTX_DIR=$(pwd)` (project root instead of
    -// the `.context` subdirectory) on first use rather than letting init
    -// deposit canonical files into the project root.
    -//
    -// Wrap via [NonCanonicalBasename] for user-facing messages.
    -var ErrNonCanonicalBasename = errors.New(cfgRc.ErrMsgNonCanonicalBasename)
    +	// ErrNonCanonicalBasename is the sentinel returned when
    +	// CTX_DIR's basename is not the canonical [cfgDir.Context].
    +	// It catches the common footgun `export CTX_DIR=$(pwd)`
    +	// (project root instead of the `.context` subdirectory) on
    +	// first use rather than letting init deposit canonical
    +	// files into the project root. Wrap via
    +	// [NonCanonicalBasename] for user-facing messages.
    +	ErrNonCanonicalBasename = entity.Sentinel(
    +		text.DescKeyErrContextNonCanonicalBasenameMsg,
    +	)
     
    -// ErrContextDirNotFound is the sentinel returned by
    -// rc.RequireContextDir when CTX_DIR is shape-valid but the directory
    -// does not exist on disk. Distinct from [ErrDirNotDeclared], which
    -// fires before any filesystem check.
    -//
    -// Construct via [Missing]; the legacy [NotFoundError] type also
    -// carries this sentinel through its [NotFoundError.Is] method, so
    -// callers using either pattern can compare with [errors.Is].
    -var ErrContextDirNotFound = errors.New(cfgRc.ErrMsgContextDirNotFound)
    +	// ErrContextDirNotFound is the sentinel returned by
    +	// rc.RequireContextDir when CTX_DIR is shape-valid but the
    +	// directory does not exist on disk. Distinct from
    +	// [ErrDirNotDeclared], which fires before any filesystem
    +	// check. Construct via [Missing]; the legacy
    +	// [NotFoundError] type also carries this sentinel through
    +	// its [NotFoundError.Is] method, so callers using either
    +	// pattern can compare with [errors.Is].
    +	ErrContextDirNotFound = entity.Sentinel(
    +		text.DescKeyErrContextDirNotFound,
    +	)
     
    -// ErrContextDirNotADirectory is the sentinel returned when CTX_DIR
    -// points at an existing path that is not a directory (typically a
    -// regular file). Symlinks pointing at directories pass.
    -var ErrContextDirNotADirectory = errors.New(cfgRc.ErrMsgContextDirNotADirectory)
    +	// ErrContextDirNotADirectory is the sentinel returned when
    +	// CTX_DIR points at an existing path that is not a
    +	// directory (typically a regular file). Symlinks pointing
    +	// at directories pass.
    +	ErrContextDirNotADirectory = entity.Sentinel(
    +		text.DescKeyErrContextDirNotADirectoryMsg,
    +	)
     
    -// ErrContextDirStat is the sentinel returned when [os.Stat] on
    -// CTX_DIR fails for a reason other than not-exist (permission
    -// denied, I/O error). Wrap via [StatFailed] to attach the
    -// underlying cause.
    -var ErrContextDirStat = errors.New(cfgRc.ErrMsgContextDirStat)
    +	// ErrContextDirStat is the sentinel returned when [os.Stat]
    +	// on CTX_DIR fails for a reason other than not-exist
    +	// (permission denied, I/O error). Wrap via [StatFailed] to
    +	// attach the underlying cause.
    +	ErrContextDirStat = entity.Sentinel(
    +		text.DescKeyErrContextDirStatMsg,
    +	)
     
    -// ErrNotInitialized is the sentinel returned when CTX_DIR is
    -// declared but the project lacks the required context files
    -// (i.e., `ctx init` has not run there). Distinct from
    -// [ErrDirNotDeclared] (no CTX_DIR at all) and from
    -// [ErrContextDirNotFound] (declared dir does not exist on disk):
    -// here the directory may or may not exist, but the contents do
    -// not constitute a ctx project.
    -//
    -// The motivating bug is the cross-IDE hook leak: Cursor imports
    -// Claude Code hooks and fires them in every workspace it opens.
    -// With the ctx Claude plugin enabled globally, hooks resolve
    -// CTX_DIR=$workspace/.context and call into ctx subcommands. Any
    -// such caller that reached [state.Dir] previously mkdir'd a stub
    -// `.context/state/` (mode 0750) into the workspace, even though
    -// the user never ran `ctx init` there. Returning this sentinel
    -// from [state.Dir] before the mkdir prevents the leak.
    -//
    -// Wrap via [NotInitialized] for user-facing messages so the
    -// offending path is shown.
    -var ErrNotInitialized = errors.New(cfgRc.ErrMsgNotInitialized)
    +	// ErrNotInitialized is the sentinel returned when CTX_DIR
    +	// is declared but the project lacks the required context
    +	// files (i.e., `ctx init` has not run there). Distinct
    +	// from [ErrDirNotDeclared] (no CTX_DIR at all) and from
    +	// [ErrContextDirNotFound] (declared dir does not exist on
    +	// disk): here the directory may or may not exist, but the
    +	// contents do not constitute a ctx project. Wrap via
    +	// [NotInitialized] for user-facing messages so the
    +	// offending path is shown.
    +	ErrNotInitialized = entity.Sentinel(
    +		text.DescKeyErrContextNotInitializedMsg,
    +	)
    +)
     
     // RelativeNotAllowed wraps [ErrRelativeNotAllowed] with the
     // offending value so the user sees what they declared.
    diff --git a/internal/err/git_meta/gitmeta.go b/internal/err/git_meta/gitmeta.go
    index e3798b79a..0181f9e25 100644
    --- a/internal/err/git_meta/gitmeta.go
    +++ b/internal/err/git_meta/gitmeta.go
    @@ -7,25 +7,29 @@
     package gitmeta
     
     import (
    -	"errors"
     	"fmt"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrMissingGitTree signals that `<projectRoot>/.git` is
    -// absent. The PersistentPreRunE catches this via `errors.Is`
    -// and wraps it with the failing subcommand name through
    -// [MissingGitTreeForCmd]; direct API callers may wrap with
    -// [MissingGitTree].
    -var ErrMissingGitTree = errors.New(cfgGitmeta.ErrMsgMissingGitTree)
    -
    -// ErrResolveHeadEmpty signals that `git rev-parse --short HEAD`
    -// returned an empty string. Typically: unborn HEAD (repository
    -// initialized but no commit yet).
    -var ErrResolveHeadEmpty = errors.New(cfgGitmeta.ErrMsgResolveHeadEmpty)
    +const (
    +	// ErrMissingGitTree signals that `<projectRoot>/.git` is
    +	// absent. The PersistentPreRunE catches this via
    +	// `errors.Is` and wraps it with the failing subcommand name
    +	// through [MissingGitTreeForCmd]; direct API callers may
    +	// wrap with [MissingGitTree].
    +	ErrMissingGitTree = entity.Sentinel(
    +		text.DescKeyErrGitmetaMissingGitTreeMsg,
    +	)
    +	// ErrResolveHeadEmpty signals that `git rev-parse --short
    +	// HEAD` returned an empty string. Typically: unborn HEAD
    +	// (repository initialized but no commit yet).
    +	ErrResolveHeadEmpty = entity.Sentinel(
    +		text.DescKeyErrGitmetaResolveHeadEmpty,
    +	)
    +)
     
     // MissingGitTree wraps [ErrMissingGitTree] with the
     // project-root path that was scanned. Used by direct API
    diff --git a/internal/err/handover/handover.go b/internal/err/handover/handover.go
    index a9e8e36fe..cee11cdbe 100644
    --- a/internal/err/handover/handover.go
    +++ b/internal/err/handover/handover.go
    @@ -7,37 +7,45 @@
     package handover
     
     import (
    -	"errors"
     	"fmt"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrTitleRequired signals an empty Title supplied to
    -// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    -var ErrTitleRequired = errors.New(cfgHandover.ErrMsgTitleRequired)
    -
    -// ErrSummaryRequired signals an empty Summary supplied to
    -// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    -var ErrSummaryRequired = errors.New(cfgHandover.ErrMsgSummaryRequired)
    -
    -// ErrNextRequired signals an empty Next supplied to
    -// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    -var ErrNextRequired = errors.New(cfgHandover.ErrMsgNextRequired)
    -
    -// ErrMissingFrontmatter signals a handover file that does not
    -// open with `---`.
    -var ErrMissingFrontmatter = errors.New(cfgHandover.ErrMsgMissingFrontmatter)
    -
    -// ErrMissingClosingDelim signals a handover whose frontmatter
    -// is never closed by a second `---`.
    -var ErrMissingClosingDelim = errors.New(cfgHandover.ErrMsgMissingClosingDelim)
    -
    -// ErrMissingGeneratedAt signals a handover whose frontmatter
    -// parsed but has no generated-at value.
    -var ErrMissingGeneratedAt = errors.New(cfgHandover.ErrMsgMissingGeneratedAt)
    +const (
    +	// ErrTitleRequired signals an empty Title supplied to
    +	// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    +	ErrTitleRequired = entity.Sentinel(
    +		text.DescKeyErrHandoverTitleRequired,
    +	)
    +	// ErrSummaryRequired signals an empty Summary supplied to
    +	// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    +	ErrSummaryRequired = entity.Sentinel(
    +		text.DescKeyErrHandoverSummaryRequired,
    +	)
    +	// ErrNextRequired signals an empty Next supplied to
    +	// [github.com/ActiveMemory/ctx/internal/write/handover.Write].
    +	ErrNextRequired = entity.Sentinel(
    +		text.DescKeyErrHandoverNextRequired,
    +	)
    +	// ErrMissingFrontmatter signals a handover file that does
    +	// not open with `---`.
    +	ErrMissingFrontmatter = entity.Sentinel(
    +		text.DescKeyErrHandoverMissingFrontmatter,
    +	)
    +	// ErrMissingClosingDelim signals a handover whose
    +	// frontmatter is never closed by a second `---`.
    +	ErrMissingClosingDelim = entity.Sentinel(
    +		text.DescKeyErrHandoverMissingClosingDelim,
    +	)
    +	// ErrMissingGeneratedAt signals a handover whose
    +	// frontmatter parsed but has no generated-at value.
    +	ErrMissingGeneratedAt = entity.Sentinel(
    +		text.DescKeyErrHandoverMissingGeneratedAt,
    +	)
    +)
     
     // Latest wraps a failure encountered while reading the
     // latest handover during fold.
    diff --git a/internal/err/initialize/initialize.go b/internal/err/initialize/initialize.go
    index 25ede63f5..28900bebb 100644
    --- a/internal/err/initialize/initialize.go
    +++ b/internal/err/initialize/initialize.go
    @@ -7,7 +7,6 @@
     package initialize
     
     import (
    -	"errors"
     	"fmt"
     	"path/filepath"
     	"strings"
    @@ -16,26 +15,26 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	cfgInit "github.com/ActiveMemory/ctx/internal/config/initialize"
     	"github.com/ActiveMemory/ctx/internal/config/token"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrContextPopulated is returned by ctx init when the target
    -// .context/ directory contains a populated context (essential
    -// files present) and the operator did not pass --reset.
    -//
    -// Wrap with [Populated] to attach the file list and the
    -// recovery hint pointing at --reset.
    -//
    -// The message lives in config/initialize (not resolved through
    -// desc.Text) because the sentinel is initialized at package
    -// load time, before the embedded YAML lookup is populated.
    -var ErrContextPopulated = errors.New(cfgInit.ErrMsgContextPopulated)
    -
    -// ErrResetRequiresInteractive is returned by ctx init --reset
    -// when the invocation is non-interactive (the --caller flag
    -// is set, indicating an editor or scripted entry point). Reset
    -// is destructive and must come from a real terminal session.
    -var ErrResetRequiresInteractive = errors.New(
    -	cfgInit.ErrMsgResetRequiresInteractive,
    +const (
    +	// ErrContextPopulated is returned by ctx init when the
    +	// target .context/ directory contains a populated context
    +	// (essential files present) and the operator did not pass
    +	// --reset. Wrap with [Populated] to attach the file list
    +	// and the recovery hint pointing at --reset.
    +	ErrContextPopulated = entity.Sentinel(
    +		text.DescKeyErrInitContextPopulatedMsg,
    +	)
    +	// ErrResetRequiresInteractive is returned by ctx init
    +	// --reset when the invocation is non-interactive (the
    +	// --caller flag is set, indicating an editor or scripted
    +	// entry point). Reset is destructive and must come from a
    +	// real terminal session.
    +	ErrResetRequiresInteractive = entity.Sentinel(
    +		text.DescKeyErrInitResetRequiresInteractiveMsg,
    +	)
     )
     
     // Populated wraps [ErrContextPopulated] with the populated
    diff --git a/internal/err/kb/cli/cli.go b/internal/err/kb/cli/cli.go
    index 20f8bf731..486e8c496 100644
    --- a/internal/err/kb/cli/cli.go
    +++ b/internal/err/kb/cli/cli.go
    @@ -7,32 +7,36 @@
     package cli
     
     import (
    -	"errors"
     	"fmt"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	cfgKbCli "github.com/ActiveMemory/ctx/internal/config/kb/cli"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrAskNoQuestion signals an empty `ctx kb ask` invocation.
    -var ErrAskNoQuestion = errors.New(cfgKbCli.ErrMsgAskNoQuestion)
    -
    -// ErrIngestNoSources signals an empty `ctx kb ingest`
    -// invocation.
    -var ErrIngestNoSources = errors.New(cfgKbCli.ErrMsgIngestNoSources)
    -
    -// ErrNoteNoText signals an empty `ctx kb note` invocation.
    -var ErrNoteNoText = errors.New(cfgKbCli.ErrMsgNoteNoText)
    -
    -// ErrTopicEmptyName signals a `ctx kb topic new` invocation
    -// whose name reduces to an empty slug.
    -var ErrTopicEmptyName = errors.New(cfgKbCli.ErrMsgTopicEmptyName)
    -
    -// ErrReindexMissingBlock signals a kb landing page that is
    -// missing the CTX:KB:TOPICS managed block.
    -var ErrReindexMissingBlock = errors.New(
    -	cfgKbCli.ErrMsgReindexMissingBlock,
    +const (
    +	// ErrAskNoQuestion signals an empty `ctx kb ask`
    +	// invocation.
    +	ErrAskNoQuestion = entity.Sentinel(
    +		text.DescKeyErrKbCliAskNoQuestion,
    +	)
    +	// ErrIngestNoSources signals an empty `ctx kb ingest`
    +	// invocation.
    +	ErrIngestNoSources = entity.Sentinel(
    +		text.DescKeyErrKbCliIngestNoSources,
    +	)
    +	// ErrNoteNoText signals an empty `ctx kb note` invocation.
    +	ErrNoteNoText = entity.Sentinel(text.DescKeyErrKbCliNoteNoText)
    +	// ErrTopicEmptyName signals a `ctx kb topic new`
    +	// invocation whose name reduces to an empty slug.
    +	ErrTopicEmptyName = entity.Sentinel(
    +		text.DescKeyErrKbCliTopicEmptyName,
    +	)
    +	// ErrReindexMissingBlock signals a kb landing page that is
    +	// missing the CTX:KB:TOPICS managed block.
    +	ErrReindexMissingBlock = entity.Sentinel(
    +		text.DescKeyErrKbCliReindexMissingBlock,
    +	)
     )
     
     // GroundingMissing wraps a missing grounding-sources.md error
    diff --git a/internal/err/kb/evidence/evidence.go b/internal/err/kb/evidence/evidence.go
    index 043ed3158..9d16e99a2 100644
    --- a/internal/err/kb/evidence/evidence.go
    +++ b/internal/err/kb/evidence/evidence.go
    @@ -7,24 +7,28 @@
     package evidence
     
     import (
    -	"errors"
     	"fmt"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	cfgKbEvidence "github.com/ActiveMemory/ctx/internal/config/kb/evidence"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrDuplicateID signals that Append was called with an
    -// explicit row.ID already present in the file. Renumbering is
    -// forbidden; callers must reuse the existing row verbatim or
    -// mint a new ID by leaving row.ID empty.
    -var ErrDuplicateID = errors.New(cfgKbEvidence.ErrMsgDuplicateID)
    -
    -// ErrInvalidBand signals a row whose Confidence is not one of
    -// the four canonical bands defined in
    -// [github.com/ActiveMemory/ctx/internal/config/kb].
    -var ErrInvalidBand = errors.New(cfgKbEvidence.ErrMsgInvalidBand)
    +const (
    +	// ErrDuplicateID signals that Append was called with an
    +	// explicit row.ID already present in the file.
    +	// Renumbering is forbidden; callers must reuse the existing
    +	// row verbatim or mint a new ID by leaving row.ID empty.
    +	ErrDuplicateID = entity.Sentinel(
    +		text.DescKeyErrKbEvidenceDuplicateIDMsg,
    +	)
    +	// ErrInvalidBand signals a row whose Confidence is not one
    +	// of the four canonical bands defined in
    +	// [github.com/ActiveMemory/ctx/internal/config/kb].
    +	ErrInvalidBand = entity.Sentinel(
    +		text.DescKeyErrKbEvidenceInvalidBandMsg,
    +	)
    +)
     
     // DuplicateID wraps ErrDuplicateID with the offending
     // identifier so callers see exactly which ID collided.
    diff --git a/internal/err/kb/sourcecoverage/sourcecoverage.go b/internal/err/kb/sourcecoverage/sourcecoverage.go
    index f557d2071..59d0d1cf8 100644
    --- a/internal/err/kb/sourcecoverage/sourcecoverage.go
    +++ b/internal/err/kb/sourcecoverage/sourcecoverage.go
    @@ -7,26 +7,28 @@
     package sourcecoverage
     
     import (
    -	"errors"
     	"fmt"
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	cfgKbSC "github.com/ActiveMemory/ctx/internal/config/kb/sourcecoverage"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
    -// ErrIllegalTransition signals that Advance was called with a
    -// (from, to) state pair the source-coverage state machine
    -// rejects.
    -var ErrIllegalTransition = errors.New(
    -	cfgKbSC.ErrMsgIllegalTransition,
    +const (
    +	// ErrIllegalTransition signals that Advance was called with
    +	// a (from, to) state pair the source-coverage state machine
    +	// rejects.
    +	ErrIllegalTransition = entity.Sentinel(
    +		text.DescKeyErrKbSourcecoverageIllegalTransitionMsg,
    +	)
    +	// ErrUnknownSource signals that Advance referenced a Source
    +	// not yet present in the ledger AND the new State is not
    +	// one of the initial states (`discovered`, `admitted`).
    +	ErrUnknownSource = entity.Sentinel(
    +		text.DescKeyErrKbSourcecoverageUnknownSourceMsg,
    +	)
     )
     
    -// ErrUnknownSource signals that Advance referenced a Source
    -// not yet present in the ledger AND the new State is not one
    -// of the initial states (`discovered`, `admitted`).
    -var ErrUnknownSource = errors.New(cfgKbSC.ErrMsgUnknownSource)
    -
     // IllegalTransition wraps [ErrIllegalTransition] with the
     // offending from-state, to-state, and source name.
     //
    diff --git a/internal/err/schema/doc.go b/internal/err/schema/doc.go
    index db78de370..3ce8a6719 100644
    --- a/internal/err/schema/doc.go
    +++ b/internal/err/schema/doc.go
    @@ -28,9 +28,11 @@
     //
     // # Wrapping Strategy
     //
    -// ErrDrift is a plain errors.New sentinel with
    -// no cause wrapping. Its message text comes from
    -// [internal/config/schema.ErrMsgDrift].
    +// ErrDrift is a typed-string sentinel
    +// ([github.com/ActiveMemory/ctx/internal/entity.Sentinel])
    +// whose `Error()` resolves the user-facing string from the
    +// embedded YAML lookup at call time, keyed by
    +// `text.DescKeyErrSchemaDrift`.
     //
     // # Concurrency
     //
    diff --git a/internal/err/schema/schema.go b/internal/err/schema/schema.go
    index 7998001f1..de55e8526 100644
    --- a/internal/err/schema/schema.go
    +++ b/internal/err/schema/schema.go
    @@ -7,13 +7,12 @@
     package schema
     
     import (
    -	"errors"
    -
    -	cfgSchema "github.com/ActiveMemory/ctx/internal/config/schema"
    +	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    +	"github.com/ActiveMemory/ctx/internal/entity"
     )
     
     // ErrDrift indicates schema drift was detected.
    -var ErrDrift = errors.New(cfgSchema.ErrMsgDrift)
    +const ErrDrift = entity.Sentinel(text.DescKeyErrSchemaDrift)
     
     // Drift returns a schema drift error.
     //
    
    From d54304eb83fc27338008425a5fe20f428bb5517e Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 18:41:12 -0700
    Subject: [PATCH 03/10] docs(decisions): record entity/Sentinel placement
     rationale
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    The cross-package-types audit forced the placement choice — every
    type used across packages must live in `internal/entity/`, full
    stop, with zero grandfathered exceptions. `entity.Sentinel` is a
    behavioral helper rather than a data shape, which is a mild
    semantic stretch for `entity/`, but per-package duplication and
    audit-exemption growth were both rejected: the convention is
    load-bearing and the next session needs one obvious shape to copy.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/DECISIONS.md | 56 +++++++++++++++++++++++++++++++++++++++++++
     1 file changed, 56 insertions(+)
    
    diff --git a/.context/DECISIONS.md b/.context/DECISIONS.md
    index dc41ceb5f..3f4640157 100644
    --- a/.context/DECISIONS.md
    +++ b/.context/DECISIONS.md
    @@ -3,6 +3,7 @@
     <!-- INDEX:START -->
     | Date | Decision |
     |----|--------|
    +| 2026-05-17 | entity.Sentinel lives in internal/entity/ because the cross-package-types audit treats entity/ as the canonical home for shared types |
     | 2026-05-16 | Phase KB lifts the current upstream editorial-pipeline shape, superseding the 4-phase predecessor in the brief |
     | 2026-05-11 | Embedded and separately-published harnesses use distinct CI and release pipelines |
     | 2026-05-11 | Embedded foreign-language assets under internal/assets/ are intentional, not a smell |
    @@ -141,6 +142,61 @@ For significant decisions:
     
     -->
     
    +## [2026-05-17-181500] `entity.Sentinel` lives in `internal/entity/` because the cross-package-types audit treats `entity/` as the canonical home for shared types
    +
    +**Status**: Accepted
    +
    +**Context**: While converting the prior session's
    +`ErrMsg`-string-sentinel anti-pattern to typed-string sentinels
    +with lazy `desc.Text` resolution, the natural home for the
    +`Sentinel` type was a small shared helper used by every
    +`internal/err/<area>/` package. The first draft placed it at
    +`internal/err/sentinel/`, but `TestCrossPackageTypes` (which has
    +zero grandfathered violations and forbids weakening or
    +allowlist-bumping) flagged the cross-package usage with the hint
    +"consider entity/".
    +
    +**Alternatives Considered**:
    +- Per-package sentinel type duplicated across 9 err packages.
    +  Pros: no cross-package type. Cons: 18 boilerplate declarations
    +  (type + Error method × 9) with doc comments; convention drift
    +  risk as the duplicated shape can diverge.
    +- Keep `internal/err/sentinel/` and add it to `typeExemptPackages`
    +  in the audit. Pros: semantic home matches the type's role
    +  (behavioral mixin for errors). Cons: the audit explicitly
    +  forbids exemption-list growth as the mechanism for new code;
    +  the test header says "If a test fails after your change, fix
    +  the code under test."
    +- Move `Sentinel` to `internal/entity/`. Pros: passes the audit
    +  without weakening; one shared declaration; consistent with
    +  every other cross-cutting type. Cons: `Sentinel` is a
    +  behavioral helper, not a domain data shape — semantically
    +  stretches `entity/`'s usual contents.
    +
    +**Decision**: Place `Sentinel` in `internal/entity/sentinel.go`.
    +
    +**Rationale**: The audit's rule is the project's hardline: every
    +cross-package type goes in `entity/`. The semantic stretch is
    +real but small, and writing exceptions to the audit is more
    +expensive long-term than absorbing a one-type semantic blur in
    +a package whose contract is already "things used cross-package."
    +Per-package duplication was rejected because the convention is
    +load-bearing — the next session that touches an err package
    +needs one obvious shape to copy, not a choice between 9 nearly
    +identical copies.
    +
    +**Consequence**: `entity/` now houses a typed-string error
    +helper alongside its data shapes. Future readers landing in
    +`entity/` will find one file (`sentinel.go`) that doesn't
    +match the package's "data" theme; the doc comment on `Sentinel`
    +explains why. If `entity/` grows more behavioral helpers, the
    +package contract should be revisited; for now the precedent is
    +contained to this single type.
    +
    +**Related**: LEARNINGS.md `[2026-05-17-180000] Sentinel errors
    +use typed zero-data structs with lazy desc.Text()` records the
    +shape itself.
    +
     ## [2026-05-16-000000] Phase KB lifts the current upstream editorial-pipeline shape, superseding the 4-phase predecessor in the brief
     
     **Status**: Accepted
    
    From e7860a28a617468f9254e113578cb0466bc9e464 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 18:57:12 -0700
    Subject: [PATCH 04/10] refactor(text): drop stuttery `cli` segment from kb
     yaml keys
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    `err.kb.cli.*` and `write.kb-cli.*` keys mirrored the Go package
    path `internal/{err,cli}/kb/cli/` into the YAML namespace, but
    every other CLI command's keys (`err.add.*`, `err.fmt.*`,
    `err.backup.*`, …) sit one level shallower because
    `commands/text/{errors,write}.yaml` is implicitly the CLI-surface
    text. The extra `cli` segment was leaky Go-side detail showing
    up where users see it.
    
    Renames (purely textual; no behavioral change):
    
      err.kb.cli.<name>      → err.kb.<name>      (17 keys)
      write.kb-cli.<name>    → write.kb.<name>    (14 keys)
      DescKeyErrKbCli<X>     → DescKeyErrKb<X>    (17 consts)
      DescKeyWriteKbCli<X>   → DescKeyWriteKb<X>  (14 consts)
      embed/text/err_kb_cli.go    → embed/text/err_kb.go
      embed/text/write_kb_cli.go  → embed/text/write_kb.go
    
    The Go package paths under `internal/err/kb/cli/` and
    `internal/cli/kb/` are unchanged — those mirror the directory
    tree where `kb` legitimately has sibling subdomains
    (`evidence/`, `sourcecoverage/`, `glossary/`, etc.). The leak
    was YAML-side only.
    
    No collisions with the flat `err.kb.*` / `write.kb.*` namespace:
    existing subdomain keys all sit at depth 3+
    (`err.kb.evidence.*`, `err.kb.sourcecoverage.*`, etc.).
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     internal/assets/commands/text/errors.yaml  | 34 ++++++------
     internal/assets/commands/text/write.yaml   | 28 +++++-----
     internal/cli/kb/cmd/ask/run.go             |  6 +--
     internal/cli/kb/cmd/ground/run.go          |  4 +-
     internal/cli/kb/cmd/ingest/run.go          |  6 +--
     internal/cli/kb/cmd/note/run.go            |  4 +-
     internal/cli/kb/cmd/reindex/run.go         |  2 +-
     internal/cli/kb/cmd/sitereview/run.go      |  4 +-
     internal/cli/kb/core/topic/scaffold.go     |  2 +-
     internal/config/embed/text/err_kb.go       | 62 ++++++++++++++++++++++
     internal/config/embed/text/err_kb_cli.go   | 62 ----------------------
     internal/config/embed/text/write_kb.go     | 53 ++++++++++++++++++
     internal/config/embed/text/write_kb_cli.go | 53 ------------------
     internal/err/kb/cli/cli.go                 | 34 ++++++------
     14 files changed, 177 insertions(+), 177 deletions(-)
     create mode 100644 internal/config/embed/text/err_kb.go
     delete mode 100644 internal/config/embed/text/err_kb_cli.go
     create mode 100644 internal/config/embed/text/write_kb.go
     delete mode 100644 internal/config/embed/text/write_kb_cli.go
    
    diff --git a/internal/assets/commands/text/errors.yaml b/internal/assets/commands/text/errors.yaml
    index 6c1c5fda8..156575d30 100644
    --- a/internal/assets/commands/text/errors.yaml
    +++ b/internal/assets/commands/text/errors.yaml
    @@ -806,37 +806,37 @@ err.kb.question.write-row:
       short: 'write questions row: %w'
     err.kb.question.parse-q-number:
       short: 'parse Q number %q: %w'
    -err.kb.cli.grounding-missing:
    +err.kb.grounding-missing:
       short: 'grounding-sources.md not found at %s; list sources first'
    -err.kb.cli.grounding-empty:
    +err.kb.grounding-empty:
       short: 'grounding-sources.md is empty at %s; list sources first'
    -err.kb.cli.topic-exists:
    +err.kb.topic-exists:
       short: 'topic %s already exists at %s'
    -err.kb.cli.mkdir-ingest:
    +err.kb.mkdir-ingest:
       short: 'mkdir ingest: %w'
    -err.kb.cli.open-findings:
    +err.kb.open-findings:
       short: 'open findings: %w'
    -err.kb.cli.write-finding:
    +err.kb.write-finding:
       short: 'write finding: %w'
    -err.kb.cli.read-kb-index:
    +err.kb.read-kb-index:
       short: 'read kb index: %w'
    -err.kb.cli.write-kb-index:
    +err.kb.write-kb-index:
       short: 'write kb index: %w'
    -err.kb.cli.read-topics-dir:
    +err.kb.read-topics-dir:
       short: 'read topics dir: %w'
    -err.kb.cli.mkdir-topic:
    +err.kb.mkdir-topic:
       short: 'mkdir topic: %w'
    -err.kb.cli.read-topic-template:
    +err.kb.read-topic-template:
       short: 'read embedded topic template: %w'
    -err.kb.cli.write-topic-index:
    +err.kb.write-topic-index:
       short: 'write topic index: %w'
    -err.kb.cli.ask-no-question:
    +err.kb.ask-no-question:
       short: 'no question provided; pass a question or describe it inline'
    -err.kb.cli.ingest-no-sources:
    +err.kb.ingest-no-sources:
       short: 'no sources provided; pass a folder, a URL, an MCP resource, or describe the materials inline'
    -err.kb.cli.note-no-text:
    +err.kb.note-no-text:
       short: 'no text provided; pass a one-liner inline'
    -err.kb.cli.topic-empty-name:
    +err.kb.topic-empty-name:
       short: 'topic name must contain at least one alnum char'
    -err.kb.cli.reindex-missing-block:
    +err.kb.reindex-missing-block:
       short: 'kb/index.md is missing the CTX:KB:TOPICS managed block'
    diff --git a/internal/assets/commands/text/write.yaml b/internal/assets/commands/text/write.yaml
    index 90acd8868..2444afe47 100644
    --- a/internal/assets/commands/text/write.yaml
    +++ b/internal/assets/commands/text/write.yaml
    @@ -1069,31 +1069,31 @@ write.handover.malformed-warning:
       short: "warning: %d malformed closeout(s) skipped during fold:\n"
     write.handover.malformed-line:
       short: "  %s\n"
    -write.kb-cli.finding-line:
    +write.kb.finding-line:
       short: "- [%s] %s\n"
    -write.kb-cli.reindexed:
    +write.kb.reindexed:
       short: "reindexed %d topic(s) into %s\n"
    -write.kb-cli.scaffolded:
    +write.kb.scaffolded:
       short: "scaffolded %s\n"
    -write.kb-cli.appended-to:
    +write.kb.appended-to:
       short: "appended to %s\n"
    -write.kb-cli.ask-driven-hint:
    +write.kb.ask-driven-hint:
       short: "Q&A is driven by the /ctx-kb-ask skill.\n"
    -write.kb-cli.ask-invoke-format:
    +write.kb.ask-invoke-format:
       short: "In Claude Code, invoke: /ctx-kb-ask %s\n"
    -write.kb-cli.ask-contract-pointer:
    +write.kb.ask-contract-pointer:
       short: "See .context/ingest/40-ASK.md for the contract.\n"
    -write.kb-cli.ground-driven-hint:
    +write.kb.ground-driven-hint:
       short: "Grounding is driven by the /ctx-kb-ground skill.\n"
    -write.kb-cli.ground-contract-pointer:
    +write.kb.ground-contract-pointer:
       short: "See .context/ingest/00-GROUND.md for the contract.\n"
    -write.kb-cli.ingest-driven-hint:
    +write.kb.ingest-driven-hint:
       short: "Editorial ingestion is driven by the /ctx-kb-ingest skill.\n"
    -write.kb-cli.ingest-invoke-format:
    +write.kb.ingest-invoke-format:
       short: "In Claude Code, invoke: /ctx-kb-ingest %s\n"
    -write.kb-cli.ingest-fallback-hint:
    +write.kb.ingest-fallback-hint:
       short: "Without the skill, follow .context/ingest/PROMPT.md.\n"
    -write.kb-cli.site-review-driven-hint:
    +write.kb.site-review-driven-hint:
       short: "Site-review is driven by the /ctx-kb-site-review skill.\n"
    -write.kb-cli.site-review-contract-pointer:
    +write.kb.site-review-contract-pointer:
       short: "See .context/ingest/50-SITE_REVIEW.md for the contract.\n"
    diff --git a/internal/cli/kb/cmd/ask/run.go b/internal/cli/kb/cmd/ask/run.go
    index c445518d3..311cdb9a0 100644
    --- a/internal/cli/kb/cmd/ask/run.go
    +++ b/internal/cli/kb/cmd/ask/run.go
    @@ -37,16 +37,16 @@ func Run(cobraCmd *cobra.Command, question string, args []string) error {
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliAskDrivenHint),
    +		desc.Text(text.DescKeyWriteKbAskDrivenHint),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(),
    -		desc.Text(text.DescKeyWriteKbCliAskInvokeFormat),
    +		desc.Text(text.DescKeyWriteKbAskInvokeFormat),
     		strings.Join(args, token.Space),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliAskContractPointer),
    +		desc.Text(text.DescKeyWriteKbAskContractPointer),
     	)
     	return nil
     }
    diff --git a/internal/cli/kb/cmd/ground/run.go b/internal/cli/kb/cmd/ground/run.go
    index 0e10f35e5..9be4c3c0b 100644
    --- a/internal/cli/kb/cmd/ground/run.go
    +++ b/internal/cli/kb/cmd/ground/run.go
    @@ -53,11 +53,11 @@ func Run(cobraCmd *cobra.Command) error {
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliGroundDrivenHint),
    +		desc.Text(text.DescKeyWriteKbGroundDrivenHint),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliGroundContractPointer),
    +		desc.Text(text.DescKeyWriteKbGroundContractPointer),
     	)
     	return nil
     }
    diff --git a/internal/cli/kb/cmd/ingest/run.go b/internal/cli/kb/cmd/ingest/run.go
    index 30c30fec6..d79c80b1f 100644
    --- a/internal/cli/kb/cmd/ingest/run.go
    +++ b/internal/cli/kb/cmd/ingest/run.go
    @@ -37,16 +37,16 @@ func Run(cobraCmd *cobra.Command, args []string) error {
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliIngestDrivenHint),
    +		desc.Text(text.DescKeyWriteKbIngestDrivenHint),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(),
    -		desc.Text(text.DescKeyWriteKbCliIngestInvokeFormat),
    +		desc.Text(text.DescKeyWriteKbIngestInvokeFormat),
     		strings.Join(args, token.Space),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliIngestFallbackHint),
    +		desc.Text(text.DescKeyWriteKbIngestFallbackHint),
     	)
     	return nil
     }
    diff --git a/internal/cli/kb/cmd/note/run.go b/internal/cli/kb/cmd/note/run.go
    index 21f5269e1..ac8ae91b1 100644
    --- a/internal/cli/kb/cmd/note/run.go
    +++ b/internal/cli/kb/cmd/note/run.go
    @@ -53,14 +53,14 @@ func Run(cobraCmd *cobra.Command, noteText string) error {
     
     	stamp := time.Now().UTC().Format(time.RFC3339)
     	line := fmt.Sprintf(
    -		desc.Text(text.DescKeyWriteKbCliFindingLine), stamp, noteText,
    +		desc.Text(text.DescKeyWriteKbFindingLine), stamp, noteText,
     	)
     	if _, writeErr := f.WriteString(line); writeErr != nil {
     		return errKbCli.WriteFinding(writeErr)
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(),
    -		desc.Text(text.DescKeyWriteKbCliAppendedTo),
    +		desc.Text(text.DescKeyWriteKbAppendedTo),
     		findings,
     	)
     	return nil
    diff --git a/internal/cli/kb/cmd/reindex/run.go b/internal/cli/kb/cmd/reindex/run.go
    index d89607da9..4a9ab1b4e 100644
    --- a/internal/cli/kb/cmd/reindex/run.go
    +++ b/internal/cli/kb/cmd/reindex/run.go
    @@ -64,7 +64,7 @@ func Run(cobraCmd *cobra.Command) error {
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(),
    -		desc.Text(text.DescKeyWriteKbCliReindexed),
    +		desc.Text(text.DescKeyWriteKbReindexed),
     		len(slugs),
     		indexPath,
     	)
    diff --git a/internal/cli/kb/cmd/sitereview/run.go b/internal/cli/kb/cmd/sitereview/run.go
    index 873c76a34..f4f2372d4 100644
    --- a/internal/cli/kb/cmd/sitereview/run.go
    +++ b/internal/cli/kb/cmd/sitereview/run.go
    @@ -28,11 +28,11 @@ import (
     func Run(cobraCmd *cobra.Command) error {
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliSiteReviewDrivenHint),
    +		desc.Text(text.DescKeyWriteKbSiteReviewDrivenHint),
     	)
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(), token.FormatString,
    -		desc.Text(text.DescKeyWriteKbCliSiteReviewContractPointer),
    +		desc.Text(text.DescKeyWriteKbSiteReviewContractPointer),
     	)
     	return nil
     }
    diff --git a/internal/cli/kb/core/topic/scaffold.go b/internal/cli/kb/core/topic/scaffold.go
    index 283ebd814..f6d7a2f99 100644
    --- a/internal/cli/kb/core/topic/scaffold.go
    +++ b/internal/cli/kb/core/topic/scaffold.go
    @@ -69,7 +69,7 @@ func Scaffold(cobraCmd *cobra.Command, name string) error {
     	}
     	io.SafeFprintf(
     		cobraCmd.OutOrStdout(),
    -		desc.Text(text.DescKeyWriteKbCliScaffolded),
    +		desc.Text(text.DescKeyWriteKbScaffolded),
     		indexPath,
     	)
     	return nil
    diff --git a/internal/config/embed/text/err_kb.go b/internal/config/embed/text/err_kb.go
    new file mode 100644
    index 000000000..a58a5bfcc
    --- /dev/null
    +++ b/internal/config/embed/text/err_kb.go
    @@ -0,0 +1,62 @@
    +//   /    ctx:                         https://ctx.ist
    +// ,'`./    do you remember?
    +// `.,'\
    +//   \    Copyright 2026-present Context contributors.
    +//                 SPDX-License-Identifier: Apache-2.0
    +
    +package text
    +
    +// DescKeys for `ctx kb` CLI error wrappers.
    +const (
    +	// DescKeyErrKbGroundingMissing wraps a missing
    +	// grounding-sources.md error.
    +	DescKeyErrKbGroundingMissing = "err.kb.grounding-missing"
    +	// DescKeyErrKbGroundingEmpty wraps an empty
    +	// grounding-sources.md error.
    +	DescKeyErrKbGroundingEmpty = "err.kb.grounding-empty"
    +	// DescKeyErrKbTopicExists wraps a topic-already-exists
    +	// refusal.
    +	DescKeyErrKbTopicExists = "err.kb.topic-exists"
    +	// DescKeyErrKbMkdirIngest wraps `os.MkdirAll` for the
    +	// ingest directory.
    +	DescKeyErrKbMkdirIngest = "err.kb.mkdir-ingest"
    +	// DescKeyErrKbOpenFindings wraps `os.OpenFile` for the
    +	// findings log.
    +	DescKeyErrKbOpenFindings = "err.kb.open-findings"
    +	// DescKeyErrKbWriteFinding wraps a write to the findings
    +	// log.
    +	DescKeyErrKbWriteFinding = "err.kb.write-finding"
    +	// DescKeyErrKbReadKBIndex wraps `os.ReadFile` for the kb
    +	// landing page during reindex.
    +	DescKeyErrKbReadKBIndex = "err.kb.read-kb-index"
    +	// DescKeyErrKbWriteKBIndex wraps `os.WriteFile` for the
    +	// kb landing page during reindex.
    +	DescKeyErrKbWriteKBIndex = "err.kb.write-kb-index"
    +	// DescKeyErrKbReadTopicsDir wraps `os.ReadDir` of the
    +	// topics directory during reindex.
    +	DescKeyErrKbReadTopicsDir = "err.kb.read-topics-dir"
    +	// DescKeyErrKbMkdirTopic wraps `os.MkdirAll` for a new
    +	// topic directory.
    +	DescKeyErrKbMkdirTopic = "err.kb.mkdir-topic"
    +	// DescKeyErrKbReadTopicTemplate wraps `fs.ReadFile` for
    +	// the embedded topic template.
    +	DescKeyErrKbReadTopicTemplate = "err.kb.read-topic-template"
    +	// DescKeyErrKbWriteTopicIndex wraps `os.WriteFile` for
    +	// the topic index.md.
    +	DescKeyErrKbWriteTopicIndex = "err.kb.write-topic-index"
    +	// DescKeyErrKbAskNoQuestion is the text key for the
    +	// empty-question-arg sentinel.
    +	DescKeyErrKbAskNoQuestion = "err.kb.ask-no-question"
    +	// DescKeyErrKbIngestNoSources is the text key for the
    +	// empty-sources-arg sentinel.
    +	DescKeyErrKbIngestNoSources = "err.kb.ingest-no-sources"
    +	// DescKeyErrKbNoteNoText is the text key for the
    +	// empty-note-arg sentinel.
    +	DescKeyErrKbNoteNoText = "err.kb.note-no-text"
    +	// DescKeyErrKbTopicEmptyName is the text key for the
    +	// empty-slug topic-new sentinel.
    +	DescKeyErrKbTopicEmptyName = "err.kb.topic-empty-name"
    +	// DescKeyErrKbReindexMissingBlock is the text key for
    +	// the missing-CTX:KB:TOPICS-block sentinel.
    +	DescKeyErrKbReindexMissingBlock = "err.kb.reindex-missing-block"
    +)
    diff --git a/internal/config/embed/text/err_kb_cli.go b/internal/config/embed/text/err_kb_cli.go
    deleted file mode 100644
    index 8e4f1d39d..000000000
    --- a/internal/config/embed/text/err_kb_cli.go
    +++ /dev/null
    @@ -1,62 +0,0 @@
    -//   /    ctx:                         https://ctx.ist
    -// ,'`./    do you remember?
    -// `.,'\
    -//   \    Copyright 2026-present Context contributors.
    -//                 SPDX-License-Identifier: Apache-2.0
    -
    -package text
    -
    -// DescKeys for `ctx kb` CLI error wrappers.
    -const (
    -	// DescKeyErrKbCliGroundingMissing wraps a missing
    -	// grounding-sources.md error.
    -	DescKeyErrKbCliGroundingMissing = "err.kb.cli.grounding-missing"
    -	// DescKeyErrKbCliGroundingEmpty wraps an empty
    -	// grounding-sources.md error.
    -	DescKeyErrKbCliGroundingEmpty = "err.kb.cli.grounding-empty"
    -	// DescKeyErrKbCliTopicExists wraps a topic-already-exists
    -	// refusal.
    -	DescKeyErrKbCliTopicExists = "err.kb.cli.topic-exists"
    -	// DescKeyErrKbCliMkdirIngest wraps `os.MkdirAll` for the
    -	// ingest directory.
    -	DescKeyErrKbCliMkdirIngest = "err.kb.cli.mkdir-ingest"
    -	// DescKeyErrKbCliOpenFindings wraps `os.OpenFile` for the
    -	// findings log.
    -	DescKeyErrKbCliOpenFindings = "err.kb.cli.open-findings"
    -	// DescKeyErrKbCliWriteFinding wraps a write to the findings
    -	// log.
    -	DescKeyErrKbCliWriteFinding = "err.kb.cli.write-finding"
    -	// DescKeyErrKbCliReadKBIndex wraps `os.ReadFile` for the kb
    -	// landing page during reindex.
    -	DescKeyErrKbCliReadKBIndex = "err.kb.cli.read-kb-index"
    -	// DescKeyErrKbCliWriteKBIndex wraps `os.WriteFile` for the
    -	// kb landing page during reindex.
    -	DescKeyErrKbCliWriteKBIndex = "err.kb.cli.write-kb-index"
    -	// DescKeyErrKbCliReadTopicsDir wraps `os.ReadDir` of the
    -	// topics directory during reindex.
    -	DescKeyErrKbCliReadTopicsDir = "err.kb.cli.read-topics-dir"
    -	// DescKeyErrKbCliMkdirTopic wraps `os.MkdirAll` for a new
    -	// topic directory.
    -	DescKeyErrKbCliMkdirTopic = "err.kb.cli.mkdir-topic"
    -	// DescKeyErrKbCliReadTopicTemplate wraps `fs.ReadFile` for
    -	// the embedded topic template.
    -	DescKeyErrKbCliReadTopicTemplate = "err.kb.cli.read-topic-template"
    -	// DescKeyErrKbCliWriteTopicIndex wraps `os.WriteFile` for
    -	// the topic index.md.
    -	DescKeyErrKbCliWriteTopicIndex = "err.kb.cli.write-topic-index"
    -	// DescKeyErrKbCliAskNoQuestion is the text key for the
    -	// empty-question-arg sentinel.
    -	DescKeyErrKbCliAskNoQuestion = "err.kb.cli.ask-no-question"
    -	// DescKeyErrKbCliIngestNoSources is the text key for the
    -	// empty-sources-arg sentinel.
    -	DescKeyErrKbCliIngestNoSources = "err.kb.cli.ingest-no-sources"
    -	// DescKeyErrKbCliNoteNoText is the text key for the
    -	// empty-note-arg sentinel.
    -	DescKeyErrKbCliNoteNoText = "err.kb.cli.note-no-text"
    -	// DescKeyErrKbCliTopicEmptyName is the text key for the
    -	// empty-slug topic-new sentinel.
    -	DescKeyErrKbCliTopicEmptyName = "err.kb.cli.topic-empty-name"
    -	// DescKeyErrKbCliReindexMissingBlock is the text key for
    -	// the missing-CTX:KB:TOPICS-block sentinel.
    -	DescKeyErrKbCliReindexMissingBlock = "err.kb.cli.reindex-missing-block"
    -)
    diff --git a/internal/config/embed/text/write_kb.go b/internal/config/embed/text/write_kb.go
    new file mode 100644
    index 000000000..3a3a71a39
    --- /dev/null
    +++ b/internal/config/embed/text/write_kb.go
    @@ -0,0 +1,53 @@
    +//   /    ctx:                         https://ctx.ist
    +// ,'`./    do you remember?
    +// `.,'\
    +//   \    Copyright 2026-present Context contributors.
    +//                 SPDX-License-Identifier: Apache-2.0
    +
    +package text
    +
    +// DescKeys for `ctx kb` CLI output strings.
    +const (
    +	// DescKeyWriteKbFindingLine is the findings-log line
    +	// format (timestamp + text).
    +	DescKeyWriteKbFindingLine = "write.kb.finding-line"
    +	// DescKeyWriteKbReindexed names how many topics were
    +	// folded into the managed block and the path rewritten.
    +	DescKeyWriteKbReindexed = "write.kb.reindexed"
    +	// DescKeyWriteKbScaffolded names the path of a newly
    +	// scaffolded topic-page file.
    +	DescKeyWriteKbScaffolded = "write.kb.scaffolded"
    +	// DescKeyWriteKbAppendedTo names the destination of a
    +	// successful note append.
    +	DescKeyWriteKbAppendedTo = "write.kb.appended-to"
    +	// DescKeyWriteKbAskDrivenHint announces the canonical
    +	// /ctx-kb-ask skill invocation.
    +	DescKeyWriteKbAskDrivenHint = "write.kb.ask-driven-hint"
    +	// DescKeyWriteKbAskInvokeFormat carries the inline
    +	// invocation example.
    +	DescKeyWriteKbAskInvokeFormat = "write.kb.ask-invoke-format"
    +	// DescKeyWriteKbAskContractPointer points at the ask
    +	// contract source-of-truth.
    +	DescKeyWriteKbAskContractPointer = "write.kb.ask-contract-pointer"
    +	// DescKeyWriteKbGroundDrivenHint announces the canonical
    +	// /ctx-kb-ground skill invocation.
    +	DescKeyWriteKbGroundDrivenHint = "write.kb.ground-driven-hint"
    +	// DescKeyWriteKbGroundContractPointer points at the
    +	// ground contract source-of-truth.
    +	DescKeyWriteKbGroundContractPointer = "write.kb.ground-contract-pointer"
    +	// DescKeyWriteKbIngestDrivenHint announces the canonical
    +	// /ctx-kb-ingest skill invocation.
    +	DescKeyWriteKbIngestDrivenHint = "write.kb.ingest-driven-hint"
    +	// DescKeyWriteKbIngestInvokeFormat carries the inline
    +	// ingest invocation example.
    +	DescKeyWriteKbIngestInvokeFormat = "write.kb.ingest-invoke-format"
    +	// DescKeyWriteKbIngestFallbackHint points at the
    +	// hand-fallback prompt.
    +	DescKeyWriteKbIngestFallbackHint = "write.kb.ingest-fallback-hint"
    +	// DescKeyWriteKbSiteReviewDrivenHint announces the
    +	// canonical /ctx-kb-site-review skill invocation.
    +	DescKeyWriteKbSiteReviewDrivenHint = "write.kb.site-review-driven-hint"
    +	// DescKeyWriteKbSiteReviewContractPointer points at the
    +	// site-review contract source-of-truth.
    +	DescKeyWriteKbSiteReviewContractPointer = "write.kb.site-review-contract-pointer"
    +)
    diff --git a/internal/config/embed/text/write_kb_cli.go b/internal/config/embed/text/write_kb_cli.go
    deleted file mode 100644
    index 90f38c63d..000000000
    --- a/internal/config/embed/text/write_kb_cli.go
    +++ /dev/null
    @@ -1,53 +0,0 @@
    -//   /    ctx:                         https://ctx.ist
    -// ,'`./    do you remember?
    -// `.,'\
    -//   \    Copyright 2026-present Context contributors.
    -//                 SPDX-License-Identifier: Apache-2.0
    -
    -package text
    -
    -// DescKeys for `ctx kb` CLI output strings.
    -const (
    -	// DescKeyWriteKbCliFindingLine is the findings-log line
    -	// format (timestamp + text).
    -	DescKeyWriteKbCliFindingLine = "write.kb-cli.finding-line"
    -	// DescKeyWriteKbCliReindexed names how many topics were
    -	// folded into the managed block and the path rewritten.
    -	DescKeyWriteKbCliReindexed = "write.kb-cli.reindexed"
    -	// DescKeyWriteKbCliScaffolded names the path of a newly
    -	// scaffolded topic-page file.
    -	DescKeyWriteKbCliScaffolded = "write.kb-cli.scaffolded"
    -	// DescKeyWriteKbCliAppendedTo names the destination of a
    -	// successful note append.
    -	DescKeyWriteKbCliAppendedTo = "write.kb-cli.appended-to"
    -	// DescKeyWriteKbCliAskDrivenHint announces the canonical
    -	// /ctx-kb-ask skill invocation.
    -	DescKeyWriteKbCliAskDrivenHint = "write.kb-cli.ask-driven-hint"
    -	// DescKeyWriteKbCliAskInvokeFormat carries the inline
    -	// invocation example.
    -	DescKeyWriteKbCliAskInvokeFormat = "write.kb-cli.ask-invoke-format"
    -	// DescKeyWriteKbCliAskContractPointer points at the ask
    -	// contract source-of-truth.
    -	DescKeyWriteKbCliAskContractPointer = "write.kb-cli.ask-contract-pointer"
    -	// DescKeyWriteKbCliGroundDrivenHint announces the canonical
    -	// /ctx-kb-ground skill invocation.
    -	DescKeyWriteKbCliGroundDrivenHint = "write.kb-cli.ground-driven-hint"
    -	// DescKeyWriteKbCliGroundContractPointer points at the
    -	// ground contract source-of-truth.
    -	DescKeyWriteKbCliGroundContractPointer = "write.kb-cli.ground-contract-pointer"
    -	// DescKeyWriteKbCliIngestDrivenHint announces the canonical
    -	// /ctx-kb-ingest skill invocation.
    -	DescKeyWriteKbCliIngestDrivenHint = "write.kb-cli.ingest-driven-hint"
    -	// DescKeyWriteKbCliIngestInvokeFormat carries the inline
    -	// ingest invocation example.
    -	DescKeyWriteKbCliIngestInvokeFormat = "write.kb-cli.ingest-invoke-format"
    -	// DescKeyWriteKbCliIngestFallbackHint points at the
    -	// hand-fallback prompt.
    -	DescKeyWriteKbCliIngestFallbackHint = "write.kb-cli.ingest-fallback-hint"
    -	// DescKeyWriteKbCliSiteReviewDrivenHint announces the
    -	// canonical /ctx-kb-site-review skill invocation.
    -	DescKeyWriteKbCliSiteReviewDrivenHint = "write.kb-cli.site-review-driven-hint"
    -	// DescKeyWriteKbCliSiteReviewContractPointer points at the
    -	// site-review contract source-of-truth.
    -	DescKeyWriteKbCliSiteReviewContractPointer = "write.kb-cli.site-review-contract-pointer"
    -)
    diff --git a/internal/err/kb/cli/cli.go b/internal/err/kb/cli/cli.go
    index 486e8c496..07f750fd7 100644
    --- a/internal/err/kb/cli/cli.go
    +++ b/internal/err/kb/cli/cli.go
    @@ -18,24 +18,24 @@ const (
     	// ErrAskNoQuestion signals an empty `ctx kb ask`
     	// invocation.
     	ErrAskNoQuestion = entity.Sentinel(
    -		text.DescKeyErrKbCliAskNoQuestion,
    +		text.DescKeyErrKbAskNoQuestion,
     	)
     	// ErrIngestNoSources signals an empty `ctx kb ingest`
     	// invocation.
     	ErrIngestNoSources = entity.Sentinel(
    -		text.DescKeyErrKbCliIngestNoSources,
    +		text.DescKeyErrKbIngestNoSources,
     	)
     	// ErrNoteNoText signals an empty `ctx kb note` invocation.
    -	ErrNoteNoText = entity.Sentinel(text.DescKeyErrKbCliNoteNoText)
    +	ErrNoteNoText = entity.Sentinel(text.DescKeyErrKbNoteNoText)
     	// ErrTopicEmptyName signals a `ctx kb topic new`
     	// invocation whose name reduces to an empty slug.
     	ErrTopicEmptyName = entity.Sentinel(
    -		text.DescKeyErrKbCliTopicEmptyName,
    +		text.DescKeyErrKbTopicEmptyName,
     	)
     	// ErrReindexMissingBlock signals a kb landing page that is
     	// missing the CTX:KB:TOPICS managed block.
     	ErrReindexMissingBlock = entity.Sentinel(
    -		text.DescKeyErrKbCliReindexMissingBlock,
    +		text.DescKeyErrKbReindexMissingBlock,
     	)
     )
     
    @@ -48,7 +48,7 @@ const (
     // Returns:
     //   - error: descriptive refusal.
     func GroundingMissing(path string) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliGroundingMissing), path)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbGroundingMissing), path)
     }
     
     // GroundingEmpty wraps an empty grounding-sources.md error
    @@ -60,7 +60,7 @@ func GroundingMissing(path string) error {
     // Returns:
     //   - error: descriptive refusal.
     func GroundingEmpty(path string) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliGroundingEmpty), path)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbGroundingEmpty), path)
     }
     
     // TopicExists wraps a topic-already-exists refusal with the
    @@ -74,7 +74,7 @@ func GroundingEmpty(path string) error {
     //   - error: descriptive refusal.
     func TopicExists(slug, indexPath string) error {
     	return fmt.Errorf(
    -		desc.Text(text.DescKeyErrKbCliTopicExists), slug, indexPath,
    +		desc.Text(text.DescKeyErrKbTopicExists), slug, indexPath,
     	)
     }
     
    @@ -86,7 +86,7 @@ func TopicExists(slug, indexPath string) error {
     // Returns:
     //   - error: wrapped failure.
     func MkdirIngest(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliMkdirIngest), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbMkdirIngest), cause)
     }
     
     // OpenFindings wraps a findings-file open failure.
    @@ -97,7 +97,7 @@ func MkdirIngest(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func OpenFindings(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliOpenFindings), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbOpenFindings), cause)
     }
     
     // WriteFinding wraps a findings-file write failure.
    @@ -108,7 +108,7 @@ func OpenFindings(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func WriteFinding(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliWriteFinding), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbWriteFinding), cause)
     }
     
     // ReadKBIndex wraps a kb-index read failure during reindex.
    @@ -119,7 +119,7 @@ func WriteFinding(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func ReadKBIndex(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliReadKBIndex), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbReadKBIndex), cause)
     }
     
     // WriteKBIndex wraps a kb-index write failure during reindex.
    @@ -130,7 +130,7 @@ func ReadKBIndex(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func WriteKBIndex(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliWriteKBIndex), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbWriteKBIndex), cause)
     }
     
     // ReadTopicsDir wraps a topics-dir read failure during reindex.
    @@ -141,7 +141,7 @@ func WriteKBIndex(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func ReadTopicsDir(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliReadTopicsDir), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbReadTopicsDir), cause)
     }
     
     // MkdirTopic wraps a topic-dir create failure.
    @@ -152,7 +152,7 @@ func ReadTopicsDir(cause error) error {
     // Returns:
     //   - error: wrapped failure.
     func MkdirTopic(cause error) error {
    -	return fmt.Errorf(desc.Text(text.DescKeyErrKbCliMkdirTopic), cause)
    +	return fmt.Errorf(desc.Text(text.DescKeyErrKbMkdirTopic), cause)
     }
     
     // ReadTopicTemplate wraps an embedded-template read failure.
    @@ -164,7 +164,7 @@ func MkdirTopic(cause error) error {
     //   - error: wrapped failure.
     func ReadTopicTemplate(cause error) error {
     	return fmt.Errorf(
    -		desc.Text(text.DescKeyErrKbCliReadTopicTemplate), cause,
    +		desc.Text(text.DescKeyErrKbReadTopicTemplate), cause,
     	)
     }
     
    @@ -177,6 +177,6 @@ func ReadTopicTemplate(cause error) error {
     //   - error: wrapped failure.
     func WriteTopicIndex(cause error) error {
     	return fmt.Errorf(
    -		desc.Text(text.DescKeyErrKbCliWriteTopicIndex), cause,
    +		desc.Text(text.DescKeyErrKbWriteTopicIndex), cause,
     	)
     }
    
    From 1a840553378ef55e240c08d0b14cf7272452a063 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 18:59:00 -0700
    Subject: [PATCH 05/10] docs(conventions): codify entity.Sentinel as the
     identity-sentinel shape
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Two surface-level docs described the old contract:
    
      - `.context/CONVENTIONS.md` Error Handling: only said "error
        constructors in internal/err"; nothing about sentinels.
      - `docs/home/contributing.md` Errors section: "(never custom
        error types)" — wrong both before and after this branch.
        `internal/err/context.NotFoundError` predates the sweep;
        `entity.Sentinel` postdates it.
    
    Both updated to spell out the rule: identity sentinels are
    `entity.Sentinel` typed-string consts, parameterised errors use
    typed structs (`NotFoundError` shape), and `var ErrX =
    errors.New("english")` is forbidden because the English leaks
    into `.Error()` output and bypasses localization.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/CONVENTIONS.md   | 16 ++++++++++++++++
     docs/home/contributing.md | 25 ++++++++++++++++++++++---
     2 files changed, 38 insertions(+), 3 deletions(-)
    
    diff --git a/.context/CONVENTIONS.md b/.context/CONVENTIONS.md
    index 6ae6f92ca..e386cab7a 100644
    --- a/.context/CONVENTIONS.md
    +++ b/.context/CONVENTIONS.md
    @@ -220,6 +220,22 @@ DO NOT UPDATE FOR:
     - **Error constructors in internal/err**: Never in per-package err.go
       files — eliminates the broken-window pattern where agents add local
       errors when they see a local err.go exists
    +- **Identity sentinels are `entity.Sentinel` consts, not
    +  `errors.New`**: Declare `errors.Is` targets as
    +  `const ErrX = entity.Sentinel(text.DescKey...)`. The
    +  user-facing text lives in `commands/text/errors.yaml` keyed by
    +  `err.<pkg>.<name>`; the sentinel's `Error()` resolves it via
    +  `desc.Text` at call time. Never write
    +  `var ErrX = errors.New("english")` — the English leaks into
    +  `.Error()` output and bypasses localization. Never add an
    +  `ErrMsg* = "english"` const layer in `internal/config/<pkg>/`
    +  to back the sentinel; that layer is dead text once the typed
    +  Sentinel does the lookup itself.
    +- **Parameterised errors use typed structs**: When the error
    +  needs to carry fields (path, name, etc.), define a struct in
    +  `internal/err/<area>/` with a pointer-receiver `Error()` and
    +  optional `Is(error) bool` for sentinel-compatibility. See
    +  `internal/err/context.NotFoundError` for the canonical shape.
     
     ## CLI Structure
     
    diff --git a/docs/home/contributing.md b/docs/home/contributing.md
    index 3591ba2f1..a139beac6 100644
    --- a/docs/home/contributing.md
    +++ b/docs/home/contributing.md
    @@ -203,9 +203,28 @@ cobra is initialized.
     #### Errors: `internal/err/`
     
     Domain-specific error constructors live under `internal/err/<domain>/`.
    -Each package mirrors the write structure. Functions return `error`
    -(never custom error types) and load messages from YAML via
    -`desc.Text(text.DescKey*)`.
    +Each package mirrors the write structure. Constructor functions return
    +`error` and load messages from YAML via `desc.Text(text.DescKey*)`.
    +
    +Identity sentinels (matched at the call site with `errors.Is`) are
    +declared as `entity.Sentinel` consts:
    +
    +```go
    +const ErrMissingFoo = entity.Sentinel(text.DescKeyErrPkgMissingFoo)
    +```
    +
    +`entity.Sentinel` is a typed string whose `Error()` resolves the key
    +through `desc.Text` at call time, so the user-facing text stays in
    +`commands/text/errors.yaml` and the sentinel value itself remains
    +pure identity. Never declare sentinels as `var ErrX = errors.New(...)`
    +with a hardcoded English string — that bypasses localization and
    +materializes the string before the embedded YAML lookup is populated.
    +
    +When a sentinel needs to carry fields (a path, a name), use a typed
    +struct in `internal/err/<domain>/` instead. See
    +`internal/err/context.NotFoundError` for the canonical pattern with
    +`Error()`, `Is(target error) bool`, and an `errors.As` consumer
    +contract.
     
     ```
     internal/err/add/add.go         # errors for ctx add
    
    From 46cc95be7ab6f92ae3d9e9a7823a9eb49467e254 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 19:15:09 -0700
    Subject: [PATCH 06/10] docs(kb): drop misleading "external" framing from
     ctx-kb-ground
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    The ground skill's input contract accepts URLs, in-tree paths,
    AND MCP resources, but every user-facing description called these
    "external sources" — leading a reader (the project creator
    included) to assume "external" meant "off-repo / network only."
    The misnomer obscured ground's actual contract.
    
    Renames (purely documentary; no contract change):
    
      - Skill ledes (`internal/assets/claude/skills/ctx-kb-ground/`,
        `integrations/copilot-cli/`, `integrations/opencode/`):
        drop "external" from titles, descriptions, and prose. The
        skill is now framed as a read-only freshness audit over
        *tracked sources* declared in `grounding-sources.md`,
        where tracked sources can live anywhere the kb cites them.
      - `docs/cli/kb.md`: the `ctx kb ground` table row drops
        "External grounding" in favor of "Read-only freshness
        audit over tracked sources …".
      - `docs/recipes/build-a-knowledge-base.md`: §Step 4 prose
        corrected from "the equivalent of a fresh fetch + re-extract
        pass" (which is wrong — ground does NOT re-extract) to
        "read-only on the kb's prose and evidence; annotates the
        ledger and flags drift for ingest." Table row updated.
      - `pass-mode: external-refresh` in the ground closeout
        frontmatter renamed to `pass-mode: refresh` (no consumers
        outside the two SKILL.md drafts; grep-confirmed safe).
    
    The phrase "external citations" survives in
    `docs/recipes/build-a-knowledge-base.md` §The Problem because
    that usage is idiomatic — citations to materials outside the
    kb's own authored prose — and distinct from the "external
    sources" overload that was misleading.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     docs/cli/kb.md                                |  2 +-
     docs/recipes/build-a-knowledge-base.md        | 15 ++++--
     .../claude/skills/ctx-kb-ground/SKILL.md      | 52 ++++++++++++-------
     .../copilot-cli/skills/ctx-kb-ground/SKILL.md | 51 +++++++++++-------
     .../opencode/skills/ctx-kb-ground/SKILL.md    | 33 +++++++-----
     5 files changed, 95 insertions(+), 58 deletions(-)
    
    diff --git a/docs/cli/kb.md b/docs/cli/kb.md
    index 9d86dba4d..a66ff4a1b 100644
    --- a/docs/cli/kb.md
    +++ b/docs/cli/kb.md
    @@ -25,7 +25,7 @@ ctx kb [subcommand]
     | `ctx kb ingest <folder\|paths>`  | Skill-driven     | Mode-aware editorial pass. CLI form refuses on empty input and points at the `/ctx-kb-ingest` skill. |
     | `ctx kb ask "<question>"`        | Skill-driven     | Q&A grounded in the kb. CLI form refuses on empty input and points at the `/ctx-kb-ask` skill.  |
     | `ctx kb site-review`             | Skill-driven     | Mechanical structural audit. Points at `/ctx-kb-site-review`.                       |
    -| `ctx kb ground`                  | Skill-driven     | External grounding via `grounding-sources.md`. Refuses when the file is empty.      |
    +| `ctx kb ground`                  | Skill-driven     | Read-only freshness audit over tracked sources listed in `grounding-sources.md` (URLs, in-tree paths, MCP resources). Refuses when the file is empty. |
     
     !!! note "Skill-driven vs real CLI"
         The mode skills (`ingest`, `ask`, `site-review`, `ground`)
    diff --git a/docs/recipes/build-a-knowledge-base.md b/docs/recipes/build-a-knowledge-base.md
    index f8389c136..2b9afa9bb 100644
    --- a/docs/recipes/build-a-knowledge-base.md
    +++ b/docs/recipes/build-a-knowledge-base.md
    @@ -53,7 +53,7 @@ ctx kb topic new "Cursor Hooks"         # scaffold a topic folder
     | `/ctx-kb-ingest`           | Skill   | Mode-aware editorial pass (topic-page/triage/evidence) |
     | `/ctx-kb-ask`              | Skill   | Q&A grounded in the kb                                 |
     | `/ctx-kb-site-review`      | Skill   | Mechanical structural audit                            |
    -| `/ctx-kb-ground`           | Skill   | Re-grounding against external sources                  |
    +| `/ctx-kb-ground`           | Skill   | Read-only freshness audit over the kb's tracked sources |
     | `/ctx-kb-note`             | Skill   | Capture a finding for the next ingest pass             |
     | `/ctx-wrap-up`             | Skill   | End-of-session ceremony; delegates to the handover step |
     
    @@ -178,9 +178,16 @@ which a future `/ctx-kb-ingest` pass can close.
     flags malformed closeout frontmatter, and **refuses to make
     judgment calls that require evidence** (those go through ingest).
     
    -`ground` reads `.context/ingest/grounding-sources.md` and runs
    -the equivalent of a fresh fetch + re-extract pass for each
    -listed source, useful when an upstream source has changed.
    +`ground` reads `.context/ingest/grounding-sources.md` — the kb's
    +persistent watch list — and walks each declared source (URL,
    +in-tree path, or MCP resource) to check whether it has drifted
    +since the kb last cited it. The pass is **read-only on the kb's
    +prose and evidence**: it annotates the source-coverage ledger's
    +`Residue` / `Next action` cells and writes a ground closeout, but
    +does NOT re-extract claims, mint `EV-###` rows, or touch topic
    +pages. Drifted or new-to-kb sources are flagged for a follow-up
    +`/ctx-kb-ingest`. Use ground for "are the docs still current?"
    +hygiene; use `/ctx-kb-ingest` to actually absorb new material.
     
     ## Step 5: Browse the KB Locally
     
    diff --git a/internal/assets/claude/skills/ctx-kb-ground/SKILL.md b/internal/assets/claude/skills/ctx-kb-ground/SKILL.md
    index acf1f3821..d33d0f065 100644
    --- a/internal/assets/claude/skills/ctx-kb-ground/SKILL.md
    +++ b/internal/assets/claude/skills/ctx-kb-ground/SKILL.md
    @@ -1,22 +1,33 @@
     ---
     name: ctx-kb-ground
    -description: External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip.
    +description: Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states.
     allowed-tools: Bash(ctx:*), Bash(ls:*), Read, Edit
     ---
     
    -# Ground the KB Against External Sources
    -
    -Refresh `.context/kb/` against the external sources the user has
    -declared in `.context/ingest/grounding-sources.md`. This is the
    -*"are we still current?"* pass. It does not mint new evidence by
    -itself, does not author topic pages, does not modify Confidence
    -bands. It walks the declared external sources, checks each for
    -drift against what the kb cites, and advances the source-coverage
    -ledger to reflect what the refresh found.
    -
    -If the refresh surfaces material the kb should absorb, this skill
    -flags it and recommends `/ctx-kb-ingest`. Authoring is ingest's
    -authority, not this skill's.
    +# Ground the KB Against Its Tracked Sources
    +
    +Walk the sources declared in `.context/ingest/grounding-sources.md`
    +and report whether the kb's claims are still current. This is the
    +*"are we still current?"* pass — a **read-only freshness audit**,
    +not a re-ingest.
    +
    +Each tracked source — **URLs, in-tree paths, or MCP resources** —
    +gets resolved and classified as `unchanged`, `drifted`, `gone`,
    +`freshness opaque`, or `new to kb`. The skill annotates the
    +source-coverage ledger's `Residue` and `Next action` cells and
    +writes a ground closeout summarising findings. It does **NOT**
    +mint `EV-###` rows, author topic-page prose, transition ledger
    +states, or modify Confidence bands; those are `/ctx-kb-ingest`'s
    +authority.
    +
    +If a tracked source drifted or is new to the kb, this skill flags
    +it and recommends a follow-up `/ctx-kb-ingest`. The declarative
    +watch list in `grounding-sources.md` is what makes this skill
    +distinct from ingest: it **persists across sessions** (ingest's
    +source list is per-invocation) and tracks sources from anywhere
    +the kb cites — public web, this repo's tree, behind an MCP
    +server. Distance from the repo is irrelevant; what matters is
    +that the kb depends on them for evidence.
     
     Authoritative background reading:
     `.context/ingest/KB-RULES.md` §Authority boundary and
    @@ -40,11 +51,12 @@ Authoritative background reading:
     
     ## Authority Boundary (vs Other Skills)
     
    -- **`/ctx-kb-ground`**: refreshes external sources listed in
    -  `grounding-sources.md`; advances source-coverage ledger rows
    -  for sources where the refresh changed state; writes a ground
    -  closeout. **May not** mint `EV-###` rows, author prose,
    -  modify a topic page, or change a Confidence band.
    +- **`/ctx-kb-ground`**: read-only freshness audit over the
    +  sources listed in `grounding-sources.md` (URLs, in-tree paths,
    +  MCP resources). Annotates the source-coverage ledger's
    +  `Residue` and `Next action` cells; writes a ground closeout.
    +  **May not** mint `EV-###` rows, author prose, modify a topic
    +  page, change a Confidence band, or transition ledger states.
     - **`/ctx-kb-ingest`**: handles anything this skill surfaces
       as new material to absorb.
     - **`/ctx-kb-ask`**: handles read-only questions about kb
    @@ -180,7 +192,7 @@ forcing them to remember the filename.
        sha: <short>
        branch: <name>
        mode: ground
    -   pass-mode: external-refresh
    +   pass-mode: refresh
        life-stage: <bootstrap|maintenance>
        generated-at: <RFC-3339>
        ---
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    index eb6987175..daa6bcb31 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    @@ -1,22 +1,32 @@
     ---
     name: ctx-kb-ground
    -description: "External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip."
    +description: "Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states."
     tools: [bash]
     ---
     
    -# Ground the KB Against External Sources
    -
    -Refresh `.context/kb/` against the external sources the user has
    -declared in `.context/ingest/grounding-sources.md`. This is the
    -*"are we still current?"* pass. It does not mint new evidence by
    -itself, does not author topic pages, does not modify Confidence
    -bands. It walks the declared external sources, checks each for
    -drift against what the kb cites, and advances the source-coverage
    -ledger to reflect what the refresh found.
    -
    -If the refresh surfaces material the kb should absorb, this skill
    -flags it and recommends `/ctx-kb-ingest`. Authoring is ingest's
    -authority, not this skill's.
    +# Ground the KB Against Its Tracked Sources
    +
    +Walk the sources declared in `.context/ingest/grounding-sources.md`
    +and report whether the kb's claims are still current. This is the
    +*"are we still current?"* pass — a **read-only freshness audit**,
    +not a re-ingest.
    +
    +Each tracked source — **URLs, in-tree paths, or MCP resources** —
    +gets resolved and classified as `unchanged`, `drifted`, `gone`,
    +`freshness opaque`, or `new to kb`. The skill annotates the
    +source-coverage ledger's `Residue` and `Next action` cells and
    +writes a ground closeout summarising findings. It does **NOT**
    +mint `EV-###` rows, author topic-page prose, transition ledger
    +states, or modify Confidence bands; those are `/ctx-kb-ingest`'s
    +authority.
    +
    +If a tracked source drifted or is new to the kb, this skill flags
    +it and recommends a follow-up `/ctx-kb-ingest`. The declarative
    +watch list in `grounding-sources.md` persists across sessions
    +(ingest's source list is per-invocation) and tracks sources from
    +anywhere the kb cites — public web, this repo's tree, behind an
    +MCP server. Distance from the repo is irrelevant; what matters is
    +that the kb depends on them for evidence.
     
     Authoritative background reading:
     `.context/ingest/KB-RULES.md` §Authority boundary and
    @@ -39,11 +49,12 @@ Authoritative background reading:
     
     ## Authority Boundary (vs Other Skills)
     
    -- **`/ctx-kb-ground`**: refreshes external sources listed in
    -  `grounding-sources.md`; advances source-coverage ledger rows
    -  for sources where the refresh changed state; writes a ground
    -  closeout. **May not** mint `EV-###` rows, author prose,
    -  modify a topic page, or change a Confidence band.
    +- **`/ctx-kb-ground`**: read-only freshness audit over the
    +  sources listed in `grounding-sources.md` (URLs, in-tree paths,
    +  MCP resources). Annotates the source-coverage ledger's
    +  `Residue` and `Next action` cells; writes a ground closeout.
    +  **May not** mint `EV-###` rows, author prose, modify a topic
    +  page, change a Confidence band, or transition ledger states.
     - **`/ctx-kb-ingest`**: handles anything this skill surfaces
       as new material to absorb.
     - **`/ctx-kb-ask`**: handles read-only questions about kb
    @@ -179,7 +190,7 @@ forcing them to remember the filename.
        sha: <short>
        branch: <name>
        mode: ground
    -   pass-mode: external-refresh
    +   pass-mode: refresh
        life-stage: <bootstrap|maintenance>
        generated-at: <RFC-3339>
        ---
    diff --git a/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md
    index 6c8c58099..07e18a8c6 100644
    --- a/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md
    +++ b/internal/assets/integrations/opencode/skills/ctx-kb-ground/SKILL.md
    @@ -1,27 +1,34 @@
     ---
     name: ctx-kb-ground
    -description: "External grounding pass for the kb. Reads grounding-sources.md, refreshes the listed sources, and advances source-coverage ledger rows accordingly. Writes a ground closeout for the audit trail. Prompts once if grounding-sources.md is empty; NONE on a line is a per-pass skip."
    +description: "Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states."
     ---
     
    -Refresh `.context/kb/` against the external sources the user
    -has declared in `.context/ingest/grounding-sources.md`. This is
    -the *"are we still current?"* pass. It does not mint new
    -evidence by itself, does not author topic pages, does not
    -modify Confidence bands. It walks the declared external
    -sources, checks each for drift against what the kb cites, and
    -advances the source-coverage ledger to reflect what the
    -refresh found.
    +Walk the sources declared in `.context/ingest/grounding-sources.md`
    +and report whether the kb's claims are still current. This is the
    +*"are we still current?"* pass — a read-only freshness audit, not
    +a re-ingest.
     
    -If the refresh surfaces material the kb should absorb, this
    -skill flags it and recommends `/ctx-kb-ingest`. Authoring is
    -ingest's authority, not this skill's.
    +Each tracked source — URLs, in-tree paths, or MCP resources —
    +gets resolved and classified as `unchanged`, `drifted`, `gone`,
    +`freshness opaque`, or `new to kb`. The skill annotates the
    +source-coverage ledger's `Residue` / `Next action` cells and
    +writes a ground closeout. It does NOT mint `EV-###` rows, author
    +topic-page prose, transition ledger states, or modify Confidence
    +bands; those are `/ctx-kb-ingest`'s authority.
    +
    +If a tracked source drifted or is new to the kb, flag it and
    +recommend a follow-up `/ctx-kb-ingest`. The declarative watch
    +list in `grounding-sources.md` persists across sessions and
    +tracks sources from anywhere the kb cites — the web, this repo's
    +tree, an MCP server. Distance from the repo is irrelevant; what
    +matters is that the kb depends on them for evidence.
     
     ## When to Use
     
     - The user says "re-ground the kb", "check upstream",
       "refresh sources".
     - A grounding cadence is hitting its scheduled boundary.
    -- A prior pass left a `Q-###` row that names "needs external
    +- A prior pass left a `Q-###` row that names "needs
       re-grounding".
     
     ## When NOT to Use
    
    From 4fb7442401f19806aade564c79478e003b2ef5bf Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 19:22:07 -0700
    Subject: [PATCH 07/10] docs(recipes,learnings): bootstrap-vs-steady-state
     framing for kb pipeline
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Two related additions:
    
    1. `docs/recipes/build-a-knowledge-base.md`: new H2 section
       "Bootstrap vs Steady State: Ingest First, Ground Later".
       The recipe walked Steps 1-6 linearly but never explained
       when to invoke ingest vs ground. The new section frames the
       distinction as *authority* (ingest writes, ground audits)
       and lays out the canonical lifecycle: bootstrap with ingest,
       curate `grounding-sources.md` by hand, run ground for
       steady-state hygiene. Closes with a rule-of-thumb table.
    
    2. `.context/LEARNINGS.md`: new entry "Creator confusion is the
       strongest doc-quality signal". When the project author trips
       over the project's own framing — as happened this session
       with "external sources" — that's a louder signal than any
       user-facing confusion. The action is "rewrite to match the
       contract," not "defend or explain." Concrete handler this
       session: the external-framing rewrite landed in 46cc95be.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/LEARNINGS.md                  | 40 ++++++++++++++++++++++++
     docs/recipes/build-a-knowledge-base.md | 43 ++++++++++++++++++++++++++
     2 files changed, 83 insertions(+)
    
    diff --git a/.context/LEARNINGS.md b/.context/LEARNINGS.md
    index deea03a67..a75c4b0ed 100644
    --- a/.context/LEARNINGS.md
    +++ b/.context/LEARNINGS.md
    @@ -17,6 +17,7 @@ DO NOT UPDATE FOR:
     <!-- INDEX:START -->
     | Date | Learning |
     |----|--------|
    +| 2026-05-17 | Creator confusion is the strongest doc-quality signal — louder than any user signal |
     | 2026-05-17 | Sentinel errors use typed zero-data structs with lazy `desc.Text()` — never Go string consts |
     | 2026-05-17 | `_helpers.go` / `_utils.go` filenames are project anti-pattern; use domain nouns |
     | 2026-05-17 | Subagent parallelism shines for mechanical refactor with a worked-example reference |
    @@ -147,6 +148,45 @@ DO NOT UPDATE FOR:
     
     ---
     
    +## [2026-05-17-200000] Creator confusion is the strongest doc-quality signal — louder than any user signal
    +
    +**Context**: In this session the project author asked *"why
    +external sources only? I can ground on a repo, a MCP query, a
    +markdown I dropped into ./inbox — are they also considered
    +'external'. Or is there a nomenclature confusion here?"* — and
    +explicitly noted *"it is confusing to the very creator of this
    +pipeline. -- and that's not a good sign."* Investigation
    +confirmed the input contract accepted in-tree paths and MCP
    +resources all along, but the SKILL.md ledes, the CLI docs table
    +row, and the recipe all framed ground as "external" — which
    +the creator's own mental model couldn't reconcile with the
    +contract.
    +
    +**Lesson**: A normal-user reading-confusion signal is "I don't
    +understand this." A creator reading-confusion signal is "this
    +contradicts what I built." The second is louder by an order
    +of magnitude — the creator has the full internal model and a
    +strong prior on what the system should say. If they trip over
    +the words, the words are wrong, full stop. Don't defend the
    +existing framing; don't explain what was meant. Rewrite to
    +match what the contract actually does. The creator was a
    +control instrument; if even that instrument deflected, the
    +docs are mis-anchoring everyone.
    +
    +**Application**: When the project's own creator asks a
    +"do we even need X?" or "wait, isn't X actually doing Y?"
    +question, treat it as a doc-bug report, not an architecture
    +question. Investigate the literal contract (input/output
    +shapes, code-level reality) before debating semantics. If the
    +contract is correct but the docs misframe it, the action is
    +"rewrite the framing across every doc surface that touches
    +it" — skills, recipes, CLI tables, anything user-facing.
    +Concrete instance handled this session: dropped "external"
    +from ctx-kb-ground's prose, description, pass-mode value,
    +recipe Step 4, and CLI table row across all three skill trees.
    +
    +---
    +
     ## [2026-05-17-180000] Sentinel errors use typed zero-data structs with lazy `desc.Text()` — never Go string consts
     
     **Context**: In a prior Phase KB session I invented an intermediate
    diff --git a/docs/recipes/build-a-knowledge-base.md b/docs/recipes/build-a-knowledge-base.md
    index 2b9afa9bb..fe76be641 100644
    --- a/docs/recipes/build-a-knowledge-base.md
    +++ b/docs/recipes/build-a-knowledge-base.md
    @@ -262,6 +262,49 @@ sources you supply
                       unfolded closeouts as the recall surface
     ```
     
    +## Bootstrap vs Steady State: Ingest First, Ground Later
    +
    +`/ctx-kb-ingest` and `/ctx-kb-ground` both read sources, which
    +makes their relationship easy to misread. The distinction is
    +**authority**, not input shape:
    +
    +- **Ingest writes.** It mints `EV-###` rows, authors topic-page
    +  prose, transitions source-coverage ledger states. The source
    +  list is per-invocation (CLI args, inline gestures).
    +- **Ground audits.** It walks a *persistent watch list* in
    +  `grounding-sources.md`, reports drift, annotates the ledger's
    +  `Residue` and `Next action` cells, and **never writes prose or
    +  evidence**. Drifted sources surface as flags pointing at
    +  `/ctx-kb-ingest`.
    +
    +This drives the canonical flow:
    +
    +**Bootstrap (pristine kb).** Use `/ctx-kb-ingest <sources>` to
    +absorb the first wave of material. Ground has nothing to compare
    +against in a pristine kb — `source-map.md` is empty, and
    +`grounding-sources.md` would just prompt for entries.
    +
    +**Curate the watch list.** Once the kb has content, edit
    +`grounding-sources.md` by hand to list the canonical sources the
    +kb's claims depend on — the load-bearing citations worth
    +checking for drift. Ground refuses to synthesise this list from
    +`source-map.md` by design; the watch list is a deliberate human
    +choice about what's worth tracking.
    +
    +**Steady state.** Ingest liberally as new material lands. Run
    +`/ctx-kb-ground` periodically — before a release, after a vendor
    +version bump, on whatever cadence fits — to detect drift on the
    +tracked subset. Drift surfaces as flags in the ground closeout
    +pointing at `/ctx-kb-ingest` for the actual write-side work.
    +
    +**Rule of thumb:**
    +
    +| Situation | Skill |
    +|---|---|
    +| *"I have new material I want absorbed."* | `/ctx-kb-ingest` |
    +| *"Are the sources the kb depends on still current?"* | `/ctx-kb-ground` |
    +| *"Is the kb's structure clean (capitalisation, frontmatter)?"* | `/ctx-kb-site-review` |
    +
     ## What the Editorial Pipeline Is NOT
     
     - **Not a substitute for `DECISIONS.md`.** Project-level
    
    From 1835e02ca7391fb8e78edcca8c3214ccbc02cde8 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 19:55:10 -0700
    Subject: [PATCH 08/10] =?UTF-8?q?refactor(flagbind):=20rename=20internal/f?=
     =?UTF-8?q?lag=5Fbind=20=E2=86=92=20internal/flagbind?=
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Go package names are lowercase with no underscores per
    Effective Go: *"They are lower case, with no under_scores or
    mixedCaps. They are often simple nouns, such as time, list,
    http."* The stdlib has only no-underscore examples (`strconv`,
    `httptest`, `bufio`). The package declaration was already
    `package flagbind` (correctly); only the directory and the
    import-path basename carried the underscore.
    
    Changes are mechanical:
    
      - `internal/flag_bind/` → `internal/flagbind/` (3 files).
      - 54 import-path rewrites across cli/, audit/, doc/,
        embed/flag/. The Go package name `flagbind` was already
        correct, so call sites (`flagbind.Bool(...)` etc.) did
        not change.
      - `.context/CONVENTIONS.md`: new bullet under §Naming
        codifying the rule — lowercase, no underscores, no
        mixedCaps for Go package names; filename underscores are
        fine (`foo_test.go`), package-name underscores are not.
    
    `make lint` reports 0 issues; full `make test` is green.
    
    Spec: specs/require-git.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/CONVENTIONS.md                      | 7 +++++++
     docs/recipes/activating-context.md           | 4 ++--
     internal/audit/desckey_namespace_test.go     | 2 +-
     internal/audit/flagbind_test.go              | 6 +++---
     internal/cli/add/core/build/build.go         | 2 +-
     internal/cli/agent/cmd/root/cmd.go           | 2 +-
     internal/cli/change/cmd/root/cmd.go          | 2 +-
     internal/cli/compact/cmd/root/cmd.go         | 2 +-
     internal/cli/connection/cmd/register/cmd.go  | 2 +-
     internal/cli/doctor/cmd/root/cmd.go          | 2 +-
     internal/cli/drift/cmd/root/cmd.go           | 2 +-
     internal/cli/event/cmd.go                    | 2 +-
     internal/cli/fmt/cmd/root/cmd.go             | 2 +-
     internal/cli/guide/cmd/root/cmd.go           | 2 +-
     internal/cli/handover/cmd/write/cmd.go       | 2 +-
     internal/cli/hub/cmd/start/cmd.go            | 2 +-
     internal/cli/hub/cmd/stop/cmd.go             | 2 +-
     internal/cli/initialize/cmd/root/cmd.go      | 2 +-
     internal/cli/journal/cmd/importer/cmd.go     | 2 +-
     internal/cli/journal/cmd/lock/cmd.go         | 2 +-
     internal/cli/journal/cmd/obsidian/cmd.go     | 2 +-
     internal/cli/journal/cmd/schema/check/cmd.go | 2 +-
     internal/cli/journal/cmd/site/cmd.go         | 2 +-
     internal/cli/journal/cmd/source/cmd.go       | 2 +-
     internal/cli/journal/cmd/unlock/cmd.go       | 2 +-
     internal/cli/load/cmd/root/cmd.go            | 2 +-
     internal/cli/loop/cmd/root/cmd.go            | 2 +-
     internal/cli/memory/cmd/importer/cmd.go      | 2 +-
     internal/cli/memory/cmd/publish/cmd.go       | 2 +-
     internal/cli/memory/cmd/sync/cmd.go          | 2 +-
     internal/cli/notify/notify.go                | 2 +-
     internal/cli/pad/cmd/add/cmd.go              | 2 +-
     internal/cli/pad/cmd/edit/cmd.go             | 2 +-
     internal/cli/pad/cmd/export/cmd.go           | 2 +-
     internal/cli/pad/cmd/merge/cmd.go            | 2 +-
     internal/cli/pad/cmd/root/cmd.go             | 2 +-
     internal/cli/pad/cmd/show/cmd.go             | 2 +-
     internal/cli/pad/cmd/tag/cmd.go              | 2 +-
     internal/cli/pad/pad.go                      | 2 +-
     internal/cli/prune/cmd.go                    | 2 +-
     internal/cli/remind/cmd/add/cmd.go           | 2 +-
     internal/cli/remind/cmd/dismiss/cmd.go       | 2 +-
     internal/cli/remind/remind.go                | 2 +-
     internal/cli/setup/cmd/root/cmd.go           | 2 +-
     internal/cli/site/cmd/feed/cmd.go            | 2 +-
     internal/cli/status/cmd/root/cmd.go          | 2 +-
     internal/cli/steering/cmd/synccmd/cmd.go     | 2 +-
     internal/cli/sync/cmd/root/cmd.go            | 2 +-
     internal/cli/system/cmd/bootstrap/cmd.go     | 2 +-
     internal/cli/system/cmd/session_event/cmd.go | 2 +-
     internal/cli/task/cmd/archive/cmd.go         | 2 +-
     internal/cli/trace/cmd/collect/cmd.go        | 2 +-
     internal/cli/trace/cmd/file/cmd.go           | 2 +-
     internal/cli/trace/cmd/show/cmd.go           | 2 +-
     internal/cli/trace/cmd/tag/cmd.go            | 2 +-
     internal/cli/trigger/cmd/test/cmd.go         | 2 +-
     internal/cli/usage/cmd.go                    | 2 +-
     internal/cli/watch/cmd/root/cmd.go           | 2 +-
     internal/config/embed/flag/doc.go            | 2 +-
     internal/{flag_bind => flagbind}/batch.go    | 0
     internal/{flag_bind => flagbind}/doc.go      | 0
     internal/{flag_bind => flagbind}/flag.go     | 0
     62 files changed, 68 insertions(+), 61 deletions(-)
     rename internal/{flag_bind => flagbind}/batch.go (100%)
     rename internal/{flag_bind => flagbind}/doc.go (100%)
     rename internal/{flag_bind => flagbind}/flag.go (100%)
    
    diff --git a/.context/CONVENTIONS.md b/.context/CONVENTIONS.md
    index e386cab7a..a5f383b0f 100644
    --- a/.context/CONVENTIONS.md
    +++ b/.context/CONVENTIONS.md
    @@ -23,6 +23,13 @@ DO NOT UPDATE FOR:
     - **Package name = folder name**: Go canonical pattern
       - `package initialize` in `initialize/` folder
       - Never `package initcmd` in `init/` folder
    +- **Go package names: lowercase, no underscores, no
    +  mixedCaps**: per the [Effective Go](https://go.dev/blog/package-names)
    +  guidance and the stdlib precedent (`strconv`, `httptest`,
    +  `bufio`). Apply to the directory too — `internal/flagbind/`,
    +  not `internal/flag_bind/`. Filenames may use underscores
    +  (`foo_test.go` is canonical); package names may not. When in
    +  doubt, find the closest stdlib analogue and copy its shape.
     - **Maps reference constants**: Use constants as keys, not literals
       - `map[string]X{ConstKey: value}` not `map[string]X{"literal": value}`
     
    diff --git a/docs/recipes/activating-context.md b/docs/recipes/activating-context.md
    index 9b7065acd..3ea05293a 100644
    --- a/docs/recipes/activating-context.md
    +++ b/docs/recipes/activating-context.md
    @@ -207,6 +207,6 @@ binds to it; every `ctx` subsystem reads the project from its parent.
     
     Nested projects, submodules, rogue agent-created `.context/`
     directories, and sub-agent sessions all produced silent misrouting
    -under the old walk-up model. `ctx` decided to stop guessing and
    -require the caller to declare. Every other decision flows from
    +under a walk-up model. For consistency, `ctx` does not guess, 
    +and  requires the caller to declare. Every other decision flows from
     there.
    diff --git a/internal/audit/desckey_namespace_test.go b/internal/audit/desckey_namespace_test.go
    index c3bf57da9..9dcdbf095 100644
    --- a/internal/audit/desckey_namespace_test.go
    +++ b/internal/audit/desckey_namespace_test.go
    @@ -119,7 +119,7 @@ func TestDescKeyOnlyInLookupCalls(t *testing.T) {
     		if strings.Contains(
     			pkg.PkgPath, "config/embed/",
     		) || strings.Contains(
    -			pkg.PkgPath, "flag_bind",
    +			pkg.PkgPath, "flagbind",
     		) {
     			continue
     		}
    diff --git a/internal/audit/flagbind_test.go b/internal/audit/flagbind_test.go
    index 9a7659e9c..f0979909c 100644
    --- a/internal/audit/flagbind_test.go
    +++ b/internal/audit/flagbind_test.go
    @@ -25,7 +25,7 @@ import (
     )
     
     // flagMethods lists cobra flag registration methods
    -// that must go through internal/flag_bind/.
    +// that must go through internal/flagbind/.
     var flagMethods = map[string]bool{
     	"StringVar":      true,
     	"StringVarP":     true,
    @@ -43,7 +43,7 @@ var flagMethods = map[string]bool{
     
     // TestNoFlagBindOutsideFlagbind ensures direct cobra
     // flag registration (.Flags().StringVar, etc.) only
    -// appears in internal/flag_bind/. All other packages
    +// appears in internal/flagbind/. All other packages
     // must use the flagbind helpers.
     //
     // Test files are exempt.
    @@ -54,7 +54,7 @@ func TestNoFlagBindOutsideFlagbind(t *testing.T) {
     	var violations []string
     
     	for _, pkg := range pkgs {
    -		if strings.Contains(pkg.PkgPath, "flag_bind") {
    +		if strings.Contains(pkg.PkgPath, "flagbind") {
     			continue
     		}
     
    diff --git a/internal/cli/add/core/build/build.go b/internal/cli/add/core/build/build.go
    index b093fcdc0..137e92df5 100644
    --- a/internal/cli/add/core/build/build.go
    +++ b/internal/cli/add/core/build/build.go
    @@ -15,7 +15,7 @@ import (
     	cfgEntry "github.com/ActiveMemory/ctx/internal/config/entry"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/entity"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd builds a cobra add subcommand for the given noun.
    diff --git a/internal/cli/agent/cmd/root/cmd.go b/internal/cli/agent/cmd/root/cmd.go
    index cd6177561..44099c2c5 100644
    --- a/internal/cli/agent/cmd/root/cmd.go
    +++ b/internal/cli/agent/cmd/root/cmd.go
    @@ -19,7 +19,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/fmt"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/rc"
     )
     
    diff --git a/internal/cli/change/cmd/root/cmd.go b/internal/cli/change/cmd/root/cmd.go
    index 4460246de..f028949e5 100644
    --- a/internal/cli/change/cmd/root/cmd.go
    +++ b/internal/cli/change/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the change command.
    diff --git a/internal/cli/compact/cmd/root/cmd.go b/internal/cli/compact/cmd/root/cmd.go
    index b9db3c530..bfd562737 100644
    --- a/internal/cli/compact/cmd/root/cmd.go
    +++ b/internal/cli/compact/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx compact" command for cleaning up context files.
    diff --git a/internal/cli/connection/cmd/register/cmd.go b/internal/cli/connection/cmd/register/cmd.go
    index f6e6a0e07..bc0bbd19d 100644
    --- a/internal/cli/connection/cmd/register/cmd.go
    +++ b/internal/cli/connection/cmd/register/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the connect register subcommand.
    diff --git a/internal/cli/doctor/cmd/root/cmd.go b/internal/cli/doctor/cmd/root/cmd.go
    index 11d70178b..a2658b247 100644
    --- a/internal/cli/doctor/cmd/root/cmd.go
    +++ b/internal/cli/doctor/cmd/root/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx doctor" command.
    diff --git a/internal/cli/drift/cmd/root/cmd.go b/internal/cli/drift/cmd/root/cmd.go
    index 53457202f..6b402534c 100644
    --- a/internal/cli/drift/cmd/root/cmd.go
    +++ b/internal/cli/drift/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx drift" command for detecting stale context.
    diff --git a/internal/cli/event/cmd.go b/internal/cli/event/cmd.go
    index 1c431e9bb..230f98787 100644
    --- a/internal/cli/event/cmd.go
    +++ b/internal/cli/event/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	eventCfg "github.com/ActiveMemory/ctx/internal/config/event"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx hook event" command.
    diff --git a/internal/cli/fmt/cmd/root/cmd.go b/internal/cli/fmt/cmd/root/cmd.go
    index 86b90fabe..ac8aa6272 100644
    --- a/internal/cli/fmt/cmd/root/cmd.go
    +++ b/internal/cli/fmt/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	embedCmd "github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/wrap"
     )
     
    diff --git a/internal/cli/guide/cmd/root/cmd.go b/internal/cli/guide/cmd/root/cmd.go
    index 15210f17d..4c792e03f 100644
    --- a/internal/cli/guide/cmd/root/cmd.go
    +++ b/internal/cli/guide/cmd/root/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx guide" cobra command.
    diff --git a/internal/cli/handover/cmd/write/cmd.go b/internal/cli/handover/cmd/write/cmd.go
    index 65031f36d..359a57e80 100644
    --- a/internal/cli/handover/cmd/write/cmd.go
    +++ b/internal/cli/handover/cmd/write/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the `ctx handover write <title>` command.
    diff --git a/internal/cli/hub/cmd/start/cmd.go b/internal/cli/hub/cmd/start/cmd.go
    index 934af5d40..7d1ba48e7 100644
    --- a/internal/cli/hub/cmd/start/cmd.go
    +++ b/internal/cli/hub/cmd/start/cmd.go
    @@ -15,7 +15,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the hub start subcommand.
    diff --git a/internal/cli/hub/cmd/stop/cmd.go b/internal/cli/hub/cmd/stop/cmd.go
    index 771d4695d..529b0be55 100644
    --- a/internal/cli/hub/cmd/stop/cmd.go
    +++ b/internal/cli/hub/cmd/stop/cmd.go
    @@ -15,7 +15,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the hub stop subcommand.
    diff --git a/internal/cli/initialize/cmd/root/cmd.go b/internal/cli/initialize/cmd/root/cmd.go
    index c4897a9a9..27b013abf 100644
    --- a/internal/cli/initialize/cmd/root/cmd.go
    +++ b/internal/cli/initialize/cmd/root/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx init" command for initializing a .context/ directory.
    diff --git a/internal/cli/journal/cmd/importer/cmd.go b/internal/cli/journal/cmd/importer/cmd.go
    index 66c1fd26e..825da6f60 100644
    --- a/internal/cli/journal/cmd/importer/cmd.go
    +++ b/internal/cli/journal/cmd/importer/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/entity"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the journal import subcommand.
    diff --git a/internal/cli/journal/cmd/lock/cmd.go b/internal/cli/journal/cmd/lock/cmd.go
    index 800a3557a..e0e8a1220 100644
    --- a/internal/cli/journal/cmd/lock/cmd.go
    +++ b/internal/cli/journal/cmd/lock/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx journal lock" subcommand.
    diff --git a/internal/cli/journal/cmd/obsidian/cmd.go b/internal/cli/journal/cmd/obsidian/cmd.go
    index be4e33dc7..cd9f4979d 100644
    --- a/internal/cli/journal/cmd/obsidian/cmd.go
    +++ b/internal/cli/journal/cmd/obsidian/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the journal obsidian subcommand.
    diff --git a/internal/cli/journal/cmd/schema/check/cmd.go b/internal/cli/journal/cmd/schema/check/cmd.go
    index 0bc2624fb..c8eb90f5a 100644
    --- a/internal/cli/journal/cmd/schema/check/cmd.go
    +++ b/internal/cli/journal/cmd/schema/check/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the journal schema check subcommand.
    diff --git a/internal/cli/journal/cmd/site/cmd.go b/internal/cli/journal/cmd/site/cmd.go
    index 2a8e4e67a..045421334 100644
    --- a/internal/cli/journal/cmd/site/cmd.go
    +++ b/internal/cli/journal/cmd/site/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the journal site subcommand.
    diff --git a/internal/cli/journal/cmd/source/cmd.go b/internal/cli/journal/cmd/source/cmd.go
    index 5c4deffdc..dce783c3d 100644
    --- a/internal/cli/journal/cmd/source/cmd.go
    +++ b/internal/cli/journal/cmd/source/cmd.go
    @@ -15,7 +15,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/journal"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the journal source subcommand.
    diff --git a/internal/cli/journal/cmd/unlock/cmd.go b/internal/cli/journal/cmd/unlock/cmd.go
    index 022d20941..979546633 100644
    --- a/internal/cli/journal/cmd/unlock/cmd.go
    +++ b/internal/cli/journal/cmd/unlock/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx journal unlock" subcommand.
    diff --git a/internal/cli/load/cmd/root/cmd.go b/internal/cli/load/cmd/root/cmd.go
    index 9e4b788c2..83421d5b8 100644
    --- a/internal/cli/load/cmd/root/cmd.go
    +++ b/internal/cli/load/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/rc"
     )
     
    diff --git a/internal/cli/loop/cmd/root/cmd.go b/internal/cli/loop/cmd/root/cmd.go
    index bc78c22b7..a7a500a99 100644
    --- a/internal/cli/loop/cmd/root/cmd.go
    +++ b/internal/cli/loop/cmd/root/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/loop"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx loop" command for generating Ralph loop scripts.
    diff --git a/internal/cli/memory/cmd/importer/cmd.go b/internal/cli/memory/cmd/importer/cmd.go
    index 0cbfcfa72..5e56429b9 100644
    --- a/internal/cli/memory/cmd/importer/cmd.go
    +++ b/internal/cli/memory/cmd/importer/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the memory import subcommand.
    diff --git a/internal/cli/memory/cmd/publish/cmd.go b/internal/cli/memory/cmd/publish/cmd.go
    index 998fba772..345f5eefb 100644
    --- a/internal/cli/memory/cmd/publish/cmd.go
    +++ b/internal/cli/memory/cmd/publish/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/memory"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the memory publish subcommand.
    diff --git a/internal/cli/memory/cmd/sync/cmd.go b/internal/cli/memory/cmd/sync/cmd.go
    index 45522858a..1ad142405 100644
    --- a/internal/cli/memory/cmd/sync/cmd.go
    +++ b/internal/cli/memory/cmd/sync/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the memory sync subcommand.
    diff --git a/internal/cli/notify/notify.go b/internal/cli/notify/notify.go
    index c196c750e..53dc326f2 100644
    --- a/internal/cli/notify/notify.go
    +++ b/internal/cli/notify/notify.go
    @@ -20,7 +20,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	"github.com/ActiveMemory/ctx/internal/entity"
     	errCli "github.com/ActiveMemory/ctx/internal/err/cli"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	iNotify "github.com/ActiveMemory/ctx/internal/notify"
     )
     
    diff --git a/internal/cli/pad/cmd/add/cmd.go b/internal/cli/pad/cmd/add/cmd.go
    index 625553422..3f31368ea 100644
    --- a/internal/cli/pad/cmd/add/cmd.go
    +++ b/internal/cli/pad/cmd/add/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad add subcommand.
    diff --git a/internal/cli/pad/cmd/edit/cmd.go b/internal/cli/pad/cmd/edit/cmd.go
    index 456241bae..356a5abc7 100644
    --- a/internal/cli/pad/cmd/edit/cmd.go
    +++ b/internal/cli/pad/cmd/edit/cmd.go
    @@ -18,7 +18,7 @@ import (
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/pad"
     	errPad "github.com/ActiveMemory/ctx/internal/err/pad"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad edit subcommand.
    diff --git a/internal/cli/pad/cmd/export/cmd.go b/internal/cli/pad/cmd/export/cmd.go
    index c418a083d..29639f064 100644
    --- a/internal/cli/pad/cmd/export/cmd.go
    +++ b/internal/cli/pad/cmd/export/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/token"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad export subcommand.
    diff --git a/internal/cli/pad/cmd/merge/cmd.go b/internal/cli/pad/cmd/merge/cmd.go
    index 9280c83a0..397b187d9 100644
    --- a/internal/cli/pad/cmd/merge/cmd.go
    +++ b/internal/cli/pad/cmd/merge/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad merge subcommand.
    diff --git a/internal/cli/pad/cmd/root/cmd.go b/internal/cli/pad/cmd/root/cmd.go
    index 906f88f23..4f412489c 100644
    --- a/internal/cli/pad/cmd/root/cmd.go
    +++ b/internal/cli/pad/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad import subcommand.
    diff --git a/internal/cli/pad/cmd/show/cmd.go b/internal/cli/pad/cmd/show/cmd.go
    index b1fe87d13..2eea8dbcc 100644
    --- a/internal/cli/pad/cmd/show/cmd.go
    +++ b/internal/cli/pad/cmd/show/cmd.go
    @@ -16,7 +16,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	errPad "github.com/ActiveMemory/ctx/internal/err/pad"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad show subcommand.
    diff --git a/internal/cli/pad/cmd/tag/cmd.go b/internal/cli/pad/cmd/tag/cmd.go
    index ab20ff3ad..e2163561c 100644
    --- a/internal/cli/pad/cmd/tag/cmd.go
    +++ b/internal/cli/pad/cmd/tag/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the pad tag subcommand.
    diff --git a/internal/cli/pad/pad.go b/internal/cli/pad/pad.go
    index b38410f13..d770d57c3 100644
    --- a/internal/cli/pad/pad.go
    +++ b/internal/cli/pad/pad.go
    @@ -30,7 +30,7 @@ import (
     	embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/write/pad"
     )
     
    diff --git a/internal/cli/prune/cmd.go b/internal/cli/prune/cmd.go
    index 4e6463bc3..70d13ba16 100644
    --- a/internal/cli/prune/cmd.go
    +++ b/internal/cli/prune/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/runtime"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx prune" top-level command.
    diff --git a/internal/cli/remind/cmd/add/cmd.go b/internal/cli/remind/cmd/add/cmd.go
    index 47561e236..02cd44ad1 100644
    --- a/internal/cli/remind/cmd/add/cmd.go
    +++ b/internal/cli/remind/cmd/add/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the remind add subcommand.
    diff --git a/internal/cli/remind/cmd/dismiss/cmd.go b/internal/cli/remind/cmd/dismiss/cmd.go
    index 690332d59..322ab6a9e 100644
    --- a/internal/cli/remind/cmd/dismiss/cmd.go
    +++ b/internal/cli/remind/cmd/dismiss/cmd.go
    @@ -15,7 +15,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	errReminder "github.com/ActiveMemory/ctx/internal/err/reminder"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the remind dismiss subcommand.
    diff --git a/internal/cli/remind/remind.go b/internal/cli/remind/remind.go
    index de59887ef..c1eda6bc8 100644
    --- a/internal/cli/remind/remind.go
    +++ b/internal/cli/remind/remind.go
    @@ -17,7 +17,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the remind command with subcommands.
    diff --git a/internal/cli/setup/cmd/root/cmd.go b/internal/cli/setup/cmd/root/cmd.go
    index 08c94b717..56fd2de1f 100644
    --- a/internal/cli/setup/cmd/root/cmd.go
    +++ b/internal/cli/setup/cmd/root/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx setup" command for generating AI tool integrations.
    diff --git a/internal/cli/site/cmd/feed/cmd.go b/internal/cli/site/cmd/feed/cmd.go
    index 023c799b8..5b9d574ad 100644
    --- a/internal/cli/site/cmd/feed/cmd.go
    +++ b/internal/cli/site/cmd/feed/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/rss"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx site feed" subcommand.
    diff --git a/internal/cli/status/cmd/root/cmd.go b/internal/cli/status/cmd/root/cmd.go
    index 712a81591..32276d5e4 100644
    --- a/internal/cli/status/cmd/root/cmd.go
    +++ b/internal/cli/status/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the status command.
    diff --git a/internal/cli/steering/cmd/synccmd/cmd.go b/internal/cli/steering/cmd/synccmd/cmd.go
    index edd26f73b..2487973f6 100644
    --- a/internal/cli/steering/cmd/synccmd/cmd.go
    +++ b/internal/cli/steering/cmd/synccmd/cmd.go
    @@ -17,7 +17,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	errSteering "github.com/ActiveMemory/ctx/internal/err/steering"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/rc"
     	"github.com/ActiveMemory/ctx/internal/steering"
     )
    diff --git a/internal/cli/sync/cmd/root/cmd.go b/internal/cli/sync/cmd/root/cmd.go
    index e7865b264..d2f3369c3 100644
    --- a/internal/cli/sync/cmd/root/cmd.go
    +++ b/internal/cli/sync/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx sync" command for reconciling context with the codebase.
    diff --git a/internal/cli/system/cmd/bootstrap/cmd.go b/internal/cli/system/cmd/bootstrap/cmd.go
    index 149752eb4..fb6853bb0 100644
    --- a/internal/cli/system/cmd/bootstrap/cmd.go
    +++ b/internal/cli/system/cmd/bootstrap/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx system bootstrap" hidden command.
    diff --git a/internal/cli/system/cmd/session_event/cmd.go b/internal/cli/system/cmd/session_event/cmd.go
    index 4beccba15..6e9bed79f 100644
    --- a/internal/cli/system/cmd/session_event/cmd.go
    +++ b/internal/cli/system/cmd/session_event/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	embFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx system session-event" subcommand.
    diff --git a/internal/cli/task/cmd/archive/cmd.go b/internal/cli/task/cmd/archive/cmd.go
    index 88c523c2b..ba53f207c 100644
    --- a/internal/cli/task/cmd/archive/cmd.go
    +++ b/internal/cli/task/cmd/archive/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the `task archive` subcommand.
    diff --git a/internal/cli/trace/cmd/collect/cmd.go b/internal/cli/trace/cmd/collect/cmd.go
    index 7540716c9..06fb1b48a 100644
    --- a/internal/cli/trace/cmd/collect/cmd.go
    +++ b/internal/cli/trace/cmd/collect/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the trace collect subcommand.
    diff --git a/internal/cli/trace/cmd/file/cmd.go b/internal/cli/trace/cmd/file/cmd.go
    index 812b08116..1ede2443d 100644
    --- a/internal/cli/trace/cmd/file/cmd.go
    +++ b/internal/cli/trace/cmd/file/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	cfgTrace "github.com/ActiveMemory/ctx/internal/config/trace"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the trace file subcommand.
    diff --git a/internal/cli/trace/cmd/show/cmd.go b/internal/cli/trace/cmd/show/cmd.go
    index bb354d5f1..3a14677bb 100644
    --- a/internal/cli/trace/cmd/show/cmd.go
    +++ b/internal/cli/trace/cmd/show/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the trace command.
    diff --git a/internal/cli/trace/cmd/tag/cmd.go b/internal/cli/trace/cmd/tag/cmd.go
    index c68b8c241..f74c2de26 100644
    --- a/internal/cli/trace/cmd/tag/cmd.go
    +++ b/internal/cli/trace/cmd/tag/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the trace tag subcommand.
    diff --git a/internal/cli/trigger/cmd/test/cmd.go b/internal/cli/trigger/cmd/test/cmd.go
    index abae8ff1f..d031279ea 100644
    --- a/internal/cli/trigger/cmd/test/cmd.go
    +++ b/internal/cli/trigger/cmd/test/cmd.go
    @@ -20,7 +20,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	cfgTrigger "github.com/ActiveMemory/ctx/internal/config/trigger"
     	errTrigger "github.com/ActiveMemory/ctx/internal/err/trigger"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     	"github.com/ActiveMemory/ctx/internal/rc"
     	"github.com/ActiveMemory/ctx/internal/trigger"
     	writeTrigger "github.com/ActiveMemory/ctx/internal/write/trigger"
    diff --git a/internal/cli/usage/cmd.go b/internal/cli/usage/cmd.go
    index 0f01b4d58..876c73024 100644
    --- a/internal/cli/usage/cmd.go
    +++ b/internal/cli/usage/cmd.go
    @@ -14,7 +14,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	cfgStats "github.com/ActiveMemory/ctx/internal/config/stats"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the "ctx usage" top-level command.
    diff --git a/internal/cli/watch/cmd/root/cmd.go b/internal/cli/watch/cmd/root/cmd.go
    index aa385965a..254af31f6 100644
    --- a/internal/cli/watch/cmd/root/cmd.go
    +++ b/internal/cli/watch/cmd/root/cmd.go
    @@ -13,7 +13,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     	"github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
    -	"github.com/ActiveMemory/ctx/internal/flag_bind"
    +	"github.com/ActiveMemory/ctx/internal/flagbind"
     )
     
     // Cmd returns the watch command.
    diff --git a/internal/config/embed/flag/doc.go b/internal/config/embed/flag/doc.go
    index 3c41ce11b..e4b59ca39 100644
    --- a/internal/config/embed/flag/doc.go
    +++ b/internal/config/embed/flag/doc.go
    @@ -46,6 +46,6 @@
     //	c.Flags().Bool("dry-run", false, desc.Flag(flag.DescKeyAddDryRun))
     //
     // In practice, most flag binding goes through
    -// [internal/flag_bind] which already knows how to look up
    +// [internal/flagbind] which already knows how to look up
     // the desc key, so callers rarely call `desc.Flag` directly.
     package flag
    diff --git a/internal/flag_bind/batch.go b/internal/flagbind/batch.go
    similarity index 100%
    rename from internal/flag_bind/batch.go
    rename to internal/flagbind/batch.go
    diff --git a/internal/flag_bind/doc.go b/internal/flagbind/doc.go
    similarity index 100%
    rename from internal/flag_bind/doc.go
    rename to internal/flagbind/doc.go
    diff --git a/internal/flag_bind/flag.go b/internal/flagbind/flag.go
    similarity index 100%
    rename from internal/flag_bind/flag.go
    rename to internal/flagbind/flag.go
    
    From 42491a1b58288e06c0eaefee3c2a8eb2173a9398 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 19:55:27 -0700
    Subject: [PATCH 09/10] sync(copilot-cli): regenerate skills from canonical
     claude tree
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    `make test` runs the copilot-skills freshness check, which
    invokes `hack/sync-copilot-skills.sh` against the actual skills
    directory. Earlier commits on this branch updated canonical
    claude SKILLs without re-running the sync; this commit captures
    the pending drift.
    
    The sync strips `allowed-tools:` (claude-only), adds
    `tools: [bash]` (copilot-cli surface), unquotes the
    `description:` string, and mirrors prose changes from the
    canonical tree.
    
    Eight skills updated:
    
      - ctx-handover: lede now references KB-RULES.md §Four
        inviolable rules; adds the authoritative-background-reading
        pointer.
      - ctx-kb-ask, ctx-kb-ingest, ctx-kb-note,
        ctx-kb-site-review, ctx-remember, ctx-wrap-up: minor
        canonical drift (prose alignment, frontmatter formatting).
      - ctx-kb-ground: matches the "drop external" rewrite from
        46cc95be (frontmatter requoting + tools-key strip from the
        sync side; lede prose was already aligned).
    
    No content authored here — this is mechanical sync output.
    
    Spec: specs/kb-editorial-pipeline.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .../copilot-cli/skills/ctx-handover/SKILL.md  | 98 +++++++++++--------
     .../copilot-cli/skills/ctx-kb-ask/SKILL.md    |  5 +-
     .../copilot-cli/skills/ctx-kb-ground/SKILL.md | 15 +--
     .../copilot-cli/skills/ctx-kb-ingest/SKILL.md | 10 +-
     .../copilot-cli/skills/ctx-kb-note/SKILL.md   | 18 ++--
     .../skills/ctx-kb-site-review/SKILL.md        |  6 +-
     .../copilot-cli/skills/ctx-remember/SKILL.md  |  6 +-
     .../copilot-cli/skills/ctx-wrap-up/SKILL.md   | 25 ++---
     8 files changed, 98 insertions(+), 85 deletions(-)
    
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md
    index 4e6cfe171..2bc7c835f 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-handover/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-handover
    -description: "Per-session handover artifact writer. Wraps `ctx handover write` with `--summary` and `--next` (both required, both validated non-placeholder by the CLI). Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them."
    -tools: [bash]
    +description: Per-session handover artifact writer. Wraps `ctx handover write` with `--summary` and `--next` (both required, both validated non-placeholder by the CLI). Always invoked as the final step of `/ctx-wrap-up`; not the user-facing trigger. When `.context/kb/` exists, also folds postdated closeouts into the handover and archives them.
     ---
     
     # Write a Handover
    @@ -11,10 +10,15 @@ fresh agent, a different operator, a cold restart the next
     morning) can resume without re-deriving context probabilistically
     from canonical files plus journal.
     
    -This skill is the **sole authoritative recall artifact** writer.
    -`SESSION_LOG.md` entries, closeouts, and journal entries are
    -mid-flight surfaces; the handover is what `/ctx-remember` reads
    -on session start.
    +This skill is the **sole authoritative recall artifact** writer
    +(per `KB-RULES.md` §Four inviolable rules: *"the handover is
    +the sole authoritative recall artifact"*). `SESSION_LOG.md`
    +entries, closeouts, and journal entries are mid-flight surfaces;
    +the handover is what `/ctx-remember` reads on session start.
    +
    +Authoritative background reading:
    +`.context/ingest/KB-RULES.md` §Four inviolable rules;
    +`specs/kb-editorial-pipeline.md` §Interface.
     
     ## When to Use
     
    @@ -37,8 +41,8 @@ step. Do not advertise this skill as a direct user trigger.
       session has zero context.
     - The user already ran `/ctx-handover` recently in this session
       and nothing has changed since.
    -- The user invokes a capture skill (`/ctx-add-task`,
    -  `/ctx-add-decision`, etc.); those write to canonical files,
    +- The user invokes a capture skill (`/ctx-task-add`,
    +  `/ctx-decision-add`, etc.); those write to canonical files,
       not to a handover artifact.
     
     ## Authority Boundary (vs Other Skills)
    @@ -46,7 +50,7 @@ step. Do not advertise this skill as a direct user trigger.
     - **`/ctx-handover`**: writes
       `.context/handovers/<TS>-<slug>.md`; folds postdated
       closeouts from `.context/ingest/closeouts/` into the
    -  handover's `## Folded Closeouts` section; archives folded
    +  handover's `## Folded closeouts` section; archives folded
       closeouts to `.context/archive/closeouts/`. Single writer of
       this artifact.
     - **`/ctx-wrap-up`**: owns the user-facing session-end
    @@ -56,8 +60,8 @@ step. Do not advertise this skill as a direct user trigger.
     - **`/ctx-remember`**: reads the latest handover plus any
       closeouts whose `generated-at` postdates the handover; the
       read-side counterpart to this skill's write surface.
    -- **Capture skills** (`/ctx-add-task`, `/ctx-add-decision`,
    -  `/ctx-add-learning`, `/ctx-add-convention`): write to the
    +- **Capture skills** (`/ctx-task-add`, `/ctx-decision-add`,
    +  `/ctx-learning-add`, `/ctx-convention-add`): write to the
       five canonical files. This skill never modifies those files;
       the handover narrative *references* them, it does not author
       them.
    @@ -65,7 +69,7 @@ step. Do not advertise this skill as a direct user trigger.
     ## Usage Examples
     
     ```text
    -/ctx-handover "Cursor Hooks deep dive"
    +/ctx-handover "kb editorial pipeline phase KB skills drafted"
     /ctx-handover "rev2 spec landed; tomorrow start the writer package"
     /ctx-handover "research session on cursor hooks"
     /ctx-handover --no-fold "mid-session checkpoint before lunch"
    @@ -74,9 +78,10 @@ step. Do not advertise this skill as a direct user trigger.
     ## Input Contract
     
     The skill wraps `ctx handover write`, which enforces required
    -flags via `MarkFlagRequired` and rejects placeholder bodies.
    -Empty `TBD`, `see chat`, whitespace-only values are rejected by
    -the CLI, not just by the skill text.
    +flags via `MarkFlagRequired` and rejects placeholder bodies via
    +the Phase SK validation pattern. Empty `TBD`, `see chat`,
    +whitespace-only values are rejected by the CLI, not just by the
    +skill text.
     
     | Flag | Type | Default | Description |
     |------|------|---------|-------------|
    @@ -153,16 +158,20 @@ conditional on the directory's existence (see §Process).
        ```
     
        The CLI:
    -   - Validates flags (rejects placeholder values).
    -   - Resolves git HEAD (honors `CTX_TASK_COMMIT` and
    -     `GITHUB_SHA` for CI replay).
    -   - Reads the latest-handover cursor to find the postdated
    +   - Validates flags (placeholder rejection per Phase SK).
    +   - Resolves git HEAD via `gitmeta.ResolveHead` (honors
    +     `CTX_TASK_COMMIT` and `GITHUB_SHA` for CI replay).
    +   - Reads `LatestHandoverCursor` to find the postdated
          closeout window.
    +   - Lists `UnconsumedCloseouts` (closeouts whose
    +     `generated-at` postdates the cursor).
        - For each unconsumed closeout, folds its body into the
    -     handover's `## Folded Closeouts` section. Malformed
    -     closeouts are skipped with a warning.
    -   - Moves folded closeouts to `.context/archive/closeouts/`.
    -     Archived closeouts are immutable.
    +     handover's `## Folded closeouts` section. Malformed
    +     closeouts (missing `generated-at`, malformed frontmatter)
    +     are skipped with a warning.
    +   - Calls `ArchiveCloseouts` to move folded closeouts to
    +     `.context/archive/closeouts/`. Archived closeouts are
    +     immutable.
        - Writes `.context/handovers/<TS>-<slug>.md`.
     
        When `--no-fold` is set, the fold + archive steps are
    @@ -175,31 +184,33 @@ conditional on the directory's existence (see §Process).
        - Count of closeouts folded (or *"none postdated the prior
          handover"*).
        - Count of malformed closeouts skipped (with filenames so
    -     the user can fix or delete).
    +     the user can fix or delete; site-review's job to flag,
    +     but the warning here is opportunistic).
        - Any CLI validation failures (with the placeholder text
          that triggered rejection).
     
     ## Closeout Fold Mechanics
     
    -When `.context/kb/` is present, the editorial pipeline
    -(`/ctx-kb-*`) writes per-pass closeouts under
    -`.context/ingest/closeouts/`. The handover fold is how those
    -closeouts are tied back to a session boundary:
    +The fold mechanism is the integration point between the
    +editorial pipeline (`/ctx-kb-*` closeouts) and session continuity
    +(handover artifacts). Mechanically:
     
    -- The latest-handover cursor is the `generated-at` of the
    -  newest handover (or zero time if none exists).
    -- Every closeout whose `generated-at` postdates the cursor is
    -  folded.
    +- `LatestHandoverCursor` reads `.context/handovers/` and returns
    +  the `generated-at` of the newest handover (or zero time if
    +  none exists).
    +- `UnconsumedCloseouts` walks `.context/ingest/closeouts/` and
    +  returns every closeout whose `generated-at` postdates the
    +  cursor.
     - Each folded closeout's body is embedded under
    -  `## Folded Closeouts` in the new handover, in
    -  `generated-at` order. The frontmatter is preserved
    -  verbatim so the audit trail survives the fold.
    -- After the fold, source closeouts are moved to
    +  `## Folded closeouts` in the new handover, in `generated-at`
    +  order. The frontmatter is preserved verbatim so the audit
    +  trail survives the fold.
    +- After the fold, `ArchiveCloseouts` moves the source files to
       `.context/archive/closeouts/`. Archived closeouts are
       immutable; subsequent passes never re-fold them.
     
     A handover with no postdated closeouts to fold writes a
    -`## Folded Closeouts` section with the body *"none"*; never
    +`## Folded closeouts` section with the body *"none"*; never
     omit the section, so the audit trail is explicit.
     
     ## Edge Cases
    @@ -209,14 +220,15 @@ omit the section, so the audit trail is explicit.
     | `.context/` missing | Refuse; suggest `ctx init`. No residue. |
     | `.context/handovers/` missing | Refuse; suggest `ctx init --upgrade`. No residue. |
     | Empty `--summary` or `--next` | The CLI rejects with the placeholder-rejection message; surface verbatim. |
    -| Placeholder values (`TBD`, `see chat`, whitespace-only) | The CLI rejects; surface verbatim and ask the user to redraft. |
    -| No postdated closeouts to fold | Write the handover with `## Folded Closeouts` body *"none"*. Never omit the section. |
    -| Postdated closeout has malformed frontmatter | The CLI skips the file with a warning naming it. Report the warning so the user can fix or delete. |
    +| Placeholder values (`TBD`, `see chat`, whitespace-only) for `--summary` or `--next` | The CLI rejects; surface verbatim and ask the user to redraft. |
    +| No postdated closeouts to fold | Write the handover with `## Folded closeouts` body *"none"*. Never omit the section. |
    +| Postdated closeout has malformed frontmatter | The CLI skips the file with a warning naming it. Report the warning to the user so they can fix or delete. |
     | `--no-fold` set | Skip the fold + archive steps; the handover stands alone; closeouts stay in `.context/ingest/closeouts/` for the next invocation. |
     | Mid-session re-invocation | Each invocation writes a new handover file. The newest one is what `/ctx-remember` reads next session. Multiple per session are fine. |
     | Session aborted before wrap-up | Closeouts stay in place; next session's `/ctx-remember` reads canonical files + the last handover + any postdated unfolded closeouts. Editorial work survives. |
    -| `gitmeta.ResolveHead` returns an error (no git, detached HEAD with no fallback) | The CLI surfaces a typed error; relay verbatim. The recovery path is to add a git repo, not to invent one here. |
    -| `CTX_TASK_COMMIT` or `GITHUB_SHA` set | Honored for the Provenance line per the resolver's precedence rules; no special handling here. |
    +| User runs `/ctx-wrap-up` without `.context/kb/` present | `/ctx-wrap-up` still drives `/ctx-handover` as its final step; kb-presence affects what gets folded, not whether the handover is written. |
    +| `gitmeta.ResolveHead` returns an error (no git, detached HEAD with no fallback) | The CLI surfaces the typed `MissingGitError`; relay verbatim. Phase RG owns the recovery path; this skill does not invent one. |
    +| `CTX_TASK_COMMIT` or `GITHUB_SHA` set | Honoured for the Provenance line per `gitmeta.ResolveHead`'s precedence rules; no special handling here. |
     
     ## Anti-Patterns
     
    @@ -232,7 +244,7 @@ omit the section, so the audit trail is explicit.
     - Hand-writing a handover file. The CLI is the sole writer.
       Hand-edits drift from the schema the read side expects.
     - Modifying an archived closeout. Archived closeouts are
    -  immutable.
    +  immutable per `KB-RULES.md` §Closeout shape.
     - Inventing `--highlights` or `--open-questions` content the
       session did not actually produce. Light compression for
       clarity is allowed; new facts are not.
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md
    index ce28465f0..0fd2f001f 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ask/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-kb-ask
    -description: "Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail."
    -tools: [bash]
    +description: Q&A grounded in the existing kb. Read-only on prose; refuses to web-jump; if the kb cannot answer, opens a Q-### row in outstanding-questions.md and reports the gap. Writes an ask closeout for the audit trail.
     ---
     
     # Ask the KB
    @@ -17,7 +16,7 @@ this skill is read-only on prose.
     
     Authoritative background reading:
     `.context/ingest/KB-RULES.md` §Authority boundary and
    -§Evidence discipline.
    +§Evidence discipline; `specs/kb-editorial-pipeline.md` §Interface.
     
     ## When to Use
     
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    index daa6bcb31..869ca9750 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ground/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-kb-ground
    -description: "Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states."
    -tools: [bash]
    +description: Read-only freshness audit over the kb's tracked sources (URLs, in-tree paths, MCP resources) declared in grounding-sources.md. Classifies each source's drift state, annotates the source-coverage ledger, and writes a ground closeout; flags drifted or new-to-kb sources for /ctx-kb-ingest. Never mints evidence, authors prose, or transitions ledger states.
     ---
     
     # Ground the KB Against Its Tracked Sources
    @@ -22,15 +21,17 @@ authority.
     
     If a tracked source drifted or is new to the kb, this skill flags
     it and recommends a follow-up `/ctx-kb-ingest`. The declarative
    -watch list in `grounding-sources.md` persists across sessions
    -(ingest's source list is per-invocation) and tracks sources from
    -anywhere the kb cites — public web, this repo's tree, behind an
    -MCP server. Distance from the repo is irrelevant; what matters is
    +watch list in `grounding-sources.md` is what makes this skill
    +distinct from ingest: it **persists across sessions** (ingest's
    +source list is per-invocation) and tracks sources from anywhere
    +the kb cites — public web, this repo's tree, behind an MCP
    +server. Distance from the repo is irrelevant; what matters is
     that the kb depends on them for evidence.
     
     Authoritative background reading:
     `.context/ingest/KB-RULES.md` §Authority boundary and
    -§Source-coverage ledger.
    +§Source-coverage ledger; `specs/kb-editorial-pipeline.md`
    +§Interface and §Edge Cases.
     
     ## When to Use
     
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md
    index 1f22f8c0c..4c6e4d9d5 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-ingest/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-kb-ingest
    -description: "Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail."
    -tools: [bash]
    +description: Editorial knowledge-ingestion pass. Reads sources the user supplies, declares its pass-mode (topic-page / triage / evidence-only) before extraction, and is held to mode-specific completion semantics. The topic page is the deliverable; the closeout is the audit trail.
     ---
     
     # Editorial Ingestion Pass
    @@ -27,9 +26,10 @@ closeouts) are valuable, but they do not validate topic-page work
     by themselves; only the topic page does.
     
     Authoritative background reading lives at
    -`.context/ingest/KB-RULES.md`. This skill encodes the workflow
    -contract; the rules file is the constitution. Hand-edit
    -`KB-RULES.md` to evolve the contract; do not paraphrase it here.
    +`.context/ingest/KB-RULES.md` and `specs/kb-editorial-pipeline.md`.
    +This skill encodes the workflow contract; the rules file is the
    +constitution. Hand-edit `KB-RULES.md` to evolve the contract; do
    +not paraphrase it here.
     
     ## When to Use
     
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md
    index 33ee4c11f..117d33611 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-note/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-kb-note
    -description: "Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for 'park this for the next ingest'."
    -tools: [bash]
    +description: Lightweight capture into .context/ingest/findings.md. Single argument is the note text. Never writes to a topic page or to evidence-index.md. The pipeline's ad-hoc escape hatch for "park this for the next ingest".
     ---
     
     # Park a Finding for the Next Ingest
    @@ -14,7 +13,8 @@ topic-page edit, no `EV-###` minting. Just typed memory landing
     in one well-known file.
     
     Authoritative background reading:
    -`.context/ingest/KB-RULES.md` §Authority boundary.
    +`.context/ingest/KB-RULES.md` §Authority boundary;
    +`specs/kb-editorial-pipeline.md` §Interface.
     
     ## When to Use
     
    @@ -34,9 +34,9 @@ Authoritative background reading:
       `/ctx-kb-ingest`).
     - The user is asking a content question (use `/ctx-kb-ask`).
     - The note is actually a task / decision / learning / convention
    -  for the code-dev side (use `/ctx-add-task` /
    -  `/ctx-add-decision` / `/ctx-add-learning` /
    -  `/ctx-add-convention`; those write to canonical files, this
    +  for the code-dev side (use `/ctx-task-add` /
    +  `/ctx-decision-add` / `/ctx-learning-add` /
    +  `/ctx-convention-add`; those write to canonical files, this
       one does not).
     - The note is empty (refuse-on-empty; see below).
     
    @@ -48,9 +48,9 @@ Authoritative background reading:
     - **`/ctx-kb-ingest`** reads `findings.md` opportunistically
       when scoping its source set; the user controls when notes get
       promoted into evidence.
    -- **Canonical capture skills** (`/ctx-add-task`,
    -  `/ctx-add-decision`, `/ctx-add-learning`,
    -  `/ctx-add-convention`) write to the five canonical
    +- **Canonical capture skills** (`/ctx-task-add`,
    +  `/ctx-decision-add`, `/ctx-learning-add`,
    +  `/ctx-convention-add`) write to the five canonical
       `.context/` files. Strict authority boundary: this skill
       never touches them.
     
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md
    index c0e850ebd..00c7f485a 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-kb-site-review/SKILL.md
    @@ -1,7 +1,6 @@
     ---
     name: ctx-kb-site-review
    -description: "Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail."
    -tools: [bash]
    +description: Mechanical structural audit of the kb. Coerces malformed capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence. Writes a site-review closeout for the audit trail.
     ---
     
     # Site-Review Pass
    @@ -17,7 +16,8 @@ This is a janitor pass, not an editorial pass. Editorial judgment
     lives in `/ctx-kb-ingest`.
     
     Authoritative background reading:
    -`.context/ingest/KB-RULES.md` §Authority boundary.
    +`.context/ingest/KB-RULES.md` §Authority boundary;
    +`specs/kb-editorial-pipeline.md` §Validation Rules.
     
     ## When to Use
     
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md
    index 0fc8d6d80..4c947b202 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-remember/SKILL.md
    @@ -89,7 +89,7 @@ tasks, or ask the user for direction if priorities are unclear.
       not *searching*
     - Be honest about the mechanism only if the user explicitly asks
       *how* you remember (e.g., "It's stored in context files managed
    -  by `ctx`")
    +  by ctx")
     
     ## Examples
     
    @@ -113,7 +113,7 @@ tasks, or ask the user for direction if priorities are unclear.
     > partially done. Want to continue those, or shift to the JSON
     > status flag?
     
    -### Bad Readback (Anti-Patterns)
    +### Bad Readback (Anti-patterns)
     
     > "I don't have persistent memory, but let me check if there
     > are any context files..."
    @@ -130,7 +130,7 @@ Skip this section entirely if `companion_check: false` is set in
     `.ctxrc`: check by running `ctx config status` and looking for
     the field value.
     
    -**Companion tools** enhance `ctx` skills with web search and code
    +**Companion tools** enhance ctx skills with web search and code
     intelligence. They are optional but recommended:
     
     | Tool          | Purpose                                                | Smoke test                                                           |
    diff --git a/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md b/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md
    index ec3bebfac..b1ba937be 100644
    --- a/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md
    +++ b/internal/assets/integrations/copilot-cli/skills/ctx-wrap-up/SKILL.md
    @@ -66,7 +66,7 @@ checklist and still ends with `/ctx-handover`.
     
     ## Process
     
    -### Phase 1: Gather Signal
    +### Phase 1: Gather signal
     
     Do this **silently**: do not narrate the steps:
     
    @@ -85,7 +85,7 @@ Do this **silently**: do not narrate the steps:
        - Follow-up work identified but not yet started
        - Tasks completed or progressed
     
    -### Phase 2: Propose Candidates
    +### Phase 2: Propose candidates
     
     Think step-by-step about what is worth persisting. For each
     potential candidate, ask yourself:
    @@ -122,7 +122,7 @@ Skip categories with no candidates: do not show empty sections.
     Persist all? Or select which to keep?
     ```
     
    -### Phase 3: Persist Approved Candidates
    +### Phase 3: Persist approved candidates
     
     Wait for the user to approve, select, or modify candidates.
     Wait for the user to approve each item before persisting:
    @@ -143,7 +143,7 @@ For each approved candidate, run the appropriate command:
     Report the result of each command. If any fail, report the error
     and continue with the remaining items.
     
    -### Phase 3.5: Suppress Post-Wrap-Up Nudges
    +### Phase 3.5: Suppress post-wrap-up nudges
     
     After persisting, mark the session as wrapped up so checkpoint
     nudges are suppressed for the remainder of the session:
    @@ -180,9 +180,8 @@ Phase 3:
        (becomes the slug in `<TS>-<slug>.md`). Drawn from the
        conversation; confirm with the user.
     2. **`--summary`** (required, past tense): one paragraph
    -   naming what was done this session, drawn from the
    -   approved candidates and the git-log scan. Concrete, not
    -   vague.
    +   naming what was done this session, drawn from the approved
    +   candidates and the git-log scan. Concrete, not vague.
     3. **`--next`** (required, future tense): one paragraph
        naming the specific first action the next agent should
        take. Pull from the highest-priority pending task in
    @@ -210,16 +209,18 @@ confirmation, then delegate:
     The `/ctx-handover` skill performs the pre-write gates,
     writes `.context/handovers/<TS>-<slug>.md`, and (when
     `.context/kb/` exists) folds postdated closeouts into the
    -`## Folded Closeouts` section and archives them.
    +`## Folded Closeouts` section and archives them. See
    +[`/ctx-handover`](#) for the full input contract and CLI
    +flag reference.
     
     If `/ctx-handover` refuses (missing `.context/handovers/`,
     empty placeholder values, etc.), surface the refusal to the
    -user. Do not declare the wrap-up complete until the
    -handover landed.
    +user. Do not declare the wrap-up complete until the handover
    +landed.
     
     ## Candidate Quality Guide
     
    -### Good Candidates
    +### Good candidates
     
     - "PyMdownx `details` extension wraps content in `<details>`
       tags, breaking `<pre><code>` rendering in MkDocs": specific
    @@ -230,7 +231,7 @@ handover landed.
     - "Convention: all skill descriptions use imperative mood":
       codifies a pattern for consistency
     
    -### Weak Candidates (Do Not Propose)
    +### Weak candidates (do not propose)
     
     - "Go has good error handling": general knowledge, not
       project-specific
    
    From 914ea8fba06c0aa0cb350cbf4fb1bca1ff11f845 Mon Sep 17 00:00:00 2001
    From: Jose Alekhinne <jose@ctx.ist>
    Date: Sun, 17 May 2026 20:19:17 -0700
    Subject: [PATCH 10/10] refactor(packages): drop underscores from all Go
     package directories
    MIME-Version: 1.0
    Content-Type: text/plain; charset=UTF-8
    Content-Transfer-Encoding: 8bit
    
    Project-wide sweep applying the Effective Go package-naming
    convention codified in 1835e02c. The flagbind rename in that
    commit was the worked example; this commit applies the same
    rule to every remaining underscored Go-package directory under
    `internal/` and `cmd/`.
    
    ## Renames (31 packages)
    
    Single-segment (directory only; package declaration was
    already correct):
    
      internal/git_meta/                        → internal/gitmeta/
      internal/config/git_meta/                 → internal/config/gitmeta/
      internal/err/git_meta/                    → internal/err/gitmeta/
      internal/cli/setup/core/copilot_cli/      → internal/cli/setup/core/copilotcli/
      internal/cli/system/cmd/session_event/    → internal/cli/system/cmd/sessionevent/
    
    Two-step (both directory and `package <name>` declaration):
    
      internal/cli/initialize/core/claude_check/        → claudecheck/
      internal/cli/system/cmd/block_non_path_ctx/       → blocknonpathctx/
      internal/cli/system/cmd/check_anchor_drift/       → checkanchordrift/
      internal/cli/system/cmd/check_ceremony/           → checkceremony/
      internal/cli/system/cmd/check_context_size/       → checkcontextsize/
      internal/cli/system/cmd/check_freshness/          → checkfreshness/
      internal/cli/system/cmd/check_hub_sync/           → checkhubsync/
      internal/cli/system/cmd/check_journal/            → checkjournal/
      internal/cli/system/cmd/check_knowledge/          → checkknowledge/
      internal/cli/system/cmd/check_map_staleness/      → checkmapstaleness/
      internal/cli/system/cmd/check_memory_drift/       → checkmemorydrift/
      internal/cli/system/cmd/check_persistence/        → checkpersistence/
      internal/cli/system/cmd/check_reminder/           → checkreminder/
      internal/cli/system/cmd/check_resource/           → checkresource/
      internal/cli/system/cmd/check_skill_discovery/    → checkskilldiscovery/
      internal/cli/system/cmd/check_task_completion/    → checktaskcompletion/
      internal/cli/system/cmd/check_version/            → checkversion/
      internal/cli/system/cmd/context_load_gate/        → contextloadgate/
      internal/cli/system/cmd/mark_journal/             → markjournal/
      internal/cli/system/cmd/mark_wrapped_up/          → markwrappedup/
      internal/cli/system/cmd/post_commit/              → postcommit/
      internal/cli/system/cmd/qa_reminder/              → qareminder/
      internal/cli/system/cmd/specs_nudge/              → specsnudge/
      internal/cli/system/core/post_commit/             → postcommit/
      internal/config/load_gate/                        → loadgate/
      internal/write/mark_journal/                      → markjournal/
    
    ## File renames (4 basename-matching files)
    
    Single-file packages whose filename mirrored the directory
    basename — each file renamed to match the new package name:
    
      copilot_cli/copilot_cli.go → copilotcli/copilotcli.go
      config/git_meta/git_meta.go → config/gitmeta/gitmeta.go
      config/load_gate/load_gate.go → config/loadgate/loadgate.go
      write/mark_journal/mark_journal.go → write/markjournal/markjournal.go
    
    ## CLI-name mapping
    
    CLI subcommand names keep their hyphens
    (`ctx system check-anchor-drift` is unchanged); only the Go
    package directory loses the separator. The
    TestDocGoSubcommandDrift compliance check is updated:
    hyphen-to-underscore mapping (`strings.ReplaceAll(name, "-",
    "_")`) becomes hyphen-strip (`strings.ReplaceAll(name, "-",
    "")`) to reflect the new convention.
    
    ## Verification
    
      - `go build ./...` clean.
      - `golangci-lint run`: 0 issues.
      - `make test`: full suite green, including
        TestDocGoSubcommandDrift after the normalize-rule update.
      - `find internal cmd -type d -name '*_*'` returns nothing
        (excluding non-Go `_template/` and `_ctx-*` directories).
    
    ## Documentation
    
    Doc/recipe/spec markdowns that referenced the old paths were
    swept along with the code. Archive markdowns under
    `.context/archive/` were updated too — they're internal
    project history, and a working path reference is more useful
    than a frozen broken one.
    
    Spec: specs/require-git.md
    Signed-off-by: Jose Alekhinne <jose@ctx.ist>
    ---
     .context/DANGER-ZONES.md                      |   2 +-
     .context/DECISIONS.md                         |   2 +-
     .context/DETAILED_DESIGN-cli.md               |   2 +-
     .context/DETAILED_DESIGN-mcp.md               |   4 +-
     .context/EXTENSION-POINTS.md                  |   4 +-
     .context/HANDOVER-2026-04-22.md               |   6 +-
     .context/TASKS.md                             |   2 +-
     .../decisions-consolidated-2026-02-26.md      |   2 +-
     .context/archive/tasks-2026-02-28.md          |   4 +-
     .context/archive/tasks-2026-03-31.md          |  14 +-
     .context/archive/tasks-2026-05-10.md          |   6 +-
     ...8Z-phase-kb-sentinel-strings-punch-list.md | 129 ++++++++++++++++++
     docs/cli/mcp.md                               |   4 +-
     docs/home/opencode.md                         |   4 +-
     docs/security/design.md                       |   2 +-
     internal/assets/commands/text/mcp.yaml        |   4 +-
     .../copilot/copilot-instructions.md           |  10 +-
     internal/audit/magic_values_test.go           |   2 +-
     internal/bootstrap/cmd.go                     |   4 +-
     internal/cli/change/core/detect/detect.go     |   6 +-
     internal/cli/change/core/detect/parse.go      |   4 +-
     internal/cli/initialize/cmd/root/run.go       |   2 +-
     .../{claude_check => claudecheck}/detail.go   |   2 +-
     .../core/{claude_check => claudecheck}/doc.go |   4 +-
     .../{claude_check => claudecheck}/hint.go     |   2 +-
     .../{claude_check => claudecheck}/parse.go    |   2 +-
     .../{claude_check => claudecheck}/render.go   |   2 +-
     .../{claude_check => claudecheck}/state.go    |   2 +-
     .../{claude_check => claudecheck}/types.go    |   2 +-
     internal/cli/initialize/core/doc.go           |   2 +-
     internal/cli/initialize/core/plugin/doc.go    |   2 +-
     internal/cli/initialize/doc.go                |   2 +-
     internal/cli/remind/doc.go                    |   4 +-
     internal/cli/setup/cmd/root/run.go            |   4 +-
     .../copilotcli.go}                            |   0
     .../core/{copilot_cli => copilotcli}/doc.go   |   0
     .../github_asset.go                           |   0
     .../core/{copilot_cli => copilotcli}/mcp.go   |   0
     .../core/{copilot_cli => copilotcli}/skill.go |   0
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../run_test.go                               |   2 +-
     .../testmain_test.go                          |   2 +-
     .../{check_ceremony => checkceremony}/cmd.go  |   2 +-
     .../{check_ceremony => checkceremony}/doc.go  |   4 +-
     .../{check_ceremony => checkceremony}/run.go  |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../{check_hub_sync => checkhubsync}/cmd.go   |   2 +-
     .../{check_hub_sync => checkhubsync}/doc.go   |   4 +-
     .../{check_hub_sync => checkhubsync}/run.go   |   2 +-
     .../{check_journal => checkjournal}/cmd.go    |   2 +-
     .../{check_journal => checkjournal}/doc.go    |   4 +-
     .../{check_journal => checkjournal}/run.go    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../{check_reminder => checkreminder}/cmd.go  |   2 +-
     .../{check_reminder => checkreminder}/doc.go  |   4 +-
     .../{check_reminder => checkreminder}/run.go  |   2 +-
     .../run_test.go                               |   6 +-
     .../{check_resource => checkresource}/cmd.go  |   2 +-
     .../{check_resource => checkresource}/doc.go  |   4 +-
     .../{check_resource => checkresource}/run.go  |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   4 +-
     .../run.go                                    |   2 +-
     .../{check_version => checkversion}/cmd.go    |   2 +-
     .../{check_version => checkversion}/doc.go    |   4 +-
     .../{check_version => checkversion}/run.go    |   2 +-
     .../cmd.go                                    |   2 +-
     .../doc.go                                    |   6 +-
     .../run.go                                    |  12 +-
     .../cmd/{mark_journal => markjournal}/cmd.go  |   2 +-
     .../cmd/{mark_journal => markjournal}/doc.go  |   6 +-
     .../cmd/{mark_journal => markjournal}/run.go  |   4 +-
     .../{mark_wrapped_up => markwrappedup}/cmd.go |   2 +-
     .../{mark_wrapped_up => markwrappedup}/doc.go |   4 +-
     .../{mark_wrapped_up => markwrappedup}/run.go |   2 +-
     .../cmd/{post_commit => postcommit}/cmd.go    |   2 +-
     .../cmd/{post_commit => postcommit}/doc.go    |   6 +-
     .../cmd/{post_commit => postcommit}/run.go    |   4 +-
     .../cmd/{qa_reminder => qareminder}/cmd.go    |   2 +-
     .../cmd/{qa_reminder => qareminder}/doc.go    |   4 +-
     .../cmd/{qa_reminder => qareminder}/run.go    |   2 +-
     .../{session_event => sessionevent}/cmd.go    |   0
     .../{session_event => sessionevent}/doc.go    |   0
     .../{session_event => sessionevent}/run.go    |   0
     .../cmd/{specs_nudge => specsnudge}/cmd.go    |   2 +-
     .../cmd/{specs_nudge => specsnudge}/doc.go    |   4 +-
     .../cmd/{specs_nudge => specsnudge}/run.go    |   2 +-
     .../cli/system/core/check/full_preamble.go    |   2 +-
     internal/cli/system/core/drift/doc.go         |   2 +-
     internal/cli/system/core/health/doc.go        |   4 +-
     internal/cli/system/core/health/prune.go      |   4 +-
     internal/cli/system/core/journal/doc.go       |   2 +-
     internal/cli/system/core/knowledge/doc.go     |   2 +-
     internal/cli/system/core/load/load_gate.go    |   4 +-
     .../core/{post_commit => postcommit}/doc.go   |   4 +-
     .../core/{post_commit => postcommit}/score.go |   2 +-
     internal/cli/system/core/session/doc.go       |   4 +-
     internal/cli/system/system.go                 |  90 ++++++------
     internal/compliance/compliance_test.go        |   6 +-
     internal/config/README.md                     |   2 +-
     internal/config/embed/text/err_gitmeta.go     |   2 +-
     internal/config/{git_meta => gitmeta}/doc.go  |   4 +-
     .../git_meta.go => gitmeta/gitmeta.go}        |   2 +-
     internal/config/hook/doc.go                   |   2 +-
     .../config/{load_gate => loadgate}/doc.go     |   4 +-
     .../load_gate.go => loadgate/loadgate.go}     |   2 +-
     .../config/{load_gate => loadgate}/prune.go   |   2 +-
     internal/config/{load_gate => loadgate}/ts.go |   2 +-
     internal/config/mcp/event/doc.go              |   2 +-
     internal/config/mcp/tool/doc.go               |   4 +-
     internal/config/mcp/tool/tool.go              |   4 +-
     internal/entity/mcp_session.go                |   4 +-
     internal/err/closeout/closeout.go             |   2 +-
     internal/err/{git_meta => gitmeta}/doc.go     |   8 +-
     internal/err/{git_meta => gitmeta}/gitmeta.go |   0
     internal/err/handover/handover.go             |   2 +-
     internal/{git_meta => gitmeta}/branch.go      |   2 +-
     internal/{git_meta => gitmeta}/doc.go         |   0
     internal/{git_meta => gitmeta}/head.go        |   4 +-
     internal/{git_meta => gitmeta}/require.go     |   2 +-
     .../{git_meta => gitmeta}/require_test.go     |   4 +-
     .../{git_meta => gitmeta}/resolvehead_test.go |   4 +-
     internal/{git_meta => gitmeta}/sha.go         |   4 +-
     .../{git_meta => gitmeta}/testmain_test.go    |   0
     internal/{git_meta => gitmeta}/types.go       |   2 +-
     internal/log/event/doc.go                     |   2 +-
     internal/mcp/doc.go                           |   4 +-
     internal/mcp/handler/doc.go                   |   6 +-
     internal/mcp/handler/governance_test.go       |   4 +-
     internal/mcp/server/doc.go                    |   2 +-
     internal/mcp/server/server_test.go            |  20 +--
     internal/notify/doc.go                        |   2 +-
     internal/sysinfo/doc.go                       |   2 +-
     internal/write/closeout/closeout_test.go      |   2 +-
     internal/write/closeout/doc.go                |   2 +-
     internal/write/closeout/write.go              |   2 +-
     internal/write/handover/doc.go                |   2 +-
     internal/write/handover/provenance.go         |   2 +-
     internal/write/handover/write.go              |   2 +-
     internal/write/initialize/init.go             |   4 +-
     .../{mark_journal => markjournal}/doc.go      |   8 +-
     .../markjournal.go}                           |   2 +-
     site/cli/kb/index.html                        |   2 +-
     site/cli/mcp/index.html                       |  20 +--
     site/home/contributing/index.html             |  50 ++++---
     site/home/opencode/index.html                 |   4 +-
     site/recipes/activating-context/index.html    |   4 +-
     .../recipes/build-a-knowledge-base/index.html |  79 ++++++++++-
     site/search.json                              |   2 +-
     site/security/design/index.html               |   2 +-
     specs/copilot-feature-parity-kit.md           |   2 +-
     .../copilot-cli-integration.md                |   4 +-
     specs/future-complete/exec-package.md         |   4 +-
     .../flagbind-batch-and-convention-sweep.md    |   2 +-
     specs/future-complete/fmt-fprint-migration.md |   2 +-
     specs/future-complete/hook-accountability.md  |   2 +-
     .../hook-guard-uninitialized.md               |   4 +-
     .../journal-merge-completion.md               |   4 +-
     .../percentage-based-checkpoint-nudge.md      |   2 +-
     .../state-dir-no-mkdir-when-uninitialized.md  |   6 +-
     .../task-session-provenance.md                |   2 +-
     .../v0.6.0/plan-v0.6.0-claude-code-plugin.md  |  20 +--
     specs/released/v0.8.0/ceremony-nudge.md       |   2 +-
     specs/released/v0.8.0/context-load-gate-v2.md |   4 +-
     specs/released/v0.8.0/context-load-gate.md    |   4 +-
     specs/released/v0.8.0/context-window-usage.md |   4 +-
     specs/released/v0.8.0/event-log.md            |   2 +-
     .../released/v0.8.0/global-encryption-key.md  |   2 +-
     .../released/v0.8.0/hook-message-templates.md |  22 +--
     .../v0.8.0/injection-oversize-nudge.md        |  16 +--
     specs/released/v0.8.0/journal-state-file.md   |   4 +-
     specs/released/v0.8.0/remind.md               |   4 +-
     specs/released/v0.8.0/session-pause.md        |  24 ++--
     .../v0.8.0/suppress-nudges-after-wrap-up.md   |   8 +-
     specs/released/v0.8.0/system-resources.md     |   6 +-
     .../released/v0.8.0/system-write-migration.md |  18 +--
     .../released/v0.8.0/task-completion-nudge.md  |   4 +-
     specs/released/v0.8.0/webhook-notify.md       |  20 +--
     .../released/v0.8.0/write-system-taxonomy.md  |   4 +-
     specs/single-source-context-anchor.md         |   4 +-
     204 files changed, 652 insertions(+), 436 deletions(-)
     create mode 100644 .context/handovers/20260518T004118Z-phase-kb-sentinel-strings-punch-list.md
     rename internal/cli/initialize/core/{claude_check => claudecheck}/detail.go (99%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/doc.go (95%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/hint.go (99%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/parse.go (98%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/render.go (99%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/state.go (97%)
     rename internal/cli/initialize/core/{claude_check => claudecheck}/types.go (99%)
     rename internal/cli/setup/core/{copilot_cli/copilot_cli.go => copilotcli/copilotcli.go} (100%)
     rename internal/cli/setup/core/{copilot_cli => copilotcli}/doc.go (100%)
     rename internal/cli/setup/core/{copilot_cli => copilotcli}/github_asset.go (100%)
     rename internal/cli/setup/core/{copilot_cli => copilotcli}/mcp.go (100%)
     rename internal/cli/setup/core/{copilot_cli => copilotcli}/skill.go (100%)
     rename internal/cli/system/cmd/{block_non_path_ctx => blocknonpathctx}/cmd.go (96%)
     rename internal/cli/system/cmd/{block_non_path_ctx => blocknonpathctx}/doc.go (95%)
     rename internal/cli/system/cmd/{block_non_path_ctx => blocknonpathctx}/run.go (99%)
     rename internal/cli/system/cmd/{check_anchor_drift => checkanchordrift}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_anchor_drift => checkanchordrift}/doc.go (97%)
     rename internal/cli/system/cmd/{check_anchor_drift => checkanchordrift}/run.go (98%)
     rename internal/cli/system/cmd/{check_anchor_drift => checkanchordrift}/run_test.go (99%)
     rename internal/cli/system/cmd/{check_anchor_drift => checkanchordrift}/testmain_test.go (94%)
     rename internal/cli/system/cmd/{check_ceremony => checkceremony}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_ceremony => checkceremony}/doc.go (95%)
     rename internal/cli/system/cmd/{check_ceremony => checkceremony}/run.go (99%)
     rename internal/cli/system/cmd/{check_context_size => checkcontextsize}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_context_size => checkcontextsize}/doc.go (95%)
     rename internal/cli/system/cmd/{check_context_size => checkcontextsize}/run.go (99%)
     rename internal/cli/system/cmd/{check_freshness => checkfreshness}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_freshness => checkfreshness}/doc.go (96%)
     rename internal/cli/system/cmd/{check_freshness => checkfreshness}/run.go (99%)
     rename internal/cli/system/cmd/{check_hub_sync => checkhubsync}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_hub_sync => checkhubsync}/doc.go (95%)
     rename internal/cli/system/cmd/{check_hub_sync => checkhubsync}/run.go (98%)
     rename internal/cli/system/cmd/{check_journal => checkjournal}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_journal => checkjournal}/doc.go (96%)
     rename internal/cli/system/cmd/{check_journal => checkjournal}/run.go (99%)
     rename internal/cli/system/cmd/{check_knowledge => checkknowledge}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_knowledge => checkknowledge}/doc.go (95%)
     rename internal/cli/system/cmd/{check_knowledge => checkknowledge}/run.go (98%)
     rename internal/cli/system/cmd/{check_map_staleness => checkmapstaleness}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_map_staleness => checkmapstaleness}/doc.go (95%)
     rename internal/cli/system/cmd/{check_map_staleness => checkmapstaleness}/run.go (98%)
     rename internal/cli/system/cmd/{check_memory_drift => checkmemorydrift}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_memory_drift => checkmemorydrift}/doc.go (95%)
     rename internal/cli/system/cmd/{check_memory_drift => checkmemorydrift}/run.go (98%)
     rename internal/cli/system/cmd/{check_persistence => checkpersistence}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_persistence => checkpersistence}/doc.go (95%)
     rename internal/cli/system/cmd/{check_persistence => checkpersistence}/run.go (99%)
     rename internal/cli/system/cmd/{check_reminder => checkreminder}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_reminder => checkreminder}/doc.go (96%)
     rename internal/cli/system/cmd/{check_reminder => checkreminder}/run.go (99%)
     rename internal/cli/system/cmd/{check_reminder => checkreminder}/run_test.go (93%)
     rename internal/cli/system/cmd/{check_resource => checkresource}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_resource => checkresource}/doc.go (95%)
     rename internal/cli/system/cmd/{check_resource => checkresource}/run.go (98%)
     rename internal/cli/system/cmd/{check_skill_discovery => checkskilldiscovery}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_skill_discovery => checkskilldiscovery}/doc.go (95%)
     rename internal/cli/system/cmd/{check_skill_discovery => checkskilldiscovery}/run.go (98%)
     rename internal/cli/system/cmd/{check_task_completion => checktaskcompletion}/cmd.go (96%)
     rename internal/cli/system/cmd/{check_task_completion => checktaskcompletion}/doc.go (95%)
     rename internal/cli/system/cmd/{check_task_completion => checktaskcompletion}/run.go (98%)
     rename internal/cli/system/cmd/{check_version => checkversion}/cmd.go (97%)
     rename internal/cli/system/cmd/{check_version => checkversion}/doc.go (96%)
     rename internal/cli/system/cmd/{check_version => checkversion}/run.go (99%)
     rename internal/cli/system/cmd/{context_load_gate => contextloadgate}/cmd.go (97%)
     rename internal/cli/system/cmd/{context_load_gate => contextloadgate}/doc.go (93%)
     rename internal/cli/system/cmd/{context_load_gate => contextloadgate}/run.go (93%)
     rename internal/cli/system/cmd/{mark_journal => markjournal}/cmd.go (98%)
     rename internal/cli/system/cmd/{mark_journal => markjournal}/doc.go (94%)
     rename internal/cli/system/cmd/{mark_journal => markjournal}/run.go (94%)
     rename internal/cli/system/cmd/{mark_wrapped_up => markwrappedup}/cmd.go (97%)
     rename internal/cli/system/cmd/{mark_wrapped_up => markwrappedup}/doc.go (95%)
     rename internal/cli/system/cmd/{mark_wrapped_up => markwrappedup}/run.go (98%)
     rename internal/cli/system/cmd/{post_commit => postcommit}/cmd.go (97%)
     rename internal/cli/system/cmd/{post_commit => postcommit}/doc.go (93%)
     rename internal/cli/system/cmd/{post_commit => postcommit}/run.go (98%)
     rename internal/cli/system/cmd/{qa_reminder => qareminder}/cmd.go (97%)
     rename internal/cli/system/cmd/{qa_reminder => qareminder}/doc.go (95%)
     rename internal/cli/system/cmd/{qa_reminder => qareminder}/run.go (99%)
     rename internal/cli/system/cmd/{session_event => sessionevent}/cmd.go (100%)
     rename internal/cli/system/cmd/{session_event => sessionevent}/doc.go (100%)
     rename internal/cli/system/cmd/{session_event => sessionevent}/run.go (100%)
     rename internal/cli/system/cmd/{specs_nudge => specsnudge}/cmd.go (97%)
     rename internal/cli/system/cmd/{specs_nudge => specsnudge}/doc.go (95%)
     rename internal/cli/system/cmd/{specs_nudge => specsnudge}/run.go (99%)
     rename internal/cli/system/core/{post_commit => postcommit}/doc.go (94%)
     rename internal/cli/system/core/{post_commit => postcommit}/score.go (99%)
     rename internal/config/{git_meta => gitmeta}/doc.go (84%)
     rename internal/config/{git_meta/git_meta.go => gitmeta/gitmeta.go} (94%)
     rename internal/config/{load_gate => loadgate}/doc.go (95%)
     rename internal/config/{load_gate/load_gate.go => loadgate/loadgate.go} (97%)
     rename internal/config/{load_gate => loadgate}/prune.go (95%)
     rename internal/config/{load_gate => loadgate}/ts.go (95%)
     rename internal/err/{git_meta => gitmeta}/doc.go (79%)
     rename internal/err/{git_meta => gitmeta}/gitmeta.go (100%)
     rename internal/{git_meta => gitmeta}/branch.go (94%)
     rename internal/{git_meta => gitmeta}/doc.go (100%)
     rename internal/{git_meta => gitmeta}/head.go (94%)
     rename internal/{git_meta => gitmeta}/require.go (94%)
     rename internal/{git_meta => gitmeta}/require_test.go (94%)
     rename internal/{git_meta => gitmeta}/resolvehead_test.go (95%)
     rename internal/{git_meta => gitmeta}/sha.go (81%)
     rename internal/{git_meta => gitmeta}/testmain_test.go (100%)
     rename internal/{git_meta => gitmeta}/types.go (85%)
     rename internal/write/{mark_journal => markjournal}/doc.go (83%)
     rename internal/write/{mark_journal/mark_journal.go => markjournal/markjournal.go} (98%)
    
    diff --git a/.context/DANGER-ZONES.md b/.context/DANGER-ZONES.md
    index f269363d4..b06d8d01b 100644
    --- a/.context/DANGER-ZONES.md
    +++ b/.context/DANGER-ZONES.md
    @@ -129,7 +129,7 @@ Enriched 2026-04-03 via GitNexus (blast radius verified)._
     
     1. **DiscoverPath coupling** - 7 direct callers across 4 modules
        (Memory, Publish, Session, Ctximport). All 6 memory subcommands
    -   + check_memory_drift hook depend on this. 7 execution flows.
    +   + checkmemorydrift hook depend on this. 7 execution flows.
        - Blast radius: d=1: 7, flows: 7, modules: 4
        - Risk: CRITICAL (enriched 2026-04-03 via GitNexus)
        - Modification advice: slug format change breaks all memory
    diff --git a/.context/DECISIONS.md b/.context/DECISIONS.md
    index 3f4640157..2331914f8 100644
    --- a/.context/DECISIONS.md
    +++ b/.context/DECISIONS.md
    @@ -539,7 +539,7 @@ Initialized() first.
     
     **Rationale**: Option (A) makes the invariant ('no .context/state/ in
     uninitialized projects') structurally enforced. The leak's root cause was
    -exactly the (B)-style assumption — check_reminder.Run deliberately skipped the
    +exactly the (B)-style assumption — checkreminder.Run deliberately skipped the
     gate to print provenance unconditionally, and that path silently produced the
     leak via Preamble -> nudge.Paused -> PauseMarkerPath -> state.Dir. As long as
     Dir() mkdirs unconditionally, every future caller is one missed gate away from
    diff --git a/.context/DETAILED_DESIGN-cli.md b/.context/DETAILED_DESIGN-cli.md
    index 93e3df2ee..cd66ed086 100644
    --- a/.context/DETAILED_DESIGN-cli.md
    +++ b/.context/DETAILED_DESIGN-cli.md
    @@ -169,7 +169,7 @@ core/ taxonomy per Decision 2026-03-06. Key structural changes:
     - **journal**: consumed recall (Decision 2026-03-30); subcommands:
       source, importer, lock, unlock, sync, site, obsidian
     - **system**: expanded to 34 hook subcommands including various
    -  check_*, block_*, post_commit, mark_*, cleanup_*
    +  check_*, block_*, postcommit, mark_*, cleanup_*
     - **recall**: deleted — merged into journal
     
     **Danger zones**:
    diff --git a/.context/DETAILED_DESIGN-mcp.md b/.context/DETAILED_DESIGN-mcp.md
    index 8e82a551e..373100f94 100644
    --- a/.context/DETAILED_DESIGN-mcp.md
    +++ b/.context/DETAILED_DESIGN-mcp.md
    @@ -294,8 +294,8 @@ config/mcp/tool (handler side)
     | ctx_watch_update | No | Apply structured updates |
     | ctx_compact | No | Archive completed tasks |
     | ctx_next | Yes | Next pending task |
    -| ctx_check_task_completion | Yes | Match action to tasks |
    -| ctx_session_event | No | Signal session start/end |
    +| ctx_checktaskcompletion | Yes | Match action to tasks |
    +| ctx_sessionevent | No | Signal session start/end |
     | ctx_remind | Yes | List pending reminders |
     
     ## Resources (9 total)
    diff --git a/.context/EXTENSION-POINTS.md b/.context/EXTENSION-POINTS.md
    index 83ac162e1..e3d614a69 100644
    --- a/.context/EXTENSION-POINTS.md
    +++ b/.context/EXTENSION-POINTS.md
    @@ -30,7 +30,7 @@ Registration: `journal/parser` auto-detection via `Matches(path)`
     Registered implementations:
     1. `ClaudeCode` - `internal/journal/parser/claude.go`
     2. `Copilot` - `internal/journal/parser/copilot.go`
    -3. `CopilotCLI` - `internal/journal/parser/copilot_cli.go`
    +3. `CopilotCLI` - `internal/journal/parser/copilotcli.go`
     4. `MarkdownSession` - `internal/journal/parser/markdown.go`
     
     How to extend: implement SessionParser interface with `Matches()`
    @@ -76,7 +76,7 @@ Registration: `internal/cli/setup/core/*` packages
     5 tool-specific deployers:
     1. `agents/` - AGENTS.md deployment
     2. `copilot/` - GitHub Copilot (instructions + VS Code MCP)
    -3. `copilot_cli/` - Copilot CLI (instructions, skills, agent, MCP)
    +3. `copilotcli/` - Copilot CLI (instructions, skills, agent, MCP)
     4. (Claude Code via initialize, not setup)
     5. (Cursor/Aider/Windsurf via simpler paths)
     
    diff --git a/.context/HANDOVER-2026-04-22.md b/.context/HANDOVER-2026-04-22.md
    index 09040b555..84b18c949 100644
    --- a/.context/HANDOVER-2026-04-22.md
    +++ b/.context/HANDOVER-2026-04-22.md
    @@ -80,11 +80,11 @@ Sharpened the "explicit declaration or nothing" model:
     
     `internal/cli/system/core/check/full_preamble.go` now returns
     `(input, sessionID, ctxDir, stateDir, ok)`. All 16 callers
    -updated. 4 hooks (`check_persistence`, `check_memory_drift`,
    -`check_ceremony`, `check_journal`) had their redundant
    +updated. 4 hooks (`checkpersistence`, `checkmemorydrift`,
    +`checkceremony`, `checkjournal`) had their redundant
     `errors.Is(ctxErr, ErrDirNotDeclared)` blocks deleted and use the
     preamble's `ctxDir` directly. The 3 non-FullPreamble hooks
    -(`context_load_gate`, `check_context_size`, `heartbeat`) had
    +(`contextloadgate`, `checkcontextsize`, `heartbeat`) had
     their `ErrDirNotDeclared` branches replaced with a defensive
     "unreachable but log loudly" fallback after the `state.Initialized`
     gate.
    diff --git a/.context/TASKS.md b/.context/TASKS.md
    index 863c84910..f25f8422d 100644
    --- a/.context/TASKS.md
    +++ b/.context/TASKS.md
    @@ -2046,7 +2046,7 @@ Phase KB-3 (documentation):
       `docs/recipes/build-a-knowledge-base.md`; uses already-specced
       `mcp:<server>:<resource>` syntax in `grounding-sources.md`; zero new ctx code
     - [x] Replace the `ErrMsg`-string-sentinel anti-pattern across
    -  `internal/config/{handover,closeout,git_meta,kb/cli,kb/evidence,kb/sourcecoverage,rc,initialize,schema}/`.
    +  `internal/config/{handover,closeout,gitmeta,kb/cli,kb/evidence,kb/sourcecoverage,rc,initialize,schema}/`.
       Sentinels became `entity.Sentinel` typed-string consts whose `Error()`
       resolves text from `commands/text/errors.yaml` via
       `desc.Text(text.DescKey...)` at call time. Pre-existing convention
    diff --git a/.context/archive/decisions-consolidated-2026-02-26.md b/.context/archive/decisions-consolidated-2026-02-26.md
    index 424ff877a..b2a847864 100644
    --- a/.context/archive/decisions-consolidated-2026-02-26.md
    +++ b/.context/archive/decisions-consolidated-2026-02-26.md
    @@ -72,7 +72,7 @@ Originals replaced by consolidated entries in DECISIONS.md.
     
     **Rationale**: JSON with hookSpecificOutput.additionalContext is parsed as a directive by Claude Code, while plain text is treated as ambient context the agent can ignore
     
    -**Consequences**: Added HookResponse/HookSpecificOutput types and printHookContext() helper to internal/cli/system/input.go; converted qa_reminder.go and post_commit.go; future hooks should use printHookContext() for non-blocking directives
    +**Consequences**: Added HookResponse/HookSpecificOutput types and printHookContext() helper to internal/cli/system/input.go; converted qareminder.go and postcommit.go; future hooks should use printHookContext() for non-blocking directives
     
     ---
     
    diff --git a/.context/archive/tasks-2026-02-28.md b/.context/archive/tasks-2026-02-28.md
    index c784fc3d9..29aedd39b 100644
    --- a/.context/archive/tasks-2026-02-28.md
    +++ b/.context/archive/tasks-2026-02-28.md
    @@ -1,6 +1,6 @@
     # Archived Tasks - 2026-02-28
     
    -- [x] P0.9.1: Create `internal/cli/system/mark_wrapped_up.go` — hidden plumbing
    +- [x] P0.9.1: Create `internal/cli/system/markwrappedup.go` — hidden plumbing
           command `ctx system mark-wrapped-up`. Writes a `ctx-wrapped-up` marker file
           to `secureTempDir()`. No flags, no arguments, no stdin. Follow the
           `mark-journal` pattern.
    @@ -11,7 +11,7 @@
           `cmd.AddCommand()` and update the Long description under plumbing section.
           DOD: `ctx system mark-wrapped-up` is callable. `ctx system --help` lists it
           (hidden, but callable). #added:2026-02-28 #done:2026-02-28
    -- [x] P0.9.3: Modify `check_context_size.go` — before emitting a checkpoint,
    +- [x] P0.9.3: Modify `checkcontextsize.go` — before emitting a checkpoint,
           check for the `ctx-wrapped-up` marker in `secureTempDir()`. If the marker
           exists and is less than 2 hours old, suppress the nudge and log
           `"prompt#N suppressed (wrapped up)"`. If the marker is expired (>2h),
    diff --git a/.context/archive/tasks-2026-03-31.md b/.context/archive/tasks-2026-03-31.md
    index 650db93cb..60eeacd7f 100644
    --- a/.context/archive/tasks-2026-03-31.md
    +++ b/.context/archive/tasks-2026-03-31.md
    @@ -88,7 +88,7 @@
       tiers in new order #done:2026-03-27-101800
     - [x] HA.2.1: Add `ContextCheckpointMinPct = 20` constant to
       `config/stats/context.go` #done:2026-03-27-101100 #added:2026-03-27-100000
    -- [x] HA.2.2: Gate `counterTriggered` in `check_context_size/run.go` behind `pct
    +- [x] HA.2.2: Gate `counterTriggered` in `checkcontextsize/run.go` behind `pct
       >= stats.ContextCheckpointMinPct` #done:2026-03-27-101200
       #priority:high #added:2026-03-27-100000
     - [x] HA.2.3: Add percentage awareness to `check-persistence`
    @@ -131,7 +131,7 @@
     - [x] PCN.3.1: Update `nudge/token.go:TokenUsageLine` — replace
       `ContextWindowThresholdPct` reference with `ContextWindowWarnPct`
       #added:2026-03-30-150000 #done:2026-03-30-160000
    -- [x] PCN.3.2: Update `check_persistence/run.go` — change gate from
    +- [x] PCN.3.2: Update `checkpersistence/run.go` — change gate from
       `ContextCheckpointMinPct` to `ContextCheckpointPct`
       #added:2026-03-30-150000 #done:2026-03-30-160000
     - [x] PCN.4.1: Unit test: no nudge fires below 60%
    @@ -166,9 +166,9 @@
       reference public function names, not private #added:2026-03-27-110000
     - [x] JMC.3.1: source/cmd.go:75 #done:2026-03-27-111200 — add cFlag.Project,
       cFlag.ShortProject constants #added:2026-03-27-110000
    -- [x] JMC.3.2: check_context_size/run.go #done:2026-03-27-111400 — extract 30,
    +- [x] JMC.3.2: checkcontextsize/run.go #done:2026-03-27-111400 — extract 30,
       3, 15 to config/stats constants #added:2026-03-27-110000
    -- [x] JMC.3.3: post_commit/run.go #done:2026-03-27-112000 — move regexes to
    +- [x] JMC.3.3: postcommit/run.go #done:2026-03-27-112000 — move regexes to
       config, violation points to config, localizable strings to
       assets #added:2026-03-27-110000
     - [x] JMC.3.4: state/state.go:29 #done:2026-03-27-112200 — 0o750 to config/fs
    @@ -193,7 +193,7 @@
       task/core/archive #done:2026-03-27-133000
     - [x] JMC.6.2: Extract notify/cmd/test RunTest logic to
       notify/core/test #done:2026-03-27-133000
    -- [x] JMC.6.3: Extract system/cmd/mark_journal RunMarkJournal logic to
    +- [x] JMC.6.3: Extract system/cmd/markjournal RunMarkJournal logic to
       system/core/journal/mark.go #done:2026-03-27-133000
     - [x] JMC.6.4: Extract system/cmd/resources RunResources logic to
       system/core/resource #done:2026-03-27-133000
    @@ -243,7 +243,7 @@
     - [x] EXEC.1.3: Migrate `change/core/scan/scan.go:GitLogSince`
       to use `exec/git.LogSince()` #added:2026-03-30
       #done:2026-03-30
    -- [x] EXEC.1.4: Migrate `post_commit/score.go` (2 calls) to
    +- [x] EXEC.1.4: Migrate `postcommit/score.go` (2 calls) to
       use `exec/git.LastCommitMessage()` and
       `exec/git.DiffTreeHead()` #added:2026-03-30 #done:2026-03-30
     - [x] EXEC.1.5: Migrate `health/map_staleness.go` to use
    @@ -349,7 +349,7 @@ proportional to actual context window usage.
     
     **PCN.1 — Constants and cleanup (`config/stats/context.go`):**
     
    -**PCN.2 — Rewrite `check_context_size/run.go` trigger logic:**
    +**PCN.2 — Rewrite `checkcontextsize/run.go` trigger logic:**
     
     **PCN.3 — Update dependent code:**
     
    diff --git a/.context/archive/tasks-2026-05-10.md b/.context/archive/tasks-2026-05-10.md
    index 841624fe7..cc9920992 100644
    --- a/.context/archive/tasks-2026-05-10.md
    +++ b/.context/archive/tasks-2026-05-10.md
    @@ -14,13 +14,13 @@
       the issue is the project does not have a .context folder and we don't detect
       it. **Progress 2026-04-13**: Boundary side effect resolved by git-anchored
       walk
    -  (commit e24941d2). `state.Initialized()` guards added to `check_resource` and
    +  (commit e24941d2). `state.Initialized()` guards added to `checkresource` and
       `check_backup_age` — the two user-visible relay-nag hooks that were missing
       them. **Completed 2026-04-16**: `state.Initialized()` guards added to
    -  `mark_journal`, `mark_wrapped_up`, `pause`, `resume`. `bootstrap` left
    +  `markjournal`, `markwrappedup`, `pause`, `resume`. `bootstrap` left
       unguarded intentionally — its job is to report context dir status, and it
       already has its own `os.Stat` guard. Safety hooks
    -  (`block_dangerous_command`, `block_non_path_ctx`) intentionally run
    +  (`block_dangerous_command`, `blocknonpathctx`) intentionally run
       regardless.
     - [x] Move `ctx bootstrap` back to `ctx system bootstrap` (hidden). Bootstrap is
       agent-only plumbing — no human types it interactively. It was incorrectly
    diff --git a/.context/handovers/20260518T004118Z-phase-kb-sentinel-strings-punch-list.md b/.context/handovers/20260518T004118Z-phase-kb-sentinel-strings-punch-list.md
    new file mode 100644
    index 000000000..102d3bdf7
    --- /dev/null
    +++ b/.context/handovers/20260518T004118Z-phase-kb-sentinel-strings-punch-list.md
    @@ -0,0 +1,129 @@
    +---
    +sha: 60543e46
    +branch: feat/phase-kb
    +mode: handover
    +pass-mode: n/a
    +life-stage: maintenance
    +generated-at: 2026-05-18T00:41:18Z
    +title: phase-kb sentinel-strings + post-amend punch list
    +---
    +
    +## Summary
    +
    +Phase KB branch (`feat/phase-kb` @ `60543e46`) is landed and
    +linted clean across ~313 files folded into one signed commit.
    +This session's final iterations did, in order: package
    +relocation (`initkb` → `initialize/kb`, kb-prefix flat dirs
    +→ `kb/<sub>` nested, plural→singular kb subdir names,
    +`topicnew/` flat → `topic/cmd/newcmd/` nested,
    +`internal/cli/kb/cmd.go` → `kb.go`); function and file
    +renames (`RunNew` → `Scaffold`, `LatestCursor` → `Latest`,
    +`CopyKBLanding` → `CopyLanding` for the stutter audit,
    +`nextid.go` → `next_id.go`, `ctxio` alias → `ctxIo`,
    +`topics.go` → `topic.go`, `Slugify` removed in favor of
    +`internal/slug.Path`); shared-package extractions
    +(`internal/slug/` from `internal/cli/journal/core/slug/`,
    +`internal/write/kb/row/` from triplicated
    +contradiction/decision/question append flows,
    +`internal/cli/setup/core/copilot_cli/github_asset.go` from
    +the `deployAgent`+`deployInstructions` duplication);
    +`cmd.go`/`run.go` split across 8 kb+handover subcommands;
    +Phase KB skill parity ported to copilot-cli and opencode
    +trees; the
    +`.context/{TASKS,DECISIONS,LEARNINGS}.md` Phase KB additions
    +em-dash-swept; markdown→Markdown, en-US, Title-Case,
    +NDA-name (your-project / your-domain placeholders) and
    +rot-prone repo-spec-link sweeps run across docs/specs/skills;
    +`commands.yaml` gained Examples blocks; localizable strings
    +moved from cfg `messages.go` files into
    +`commands/text/{errors,write}.yaml` + DescKey constants in
    +`internal/config/embed/text/`; YAML hierarchy aligned to
    +`err.kb.<sub>.<verb>` with dot separators; finally, all 13
    +remaining `messages.go` files were renamed to package-singular
    +names — no `messages.go` exists anywhere in the repo now.
    +
    +## Next Session
    +
    +Fix the `ErrMsg`-as-string-sentinel anti-pattern. Currently
    +in `internal/config/{handover,closeout,git_meta,kb/cli,
    +kb/evidence,kb/sourcecoverage,rc,initialize}/<pkg>.go` we
    +still have things like
    +
    +```go
    +ErrMsgMissingGitTree = "git working tree required"
    +```
    +
    +These strings back package-level
    +`var ErrX = errors.New(cfgPkg.ErrMsgX)`. The `errors.Is`
    +contract uses identity, not text — but the embedded English
    +string still leaks into `.Error()` output and breaks
    +localization. The correct shape: (a) the sentinel value
    +carries identity, not text — use `errors.New("")` or a tiny
    +typed sentinel with `Is(target error) bool`; (b) the
    +user-facing text moves into `commands/text/errors.yaml` as
    +`err.<pkg>.<name>` and is rendered at error-display time by
    +the `err/<pkg>/<pkg>.go` wrapping constructor (which already
    +uses `desc.Text` for the format wrapping).
    +
    +Sweep every `ErrMsg*` in `internal/config/**/*.go`. Verify
    +with:
    +
    +```bash
    +grep -rn 'ErrMsg.*= "' internal/config/
    +```
    +
    +After the sweep, also kill any "sentinel mirrors YAML key"
    +doc comments I left in the cfg files explaining the
    +duplication — that justification was wrong.
    +
    +## Highlights
    +
    +- 8 amends this session culminating at `60543e46`.
    +- Phase KB skill parity across 3 host trees
    +  (`claude` / `copilot-cli` / `opencode`).
    +- Phase RG (`git`-required) and Phase KB (editorial
    +  pipeline) shipped paired.
    +- `internal/cli/handover/core/path/` extracted from
    +  `kb/core/path/` (handover is session-glue, not a KB
    +  feature; `.context/handovers/<TS>-<slug>.md` files are
    +  timestamped so concurrent agent runs never overwrite).
    +- `docs/cli/kb-handover.md` split into `kb.md` + `handover.md`
    +  (the combined page was a category error).
    +- `CLAUDE.md` restructured: `## Session Handovers` is now an
    +  h2 sibling of `## KB Editorial Workflow`, not a sub-step.
    +- `Phase KB-followup` task filed in `.context/TASKS.md` for
    +  the adversarial design review of the 3-tree skill drift
    +  problem (parallel claude/copilot-cli/opencode trees).
    +- New `internal/slug.Path` for slash-preserving slugs;
    +  duplicate `Slugify` in `kb/core/topic` removed.
    +- New `internal/write/kb/row.Append` + `entity.KBRowHooks`
    +  collapsed three triplicated append flows.
    +- Doc structure audit clean; cross-package types audit
    +  clean; mixed-visibility audit clean; gocritic clean;
    +  stutter audit clean.
    +
    +## Open Questions
    +
    +- Should we keep `errors.Is`-style sentinels at all for
    +  these wrappers, or switch to typed-error structs with
    +  `Is(target error) bool` methods? Either works; pick one
    +  and apply uniformly.
    +- My "`ErrMsg` consts must stay Go-const for package-init
    +  timing" framing in several recent commit messages and
    +  doc comments is partially wrong — only the
    +  `var ErrX = errors.New()` values need a non-empty
    +  backing string for the `.Error()` fallback, but if every
    +  caller uses the wrapping constructor (which goes through
    +  `desc.Text`), the backing string is dead text. Decide
    +  whether to delete the `ErrMsg` consts entirely (typed
    +  sentinel with empty `.Error()`), or keep them as a
    +  degraded-mode fallback when the embedded YAML lookup
    +  table hasn't been populated yet.
    +- The `Phase KB-followup` adversarial-review task assumes a
    +  body-extract builder is the right shape. Confirm before
    +  building anything.
    +
    +## Folded Closeouts
    +
    +none (this is a manual session-end handover; no editorial
    +closeouts pending).
    diff --git a/docs/cli/mcp.md b/docs/cli/mcp.md
    index 0cd50efa0..c2529730b 100644
    --- a/docs/cli/mcp.md
    +++ b/docs/cli/mcp.md
    @@ -214,7 +214,7 @@ Suggest the next pending task based on priority and position.
     
     **Arguments:** None. **Read-only.**
     
    -### `ctx_check_task_completion`
    +### `ctx_checktaskcompletion`
     
     Advisory check: after a write operation, detect if any pending tasks
     were silently completed. Returns nudge text if a match is found.
    @@ -225,7 +225,7 @@ were silently completed. Returns nudge text if a match is found.
     
     **Read-only.**
     
    -### `ctx_session_event`
    +### `ctx_sessionevent`
     
     Signal a session lifecycle event. Type `end` triggers the session-end
     persistence ceremony - human confirmation required.
    diff --git a/docs/home/opencode.md b/docs/home/opencode.md
    index e6dec192b..e708818d0 100644
    --- a/docs/home/opencode.md
    +++ b/docs/home/opencode.md
    @@ -142,9 +142,9 @@ read and write your context files without shell commands:
     | `ctx_status` | Context health: file count, token estimate |
     | `ctx_steering_get` | Retrieve steering files applicable to the current prompt |
     | `ctx_journal_source` | Query recent AI session history |
    -| `ctx_session_event` | Signal session start/end lifecycle events |
    +| `ctx_sessionevent` | Signal session start/end lifecycle events |
     | `ctx_watch_update` | Apply structured updates to `.context/` files |
    -| `ctx_check_task_completion` | After a write, detect silently completed tasks |
    +| `ctx_checktaskcompletion` | After a write, detect silently completed tasks |
     
     You don't invoke these yourself. The agent uses them as needed.
     
    diff --git a/docs/security/design.md b/docs/security/design.md
    index e4f56fa34..086fe71c8 100644
    --- a/docs/security/design.md
    +++ b/docs/security/design.md
    @@ -136,7 +136,7 @@ if sendErr := notify.Send(channel, msg, sessionID, ref); sendErr != nil {
     
     The `nudge.Relay` helper in `internal/cli/system/core/nudge`
     enforces this for the common "log + webhook" pair. Hook `Run`
    -functions that compose their own sequence (`session_event`,
    +functions that compose their own sequence (`sessionevent`,
     `heartbeat`, several `check_*` hooks) follow the same ordering
     explicitly.
     
    diff --git a/internal/assets/commands/text/mcp.yaml b/internal/assets/commands/text/mcp.yaml
    index 8785a546d..847658bab 100644
    --- a/internal/assets/commands/text/mcp.yaml
    +++ b/internal/assets/commands/text/mcp.yaml
    @@ -229,7 +229,7 @@ mcp.prompt-checkpoint-steps:
     
         4. Run ctx_compact if needed
     
    -    5. Call ctx_session_event type="end" when done
    +    5. Call ctx_sessionevent type="end" when done
     
     
         '
    @@ -360,7 +360,7 @@ mcp.format-wrote:
       short: Wrote %s to .context/%s.
     
     mcp.gov-session-not-started:
    -  short: '⚠ Session not started. Call ctx_session_event(type="start") to enable tracking.'
    +  short: '⚠ Session not started. Call ctx_sessionevent(type="start") to enable tracking.'
     mcp.gov-context-not-loaded:
       short: '⚠ Context not loaded. Call ctx_status() to load context before proceeding.'
     mcp.gov-drift-not-checked:
    diff --git a/internal/assets/integrations/copilot/copilot-instructions.md b/internal/assets/integrations/copilot/copilot-instructions.md
    index 0d63db10b..7dd6a0818 100644
    --- a/internal/assets/integrations/copilot/copilot-instructions.md
    +++ b/internal/assets/integrations/copilot/copilot-instructions.md
    @@ -119,8 +119,8 @@ validation, session tracking, and boundary checks automatically.
     | `ctx_next`                  | Get the next task to work on         |
     | `ctx_compact`               | Archive completed tasks              |
     | `ctx_watch_update`          | Write entry and queue for review     |
    -| `ctx_check_task_completion` | Match recent work to open tasks      |
    -| `ctx_session_event`         | Signal session start or end          |
    +| `ctx_checktaskcompletion` | Match recent work to open tasks      |
    +| `ctx_sessionevent`         | Signal session start or end          |
     | `ctx_remind`                | List pending reminders               |
     
     **Rule**: Do NOT run `ctx` in the terminal when the equivalent MCP tool
    @@ -135,9 +135,9 @@ responses when governance actions are overdue. Follow this protocol:
     
     ### Session Lifecycle
     
    -1. **BEFORE any work**: call `ctx_session_event(type="start")`, then
    +1. **BEFORE any work**: call `ctx_sessionevent(type="start")`, then
        `ctx_status()` to load context.
    -2. **Before ending**: call `ctx_session_event(type="end")` to flush
    +2. **Before ending**: call `ctx_sessionevent(type="end")` to flush
        pending state.
     
     ### During Work
    @@ -145,7 +145,7 @@ responses when governance actions are overdue. Follow this protocol:
     - **After making a decision or discovering a gotcha**: call `ctx_add()`
       to persist it immediately, not at session end.
     - **After completing a task**: call `ctx_complete()` or
    -  `ctx_check_task_completion()`.
    +  `ctx_checktaskcompletion()`.
     - **Every 10-15 tool calls or 15 minutes**: call `ctx_drift()` to
       check for stale context.
     - **Before git commit**: call `ctx_status()` to verify context health.
    diff --git a/internal/audit/magic_values_test.go b/internal/audit/magic_values_test.go
    index 0db455345..8451b602c 100644
    --- a/internal/audit/magic_values_test.go
    +++ b/internal/audit/magic_values_test.go
    @@ -80,7 +80,7 @@ var exemptPackagePaths = []string{
     	"internal/err/",
     	"internal/hub",
     	"internal/cli/hub/core/server",
    -	"internal/cli/initialize/core/claude_check",
    +	"internal/cli/initialize/core/claudecheck",
     	"internal/sysinfo",
     }
     
    diff --git a/internal/bootstrap/cmd.go b/internal/bootstrap/cmd.go
    index 377315506..efacbd9ed 100644
    --- a/internal/bootstrap/cmd.go
    +++ b/internal/bootstrap/cmd.go
    @@ -22,9 +22,9 @@ import (
     	embedFlag "github.com/ActiveMemory/ctx/internal/config/embed/flag"
     	"github.com/ActiveMemory/ctx/internal/config/flag"
     	ctxContext "github.com/ActiveMemory/ctx/internal/context/validate"
    -	errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta"
    +	errGitmeta "github.com/ActiveMemory/ctx/internal/err/gitmeta"
     	errInit "github.com/ActiveMemory/ctx/internal/err/initialize"
    -	"github.com/ActiveMemory/ctx/internal/git_meta"
    +	"github.com/ActiveMemory/ctx/internal/gitmeta"
     	"github.com/ActiveMemory/ctx/internal/rc"
     	writeBootstrap "github.com/ActiveMemory/ctx/internal/write/bootstrap"
     )
    diff --git a/internal/cli/change/core/detect/detect.go b/internal/cli/change/core/detect/detect.go
    index 91e67d71e..73464cedf 100644
    --- a/internal/cli/change/core/detect/detect.go
    +++ b/internal/cli/change/core/detect/detect.go
    @@ -15,7 +15,7 @@ import (
     
     	"github.com/ActiveMemory/ctx/internal/config/dir"
     	"github.com/ActiveMemory/ctx/internal/config/event"
    -	"github.com/ActiveMemory/ctx/internal/config/load_gate"
    +	"github.com/ActiveMemory/ctx/internal/config/loadgate"
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	"github.com/ActiveMemory/ctx/internal/io"
     	"github.com/ActiveMemory/ctx/internal/rc"
    @@ -49,7 +49,7 @@ func FromMarkers() (time.Time, error) {
     
     	var markers []markerInfo
     	for _, e := range entries {
    -		if !strings.HasPrefix(e.Name(), load_gate.PrefixCtxLoaded) {
    +		if !strings.HasPrefix(e.Name(), loadgate.PrefixCtxLoaded) {
     			continue
     		}
     		info, infoErr := e.Info()
    @@ -98,7 +98,7 @@ func FromEvents() (time.Time, error) {
     	// Scan in reverse for the last context-load-gate event.
     	for i := len(lines) - 1; i >= 0; i-- {
     		line := lines[i]
    -		if !strings.Contains(line, load_gate.EventContextLoadGate) {
    +		if !strings.Contains(line, loadgate.EventContextLoadGate) {
     			continue
     		}
     		if t, ok := ExtractTimestamp(line); ok {
    diff --git a/internal/cli/change/core/detect/parse.go b/internal/cli/change/core/detect/parse.go
    index bd4e9a503..7ef1472ca 100644
    --- a/internal/cli/change/core/detect/parse.go
    +++ b/internal/cli/change/core/detect/parse.go
    @@ -13,7 +13,7 @@ import (
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
    -	"github.com/ActiveMemory/ctx/internal/config/load_gate"
    +	"github.com/ActiveMemory/ctx/internal/config/loadgate"
     	cfgTime "github.com/ActiveMemory/ctx/internal/config/time"
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	"github.com/ActiveMemory/ctx/internal/format"
    @@ -94,7 +94,7 @@ func ParseSinceFlag(since string) (time.Time, string, error) {
     //   - time.Time: Parsed timestamp
     //   - bool: True if extraction succeeded
     func ExtractTimestamp(jsonLine string) (time.Time, bool) {
    -	key := load_gate.JSONKeyTimestamp
    +	key := loadgate.JSONKeyTimestamp
     	idx := strings.Index(jsonLine, key)
     	if idx < 0 {
     		return time.Time{}, false
    diff --git a/internal/cli/initialize/cmd/root/run.go b/internal/cli/initialize/cmd/root/run.go
    index e321774ed..6ae8e5299 100644
    --- a/internal/cli/initialize/cmd/root/run.go
    +++ b/internal/cli/initialize/cmd/root/run.go
    @@ -20,7 +20,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/assets/read/template"
     	"github.com/ActiveMemory/ctx/internal/cli/initialize/core/backup"
     	coreClaude "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude"
    -	coreCC "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude_check"
    +	coreCC "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claudecheck"
     	"github.com/ActiveMemory/ctx/internal/cli/initialize/core/entry"
     	coreKB "github.com/ActiveMemory/ctx/internal/cli/initialize/core/kb"
     	coreMerge "github.com/ActiveMemory/ctx/internal/cli/initialize/core/merge"
    diff --git a/internal/cli/initialize/core/claude_check/detail.go b/internal/cli/initialize/core/claudecheck/detail.go
    similarity index 99%
    rename from internal/cli/initialize/core/claude_check/detail.go
    rename to internal/cli/initialize/core/claudecheck/detail.go
    index b7a8374ae..8eac3ea82 100644
    --- a/internal/cli/initialize/core/claude_check/detail.go
    +++ b/internal/cli/initialize/core/claudecheck/detail.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     import (
     	"os"
    diff --git a/internal/cli/initialize/core/claude_check/doc.go b/internal/cli/initialize/core/claudecheck/doc.go
    similarity index 95%
    rename from internal/cli/initialize/core/claude_check/doc.go
    rename to internal/cli/initialize/core/claudecheck/doc.go
    index 48865dd24..54b624689 100644
    --- a/internal/cli/initialize/core/claude_check/doc.go
    +++ b/internal/cli/initialize/core/claudecheck/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package claude_check detects the install state of Claude
    +// Package claudecheck detects the install state of Claude
     // Code and the ctx plugin so `ctx init` and `ctx setup
     // claude-code` can print **stage-aware** guidance instead of
     // dumping every possible setup step at once.
    @@ -42,4 +42,4 @@
     // not cached because users frequently install /
     // uninstall mid-session and stale-cache bugs are worse
     // than the trivial re-read cost.
    -package claude_check
    +package claudecheck
    diff --git a/internal/cli/initialize/core/claude_check/hint.go b/internal/cli/initialize/core/claudecheck/hint.go
    similarity index 99%
    rename from internal/cli/initialize/core/claude_check/hint.go
    rename to internal/cli/initialize/core/claudecheck/hint.go
    index 565a8ae73..c66a204a7 100644
    --- a/internal/cli/initialize/core/claude_check/hint.go
    +++ b/internal/cli/initialize/core/claudecheck/hint.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     import (
     	"github.com/spf13/cobra"
    diff --git a/internal/cli/initialize/core/claude_check/parse.go b/internal/cli/initialize/core/claudecheck/parse.go
    similarity index 98%
    rename from internal/cli/initialize/core/claude_check/parse.go
    rename to internal/cli/initialize/core/claudecheck/parse.go
    index 8d37748e3..f25ac8d65 100644
    --- a/internal/cli/initialize/core/claude_check/parse.go
    +++ b/internal/cli/initialize/core/claudecheck/parse.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     import (
     	"encoding/json"
    diff --git a/internal/cli/initialize/core/claude_check/render.go b/internal/cli/initialize/core/claudecheck/render.go
    similarity index 99%
    rename from internal/cli/initialize/core/claude_check/render.go
    rename to internal/cli/initialize/core/claudecheck/render.go
    index 64b8ff77d..53195a208 100644
    --- a/internal/cli/initialize/core/claude_check/render.go
    +++ b/internal/cli/initialize/core/claudecheck/render.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     import (
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
    diff --git a/internal/cli/initialize/core/claude_check/state.go b/internal/cli/initialize/core/claudecheck/state.go
    similarity index 97%
    rename from internal/cli/initialize/core/claude_check/state.go
    rename to internal/cli/initialize/core/claudecheck/state.go
    index 116ab851e..48c8c60d3 100644
    --- a/internal/cli/initialize/core/claude_check/state.go
    +++ b/internal/cli/initialize/core/claudecheck/state.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     import (
     	"os/exec"
    diff --git a/internal/cli/initialize/core/claude_check/types.go b/internal/cli/initialize/core/claudecheck/types.go
    similarity index 99%
    rename from internal/cli/initialize/core/claude_check/types.go
    rename to internal/cli/initialize/core/claudecheck/types.go
    index 8203c1668..d43684dbf 100644
    --- a/internal/cli/initialize/core/claude_check/types.go
    +++ b/internal/cli/initialize/core/claudecheck/types.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package claude_check
    +package claudecheck
     
     // PluginDetails describes a fully-installed ctx plugin for
     // the Ready-state output. All string fields are
    diff --git a/internal/cli/initialize/core/doc.go b/internal/cli/initialize/core/doc.go
    index 5c63ec2af..92ade992a 100644
    --- a/internal/cli/initialize/core/doc.go
    +++ b/internal/cli/initialize/core/doc.go
    @@ -21,7 +21,7 @@
     //     overwriting existing files.
     //   - claude: creates or merges CLAUDE.md with the
     //     ctx-managed section.
    -//   - claude_check: validates CLAUDE.md structure and
    +//   - claudecheck: validates CLAUDE.md structure and
     //     renders diagnostic hints.
     //   - entry: creates context file templates (TASKS.md,
     //     DECISIONS.md, etc.) and locates insertion points
    diff --git a/internal/cli/initialize/core/plugin/doc.go b/internal/cli/initialize/core/plugin/doc.go
    index c36d94e44..08cc1f05a 100644
    --- a/internal/cli/initialize/core/plugin/doc.go
    +++ b/internal/cli/initialize/core/plugin/doc.go
    @@ -7,7 +7,7 @@
     // Package plugin handles **Claude Code plugin enablement**
     // during `ctx init`, the read/write side of the same
     // settings files that
    -// [internal/cli/initialize/core/claude_check] only reads.
    +// [internal/cli/initialize/core/claudecheck] only reads.
     //
     // Claude Code keeps two layers of plugin state:
     //
    diff --git a/internal/cli/initialize/doc.go b/internal/cli/initialize/doc.go
    index 5c46c4bf5..5cc6d2a6b 100644
    --- a/internal/cli/initialize/doc.go
    +++ b/internal/cli/initialize/doc.go
    @@ -49,7 +49,7 @@
     //     foundation file creation.
     //   - **[core/plugin]**: Claude Code plugin
     //     detection and global enablement.
    -//   - **[core/claude_check]**: stage-aware detection of
    +//   - **[core/claudecheck]**: stage-aware detection of
     //     Claude Code state used to print contextual
     //     guidance during init.
     //   - **[core/merge]**: create-or-merge file
    diff --git a/internal/cli/remind/doc.go b/internal/cli/remind/doc.go
    index d423ca3bc..3fc132a3d 100644
    --- a/internal/cli/remind/doc.go
    +++ b/internal/cli/remind/doc.go
    @@ -26,8 +26,8 @@
     //
     // # The Surface Path
     //
    -// At session start, the `check_reminder` system hook
    -// (`internal/cli/system/cmd/check_reminder`) reads the
    +// At session start, the `checkreminder` system hook
    +// (`internal/cli/system/cmd/checkreminder`) reads the
     // reminder store, filters by date and dismissal
     // status, and emits the un-dismissed entries through
     // the VERBATIM relay so the user (and the agent) both
    diff --git a/internal/cli/setup/cmd/root/run.go b/internal/cli/setup/cmd/root/run.go
    index e6c0cf04c..dd0b414e5 100644
    --- a/internal/cli/setup/cmd/root/run.go
    +++ b/internal/cli/setup/cmd/root/run.go
    @@ -13,11 +13,11 @@ import (
     
     	"github.com/ActiveMemory/ctx/internal/assets/read/agent"
     	"github.com/ActiveMemory/ctx/internal/assets/read/desc"
    -	coreCC "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claude_check"
    +	coreCC "github.com/ActiveMemory/ctx/internal/cli/initialize/core/claudecheck"
     	coreAgents "github.com/ActiveMemory/ctx/internal/cli/setup/core/agents"
     	coreCline "github.com/ActiveMemory/ctx/internal/cli/setup/core/cline"
     	coreCopilot "github.com/ActiveMemory/ctx/internal/cli/setup/core/copilot"
    -	coreCopCLI "github.com/ActiveMemory/ctx/internal/cli/setup/core/copilot_cli"
    +	coreCopCLI "github.com/ActiveMemory/ctx/internal/cli/setup/core/copilotcli"
     	coreCursor "github.com/ActiveMemory/ctx/internal/cli/setup/core/cursor"
     	coreKiro "github.com/ActiveMemory/ctx/internal/cli/setup/core/kiro"
     	coreOpenCode "github.com/ActiveMemory/ctx/internal/cli/setup/core/opencode"
    diff --git a/internal/cli/setup/core/copilot_cli/copilot_cli.go b/internal/cli/setup/core/copilotcli/copilotcli.go
    similarity index 100%
    rename from internal/cli/setup/core/copilot_cli/copilot_cli.go
    rename to internal/cli/setup/core/copilotcli/copilotcli.go
    diff --git a/internal/cli/setup/core/copilot_cli/doc.go b/internal/cli/setup/core/copilotcli/doc.go
    similarity index 100%
    rename from internal/cli/setup/core/copilot_cli/doc.go
    rename to internal/cli/setup/core/copilotcli/doc.go
    diff --git a/internal/cli/setup/core/copilot_cli/github_asset.go b/internal/cli/setup/core/copilotcli/github_asset.go
    similarity index 100%
    rename from internal/cli/setup/core/copilot_cli/github_asset.go
    rename to internal/cli/setup/core/copilotcli/github_asset.go
    diff --git a/internal/cli/setup/core/copilot_cli/mcp.go b/internal/cli/setup/core/copilotcli/mcp.go
    similarity index 100%
    rename from internal/cli/setup/core/copilot_cli/mcp.go
    rename to internal/cli/setup/core/copilotcli/mcp.go
    diff --git a/internal/cli/setup/core/copilot_cli/skill.go b/internal/cli/setup/core/copilotcli/skill.go
    similarity index 100%
    rename from internal/cli/setup/core/copilot_cli/skill.go
    rename to internal/cli/setup/core/copilotcli/skill.go
    diff --git a/internal/cli/system/cmd/block_non_path_ctx/cmd.go b/internal/cli/system/cmd/blocknonpathctx/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/block_non_path_ctx/cmd.go
    rename to internal/cli/system/cmd/blocknonpathctx/cmd.go
    index 4d6a754ff..647703db2 100644
    --- a/internal/cli/system/cmd/block_non_path_ctx/cmd.go
    +++ b/internal/cli/system/cmd/blocknonpathctx/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package block_non_path_ctx
    +package blocknonpathctx
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/block_non_path_ctx/doc.go b/internal/cli/system/cmd/blocknonpathctx/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/block_non_path_ctx/doc.go
    rename to internal/cli/system/cmd/blocknonpathctx/doc.go
    index 43ae0bf68..6ba8ac5f9 100644
    --- a/internal/cli/system/cmd/block_non_path_ctx/doc.go
    +++ b/internal/cli/system/cmd/blocknonpathctx/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package block_non_path_ctx implements the
    +// Package blocknonpathctx implements the
     // **`ctx system block-non-path-ctx`** hidden hook,
     // which blocks invocations of ctx that bypass the
     // PATH-installed binary.
    @@ -45,4 +45,4 @@
     // pattern, loads the variant message via
     // [core/message.Load], and marshals the block response.
     // Relay notifications go through [core/nudge.Relay].
    -package block_non_path_ctx
    +package blocknonpathctx
    diff --git a/internal/cli/system/cmd/block_non_path_ctx/run.go b/internal/cli/system/cmd/blocknonpathctx/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/block_non_path_ctx/run.go
    rename to internal/cli/system/cmd/blocknonpathctx/run.go
    index 0a2ee699d..59d7d3617 100644
    --- a/internal/cli/system/cmd/block_non_path_ctx/run.go
    +++ b/internal/cli/system/cmd/blocknonpathctx/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package block_non_path_ctx
    +package blocknonpathctx
     
     import (
     	"encoding/json"
    diff --git a/internal/cli/system/cmd/check_anchor_drift/cmd.go b/internal/cli/system/cmd/checkanchordrift/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_anchor_drift/cmd.go
    rename to internal/cli/system/cmd/checkanchordrift/cmd.go
    index 9b112cf40..de7b2e916 100644
    --- a/internal/cli/system/cmd/check_anchor_drift/cmd.go
    +++ b/internal/cli/system/cmd/checkanchordrift/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_anchor_drift
    +package checkanchordrift
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_anchor_drift/doc.go b/internal/cli/system/cmd/checkanchordrift/doc.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_anchor_drift/doc.go
    rename to internal/cli/system/cmd/checkanchordrift/doc.go
    index 822c68149..c5846640f 100644
    --- a/internal/cli/system/cmd/check_anchor_drift/doc.go
    +++ b/internal/cli/system/cmd/checkanchordrift/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_anchor_drift implements the
    +// Package checkanchordrift implements the
     // **`ctx system check-anchor-drift`** hook (added by
     // specs/single-source-context-anchor.md).
     //
    @@ -61,4 +61,4 @@
     // hook is a diagnostic: it must accept any observed value
     // (including unset, including non-canonical) so it can
     // describe reality, not impose policy.
    -package check_anchor_drift
    +package checkanchordrift
    diff --git a/internal/cli/system/cmd/check_anchor_drift/run.go b/internal/cli/system/cmd/checkanchordrift/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_anchor_drift/run.go
    rename to internal/cli/system/cmd/checkanchordrift/run.go
    index df9691bba..57b6f43ab 100644
    --- a/internal/cli/system/cmd/check_anchor_drift/run.go
    +++ b/internal/cli/system/cmd/checkanchordrift/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_anchor_drift
    +package checkanchordrift
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_anchor_drift/run_test.go b/internal/cli/system/cmd/checkanchordrift/run_test.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_anchor_drift/run_test.go
    rename to internal/cli/system/cmd/checkanchordrift/run_test.go
    index 3e9d960ad..df2ada7d7 100644
    --- a/internal/cli/system/cmd/check_anchor_drift/run_test.go
    +++ b/internal/cli/system/cmd/checkanchordrift/run_test.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_anchor_drift
    +package checkanchordrift
     
     import (
     	"bytes"
    diff --git a/internal/cli/system/cmd/check_anchor_drift/testmain_test.go b/internal/cli/system/cmd/checkanchordrift/testmain_test.go
    similarity index 94%
    rename from internal/cli/system/cmd/check_anchor_drift/testmain_test.go
    rename to internal/cli/system/cmd/checkanchordrift/testmain_test.go
    index 7501d6560..5eb2b3fe2 100644
    --- a/internal/cli/system/cmd/check_anchor_drift/testmain_test.go
    +++ b/internal/cli/system/cmd/checkanchordrift/testmain_test.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_anchor_drift
    +package checkanchordrift
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_ceremony/cmd.go b/internal/cli/system/cmd/checkceremony/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_ceremony/cmd.go
    rename to internal/cli/system/cmd/checkceremony/cmd.go
    index 600c38caa..e16cfae12 100644
    --- a/internal/cli/system/cmd/check_ceremony/cmd.go
    +++ b/internal/cli/system/cmd/checkceremony/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_ceremony
    +package checkceremony
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_ceremony/doc.go b/internal/cli/system/cmd/checkceremony/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_ceremony/doc.go
    rename to internal/cli/system/cmd/checkceremony/doc.go
    index 9f4acecab..e89d1a1b2 100644
    --- a/internal/cli/system/cmd/check_ceremony/doc.go
    +++ b/internal/cli/system/cmd/checkceremony/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_ceremony implements the
    +// Package checkceremony implements the
     // **`ctx system check-ceremony`** hidden hook, which
     // nudges adoption of session ceremonies when they are
     // missing from recent journal entries.
    @@ -47,4 +47,4 @@
     // [core/ceremony.ScanJournalsForCeremonies], then
     // emits the nudge via [write/setup.Nudge] and sends
     // a relay notification through [core/nudge].
    -package check_ceremony
    +package checkceremony
    diff --git a/internal/cli/system/cmd/check_ceremony/run.go b/internal/cli/system/cmd/checkceremony/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_ceremony/run.go
    rename to internal/cli/system/cmd/checkceremony/run.go
    index 215e5d255..f19e3eb76 100644
    --- a/internal/cli/system/cmd/check_ceremony/run.go
    +++ b/internal/cli/system/cmd/checkceremony/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_ceremony
    +package checkceremony
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_context_size/cmd.go b/internal/cli/system/cmd/checkcontextsize/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_context_size/cmd.go
    rename to internal/cli/system/cmd/checkcontextsize/cmd.go
    index 7a39536f3..1963a3000 100644
    --- a/internal/cli/system/cmd/check_context_size/cmd.go
    +++ b/internal/cli/system/cmd/checkcontextsize/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_context_size
    +package checkcontextsize
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_context_size/doc.go b/internal/cli/system/cmd/checkcontextsize/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_context_size/doc.go
    rename to internal/cli/system/cmd/checkcontextsize/doc.go
    index b623fcdf8..4c29e6cc3 100644
    --- a/internal/cli/system/cmd/check_context_size/doc.go
    +++ b/internal/cli/system/cmd/checkcontextsize/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_context_size implements the
    +// Package checkcontextsize implements the
     // **`ctx system check-context-size`** hook, the prompt-
     // counter that fires the periodic "context checkpoint"
     // nudge so users remember to wrap up their session
    @@ -42,4 +42,4 @@
     // Single-process per session. The hook is
     // invoked by the AI tool's hook runtime
     // serially per turn.
    -package check_context_size
    +package checkcontextsize
    diff --git a/internal/cli/system/cmd/check_context_size/run.go b/internal/cli/system/cmd/checkcontextsize/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_context_size/run.go
    rename to internal/cli/system/cmd/checkcontextsize/run.go
    index 9796b50e4..64574b584 100644
    --- a/internal/cli/system/cmd/check_context_size/run.go
    +++ b/internal/cli/system/cmd/checkcontextsize/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_context_size
    +package checkcontextsize
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_freshness/cmd.go b/internal/cli/system/cmd/checkfreshness/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_freshness/cmd.go
    rename to internal/cli/system/cmd/checkfreshness/cmd.go
    index a48628266..a04ec5c56 100644
    --- a/internal/cli/system/cmd/check_freshness/cmd.go
    +++ b/internal/cli/system/cmd/checkfreshness/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_freshness
    +package checkfreshness
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_freshness/doc.go b/internal/cli/system/cmd/checkfreshness/doc.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_freshness/doc.go
    rename to internal/cli/system/cmd/checkfreshness/doc.go
    index eae206ce4..25901fded 100644
    --- a/internal/cli/system/cmd/check_freshness/doc.go
    +++ b/internal/cli/system/cmd/checkfreshness/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_freshness implements the
    +// Package checkfreshness implements the
     // **`ctx system check-freshness`** hidden hook, which
     // warns when tracked source files have not been
     // reviewed within the configured freshness window.
    @@ -47,4 +47,4 @@
     // [rc.FreshnessFiles], formats stale entries through
     // [core/drift.FormatStaleEntries], and emits the nudge
     // via [core/nudge.LoadAndEmit].
    -package check_freshness
    +package checkfreshness
    diff --git a/internal/cli/system/cmd/check_freshness/run.go b/internal/cli/system/cmd/checkfreshness/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_freshness/run.go
    rename to internal/cli/system/cmd/checkfreshness/run.go
    index 3d738a2d8..ac8fb229a 100644
    --- a/internal/cli/system/cmd/check_freshness/run.go
    +++ b/internal/cli/system/cmd/checkfreshness/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_freshness
    +package checkfreshness
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_hub_sync/cmd.go b/internal/cli/system/cmd/checkhubsync/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_hub_sync/cmd.go
    rename to internal/cli/system/cmd/checkhubsync/cmd.go
    index 39a938afe..9144c00d9 100644
    --- a/internal/cli/system/cmd/check_hub_sync/cmd.go
    +++ b/internal/cli/system/cmd/checkhubsync/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_hub_sync
    +package checkhubsync
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_hub_sync/doc.go b/internal/cli/system/cmd/checkhubsync/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_hub_sync/doc.go
    rename to internal/cli/system/cmd/checkhubsync/doc.go
    index f9c89bb5d..08a4be64d 100644
    --- a/internal/cli/system/cmd/check_hub_sync/doc.go
    +++ b/internal/cli/system/cmd/checkhubsync/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_hub_sync implements the
    +// Package checkhubsync implements the
     // **`ctx system check-hub-sync`** hidden hook, which
     // pulls new entries from a registered ctx Hub at
     // session start.
    @@ -44,4 +44,4 @@
     // delegates the actual sync to [core/hubsync.Sync].
     // The daily throttle marker is managed via
     // [io.TouchFile].
    -package check_hub_sync
    +package checkhubsync
    diff --git a/internal/cli/system/cmd/check_hub_sync/run.go b/internal/cli/system/cmd/checkhubsync/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_hub_sync/run.go
    rename to internal/cli/system/cmd/checkhubsync/run.go
    index 87e02ee81..8805e5575 100644
    --- a/internal/cli/system/cmd/check_hub_sync/run.go
    +++ b/internal/cli/system/cmd/checkhubsync/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_hub_sync
    +package checkhubsync
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_journal/cmd.go b/internal/cli/system/cmd/checkjournal/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_journal/cmd.go
    rename to internal/cli/system/cmd/checkjournal/cmd.go
    index db39e0b4a..d76ca0d50 100644
    --- a/internal/cli/system/cmd/check_journal/cmd.go
    +++ b/internal/cli/system/cmd/checkjournal/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_journal
    +package checkjournal
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_journal/doc.go b/internal/cli/system/cmd/checkjournal/doc.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_journal/doc.go
    rename to internal/cli/system/cmd/checkjournal/doc.go
    index b71c26f46..10141a479 100644
    --- a/internal/cli/system/cmd/check_journal/doc.go
    +++ b/internal/cli/system/cmd/checkjournal/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_journal implements the
    +// Package checkjournal implements the
     // **`ctx system check-journal`** hidden hook, which
     // reminds the user about unprocessed journal entries
     // at session start.
    @@ -51,4 +51,4 @@
     // the appropriate message template via
     // [core/message.Load], and emits the nudge through
     // [write/setup.Nudge] and [core/nudge].
    -package check_journal
    +package checkjournal
    diff --git a/internal/cli/system/cmd/check_journal/run.go b/internal/cli/system/cmd/checkjournal/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_journal/run.go
    rename to internal/cli/system/cmd/checkjournal/run.go
    index 0a2f58c0b..3d9d7520e 100644
    --- a/internal/cli/system/cmd/check_journal/run.go
    +++ b/internal/cli/system/cmd/checkjournal/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_journal
    +package checkjournal
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_knowledge/cmd.go b/internal/cli/system/cmd/checkknowledge/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_knowledge/cmd.go
    rename to internal/cli/system/cmd/checkknowledge/cmd.go
    index 48dc2aaf5..4120402b5 100644
    --- a/internal/cli/system/cmd/check_knowledge/cmd.go
    +++ b/internal/cli/system/cmd/checkknowledge/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_knowledge
    +package checkknowledge
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_knowledge/doc.go b/internal/cli/system/cmd/checkknowledge/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_knowledge/doc.go
    rename to internal/cli/system/cmd/checkknowledge/doc.go
    index 84a2fb8c6..b3a5c5346 100644
    --- a/internal/cli/system/cmd/check_knowledge/doc.go
    +++ b/internal/cli/system/cmd/checkknowledge/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_knowledge implements the
    +// Package checkknowledge implements the
     // **`ctx system check-knowledge`** hidden hook, which
     // warns when context knowledge files grow beyond their
     // configured size thresholds.
    @@ -46,4 +46,4 @@
     // health check to [core/knowledge.CheckHealth], which
     // handles file scanning, threshold comparison, and
     // nudge box formatting in a single call.
    -package check_knowledge
    +package checkknowledge
    diff --git a/internal/cli/system/cmd/check_knowledge/run.go b/internal/cli/system/cmd/checkknowledge/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_knowledge/run.go
    rename to internal/cli/system/cmd/checkknowledge/run.go
    index f00bb56fd..bafc3e321 100644
    --- a/internal/cli/system/cmd/check_knowledge/run.go
    +++ b/internal/cli/system/cmd/checkknowledge/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_knowledge
    +package checkknowledge
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_map_staleness/cmd.go b/internal/cli/system/cmd/checkmapstaleness/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_map_staleness/cmd.go
    rename to internal/cli/system/cmd/checkmapstaleness/cmd.go
    index cf698f30c..4443845f1 100644
    --- a/internal/cli/system/cmd/check_map_staleness/cmd.go
    +++ b/internal/cli/system/cmd/checkmapstaleness/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_map_staleness
    +package checkmapstaleness
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_map_staleness/doc.go b/internal/cli/system/cmd/checkmapstaleness/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_map_staleness/doc.go
    rename to internal/cli/system/cmd/checkmapstaleness/doc.go
    index d23e278eb..f23050554 100644
    --- a/internal/cli/system/cmd/check_map_staleness/doc.go
    +++ b/internal/cli/system/cmd/checkmapstaleness/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_map_staleness implements the
    +// Package checkmapstaleness implements the
     // **`ctx system check-map-staleness`** hidden hook,
     // which detects when the architecture map has not been
     // refreshed for too long.
    @@ -46,4 +46,4 @@
     // commits via [core/health.CountModuleCommits], and
     // emits the warning through
     // [core/health.EmitMapStalenessWarning].
    -package check_map_staleness
    +package checkmapstaleness
    diff --git a/internal/cli/system/cmd/check_map_staleness/run.go b/internal/cli/system/cmd/checkmapstaleness/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_map_staleness/run.go
    rename to internal/cli/system/cmd/checkmapstaleness/run.go
    index 70f8cd518..0efc5a9e7 100644
    --- a/internal/cli/system/cmd/check_map_staleness/run.go
    +++ b/internal/cli/system/cmd/checkmapstaleness/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_map_staleness
    +package checkmapstaleness
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_memory_drift/cmd.go b/internal/cli/system/cmd/checkmemorydrift/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_memory_drift/cmd.go
    rename to internal/cli/system/cmd/checkmemorydrift/cmd.go
    index 29eb5ad6b..1613130c2 100644
    --- a/internal/cli/system/cmd/check_memory_drift/cmd.go
    +++ b/internal/cli/system/cmd/checkmemorydrift/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_memory_drift
    +package checkmemorydrift
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_memory_drift/doc.go b/internal/cli/system/cmd/checkmemorydrift/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_memory_drift/doc.go
    rename to internal/cli/system/cmd/checkmemorydrift/doc.go
    index 66d809408..8db27c065 100644
    --- a/internal/cli/system/cmd/check_memory_drift/doc.go
    +++ b/internal/cli/system/cmd/checkmemorydrift/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_memory_drift implements the
    +// Package checkmemorydrift implements the
     // **`ctx system check-memory-drift`** hidden hook,
     // which detects drift between the agent's working
     // memory and the persisted MEMORY.md file.
    @@ -45,4 +45,4 @@
     // memory source via [memory.DiscoverPath], checks for
     // drift with [memory.HasDrift], and emits the nudge
     // through [core/nudge.LoadAndEmit].
    -package check_memory_drift
    +package checkmemorydrift
    diff --git a/internal/cli/system/cmd/check_memory_drift/run.go b/internal/cli/system/cmd/checkmemorydrift/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_memory_drift/run.go
    rename to internal/cli/system/cmd/checkmemorydrift/run.go
    index 7afec9cf3..1bdfb9267 100644
    --- a/internal/cli/system/cmd/check_memory_drift/run.go
    +++ b/internal/cli/system/cmd/checkmemorydrift/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_memory_drift
    +package checkmemorydrift
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_persistence/cmd.go b/internal/cli/system/cmd/checkpersistence/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_persistence/cmd.go
    rename to internal/cli/system/cmd/checkpersistence/cmd.go
    index d16f7477e..ab7bb8be9 100644
    --- a/internal/cli/system/cmd/check_persistence/cmd.go
    +++ b/internal/cli/system/cmd/checkpersistence/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_persistence
    +package checkpersistence
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_persistence/doc.go b/internal/cli/system/cmd/checkpersistence/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_persistence/doc.go
    rename to internal/cli/system/cmd/checkpersistence/doc.go
    index c1a4b204c..235f2da67 100644
    --- a/internal/cli/system/cmd/check_persistence/doc.go
    +++ b/internal/cli/system/cmd/checkpersistence/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_persistence implements the
    +// Package checkpersistence implements the
     // **`ctx system check-persistence`** hook, the nudge
     // that tells the user (and the agent) "you have done a
     // lot of work without persisting anything to
    @@ -40,4 +40,4 @@
     // # Concurrency
     //
     // Single-process per session.
    -package check_persistence
    +package checkpersistence
    diff --git a/internal/cli/system/cmd/check_persistence/run.go b/internal/cli/system/cmd/checkpersistence/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_persistence/run.go
    rename to internal/cli/system/cmd/checkpersistence/run.go
    index e6e481fa9..5a6bcd24b 100644
    --- a/internal/cli/system/cmd/check_persistence/run.go
    +++ b/internal/cli/system/cmd/checkpersistence/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_persistence
    +package checkpersistence
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_reminder/cmd.go b/internal/cli/system/cmd/checkreminder/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_reminder/cmd.go
    rename to internal/cli/system/cmd/checkreminder/cmd.go
    index b05ebcf24..86e6fa533 100644
    --- a/internal/cli/system/cmd/check_reminder/cmd.go
    +++ b/internal/cli/system/cmd/checkreminder/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_reminder
    +package checkreminder
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_reminder/doc.go b/internal/cli/system/cmd/checkreminder/doc.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_reminder/doc.go
    rename to internal/cli/system/cmd/checkreminder/doc.go
    index c0847eff2..9cb1a5ec6 100644
    --- a/internal/cli/system/cmd/check_reminder/doc.go
    +++ b/internal/cli/system/cmd/checkreminder/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_reminder implements the
    +// Package checkreminder implements the
     // **`ctx system check-reminder`** hidden hook, which
     // surfaces pending reminders at session start so the
     // agent can act on deferred tasks.
    @@ -47,4 +47,4 @@
     // through [core/provenance.Emit], loads reminders from
     // [remind/core/store.Read], filters by date, and emits
     // the nudge via [core/nudge.LoadAndEmit].
    -package check_reminder
    +package checkreminder
    diff --git a/internal/cli/system/cmd/check_reminder/run.go b/internal/cli/system/cmd/checkreminder/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_reminder/run.go
    rename to internal/cli/system/cmd/checkreminder/run.go
    index cf74248ac..13bc446ea 100644
    --- a/internal/cli/system/cmd/check_reminder/run.go
    +++ b/internal/cli/system/cmd/checkreminder/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_reminder
    +package checkreminder
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_reminder/run_test.go b/internal/cli/system/cmd/checkreminder/run_test.go
    similarity index 93%
    rename from internal/cli/system/cmd/check_reminder/run_test.go
    rename to internal/cli/system/cmd/checkreminder/run_test.go
    index f1c2e1750..41bbffe58 100644
    --- a/internal/cli/system/cmd/check_reminder/run_test.go
    +++ b/internal/cli/system/cmd/checkreminder/run_test.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_reminder_test
    +package checkreminder_test
     
     import (
     	"bytes"
    @@ -15,7 +15,7 @@ import (
     
     	"github.com/spf13/cobra"
     
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_reminder"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkreminder"
     	"github.com/ActiveMemory/ctx/internal/config/dir"
     	"github.com/ActiveMemory/ctx/internal/config/env"
     	"github.com/ActiveMemory/ctx/internal/rc"
    @@ -60,7 +60,7 @@ func TestRun_NoLeakInUninitializedProject(t *testing.T) {
     	cmd.SetOut(io.Discard)
     	cmd.SetErr(io.Discard)
     
    -	if err := check_reminder.Run(cmd, r); err != nil {
    +	if err := checkreminder.Run(cmd, r); err != nil {
     		t.Fatalf("Run() error = %v, want nil (hooks must never fail)", err)
     	}
     
    diff --git a/internal/cli/system/cmd/check_resource/cmd.go b/internal/cli/system/cmd/checkresource/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_resource/cmd.go
    rename to internal/cli/system/cmd/checkresource/cmd.go
    index 7a0381a16..d7347ce19 100644
    --- a/internal/cli/system/cmd/check_resource/cmd.go
    +++ b/internal/cli/system/cmd/checkresource/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_resource
    +package checkresource
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_resource/doc.go b/internal/cli/system/cmd/checkresource/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_resource/doc.go
    rename to internal/cli/system/cmd/checkresource/doc.go
    index 1163b5e6a..c342b6af9 100644
    --- a/internal/cli/system/cmd/check_resource/doc.go
    +++ b/internal/cli/system/cmd/checkresource/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_resource implements the
    +// Package checkresource implements the
     // **`ctx system check-resource`** hidden hook, which
     // warns when system resources reach dangerous levels.
     //
    @@ -45,4 +45,4 @@
     // through [sysinfo.Collect], evaluates thresholds with
     // [sysinfo.Evaluate], and emits the warning via
     // [core/nudge.LoadAndEmit].
    -package check_resource
    +package checkresource
    diff --git a/internal/cli/system/cmd/check_resource/run.go b/internal/cli/system/cmd/checkresource/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_resource/run.go
    rename to internal/cli/system/cmd/checkresource/run.go
    index 08cc354e1..f1761267a 100644
    --- a/internal/cli/system/cmd/check_resource/run.go
    +++ b/internal/cli/system/cmd/checkresource/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_resource
    +package checkresource
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_skill_discovery/cmd.go b/internal/cli/system/cmd/checkskilldiscovery/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_skill_discovery/cmd.go
    rename to internal/cli/system/cmd/checkskilldiscovery/cmd.go
    index 40f33b3d4..cbf6fa9c7 100644
    --- a/internal/cli/system/cmd/check_skill_discovery/cmd.go
    +++ b/internal/cli/system/cmd/checkskilldiscovery/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_skill_discovery
    +package checkskilldiscovery
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_skill_discovery/doc.go b/internal/cli/system/cmd/checkskilldiscovery/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_skill_discovery/doc.go
    rename to internal/cli/system/cmd/checkskilldiscovery/doc.go
    index 41c7154d3..2372f948d 100644
    --- a/internal/cli/system/cmd/check_skill_discovery/doc.go
    +++ b/internal/cli/system/cmd/checkskilldiscovery/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_skill_discovery implements the
    +// Package checkskilldiscovery implements the
     // **`ctx system check-skill-discovery`** hidden hook,
     // which fires a one-shot nudge mid-session to remind
     // the agent about available skills.
    @@ -49,4 +49,4 @@
     // counter from [core/counter.Read], loads the skill
     // list message via [core/message.Load], and emits
     // the nudge through [write/setup.NudgeBlock].
    -package check_skill_discovery
    +package checkskilldiscovery
    diff --git a/internal/cli/system/cmd/check_skill_discovery/run.go b/internal/cli/system/cmd/checkskilldiscovery/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_skill_discovery/run.go
    rename to internal/cli/system/cmd/checkskilldiscovery/run.go
    index 8765a19a0..c4f32821b 100644
    --- a/internal/cli/system/cmd/check_skill_discovery/run.go
    +++ b/internal/cli/system/cmd/checkskilldiscovery/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_skill_discovery
    +package checkskilldiscovery
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_task_completion/cmd.go b/internal/cli/system/cmd/checktaskcompletion/cmd.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_task_completion/cmd.go
    rename to internal/cli/system/cmd/checktaskcompletion/cmd.go
    index 27f93a5a4..321cf6d1b 100644
    --- a/internal/cli/system/cmd/check_task_completion/cmd.go
    +++ b/internal/cli/system/cmd/checktaskcompletion/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_task_completion
    +package checktaskcompletion
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_task_completion/doc.go b/internal/cli/system/cmd/checktaskcompletion/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/check_task_completion/doc.go
    rename to internal/cli/system/cmd/checktaskcompletion/doc.go
    index 0c39784a8..b1effdd19 100644
    --- a/internal/cli/system/cmd/check_task_completion/doc.go
    +++ b/internal/cli/system/cmd/checktaskcompletion/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_task_completion implements the
    +// Package checktaskcompletion implements the
     // **`ctx system check-task-completion`** hidden hook,
     // which periodically nudges the agent to update
     // TASKS.md after tool use.
    @@ -46,4 +46,4 @@
     // [core/message.Load], and emits it as a context
     // block through [write/setup.Context]. Relay
     // notifications go through [core/nudge.Relay].
    -package check_task_completion
    +package checktaskcompletion
    diff --git a/internal/cli/system/cmd/check_task_completion/run.go b/internal/cli/system/cmd/checktaskcompletion/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/check_task_completion/run.go
    rename to internal/cli/system/cmd/checktaskcompletion/run.go
    index 69a919246..7534d8cc5 100644
    --- a/internal/cli/system/cmd/check_task_completion/run.go
    +++ b/internal/cli/system/cmd/checktaskcompletion/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_task_completion
    +package checktaskcompletion
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/check_version/cmd.go b/internal/cli/system/cmd/checkversion/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/check_version/cmd.go
    rename to internal/cli/system/cmd/checkversion/cmd.go
    index 2ccf463b4..2231c1065 100644
    --- a/internal/cli/system/cmd/check_version/cmd.go
    +++ b/internal/cli/system/cmd/checkversion/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_version
    +package checkversion
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/check_version/doc.go b/internal/cli/system/cmd/checkversion/doc.go
    similarity index 96%
    rename from internal/cli/system/cmd/check_version/doc.go
    rename to internal/cli/system/cmd/checkversion/doc.go
    index cc099c909..983c2bed4 100644
    --- a/internal/cli/system/cmd/check_version/doc.go
    +++ b/internal/cli/system/cmd/checkversion/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package check_version implements the
    +// Package checkversion implements the
     // **`ctx system check-version`** hidden hook, which
     // warns when the ctx binary and the installed plugin
     // are on different versions.
    @@ -51,4 +51,4 @@
     // loads the mismatch message via [core/message.Load],
     // and emits the nudge through [write/setup.Nudge].
     // Key age is checked via [core/version.CheckKeyAge].
    -package check_version
    +package checkversion
    diff --git a/internal/cli/system/cmd/check_version/run.go b/internal/cli/system/cmd/checkversion/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/check_version/run.go
    rename to internal/cli/system/cmd/checkversion/run.go
    index 2c386837e..ec04bd748 100644
    --- a/internal/cli/system/cmd/check_version/run.go
    +++ b/internal/cli/system/cmd/checkversion/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package check_version
    +package checkversion
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/context_load_gate/cmd.go b/internal/cli/system/cmd/contextloadgate/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/context_load_gate/cmd.go
    rename to internal/cli/system/cmd/contextloadgate/cmd.go
    index c8de77548..d519a06a1 100644
    --- a/internal/cli/system/cmd/context_load_gate/cmd.go
    +++ b/internal/cli/system/cmd/contextloadgate/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package context_load_gate
    +package contextloadgate
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/context_load_gate/doc.go b/internal/cli/system/cmd/contextloadgate/doc.go
    similarity index 93%
    rename from internal/cli/system/cmd/context_load_gate/doc.go
    rename to internal/cli/system/cmd/contextloadgate/doc.go
    index cf735a799..6b3522413 100644
    --- a/internal/cli/system/cmd/context_load_gate/doc.go
    +++ b/internal/cli/system/cmd/contextloadgate/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package context_load_gate implements the hidden
    +// Package contextloadgate implements the hidden
     // "ctx system context-load-gate" cobra subcommand.
     //
     // This hook fires on the first PreToolUse event of each
    @@ -54,6 +54,6 @@
     // Token estimation is handled by context/token.
     // Change detection delegates to change/core/detect
     // and change/core/scan. State management uses
    -// system/core/state and config/load_gate. Webhook
    +// system/core/state and config/loadgate. Webhook
     // relay uses system/core/nudge.
    -package context_load_gate
    +package contextloadgate
    diff --git a/internal/cli/system/cmd/context_load_gate/run.go b/internal/cli/system/cmd/contextloadgate/run.go
    similarity index 93%
    rename from internal/cli/system/cmd/context_load_gate/run.go
    rename to internal/cli/system/cmd/contextloadgate/run.go
    index 1acc96aae..a01928e3c 100644
    --- a/internal/cli/system/cmd/context_load_gate/run.go
    +++ b/internal/cli/system/cmd/contextloadgate/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package context_load_gate
    +package contextloadgate
     
     import (
     	"fmt"
    @@ -26,7 +26,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/ctx"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	"github.com/ActiveMemory/ctx/internal/config/hook"
    -	"github.com/ActiveMemory/ctx/internal/config/load_gate"
    +	"github.com/ActiveMemory/ctx/internal/config/loadgate"
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	"github.com/ActiveMemory/ctx/internal/config/warn"
     	ctxToken "github.com/ActiveMemory/ctx/internal/context/token"
    @@ -76,7 +76,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error {
     		logWarn.Warn(warn.StateDirProbe, dirErr)
     		return nil
     	}
    -	marker := filepath.Join(tmpDir, load_gate.PrefixCtxLoaded+input.SessionID)
    +	marker := filepath.Join(tmpDir, loadgate.PrefixCtxLoaded+input.SessionID)
     
     	if _, statErr := os.Stat(marker); statErr == nil {
     		return nil // already fired this session
    @@ -88,7 +88,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error {
     
     	// Auto-prune stale session state files (best-effort, silent).
     	// Runs once per session at startup - fast directory scan.
    -	health.AutoPrune(load_gate.AutoPruneStaleDays)
    +	health.AutoPrune(loadgate.AutoPruneStaleDays)
     
     	// Unreachable under normal flow: state.Initialized() above already
     	// proved ContextDir succeeds. Kept defensive so a future ContextDir
    @@ -106,7 +106,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error {
     	content.WriteString(
     		desc.Text(text.DescKeyContextLoadGateHeader) +
     			strings.Repeat(
    -				load_gate.ContextLoadSeparatorChar, load_gate.ContextLoadSeparatorWidth,
    +				loadgate.ContextLoadSeparatorChar, loadgate.ContextLoadSeparatorWidth,
     			) +
     			token.NewlineLF + token.NewlineLF,
     	)
    @@ -142,7 +142,7 @@ func Run(cmd *cobra.Command, stdin *os.File) error {
     
     	content.WriteString(
     		strings.Repeat(
    -			load_gate.ContextLoadSeparatorChar, load_gate.ContextLoadSeparatorWidth,
    +			loadgate.ContextLoadSeparatorChar, loadgate.ContextLoadSeparatorWidth,
     		) + token.NewlineLF)
     	internalIo.SafeFprintf(&content, desc.Text(text.DescKeyContextLoadGateFooter),
     		filesLoaded, totalTokens)
    diff --git a/internal/cli/system/cmd/mark_journal/cmd.go b/internal/cli/system/cmd/markjournal/cmd.go
    similarity index 98%
    rename from internal/cli/system/cmd/mark_journal/cmd.go
    rename to internal/cli/system/cmd/markjournal/cmd.go
    index 431c44182..0fe7ea40d 100644
    --- a/internal/cli/system/cmd/mark_journal/cmd.go
    +++ b/internal/cli/system/cmd/markjournal/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package mark_journal
    +package markjournal
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/mark_journal/doc.go b/internal/cli/system/cmd/markjournal/doc.go
    similarity index 94%
    rename from internal/cli/system/cmd/mark_journal/doc.go
    rename to internal/cli/system/cmd/markjournal/doc.go
    index d6f033ccd..f37124b27 100644
    --- a/internal/cli/system/cmd/mark_journal/doc.go
    +++ b/internal/cli/system/cmd/markjournal/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package mark_journal implements the hidden
    +// Package markjournal implements the hidden
     // "ctx system mark-journal" cobra subcommand.
     //
     // This command tracks processing stages for journal
    @@ -52,5 +52,5 @@
     //
     // Stage persistence is handled by
     // system/core/journal. Output formatting is handled
    -// by write/mark_journal.
    -package mark_journal
    +// by write/markjournal.
    +package markjournal
    diff --git a/internal/cli/system/cmd/mark_journal/run.go b/internal/cli/system/cmd/markjournal/run.go
    similarity index 94%
    rename from internal/cli/system/cmd/mark_journal/run.go
    rename to internal/cli/system/cmd/markjournal/run.go
    index 4b219db69..5007495a4 100644
    --- a/internal/cli/system/cmd/mark_journal/run.go
    +++ b/internal/cli/system/cmd/markjournal/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package mark_journal
    +package markjournal
     
     import (
     	"github.com/spf13/cobra"
    @@ -14,7 +14,7 @@ import (
     	cFlag "github.com/ActiveMemory/ctx/internal/config/flag"
     	"github.com/ActiveMemory/ctx/internal/config/warn"
     	logWarn "github.com/ActiveMemory/ctx/internal/log/warn"
    -	writeJournal "github.com/ActiveMemory/ctx/internal/write/mark_journal"
    +	writeJournal "github.com/ActiveMemory/ctx/internal/write/markjournal"
     )
     
     // Run handles the mark-journal command.
    diff --git a/internal/cli/system/cmd/mark_wrapped_up/cmd.go b/internal/cli/system/cmd/markwrappedup/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/mark_wrapped_up/cmd.go
    rename to internal/cli/system/cmd/markwrappedup/cmd.go
    index 231f17255..f391d4552 100644
    --- a/internal/cli/system/cmd/mark_wrapped_up/cmd.go
    +++ b/internal/cli/system/cmd/markwrappedup/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package mark_wrapped_up
    +package markwrappedup
     
     import (
     	"github.com/spf13/cobra"
    diff --git a/internal/cli/system/cmd/mark_wrapped_up/doc.go b/internal/cli/system/cmd/markwrappedup/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/mark_wrapped_up/doc.go
    rename to internal/cli/system/cmd/markwrappedup/doc.go
    index e0870e203..052f419bf 100644
    --- a/internal/cli/system/cmd/mark_wrapped_up/doc.go
    +++ b/internal/cli/system/cmd/markwrappedup/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package mark_wrapped_up implements the hidden
    +// Package markwrappedup implements the hidden
     // "ctx system mark-wrapped-up" cobra subcommand.
     //
     // This command writes a marker file that suppresses
    @@ -44,4 +44,4 @@
     // State directory resolution uses system/core/state.
     // File writing delegates to internal/io.SafeWriteFile.
     // Output formatting uses write/session.WrappedUp.
    -package mark_wrapped_up
    +package markwrappedup
    diff --git a/internal/cli/system/cmd/mark_wrapped_up/run.go b/internal/cli/system/cmd/markwrappedup/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/mark_wrapped_up/run.go
    rename to internal/cli/system/cmd/markwrappedup/run.go
    index e9998c253..875bdd95f 100644
    --- a/internal/cli/system/cmd/mark_wrapped_up/run.go
    +++ b/internal/cli/system/cmd/markwrappedup/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package mark_wrapped_up
    +package markwrappedup
     
     import (
     	"path/filepath"
    diff --git a/internal/cli/system/cmd/post_commit/cmd.go b/internal/cli/system/cmd/postcommit/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/post_commit/cmd.go
    rename to internal/cli/system/cmd/postcommit/cmd.go
    index 84cd5b4ce..d4b104e23 100644
    --- a/internal/cli/system/cmd/post_commit/cmd.go
    +++ b/internal/cli/system/cmd/postcommit/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package post_commit
    +package postcommit
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/post_commit/doc.go b/internal/cli/system/cmd/postcommit/doc.go
    similarity index 93%
    rename from internal/cli/system/cmd/post_commit/doc.go
    rename to internal/cli/system/cmd/postcommit/doc.go
    index 433a44ffe..eef2eaf09 100644
    --- a/internal/cli/system/cmd/post_commit/doc.go
    +++ b/internal/cli/system/cmd/postcommit/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package post_commit implements the hidden
    +// Package postcommit implements the hidden
     // "ctx system post-commit" cobra subcommand.
     //
     // This hook fires after a non-amend git commit and
    @@ -52,6 +52,6 @@
     //
     // Message loading uses system/core/message. Version
     // drift checking uses system/core/drift. Spec scoring
    -// delegates to system/core/post_commit. Notification
    +// delegates to system/core/postcommit. Notification
     // relay uses system/core/nudge.
    -package post_commit
    +package postcommit
    diff --git a/internal/cli/system/cmd/post_commit/run.go b/internal/cli/system/cmd/postcommit/run.go
    similarity index 98%
    rename from internal/cli/system/cmd/post_commit/run.go
    rename to internal/cli/system/cmd/postcommit/run.go
    index d6f0329a0..80b12676b 100644
    --- a/internal/cli/system/cmd/post_commit/run.go
    +++ b/internal/cli/system/cmd/postcommit/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package post_commit
    +package postcommit
     
     import (
     	"fmt"
    @@ -17,7 +17,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/cli/system/core/drift"
     	"github.com/ActiveMemory/ctx/internal/cli/system/core/message"
     	"github.com/ActiveMemory/ctx/internal/cli/system/core/nudge"
    -	corePC "github.com/ActiveMemory/ctx/internal/cli/system/core/post_commit"
    +	corePC "github.com/ActiveMemory/ctx/internal/cli/system/core/postcommit"
     	coreSession "github.com/ActiveMemory/ctx/internal/cli/system/core/session"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	"github.com/ActiveMemory/ctx/internal/config/hook"
    diff --git a/internal/cli/system/cmd/qa_reminder/cmd.go b/internal/cli/system/cmd/qareminder/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/qa_reminder/cmd.go
    rename to internal/cli/system/cmd/qareminder/cmd.go
    index c7885dc62..a54a2ab53 100644
    --- a/internal/cli/system/cmd/qa_reminder/cmd.go
    +++ b/internal/cli/system/cmd/qareminder/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package qa_reminder
    +package qareminder
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/qa_reminder/doc.go b/internal/cli/system/cmd/qareminder/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/qa_reminder/doc.go
    rename to internal/cli/system/cmd/qareminder/doc.go
    index 8cc509928..5b59d257f 100644
    --- a/internal/cli/system/cmd/qa_reminder/doc.go
    +++ b/internal/cli/system/cmd/qareminder/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package qa_reminder implements the hidden
    +// Package qareminder implements the hidden
     // "ctx system qa-reminder" cobra subcommand.
     //
     // This hook fires before any git command and injects
    @@ -46,4 +46,4 @@
     // Message loading uses system/core/message. Context
     // directory resolution uses context/resolve. Relay
     // notification uses system/core/nudge.
    -package qa_reminder
    +package qareminder
    diff --git a/internal/cli/system/cmd/qa_reminder/run.go b/internal/cli/system/cmd/qareminder/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/qa_reminder/run.go
    rename to internal/cli/system/cmd/qareminder/run.go
    index b34e55a8f..3ba9097ba 100644
    --- a/internal/cli/system/cmd/qa_reminder/run.go
    +++ b/internal/cli/system/cmd/qareminder/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package qa_reminder
    +package qareminder
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/cmd/session_event/cmd.go b/internal/cli/system/cmd/sessionevent/cmd.go
    similarity index 100%
    rename from internal/cli/system/cmd/session_event/cmd.go
    rename to internal/cli/system/cmd/sessionevent/cmd.go
    diff --git a/internal/cli/system/cmd/session_event/doc.go b/internal/cli/system/cmd/sessionevent/doc.go
    similarity index 100%
    rename from internal/cli/system/cmd/session_event/doc.go
    rename to internal/cli/system/cmd/sessionevent/doc.go
    diff --git a/internal/cli/system/cmd/session_event/run.go b/internal/cli/system/cmd/sessionevent/run.go
    similarity index 100%
    rename from internal/cli/system/cmd/session_event/run.go
    rename to internal/cli/system/cmd/sessionevent/run.go
    diff --git a/internal/cli/system/cmd/specs_nudge/cmd.go b/internal/cli/system/cmd/specsnudge/cmd.go
    similarity index 97%
    rename from internal/cli/system/cmd/specs_nudge/cmd.go
    rename to internal/cli/system/cmd/specsnudge/cmd.go
    index d28077629..5073c2f10 100644
    --- a/internal/cli/system/cmd/specs_nudge/cmd.go
    +++ b/internal/cli/system/cmd/specsnudge/cmd.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package specs_nudge
    +package specsnudge
     
     import (
     	"os"
    diff --git a/internal/cli/system/cmd/specs_nudge/doc.go b/internal/cli/system/cmd/specsnudge/doc.go
    similarity index 95%
    rename from internal/cli/system/cmd/specs_nudge/doc.go
    rename to internal/cli/system/cmd/specsnudge/doc.go
    index a5c407ed4..e47f6e687 100644
    --- a/internal/cli/system/cmd/specs_nudge/doc.go
    +++ b/internal/cli/system/cmd/specsnudge/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package specs_nudge implements the hidden
    +// Package specsnudge implements the hidden
     // "ctx system specs-nudge" cobra subcommand.
     //
     // This hook fires on PreToolUse events and reminds
    @@ -46,4 +46,4 @@
     // Message loading uses system/core/message. Context
     // directory resolution uses context/resolve. Relay
     // notification uses system/core/nudge.
    -package specs_nudge
    +package specsnudge
    diff --git a/internal/cli/system/cmd/specs_nudge/run.go b/internal/cli/system/cmd/specsnudge/run.go
    similarity index 99%
    rename from internal/cli/system/cmd/specs_nudge/run.go
    rename to internal/cli/system/cmd/specsnudge/run.go
    index a9385edd0..0dcb4f494 100644
    --- a/internal/cli/system/cmd/specs_nudge/run.go
    +++ b/internal/cli/system/cmd/specsnudge/run.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package specs_nudge
    +package specsnudge
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/core/check/full_preamble.go b/internal/cli/system/core/check/full_preamble.go
    index 80c134c37..79d19c9ef 100644
    --- a/internal/cli/system/core/check/full_preamble.go
    +++ b/internal/cli/system/core/check/full_preamble.go
    @@ -55,7 +55,7 @@ import (
     // it over input.SessionID when touching state files keyed by session.
     //
     // The regular [Preamble] stays available for hooks that do not need
    -// the Initialized gate or a state directory (e.g. check_reminder,
    +// the Initialized gate or a state directory (e.g. checkreminder,
     // which emits provenance unconditionally and gates Initialized inline).
     //
     // Parameters:
    diff --git a/internal/cli/system/core/drift/doc.go b/internal/cli/system/core/drift/doc.go
    index 66e34814b..d138a083b 100644
    --- a/internal/cli/system/core/drift/doc.go
    +++ b/internal/cli/system/core/drift/doc.go
    @@ -8,7 +8,7 @@
     // places ctx's version can diverge: the source-of-truth
     // `VERSION` file, the installed binary's
     // `ctx --version`, and the marketplace plugin manifest. The
    -// `check_version` hook calls into here to nudge users when
    +// `checkversion` hook calls into here to nudge users when
     // any of the three drift apart.
     //
     // (This is the *system-hook* drift package and is unrelated
    diff --git a/internal/cli/system/core/health/doc.go b/internal/cli/system/core/health/doc.go
    index 82487fea4..859cb23d8 100644
    --- a/internal/cli/system/core/health/doc.go
    +++ b/internal/cli/system/core/health/doc.go
    @@ -12,14 +12,14 @@
     // process invariants.
     //
     // The package is the *measurement layer*; the hooks
    -// (`check_map_staleness`, `check_knowledge`,
    +// (`checkmapstaleness`, `checkknowledge`,
     // background pruners) decide what to do with the numbers.
     //
     // # Public Surface
     //
     //   - **[ReadMapTracking]**: reads the persisted
     //     architecture-map last-update tracking record.
    -//     Used by `check_map_staleness` to decide whether
    +//     Used by `checkmapstaleness` to decide whether
     //     ARCHITECTURE.md has fallen behind code changes.
     //   - **[CountModuleCommits](module, since)**: counts
     //     git commits touching a module path since a given
    diff --git a/internal/cli/system/core/health/prune.go b/internal/cli/system/core/health/prune.go
    index e196d527b..d6a542451 100644
    --- a/internal/cli/system/core/health/prune.go
    +++ b/internal/cli/system/core/health/prune.go
    @@ -30,11 +30,11 @@ import (
     // Returns:
     //   - int: Number of files pruned
     func AutoPrune(days int) int {
    -	// Best-effort: this runs from context_load_gate as fire-and-forget
    +	// Best-effort: this runs from contextloadgate as fire-and-forget
     	// and must never block session startup. Any state.Dir failure
     	// (including the ErrDirNotDeclared bail signal) is swallowed
     	// uniformly. ErrDirNotDeclared is unreachable here because
    -	// context_load_gate already ran state.Initialized; the check
    +	// contextloadgate already ran state.Initialized; the check
     	// stays defensive in case a future caller invokes AutoPrune
     	// outside the gate.
     	dir, dirErr := state.Dir()
    diff --git a/internal/cli/system/core/journal/doc.go b/internal/cli/system/core/journal/doc.go
    index 1d699c10e..ab188224d 100644
    --- a/internal/cli/system/core/journal/doc.go
    +++ b/internal/cli/system/core/journal/doc.go
    @@ -5,7 +5,7 @@
     //                 SPDX-License-Identifier: Apache-2.0
     
     // Package journal holds the **shared helpers** that the
    -// `check_journal` hook calls when deciding whether to nudge
    +// `checkjournal` hook calls when deciding whether to nudge
     // the user about unimported or unenriched session entries.
     //
     // The package is the *measurement layer*; the hook decides
    diff --git a/internal/cli/system/core/knowledge/doc.go b/internal/cli/system/core/knowledge/doc.go
    index ba890fb79..f988f6e48 100644
    --- a/internal/cli/system/core/knowledge/doc.go
    +++ b/internal/cli/system/core/knowledge/doc.go
    @@ -5,7 +5,7 @@
     //                 SPDX-License-Identifier: Apache-2.0
     
     // Package knowledge is the **measurement helper** the
    -// `check_knowledge` system hook uses to evaluate whether
    +// `checkknowledge` system hook uses to evaluate whether
     // a project's knowledge files (DECISIONS.md, LEARNINGS.md,
     // CONVENTIONS.md) have outgrown the configured per-file
     // thresholds and warrant a consolidation nudge.
    diff --git a/internal/cli/system/core/load/load_gate.go b/internal/cli/system/core/load/load_gate.go
    index 8d779fe7a..fdd5f94af 100644
    --- a/internal/cli/system/core/load/load_gate.go
    +++ b/internal/cli/system/core/load/load_gate.go
    @@ -15,7 +15,7 @@ import (
     	"github.com/ActiveMemory/ctx/internal/config/dir"
     	"github.com/ActiveMemory/ctx/internal/config/embed/text"
     	"github.com/ActiveMemory/ctx/internal/config/fs"
    -	"github.com/ActiveMemory/ctx/internal/config/load_gate"
    +	"github.com/ActiveMemory/ctx/internal/config/loadgate"
     	"github.com/ActiveMemory/ctx/internal/config/stats"
     	"github.com/ActiveMemory/ctx/internal/config/token"
     	"github.com/ActiveMemory/ctx/internal/config/warn"
    @@ -49,7 +49,7 @@ func WriteOversizeFlag(
     	var flag strings.Builder
     	flag.WriteString(desc.Text(text.DescKeyContextLoadGateOversizeHeader))
     	sep := strings.Repeat(
    -		load_gate.ContextLoadSeparatorChar,
    +		loadgate.ContextLoadSeparatorChar,
     		stats.ContextSizeOversizeSepLen)
     	flag.WriteString(sep + token.NewlineLF)
     	io.SafeFprintf(&flag,
    diff --git a/internal/cli/system/core/post_commit/doc.go b/internal/cli/system/core/postcommit/doc.go
    similarity index 94%
    rename from internal/cli/system/core/post_commit/doc.go
    rename to internal/cli/system/core/postcommit/doc.go
    index 0b42d7eb4..52cf69b83 100644
    --- a/internal/cli/system/core/post_commit/doc.go
    +++ b/internal/cli/system/core/postcommit/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package post_commit scores the last git commit for
    +// Package postcommit scores the last git commit for
     // signs that the agent bypassed the /ctx-commit skill.
     // It is called by the post-commit hook to nudge the
     // human when commit hygiene is poor.
    @@ -34,4 +34,4 @@
     // to the agent session. Returns an empty string when
     // the commit looks clean or the score is below the
     // nudge threshold.
    -package post_commit
    +package postcommit
    diff --git a/internal/cli/system/core/post_commit/score.go b/internal/cli/system/core/postcommit/score.go
    similarity index 99%
    rename from internal/cli/system/core/post_commit/score.go
    rename to internal/cli/system/core/postcommit/score.go
    index 311b809f5..133d6b3eb 100644
    --- a/internal/cli/system/core/post_commit/score.go
    +++ b/internal/cli/system/core/postcommit/score.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package post_commit
    +package postcommit
     
     import (
     	"fmt"
    diff --git a/internal/cli/system/core/session/doc.go b/internal/cli/system/core/session/doc.go
    index 0ccc77cf5..1435314de 100644
    --- a/internal/cli/system/core/session/doc.go
    +++ b/internal/cli/system/core/session/doc.go
    @@ -12,8 +12,8 @@
     //
     // The package owns the per-session state files under
     // `.context/state/session-<id>.json`, the lightweight
    -// counters that hooks like `check_ceremony`,
    -// `check_persistence`, and `check_context_size` evaluate
    +// counters that hooks like `checkceremony`,
    +// `checkpersistence`, and `checkcontextsize` evaluate
     // each time they fire.
     //
     // # Public Surface
    diff --git a/internal/cli/system/system.go b/internal/cli/system/system.go
    index 51ff4e9b2..65c475a58 100644
    --- a/internal/cli/system/system.go
    +++ b/internal/cli/system/system.go
    @@ -10,33 +10,33 @@ import (
     	"github.com/spf13/cobra"
     
     	"github.com/ActiveMemory/ctx/internal/cli/parent"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/block_non_path_ctx"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/blocknonpathctx"
     	sysBootstrap "github.com/ActiveMemory/ctx/internal/cli/system/cmd/bootstrap"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_anchor_drift"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_ceremony"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_context_size"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_freshness"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_hub_sync"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_journal"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_knowledge"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_map_staleness"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_memory_drift"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_persistence"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_reminder"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_resource"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_skill_discovery"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_task_completion"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/check_version"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/context_load_gate"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkanchordrift"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkceremony"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkcontextsize"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkfreshness"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkhubsync"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkjournal"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkknowledge"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkmapstaleness"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkmemorydrift"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkpersistence"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkreminder"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkresource"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkskilldiscovery"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checktaskcompletion"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/checkversion"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/contextloadgate"
     	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/heartbeat"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/mark_journal"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/mark_wrapped_up"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/markjournal"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/markwrappedup"
     	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/pause"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/post_commit"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/qa_reminder"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/postcommit"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/qareminder"
     	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/resume"
    -	sessEvent "github.com/ActiveMemory/ctx/internal/cli/system/cmd/session_event"
    -	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/specs_nudge"
    +	sessEvent "github.com/ActiveMemory/ctx/internal/cli/system/cmd/sessionevent"
    +	"github.com/ActiveMemory/ctx/internal/cli/system/cmd/specsnudge"
     	"github.com/ActiveMemory/ctx/internal/config/embed/cmd"
     )
     
    @@ -57,31 +57,31 @@ import (
     func Cmd() *cobra.Command {
     	return parent.Cmd(cmd.DescKeySystem, cmd.UseSystem,
     		sysBootstrap.Cmd(),
    -		block_non_path_ctx.Cmd(),
    -		check_anchor_drift.Cmd(),
    -		check_ceremony.Cmd(),
    -		check_context_size.Cmd(),
    -		check_freshness.Cmd(),
    -		check_hub_sync.Cmd(),
    -		check_journal.Cmd(),
    -		check_knowledge.Cmd(),
    -		check_map_staleness.Cmd(),
    -		check_memory_drift.Cmd(),
    -		check_persistence.Cmd(),
    -		check_skill_discovery.Cmd(),
    -		check_reminder.Cmd(),
    -		check_resource.Cmd(),
    -		check_task_completion.Cmd(),
    -		check_version.Cmd(),
    -		context_load_gate.Cmd(),
    +		blocknonpathctx.Cmd(),
    +		checkanchordrift.Cmd(),
    +		checkceremony.Cmd(),
    +		checkcontextsize.Cmd(),
    +		checkfreshness.Cmd(),
    +		checkhubsync.Cmd(),
    +		checkjournal.Cmd(),
    +		checkknowledge.Cmd(),
    +		checkmapstaleness.Cmd(),
    +		checkmemorydrift.Cmd(),
    +		checkpersistence.Cmd(),
    +		checkskilldiscovery.Cmd(),
    +		checkreminder.Cmd(),
    +		checkresource.Cmd(),
    +		checktaskcompletion.Cmd(),
    +		checkversion.Cmd(),
    +		contextloadgate.Cmd(),
     		heartbeat.Cmd(),
    -		mark_journal.Cmd(),
    -		mark_wrapped_up.Cmd(),
    +		markjournal.Cmd(),
    +		markwrappedup.Cmd(),
     		pause.Cmd(),
    -		post_commit.Cmd(),
    -		qa_reminder.Cmd(),
    +		postcommit.Cmd(),
    +		qareminder.Cmd(),
     		resume.Cmd(),
     		sessEvent.Cmd(),
    -		specs_nudge.Cmd(),
    +		specsnudge.Cmd(),
     	)
     }
    diff --git a/internal/compliance/compliance_test.go b/internal/compliance/compliance_test.go
    index 2c98332db..52aec71ee 100644
    --- a/internal/compliance/compliance_test.go
    +++ b/internal/compliance/compliance_test.go
    @@ -998,12 +998,14 @@ func TestDocGoSubcommandDrift(t *testing.T) {
     
     		rel, _ := filepath.Rel(root, path)
     
    -		// Normalize: hyphens → underscores, apply aliases.
    +		// Normalize: drop hyphens (Go package names have no
    +		// separator; CLI commands hyphenate the same noun).
    +		// Apply aliases for explicit overrides.
     		normalize := func(name string) string {
     			if alias, ok := knownAliases[name]; ok {
     				return alias
     			}
    -			return strings.ReplaceAll(name, "-", "_")
    +			return strings.ReplaceAll(name, "-", "")
     		}
     
     		// Expand combined entries (e.g., "pause/resume") and check
    diff --git a/internal/config/README.md b/internal/config/README.md
    index 76e974547..9d797a049 100644
    --- a/internal/config/README.md
    +++ b/internal/config/README.md
    @@ -58,7 +58,7 @@ import "github.com/ActiveMemory/ctx/internal/config"  // everything
     `cli/`, `content/`, `copilot/`, `crypto/`, `ctx/`, `dir/`,
     `entry/`, `env/`, `event/`, `flag/`, `fmt/`, `format/`,
     `freshness/`, `fs/`, `git/`, `heartbeat/`, `hook/`, `http/`,
    -`knowledge/`, `load_gate/`, `loop/`, `marker/`, `msg/`, `nudge/`,
    +`knowledge/`, `loadgate/`, `loop/`, `marker/`, `msg/`, `nudge/`,
     `obsidian/`, `pad/`, `project/`, `reminder/`, `rss/`, `runtime/`,
     `session/`, `stats/`, `sync/`, `sysinfo/`, `time/`, `token/`,
     `trace/`, `version/`, `vscode/`, `warn/`, `watch/`, `why/`,
    diff --git a/internal/config/embed/text/err_gitmeta.go b/internal/config/embed/text/err_gitmeta.go
    index 0ae62e14a..a840042f1 100644
    --- a/internal/config/embed/text/err_gitmeta.go
    +++ b/internal/config/embed/text/err_gitmeta.go
    @@ -8,7 +8,7 @@ package text
     
     // DescKeys for gitmeta error wrappers. The matching YAML
     // entries live in commands/text/errors.yaml; constructors in
    -// internal/err/git_meta/ resolve them via desc.Text at error
    +// internal/err/gitmeta/ resolve them via desc.Text at error
     // construction time.
     const (
     	// DescKeyErrGitmetaMissingGitTree is the text key for the
    diff --git a/internal/config/git_meta/doc.go b/internal/config/gitmeta/doc.go
    similarity index 84%
    rename from internal/config/git_meta/doc.go
    rename to internal/config/gitmeta/doc.go
    index 959dac406..615e39a5b 100644
    --- a/internal/config/git_meta/doc.go
    +++ b/internal/config/gitmeta/doc.go
    @@ -6,14 +6,14 @@
     
     // Package gitmeta supplies the environment-variable names,
     // special branch-name literals, and short-SHA length used by
    -// [github.com/ActiveMemory/ctx/internal/git_meta]. The constants
    +// [github.com/ActiveMemory/ctx/internal/gitmeta]. The constants
     // live here (not in the gitmeta package itself) to honor the
     // project-wide rule that magic strings and values belong in
     // internal/config/.
     //
     // # Related packages
     //
    -//   - [github.com/ActiveMemory/ctx/internal/git_meta] is the
    +//   - [github.com/ActiveMemory/ctx/internal/gitmeta] is the
     //     primary consumer.
     //   - [github.com/ActiveMemory/ctx/internal/config/git] supplies
     //     git binary + subcommand constants; gitmeta complements it
    diff --git a/internal/config/git_meta/git_meta.go b/internal/config/gitmeta/gitmeta.go
    similarity index 94%
    rename from internal/config/git_meta/git_meta.go
    rename to internal/config/gitmeta/gitmeta.go
    index a344e8ebb..644fe38b1 100644
    --- a/internal/config/git_meta/git_meta.go
    +++ b/internal/config/gitmeta/gitmeta.go
    @@ -28,7 +28,7 @@ const GithubActionsTrue = "true"
     
     // BranchDetached is the canonical branch-name placeholder when
     // HEAD is detached (points at a commit, not a symbolic ref).
    -// Used by [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead]
    +// Used by [github.com/ActiveMemory/ctx/internal/gitmeta.ResolveHead]
     // and recorded verbatim in closeout / handover frontmatter.
     const BranchDetached = "detached"
     
    diff --git a/internal/config/hook/doc.go b/internal/config/hook/doc.go
    index 2d563df5c..f7932f624 100644
    --- a/internal/config/hook/doc.go
    +++ b/internal/config/hook/doc.go
    @@ -6,7 +6,7 @@
     
     // Package hook holds the **constants** every package
     // touching the hook subsystem references: hook names
    -// (`check_persistence`, `check_context_size`, …),
    +// (`checkpersistence`, `checkcontextsize`, …),
     // lifecycle stages (sessionStart, preToolUse, …),
     // supported AI tool identifiers (`claude`, `cursor`,
     // `cline`, `kiro`, `codex`), category tags
    diff --git a/internal/config/load_gate/doc.go b/internal/config/loadgate/doc.go
    similarity index 95%
    rename from internal/config/load_gate/doc.go
    rename to internal/config/loadgate/doc.go
    index 438f5ae6b..2ad247aac 100644
    --- a/internal/config/load_gate/doc.go
    +++ b/internal/config/loadgate/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package load_gate defines constants for the context
    +// Package loadgate defines constants for the context
     // load gate hook, which controls when and how project
     // context is injected into a new Claude Code session.
     //
    @@ -44,4 +44,4 @@
     //     separator for the injected block.
     //   - JSONKeyTimestamp: JSON key used to extract
     //     timestamps from event log lines.
    -package load_gate
    +package loadgate
    diff --git a/internal/config/load_gate/load_gate.go b/internal/config/loadgate/loadgate.go
    similarity index 97%
    rename from internal/config/load_gate/load_gate.go
    rename to internal/config/loadgate/loadgate.go
    index c0a49dae1..88d751a97 100644
    --- a/internal/config/load_gate/load_gate.go
    +++ b/internal/config/loadgate/loadgate.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package load_gate
    +package loadgate
     
     // Context load gate constants.
     const (
    diff --git a/internal/config/load_gate/prune.go b/internal/config/loadgate/prune.go
    similarity index 95%
    rename from internal/config/load_gate/prune.go
    rename to internal/config/loadgate/prune.go
    index f81365b85..6123a4c46 100644
    --- a/internal/config/load_gate/prune.go
    +++ b/internal/config/loadgate/prune.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package load_gate
    +package loadgate
     
     // Auto-prune threshold constants.
     const (
    diff --git a/internal/config/load_gate/ts.go b/internal/config/loadgate/ts.go
    similarity index 95%
    rename from internal/config/load_gate/ts.go
    rename to internal/config/loadgate/ts.go
    index 0f102e3b9..c57db0528 100644
    --- a/internal/config/load_gate/ts.go
    +++ b/internal/config/loadgate/ts.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package load_gate
    +package loadgate
     
     // JSON timestamp extraction constants.
     const (
    diff --git a/internal/config/mcp/event/doc.go b/internal/config/mcp/event/doc.go
    index e457e91ce..b70bfd26e 100644
    --- a/internal/config/mcp/event/doc.go
    +++ b/internal/config/mcp/event/doc.go
    @@ -10,7 +10,7 @@
     //
     // When an AI agent connects to the ctx MCP server it
     // signals session start and session end via the
    -// ctx_session_event tool. The value passed in the event
    +// ctx_sessionevent tool. The value passed in the event
     // field is one of the constants defined here.
     //
     // # Key Constants
    diff --git a/internal/config/mcp/tool/doc.go b/internal/config/mcp/tool/doc.go
    index 016aa2136..c782b2278 100644
    --- a/internal/config/mcp/tool/doc.go
    +++ b/internal/config/mcp/tool/doc.go
    @@ -33,9 +33,9 @@
     //   - [Next] ("ctx_next"): suggests the next task
     //     to work on.
     //   - [CheckTaskCompletion]
    -//     ("ctx_check_task_completion"): checks whether
    +//     ("ctx_checktaskcompletion"): checks whether
     //     a recent action completed a pending task.
    -//   - [SessionEvent] ("ctx_session_event"): records
    +//   - [SessionEvent] ("ctx_sessionevent"): records
     //     session start/end lifecycle events.
     //   - [Remind] ("ctx_remind"): lists active
     //     reminders for the current session.
    diff --git a/internal/config/mcp/tool/tool.go b/internal/config/mcp/tool/tool.go
    index a43292250..e233bef24 100644
    --- a/internal/config/mcp/tool/tool.go
    +++ b/internal/config/mcp/tool/tool.go
    @@ -25,9 +25,9 @@ const (
     	// Next is the MCP tool name for suggesting the next task.
     	Next = "ctx_next"
     	// CheckTaskCompletion is the MCP tool name for task completion nudge.
    -	CheckTaskCompletion = "ctx_check_task_completion"
    +	CheckTaskCompletion = "ctx_checktaskcompletion"
     	// SessionEvent is the MCP tool name for session lifecycle events.
    -	SessionEvent = "ctx_session_event"
    +	SessionEvent = "ctx_sessionevent"
     	// Remind is the MCP tool name for listing reminders.
     	Remind = "ctx_remind"
     	// SteeringGet is the MCP tool name for retrieving steering files.
    diff --git a/internal/entity/mcp_session.go b/internal/entity/mcp_session.go
    index a591e6280..1130c2b2b 100644
    --- a/internal/entity/mcp_session.go
    +++ b/internal/entity/mcp_session.go
    @@ -26,7 +26,7 @@ import "time"
     //   - AddsPerformed: Entry additions by type (decision, learning, etc.)
     //   - SessionStartedAt: Session start timestamp
     //   - PendingFlush: Updates awaiting human confirmation
    -//   - SessionStarted: Whether session_event:start has fired
    +//   - SessionStarted: Whether sessionevent:start has fired
     //   - ContextLoaded: Whether context files have been read
     //   - LastDriftCheck: Timestamp of most recent drift check
     //   - LastContextWrite: Timestamp of most recent .context write
    @@ -101,7 +101,7 @@ func (ss *MCPSession) PendingCount() int {
     // RecordSessionStart marks the session as explicitly started and
     // resets the session start timestamp.
     //
    -// Called by the session_event tool when the agent reports a "start"
    +// Called by the sessionevent tool when the agent reports a "start"
     // event. Sets SessionStarted to true and captures the current wall
     // time so governance checks can measure elapsed time.
     func (ss *MCPSession) RecordSessionStart() {
    diff --git a/internal/err/closeout/closeout.go b/internal/err/closeout/closeout.go
    index 62e2944f2..de7db1dfc 100644
    --- a/internal/err/closeout/closeout.go
    +++ b/internal/err/closeout/closeout.go
    @@ -108,7 +108,7 @@ func ReadCloseoutsDir(cause error) error {
     }
     
     // ResolveHead wraps a
    -// [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead]
    +// [github.com/ActiveMemory/ctx/internal/gitmeta.ResolveHead]
     // failure when stamping sha / branch into new closeouts.
     //
     // Parameters:
    diff --git a/internal/err/git_meta/doc.go b/internal/err/gitmeta/doc.go
    similarity index 79%
    rename from internal/err/git_meta/doc.go
    rename to internal/err/gitmeta/doc.go
    index 79c71c78d..814c76c7e 100644
    --- a/internal/err/git_meta/doc.go
    +++ b/internal/err/gitmeta/doc.go
    @@ -9,20 +9,20 @@
     // surface has two pieces:
     //
     //   - The require check
    -//     ([github.com/ActiveMemory/ctx/internal/git_meta.RequireGitTree])
    +//     ([github.com/ActiveMemory/ctx/internal/gitmeta.RequireGitTree])
     //     verifies `<projectRoot>/.git` exists; failure surfaces as
     //     [ErrMissingGitTree] (sentinel) or a wrapped stat error.
     //   - The head resolver
    -//     ([github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead])
    +//     ([github.com/ActiveMemory/ctx/internal/gitmeta.ResolveHead])
     //     resolves the current commit + branch; failure surfaces as
     //     [ErrResolveHeadEmpty] (sentinel) or [ResolveHeadFailed]
     //     (wrapping the underlying exec error).
     //
     // # Related packages
     //
    -//   - [github.com/ActiveMemory/ctx/internal/config/git_meta]
    +//   - [github.com/ActiveMemory/ctx/internal/config/gitmeta]
     //     supplies the sentinel-message + format-string constants.
    -//   - [github.com/ActiveMemory/ctx/internal/git_meta] is the
    +//   - [github.com/ActiveMemory/ctx/internal/gitmeta] is the
     //     primary caller; the root command's PersistentPreRunE
     //     also calls into these constructors when wrapping the
     //     missing-tree error with the failing subcommand name.
    diff --git a/internal/err/git_meta/gitmeta.go b/internal/err/gitmeta/gitmeta.go
    similarity index 100%
    rename from internal/err/git_meta/gitmeta.go
    rename to internal/err/gitmeta/gitmeta.go
    diff --git a/internal/err/handover/handover.go b/internal/err/handover/handover.go
    index cee11cdbe..bf1af36fa 100644
    --- a/internal/err/handover/handover.go
    +++ b/internal/err/handover/handover.go
    @@ -170,7 +170,7 @@ func ParseFrontmatter(cause error) error {
     }
     
     // ResolveHead wraps a
    -// [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead]
    +// [github.com/ActiveMemory/ctx/internal/gitmeta.ResolveHead]
     // failure when stamping sha / branch into new handovers.
     //
     // Parameters:
    diff --git a/internal/git_meta/branch.go b/internal/gitmeta/branch.go
    similarity index 94%
    rename from internal/git_meta/branch.go
    rename to internal/gitmeta/branch.go
    index 7dd4c9413..78708213c 100644
    --- a/internal/git_meta/branch.go
    +++ b/internal/gitmeta/branch.go
    @@ -10,7 +10,7 @@ import (
     	"strings"
     
     	cfgGit "github.com/ActiveMemory/ctx/internal/config/git"
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    +	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/gitmeta"
     	execGit "github.com/ActiveMemory/ctx/internal/exec/git"
     )
     
    diff --git a/internal/git_meta/doc.go b/internal/gitmeta/doc.go
    similarity index 100%
    rename from internal/git_meta/doc.go
    rename to internal/gitmeta/doc.go
    diff --git a/internal/git_meta/head.go b/internal/gitmeta/head.go
    similarity index 94%
    rename from internal/git_meta/head.go
    rename to internal/gitmeta/head.go
    index 09028e950..5faa6e738 100644
    --- a/internal/git_meta/head.go
    +++ b/internal/gitmeta/head.go
    @@ -11,8 +11,8 @@ import (
     	"strings"
     
     	cfgGit "github.com/ActiveMemory/ctx/internal/config/git"
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    -	errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta"
    +	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/gitmeta"
    +	errGitmeta "github.com/ActiveMemory/ctx/internal/err/gitmeta"
     	execGit "github.com/ActiveMemory/ctx/internal/exec/git"
     )
     
    diff --git a/internal/git_meta/require.go b/internal/gitmeta/require.go
    similarity index 94%
    rename from internal/git_meta/require.go
    rename to internal/gitmeta/require.go
    index c4c5b8799..3aaa0c9a7 100644
    --- a/internal/git_meta/require.go
    +++ b/internal/gitmeta/require.go
    @@ -13,7 +13,7 @@ import (
     	"path/filepath"
     
     	cfgGit "github.com/ActiveMemory/ctx/internal/config/git"
    -	errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta"
    +	errGitmeta "github.com/ActiveMemory/ctx/internal/err/gitmeta"
     )
     
     // RequireGitTree returns nil when `<projectRoot>/.git` exists
    diff --git a/internal/git_meta/require_test.go b/internal/gitmeta/require_test.go
    similarity index 94%
    rename from internal/git_meta/require_test.go
    rename to internal/gitmeta/require_test.go
    index bd89edc46..d09981046 100644
    --- a/internal/git_meta/require_test.go
    +++ b/internal/gitmeta/require_test.go
    @@ -13,8 +13,8 @@ import (
     	"strings"
     	"testing"
     
    -	errGitmeta "github.com/ActiveMemory/ctx/internal/err/git_meta"
    -	"github.com/ActiveMemory/ctx/internal/git_meta"
    +	errGitmeta "github.com/ActiveMemory/ctx/internal/err/gitmeta"
    +	"github.com/ActiveMemory/ctx/internal/gitmeta"
     )
     
     func TestRequireGitTree_DirAccepted(t *testing.T) {
    diff --git a/internal/git_meta/resolvehead_test.go b/internal/gitmeta/resolvehead_test.go
    similarity index 95%
    rename from internal/git_meta/resolvehead_test.go
    rename to internal/gitmeta/resolvehead_test.go
    index 381576ca3..c2109eb09 100644
    --- a/internal/git_meta/resolvehead_test.go
    +++ b/internal/gitmeta/resolvehead_test.go
    @@ -9,8 +9,8 @@ package gitmeta_test
     import (
     	"testing"
     
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    -	"github.com/ActiveMemory/ctx/internal/git_meta"
    +	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/gitmeta"
    +	"github.com/ActiveMemory/ctx/internal/gitmeta"
     )
     
     func TestResolveHead_CtxTaskCommitOverrideUsedVerbatim(t *testing.T) {
    diff --git a/internal/git_meta/sha.go b/internal/gitmeta/sha.go
    similarity index 81%
    rename from internal/git_meta/sha.go
    rename to internal/gitmeta/sha.go
    index da180e865..8bb4b7d46 100644
    --- a/internal/git_meta/sha.go
    +++ b/internal/gitmeta/sha.go
    @@ -7,12 +7,12 @@
     package gitmeta
     
     import (
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    +	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/gitmeta"
     )
     
     // shortSHA truncates a full SHA to the canonical short form
     // length defined by
    -// [github.com/ActiveMemory/ctx/internal/config/git_meta.ShortLen].
    +// [github.com/ActiveMemory/ctx/internal/config/gitmeta.ShortLen].
     //
     // Parameters:
     //   - s: full or already-short SHA.
    diff --git a/internal/git_meta/testmain_test.go b/internal/gitmeta/testmain_test.go
    similarity index 100%
    rename from internal/git_meta/testmain_test.go
    rename to internal/gitmeta/testmain_test.go
    diff --git a/internal/git_meta/types.go b/internal/gitmeta/types.go
    similarity index 85%
    rename from internal/git_meta/types.go
    rename to internal/gitmeta/types.go
    index a057b750b..31522b91d 100644
    --- a/internal/git_meta/types.go
    +++ b/internal/gitmeta/types.go
    @@ -8,7 +8,7 @@ package gitmeta
     
     // HeadRef pairs a short commit SHA with the current branch
     // name. Branch is the literal "detached" (see
    -// [github.com/ActiveMemory/ctx/internal/config/git_meta.BranchDetached])
    +// [github.com/ActiveMemory/ctx/internal/config/gitmeta.BranchDetached])
     // when HEAD points at a commit instead of a symbolic ref.
     type HeadRef struct {
     	SHA    string
    diff --git a/internal/log/event/doc.go b/internal/log/event/doc.go
    index 300a197a3..110a257a7 100644
    --- a/internal/log/event/doc.go
    +++ b/internal/log/event/doc.go
    @@ -13,7 +13,7 @@
     //
     //   - **`ctx hook event`**: user-facing query: "what did
     //     the hooks do during the last session?".
    -//   - **`ctx system check_persistence`** and friends: read
    +//   - **`ctx system checkpersistence`** and friends: read
     //     the log to detect "you committed but never wrote a
     //     decision" patterns and nudge accordingly.
     //
    diff --git a/internal/mcp/doc.go b/internal/mcp/doc.go
    index 7968e4218..7e83550a1 100644
    --- a/internal/mcp/doc.go
    +++ b/internal/mcp/doc.go
    @@ -49,8 +49,8 @@
     //	ctx_watch_update       -> Apply structured updates
     //	ctx_compact            -> Archive completed tasks
     //	ctx_next               -> Get next pending task
    -//	ctx_check_task_completion -> Nudge on completion
    -//	ctx_session_event      -> Signal session lifecycle
    +//	ctx_checktaskcompletion -> Nudge on completion
    +//	ctx_sessionevent      -> Signal session lifecycle
     //	ctx_remind             -> List active reminders
     //
     // # Prompts
    diff --git a/internal/mcp/handler/doc.go b/internal/mcp/handler/doc.go
    index 83d1f2b7a..2c34d5f76 100644
    --- a/internal/mcp/handler/doc.go
    +++ b/internal/mcp/handler/doc.go
    @@ -42,11 +42,11 @@
     //   - **`ctx_remind`**:         read/dismiss reminders via
     //     [remindStore].
     //   - **`ctx_session_*`**:      `session_start`,
    -//     `session_end`, `session_event` lifecycle plumbing
    +//     `session_end`, `sessionevent` lifecycle plumbing
     //     (covered in [session_hooks.go]).
     //   - **`ctx_steering_get`**:   surface matched steering
     //     files via [steering.go] (see [internal/steering]).
    -//   - **`ctx_check_task_completion`**: match recent file
    +//   - **`ctx_checktaskcompletion`**: match recent file
     //     edits to open tasks.
     //   - **`ctx_watch_update`**:   apply context updates the
     //     agent emits in `<ctx-update>` blocks.
    @@ -86,7 +86,7 @@
     // # Session Hooks
     //
     // [session_hooks.go] implements the three lifecycle tools
    -// (`session_start`, `session_end`, `session_event`) the MCP
    +// (`session_start`, `session_end`, `sessionevent`) the MCP
     // client calls to mark transitions. They write to per-session
     // state files under `state/` and emit nudge messages when the
     // configured ceremonies have been skipped.
    diff --git a/internal/mcp/handler/governance_test.go b/internal/mcp/handler/governance_test.go
    index 18cb94d18..992579784 100644
    --- a/internal/mcp/handler/governance_test.go
    +++ b/internal/mcp/handler/governance_test.go
    @@ -37,9 +37,9 @@ func TestCheckGovernance_SessionNotStarted(t *testing.T) {
     
     func TestCheckGovernance_SessionNotStarted_SuppressedForSessionEvent(t *testing.T) {
     	d := newTestDeps()
    -	got := CheckGovernance(d, "ctx_session_event")
    +	got := CheckGovernance(d, "ctx_sessionevent")
     	if strings.Contains(got, "Session not started") {
    -		t.Errorf("session-not-started should be suppressed for ctx_session_event, got: %q", got)
    +		t.Errorf("session-not-started should be suppressed for ctx_sessionevent, got: %q", got)
     	}
     }
     
    diff --git a/internal/mcp/server/doc.go b/internal/mcp/server/doc.go
    index 8973c8bdb..5440d1a3d 100644
    --- a/internal/mcp/server/doc.go
    +++ b/internal/mcp/server/doc.go
    @@ -24,7 +24,7 @@
     //     this server provides (`ctx_status`, `ctx_add`,
     //     `ctx_complete`, `ctx_drift`, `ctx_journal_source`,
     //     `ctx_search`, `ctx_steering_get`, `ctx_remind`,
    -//     `ctx_session_*`, `ctx_check_task_completion`,
    +//     `ctx_session_*`, `ctx_checktaskcompletion`,
     //     `ctx_watch_update`).
     //   - **`tools/call`**: invoke one tool with a typed
     //     arguments map.
    diff --git a/internal/mcp/server/server_test.go b/internal/mcp/server/server_test.go
    index 3b2493831..b3ae15f95 100644
    --- a/internal/mcp/server/server_test.go
    +++ b/internal/mcp/server/server_test.go
    @@ -258,8 +258,8 @@ func TestToolsList(t *testing.T) {
     	for _, want := range []string{
     		"ctx_status", "ctx_add", "ctx_complete", "ctx_drift",
     		"ctx_journal_source", "ctx_watch_update", "ctx_compact",
    -		"ctx_next", "ctx_check_task_completion",
    -		"ctx_session_event", "ctx_remind",
    +		"ctx_next", "ctx_checktaskcompletion",
    +		"ctx_sessionevent", "ctx_remind",
     		"ctx_steering_get", "ctx_search",
     		"ctx_session_start", "ctx_session_end",
     	} {
    @@ -803,7 +803,7 @@ func TestToolNextAllComplete(t *testing.T) {
     func TestToolCheckTaskCompletion(t *testing.T) {
     	srv, _ := newTestServer(t)
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name: "ctx_check_task_completion",
    +		Name: "ctx_checktaskcompletion",
     		Arguments: map[string]interface{}{
     			"recent_action": "Finished build of the MCP server",
     		},
    @@ -828,7 +828,7 @@ func TestToolCheckTaskCompletionNoMatch(t *testing.T) {
     
     	// Prime session state to avoid governance warnings in response.
     	request(t, srv, "tools/call", proto.CallToolParams{
    -		Name:      "ctx_session_event",
    +		Name:      "ctx_sessionevent",
     		Arguments: map[string]interface{}{"type": "start"},
     	})
     	request(t, srv, "tools/call", proto.CallToolParams{
    @@ -836,7 +836,7 @@ func TestToolCheckTaskCompletionNoMatch(t *testing.T) {
     	})
     
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name: "ctx_check_task_completion",
    +		Name: "ctx_checktaskcompletion",
     		Arguments: map[string]interface{}{
     			"recent_action": "Updated CSS styles",
     		},
    @@ -861,7 +861,7 @@ func TestToolCheckTaskCompletionNoMatch(t *testing.T) {
     func TestToolSessionEventStart(t *testing.T) {
     	srv, _ := newTestServer(t)
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name: "ctx_session_event",
    +		Name: "ctx_sessionevent",
     		Arguments: map[string]interface{}{
     			"type":   "start",
     			"caller": "vscode",
    @@ -890,7 +890,7 @@ func TestToolSessionEventStart(t *testing.T) {
     func TestToolSessionEventEnd(t *testing.T) {
     	srv, _ := newTestServer(t)
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name:      "ctx_session_event",
    +		Name:      "ctx_sessionevent",
     		Arguments: map[string]interface{}{"type": "end"},
     	})
     	if resp.Error != nil {
    @@ -913,7 +913,7 @@ func TestToolSessionEventEnd(t *testing.T) {
     func TestToolSessionEventInvalid(t *testing.T) {
     	srv, _ := newTestServer(t)
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name:      "ctx_session_event",
    +		Name:      "ctx_sessionevent",
     		Arguments: map[string]interface{}{"type": "pause"},
     	})
     	if resp.Error != nil {
    @@ -1092,7 +1092,7 @@ func TestSessionStateTracking(t *testing.T) {
     
     	// Start session.
     	request(t, srv, "tools/call", proto.CallToolParams{
    -		Name:      "ctx_session_event",
    +		Name:      "ctx_sessionevent",
     		Arguments: map[string]interface{}{"type": "start"},
     	})
     
    @@ -1102,7 +1102,7 @@ func TestSessionStateTracking(t *testing.T) {
     
     	// End session - should report tool call count.
     	resp := request(t, srv, "tools/call", proto.CallToolParams{
    -		Name:      "ctx_session_event",
    +		Name:      "ctx_sessionevent",
     		Arguments: map[string]interface{}{"type": "end"},
     	})
     	raw, _ := json.Marshal(resp.Result)
    diff --git a/internal/notify/doc.go b/internal/notify/doc.go
    index 377df1c99..0b0ad0cd6 100644
    --- a/internal/notify/doc.go
    +++ b/internal/notify/doc.go
    @@ -66,7 +66,7 @@
     // `key_rotation_days`, default 90) requires re-running
     // `ctx pad init` *and* `ctx hook notify setup`. The
     // rotation nudge fires from
    -// `internal/cli/system/cmd/check_version`.
    +// `internal/cli/system/cmd/checkversion`.
     //
     // # Concurrency
     //
    diff --git a/internal/sysinfo/doc.go b/internal/sysinfo/doc.go
    index e4f5cfb81..7bba03568 100644
    --- a/internal/sysinfo/doc.go
    +++ b/internal/sysinfo/doc.go
    @@ -13,7 +13,7 @@
     //
     //   - **`ctx sysinfo`**: the top-level user-facing CLI that
     //     prints a snapshot of host resources.
    -//   - **`ctx system check_resource`**: the hook that fires a
    +//   - **`ctx system checkresource`**: the hook that fires a
     //     pressure warning during sessions when load, memory, or
     //     disk crosses a danger threshold.
     //
    diff --git a/internal/write/closeout/closeout_test.go b/internal/write/closeout/closeout_test.go
    index 79aa05929..387c4388b 100644
    --- a/internal/write/closeout/closeout_test.go
    +++ b/internal/write/closeout/closeout_test.go
    @@ -15,7 +15,7 @@ import (
     	"testing"
     	"time"
     
    -	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/git_meta"
    +	cfgGitmeta "github.com/ActiveMemory/ctx/internal/config/gitmeta"
     	cfgKB "github.com/ActiveMemory/ctx/internal/config/kb"
     	errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout"
     	"github.com/ActiveMemory/ctx/internal/write/closeout"
    diff --git a/internal/write/closeout/doc.go b/internal/write/closeout/doc.go
    index 4f7189552..0de41a85e 100644
    --- a/internal/write/closeout/doc.go
    +++ b/internal/write/closeout/doc.go
    @@ -34,7 +34,7 @@
     //   - [github.com/ActiveMemory/ctx/internal/write/handover]
     //     consumes closeouts via [List] + [PostdatedBy] + [Archive]
     //     during handover fold.
    -//   - [github.com/ActiveMemory/ctx/internal/git_meta] supplies
    +//   - [github.com/ActiveMemory/ctx/internal/gitmeta] supplies
     //     the (sha, branch) pair stamped into the frontmatter.
     //   - [github.com/ActiveMemory/ctx/internal/config/kb] supplies
     //     the mode + closeout-suffix constants.
    diff --git a/internal/write/closeout/write.go b/internal/write/closeout/write.go
    index 4693458df..8f2060410 100644
    --- a/internal/write/closeout/write.go
    +++ b/internal/write/closeout/write.go
    @@ -12,7 +12,7 @@ import (
     
     	cfgFs "github.com/ActiveMemory/ctx/internal/config/fs"
     	errCloseout "github.com/ActiveMemory/ctx/internal/err/closeout"
    -	"github.com/ActiveMemory/ctx/internal/git_meta"
    +	"github.com/ActiveMemory/ctx/internal/gitmeta"
     	"github.com/ActiveMemory/ctx/internal/io"
     )
     
    diff --git a/internal/write/handover/doc.go b/internal/write/handover/doc.go
    index fd031ee72..277834a0d 100644
    --- a/internal/write/handover/doc.go
    +++ b/internal/write/handover/doc.go
    @@ -46,7 +46,7 @@
     //
     //   - [github.com/ActiveMemory/ctx/internal/write/closeout]
     //     supplies the optional fold-source files.
    -//   - [github.com/ActiveMemory/ctx/internal/git_meta] stamps
    +//   - [github.com/ActiveMemory/ctx/internal/gitmeta] stamps
     //     `sha` / `branch` provenance.
     //   - [github.com/ActiveMemory/ctx/internal/cli/handover/core/path]
     //     resolves `.context/handovers/`.
    diff --git a/internal/write/handover/provenance.go b/internal/write/handover/provenance.go
    index c54ad01d5..3e57a712f 100644
    --- a/internal/write/handover/provenance.go
    +++ b/internal/write/handover/provenance.go
    @@ -9,7 +9,7 @@ package handover
     import (
     	cfgHandover "github.com/ActiveMemory/ctx/internal/config/handover"
     	errHandover "github.com/ActiveMemory/ctx/internal/err/handover"
    -	"github.com/ActiveMemory/ctx/internal/git_meta"
    +	"github.com/ActiveMemory/ctx/internal/gitmeta"
     )
     
     // resolveProvenance picks the SHA / branch pair for a new
    diff --git a/internal/write/handover/write.go b/internal/write/handover/write.go
    index 9ade204d7..fc19558ea 100644
    --- a/internal/write/handover/write.go
    +++ b/internal/write/handover/write.go
    @@ -29,7 +29,7 @@ import (
     //   - closeoutsDir: absolute path to .context/ingest/closeouts/.
     //   - archiveDir: absolute path to .context/archive/closeouts/.
     //   - projectRoot: absolute path to the project root; passed to
    -//     [github.com/ActiveMemory/ctx/internal/git_meta.ResolveHead].
    +//     [github.com/ActiveMemory/ctx/internal/gitmeta.ResolveHead].
     //   - entry: caller-supplied content + flags.
     //
     // Returns:
    diff --git a/internal/write/initialize/init.go b/internal/write/initialize/init.go
    index b8a161208..eb01034cc 100644
    --- a/internal/write/initialize/init.go
    +++ b/internal/write/initialize/init.go
    @@ -257,7 +257,7 @@ func ClaudePluginMissing(cmd *cobra.Command) {
     // can quickly see which clone feeds their plugin.
     //
     // Callers should fall back to [ClaudeReadyMinimal] if
    -// [claude_check.Details] couldn't parse plugin metadata.
    +// [claudecheck.Details] couldn't parse plugin metadata.
     //
     // Parameters:
     //   - cmd: Cobra command for output
    @@ -279,7 +279,7 @@ func ClaudeReady(
     }
     
     // ClaudeReadyMinimal prints the fallback one-line
    -// confirmation used when [claude_check.Details] couldn't
    +// confirmation used when [claudecheck.Details] couldn't
     // parse plugin metadata from the Claude Code plugin
     // registry files. Never fails.
     //
    diff --git a/internal/write/mark_journal/doc.go b/internal/write/markjournal/doc.go
    similarity index 83%
    rename from internal/write/mark_journal/doc.go
    rename to internal/write/markjournal/doc.go
    index 60fa418f8..c056ebb20 100644
    --- a/internal/write/mark_journal/doc.go
    +++ b/internal/write/markjournal/doc.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -// Package mark_journal provides terminal output for the
    +// Package markjournal provides terminal output for the
     // journal processing stage marker commands (ctx journal
     // mark).
     //
    @@ -31,8 +31,8 @@
     // # Usage
     //
     //	if checkOnly {
    -//	    mark_journal.StageChecked(cmd, file, stage, val)
    +//	    markjournal.StageChecked(cmd, file, stage, val)
     //	} else {
    -//	    mark_journal.StageMarked(cmd, file, stage)
    +//	    markjournal.StageMarked(cmd, file, stage)
     //	}
    -package mark_journal
    +package markjournal
    diff --git a/internal/write/mark_journal/mark_journal.go b/internal/write/markjournal/markjournal.go
    similarity index 98%
    rename from internal/write/mark_journal/mark_journal.go
    rename to internal/write/markjournal/markjournal.go
    index 83541a81d..5c4264192 100644
    --- a/internal/write/mark_journal/mark_journal.go
    +++ b/internal/write/markjournal/markjournal.go
    @@ -4,7 +4,7 @@
     //   \    Copyright 2026-present Context contributors.
     //                 SPDX-License-Identifier: Apache-2.0
     
    -package mark_journal
    +package markjournal
     
     import (
     	"fmt"
    diff --git a/site/cli/kb/index.html b/site/cli/kb/index.html
    index a1ebc45cd..c080b5455 100644
    --- a/site/cli/kb/index.html
    +++ b/site/cli/kb/index.html
    @@ -1122,7 +1122,7 @@ <h2 id="ctx-kb"><code>ctx kb</code><a class="headerlink" href="#ctx-kb" title="P
     <tr>
     <td><code>ctx kb ground</code></td>
     <td>Skill-driven</td>
    -<td>External grounding via <code>grounding-sources.md</code>. Refuses when the file is empty.</td>
    +<td>Read-only freshness audit over tracked sources listed in <code>grounding-sources.md</code> (URLs, in-tree paths, MCP resources). Refuses when the file is empty.</td>
     </tr>
     </tbody>
     </table>
    diff --git a/site/cli/mcp/index.html b/site/cli/mcp/index.html
    index c3e816e9c..fa7fc660b 100644
    --- a/site/cli/mcp/index.html
    +++ b/site/cli/mcp/index.html
    @@ -1418,10 +1418,10 @@
     </li>
             
               <li class="md-nav__item">
    -  <a href="#ctx_check_task_completion" class="md-nav__link">
    +  <a href="#ctx_checktaskcompletion" class="md-nav__link">
         <span class="md-ellipsis">
           
    -        ctx_check_task_completion
    +        ctx_checktaskcompletion
           
         </span>
       </a>
    @@ -1429,10 +1429,10 @@
     </li>
             
               <li class="md-nav__item">
    -  <a href="#ctx_session_event" class="md-nav__link">
    +  <a href="#ctx_sessionevent" class="md-nav__link">
         <span class="md-ellipsis">
           
    -        ctx_session_event
    +        ctx_sessionevent
           
         </span>
       </a>
    @@ -2394,10 +2394,10 @@
     </li>
             
               <li class="md-nav__item">
    -  <a href="#ctx_check_task_completion" class="md-nav__link">
    +  <a href="#ctx_checktaskcompletion" class="md-nav__link">
         <span class="md-ellipsis">
           
    -        ctx_check_task_completion
    +        ctx_checktaskcompletion
           
         </span>
       </a>
    @@ -2405,10 +2405,10 @@
     </li>
             
               <li class="md-nav__item">
    -  <a href="#ctx_session_event" class="md-nav__link">
    +  <a href="#ctx_sessionevent" class="md-nav__link">
         <span class="md-ellipsis">
           
    -        ctx_session_event
    +        ctx_sessionevent
           
         </span>
       </a>
    @@ -2966,7 +2966,7 @@ <h3 id="ctx_compact"><code>ctx_compact</code><a class="headerlink" href="#ctx_co
     <h3 id="ctx_next"><code>ctx_next</code><a class="headerlink" href="#ctx_next" title="Permanent link">¶</a></h3>
     <p>Suggest the next pending task based on priority and position.</p>
     <p><strong>Arguments:</strong> None. <strong>Read-only.</strong></p>
    -<h3 id="ctx_check_task_completion"><code>ctx_check_task_completion</code><a class="headerlink" href="#ctx_check_task_completion" title="Permanent link">¶</a></h3>
    +<h3 id="ctx_checktaskcompletion"><code>ctx_checktaskcompletion</code><a class="headerlink" href="#ctx_checktaskcompletion" title="Permanent link">¶</a></h3>
     <p>Advisory check: after a write operation, detect if any pending tasks
     were silently completed. Returns nudge text if a match is found.</p>
     <table>
    @@ -2988,7 +2988,7 @@ <h3 id="ctx_check_task_completion"><code>ctx_check_task_completion</code><a clas
     </tbody>
     </table>
     <p><strong>Read-only.</strong></p>
    -<h3 id="ctx_session_event"><code>ctx_session_event</code><a class="headerlink" href="#ctx_session_event" title="Permanent link">¶</a></h3>
    +<h3 id="ctx_sessionevent"><code>ctx_sessionevent</code><a class="headerlink" href="#ctx_sessionevent" title="Permanent link">¶</a></h3>
     <p>Signal a session lifecycle event. Type <code>end</code> triggers the session-end
     persistence ceremony - human confirmation required.</p>
     <table>
    diff --git a/site/home/contributing/index.html b/site/home/contributing/index.html
    index ff7de565a..01c045e00 100644
    --- a/site/home/contributing/index.html
    +++ b/site/home/contributing/index.html
    @@ -2634,12 +2634,26 @@ <h4 id="output-internalwrite">Output: <code>internal/write/</code><a class="head
     cobra is initialized.</p>
     <h4 id="errors-internalerr">Errors: <code>internal/err/</code><a class="headerlink" href="#errors-internalerr" title="Permanent link">¶</a></h4>
     <p>Domain-specific error constructors live under <code>internal/err/<domain>/</code>.
    -Each package mirrors the write structure. Functions return <code>error</code>
    -(never custom error types) and load messages from YAML via
    -<code>desc.Text(text.DescKey*)</code>.</p>
    -<div class="language-text highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a>internal/err/add/add.go         # errors for ctx add
    -</span><span id="__span-6-2"><a id="__codelineno-6-2" name="__codelineno-6-2" href="#__codelineno-6-2"></a>internal/err/config/config.go   # errors for configuration
    -</span><span id="__span-6-3"><a id="__codelineno-6-3" name="__codelineno-6-3" href="#__codelineno-6-3"></a>internal/err/cli/cli.go         # errors for CLI argument validation
    +Each package mirrors the write structure. Constructor functions return
    +<code>error</code> and load messages from YAML via <code>desc.Text(text.DescKey*)</code>.</p>
    +<p>Identity sentinels (matched at the call site with <code>errors.Is</code>) are
    +declared as <code>entity.Sentinel</code> consts:</p>
    +<div class="language-go highlight"><pre><span></span><code><span id="__span-6-1"><a id="__codelineno-6-1" name="__codelineno-6-1" href="#__codelineno-6-1"></a><span class="kd">const</span><span class="w"> </span><span class="nx">ErrMissingFoo</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">entity</span><span class="p">.</span><span class="nx">Sentinel</span><span class="p">(</span><span class="nx">text</span><span class="p">.</span><span class="nx">DescKeyErrPkgMissingFoo</span><span class="p">)</span>
    +</span></code></pre></div>
    +<p><code>entity.Sentinel</code> is a typed string whose <code>Error()</code> resolves the key
    +through <code>desc.Text</code> at call time, so the user-facing text stays in
    +<code>commands/text/errors.yaml</code> and the sentinel value itself remains
    +pure identity. Never declare sentinels as <code>var ErrX = errors.New(...)</code>
    +with a hardcoded English string — that bypasses localization and
    +materializes the string before the embedded YAML lookup is populated.</p>
    +<p>When a sentinel needs to carry fields (a path, a name), use a typed
    +struct in <code>internal/err/<domain>/</code> instead. See
    +<code>internal/err/context.NotFoundError</code> for the canonical pattern with
    +<code>Error()</code>, <code>Is(target error) bool</code>, and an <code>errors.As</code> consumer
    +contract.</p>
    +<div class="language-text highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a>internal/err/add/add.go         # errors for ctx add
    +</span><span id="__span-7-2"><a id="__codelineno-7-2" name="__codelineno-7-2" href="#__codelineno-7-2"></a>internal/err/config/config.go   # errors for configuration
    +</span><span id="__span-7-3"><a id="__codelineno-7-3" name="__codelineno-7-3" href="#__codelineno-7-3"></a>internal/err/cli/cli.go         # errors for CLI argument validation
     </span></code></pre></div>
     <h4 id="config-constants-internalconfig">Config Constants: <code>internal/config/</code><a class="headerlink" href="#config-constants-internalconfig" title="Permanent link">¶</a></h4>
     <p>Pure-constant leaf packages with zero internal dependencies (stdlib
    @@ -2739,7 +2753,7 @@ <h3 id="test-expectations">Test Expectations<a class="headerlink" href="#test-ex
     <h2 id="day-to-day-workflow">Day-to-Day Workflow<a class="headerlink" href="#day-to-day-workflow" title="Permanent link">¶</a></h2>
     <h3 id="go-code-changes">Go Code Changes<a class="headerlink" href="#go-code-changes" title="Permanent link">¶</a></h3>
     <p>After modifying Go source files, rebuild and reinstall:</p>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-7-1"><a id="__codelineno-7-1" name="__codelineno-7-1" href="#__codelineno-7-1"></a>make<span class="w"> </span>build<span class="w"> </span><span class="o">&&</span><span class="w"> </span>sudo<span class="w"> </span>make<span class="w"> </span>install
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a>make<span class="w"> </span>build<span class="w"> </span><span class="o">&&</span><span class="w"> </span>sudo<span class="w"> </span>make<span class="w"> </span>install
     </span></code></pre></div>
     <p>The <code>ctx</code> binary is statically compiled. There is no hot reload.
     You must rebuild for Go changes to take effect.</p>
    @@ -2748,8 +2762,8 @@ <h3 id="skill-or-hook-changes">Skill or Hook Changes<a class="headerlink" href="
     <code>internal/assets/claude/hooks/</code>.</p>
     <p>Claude Code caches plugin files, so edits aren't picked up automatically.</p>
     <p><strong>Clear the cache and restart</strong>:</p>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-8-1"><a id="__codelineno-8-1" name="__codelineno-8-1" href="#__codelineno-8-1"></a>make<span class="w"> </span>plugin-reload<span class="w">   </span><span class="c1"># nukes ~/.claude/plugins/cache/activememory-ctx/</span>
    -</span><span id="__span-8-2"><a id="__codelineno-8-2" name="__codelineno-8-2" href="#__codelineno-8-2"></a><span class="c1"># then restart Claude Code</span>
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a>make<span class="w"> </span>plugin-reload<span class="w">   </span><span class="c1"># nukes ~/.claude/plugins/cache/activememory-ctx/</span>
    +</span><span id="__span-9-2"><a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a><span class="c1"># then restart Claude Code</span>
     </span></code></pre></div>
     <p>The plugin will be re-installed from your local marketplace on startup.
     No version bump is needed during development.</p>
    @@ -2785,9 +2799,9 @@ <h3 id="configuration-profiles">Configuration Profiles<a class="headerlink" href
     </tbody>
     </table>
     <p>Use <code>ctx</code> commands to switch:</p>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-9-1"><a id="__codelineno-9-1" name="__codelineno-9-1" href="#__codelineno-9-1"></a>ctx<span class="w"> </span>config<span class="w"> </span>switch<span class="w"> </span>dev<span class="w">      </span><span class="c1"># switch to dev profile</span>
    -</span><span id="__span-9-2"><a id="__codelineno-9-2" name="__codelineno-9-2" href="#__codelineno-9-2"></a>ctx<span class="w"> </span>config<span class="w"> </span>switch<span class="w"> </span>base<span class="w">     </span><span class="c1"># switch to base profile</span>
    -</span><span id="__span-9-3"><a id="__codelineno-9-3" name="__codelineno-9-3" href="#__codelineno-9-3"></a>ctx<span class="w"> </span>config<span class="w"> </span>status<span class="w">          </span><span class="c1"># show which profile is active</span>
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a>ctx<span class="w"> </span>config<span class="w"> </span>switch<span class="w"> </span>dev<span class="w">      </span><span class="c1"># switch to dev profile</span>
    +</span><span id="__span-10-2"><a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a>ctx<span class="w"> </span>config<span class="w"> </span>switch<span class="w"> </span>base<span class="w">     </span><span class="c1"># switch to base profile</span>
    +</span><span id="__span-10-3"><a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a>ctx<span class="w"> </span>config<span class="w"> </span>status<span class="w">          </span><span class="c1"># show which profile is active</span>
     </span></code></pre></div>
     <p>After cloning, run <code>ctx config switch dev</code> to get started with full logging.</p>
     <p>See <a href="../configuration/">Configuration</a> for the full <code>.ctxrc</code> option reference.</p>
    @@ -2799,13 +2813,13 @@ <h3 id="backups">Backups<a class="headerlink" href="#backups" title="Permanent l
     rsync, Time Machine, Borg, or whichever tool already handles the
     rest of your files.</p>
     <h3 id="running-tests">Running Tests<a class="headerlink" href="#running-tests" title="Permanent link">¶</a></h3>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-10-1"><a id="__codelineno-10-1" name="__codelineno-10-1" href="#__codelineno-10-1"></a>make<span class="w"> </span><span class="nb">test</span><span class="w">   </span><span class="c1"># fast: all tests</span>
    -</span><span id="__span-10-2"><a id="__codelineno-10-2" name="__codelineno-10-2" href="#__codelineno-10-2"></a>make<span class="w"> </span>audit<span class="w">  </span><span class="c1"># full: fmt + vet + lint + drift + docs + test</span>
    -</span><span id="__span-10-3"><a id="__codelineno-10-3" name="__codelineno-10-3" href="#__codelineno-10-3"></a>make<span class="w"> </span>smoke<span class="w">  </span><span class="c1"># build + run basic commands end-to-end</span>
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a>make<span class="w"> </span><span class="nb">test</span><span class="w">   </span><span class="c1"># fast: all tests</span>
    +</span><span id="__span-11-2"><a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a>make<span class="w"> </span>audit<span class="w">  </span><span class="c1"># full: fmt + vet + lint + drift + docs + test</span>
    +</span><span id="__span-11-3"><a id="__codelineno-11-3" name="__codelineno-11-3" href="#__codelineno-11-3"></a>make<span class="w"> </span>smoke<span class="w">  </span><span class="c1"># build + run basic commands end-to-end</span>
     </span></code></pre></div>
     <h3 id="running-the-docs-site-locally">Running the Docs Site Locally<a class="headerlink" href="#running-the-docs-site-locally" title="Permanent link">¶</a></h3>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-11-1"><a id="__codelineno-11-1" name="__codelineno-11-1" href="#__codelineno-11-1"></a>make<span class="w"> </span>site-setup<span class="w">  </span><span class="c1"># one-time: install zensical via pipx</span>
    -</span><span id="__span-11-2"><a id="__codelineno-11-2" name="__codelineno-11-2" href="#__codelineno-11-2"></a>make<span class="w"> </span>site-serve<span class="w">  </span><span class="c1"># serve at localhost</span>
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a>make<span class="w"> </span>site-setup<span class="w">  </span><span class="c1"># one-time: install zensical via pipx</span>
    +</span><span id="__span-12-2"><a id="__codelineno-12-2" name="__codelineno-12-2" href="#__codelineno-12-2"></a>make<span class="w"> </span>site-serve<span class="w">  </span><span class="c1"># serve at localhost</span>
     </span></code></pre></div>
     <hr />
     <h2 id="submitting-changes">Submitting Changes<a class="headerlink" href="#submitting-changes" title="Permanent link">¶</a></h2>
    @@ -2867,7 +2881,7 @@ <h3 id="developer-certificate-of-origin-dco">Developer Certificate of Origin (<e
     <p>By contributing, you agree to the
     <a href="https://github.com/ActiveMemory/ctx/blob/main/CONTRIBUTING_DCO.md">Developer Certificate of Origin</a>.</p>
     <p>All commits must be signed off:</p>
    -<div class="language-bash highlight"><pre><span></span><code><span id="__span-12-1"><a id="__codelineno-12-1" name="__codelineno-12-1" href="#__codelineno-12-1"></a>git<span class="w"> </span>commit<span class="w"> </span>-s<span class="w"> </span>-m<span class="w"> </span><span class="s2">"feat: add new feature"</span>
    +<div class="language-bash highlight"><pre><span></span><code><span id="__span-13-1"><a id="__codelineno-13-1" name="__codelineno-13-1" href="#__codelineno-13-1"></a>git<span class="w"> </span>commit<span class="w"> </span>-s<span class="w"> </span>-m<span class="w"> </span><span class="s2">"feat: add new feature"</span>
     </span></code></pre></div>
     <h3 id="license">License<a class="headerlink" href="#license" title="Permanent link">¶</a></h3>
     <p>Contributions are licensed under the
    diff --git a/site/home/opencode/index.html b/site/home/opencode/index.html
    index 91c68c073..32093200b 100644
    --- a/site/home/opencode/index.html
    +++ b/site/home/opencode/index.html
    @@ -2153,7 +2153,7 @@ <h2 id="mcp-tools">MCP Tools<a class="headerlink" href="#mcp-tools" title="Perma
     <td>Query recent AI session history</td>
     </tr>
     <tr>
    -<td><code>ctx_session_event</code></td>
    +<td><code>ctx_sessionevent</code></td>
     <td>Signal session start/end lifecycle events</td>
     </tr>
     <tr>
    @@ -2161,7 +2161,7 @@ <h2 id="mcp-tools">MCP Tools<a class="headerlink" href="#mcp-tools" title="Perma
     <td>Apply structured updates to <code>.context/</code> files</td>
     </tr>
     <tr>
    -<td><code>ctx_check_task_completion</code></td>
    +<td><code>ctx_checktaskcompletion</code></td>
     <td>After a write, detect silently completed tasks</td>
     </tr>
     </tbody>
    diff --git a/site/recipes/activating-context/index.html b/site/recipes/activating-context/index.html
    index 7dd5dabba..f768aaa80 100644
    --- a/site/recipes/activating-context/index.html
    +++ b/site/recipes/activating-context/index.html
    @@ -2255,8 +2255,8 @@ <h3 id="recommended-layout">Recommended Layout<a class="headerlink" href="#recom
     <h2 id="why-not-walk-up-automatically">Why Not Walk Up Automatically?<a class="headerlink" href="#why-not-walk-up-automatically" title="Permanent link">¶</a></h2>
     <p>Nested projects, submodules, rogue agent-created <code>.context/</code>
     directories, and sub-agent sessions all produced silent misrouting
    -under the old walk-up model. <code>ctx</code> decided to stop guessing and
    -require the caller to declare. Every other decision flows from
    +under a walk-up model. For consistency, <code>ctx</code> does not guess, 
    +and  requires the caller to declare. Every other decision flows from
     there.</p>
     
     
    diff --git a/site/recipes/build-a-knowledge-base/index.html b/site/recipes/build-a-knowledge-base/index.html
    index 43f37400e..43cdb0e09 100644
    --- a/site/recipes/build-a-knowledge-base/index.html
    +++ b/site/recipes/build-a-knowledge-base/index.html
    @@ -1090,6 +1090,17 @@
         </span>
       </a>
       
    +</li>
    +      
    +        <li class="md-nav__item">
    +  <a href="#bootstrap-vs-steady-state-ingest-first-ground-later" class="md-nav__link">
    +    <span class="md-ellipsis">
    +      
    +        Bootstrap vs Steady State: Ingest First, Ground Later
    +      
    +    </span>
    +  </a>
    +  
     </li>
           
             <li class="md-nav__item">
    @@ -1227,7 +1238,7 @@ <h2 id="commands-and-skills-used">Commands and Skills Used<a class="headerlink"
     <tr>
     <td><code>/ctx-kb-ground</code></td>
     <td>Skill</td>
    -<td>Re-grounding against external sources</td>
    +<td>Read-only freshness audit over the kb's tracked sources</td>
     </tr>
     <tr>
     <td><code>/ctx-kb-note</code></td>
    @@ -1338,9 +1349,16 @@ <h2 id="step-4-audit-re-ground">Step 4: Audit + Re-Ground<a class="headerlink" h
     <p><code>site-review</code> coerces malformed Confidence-band capitalization,
     flags malformed closeout frontmatter, and <strong>refuses to make
     judgment calls that require evidence</strong> (those go through ingest).</p>
    -<p><code>ground</code> reads <code>.context/ingest/grounding-sources.md</code> and runs
    -the equivalent of a fresh fetch + re-extract pass for each
    -listed source, useful when an upstream source has changed.</p>
    +<p><code>ground</code> reads <code>.context/ingest/grounding-sources.md</code> — the kb's
    +persistent watch list — and walks each declared source (URL,
    +in-tree path, or MCP resource) to check whether it has drifted
    +since the kb last cited it. The pass is <strong>read-only on the kb's
    +prose and evidence</strong>: it annotates the source-coverage ledger's
    +<code>Residue</code> / <code>Next action</code> cells and writes a ground closeout, but
    +does NOT re-extract claims, mint <code>EV-###</code> rows, or touch topic
    +pages. Drifted or new-to-kb sources are flagged for a follow-up
    +<code>/ctx-kb-ingest</code>. Use ground for "are the docs still current?"
    +hygiene; use <code>/ctx-kb-ingest</code> to actually absorb new material.</p>
     <h2 id="step-5-browse-the-kb-locally">Step 5: Browse the KB Locally<a class="headerlink" href="#step-5-browse-the-kb-locally" title="Permanent link">¶</a></h2>
     <p><code>.context/kb/</code> is a tree of Markdown files: topic pages live
     under <code>topics/<slug>/index.md</code> and cross-cutting artifacts
    @@ -1400,6 +1418,59 @@ <h2 id="how-it-ladders-together">How It Ladders Together<a class="headerlink" hr
     </span><span id="__span-9-23"><a id="__codelineno-9-23" name="__codelineno-9-23" href="#__codelineno-9-23"></a>                  /ctx-remember reads handover + postdated
     </span><span id="__span-9-24"><a id="__codelineno-9-24" name="__codelineno-9-24" href="#__codelineno-9-24"></a>                  unfolded closeouts as the recall surface
     </span></code></pre></div>
    +<h2 id="bootstrap-vs-steady-state-ingest-first-ground-later">Bootstrap vs Steady State: Ingest First, Ground Later<a class="headerlink" href="#bootstrap-vs-steady-state-ingest-first-ground-later" title="Permanent link">¶</a></h2>
    +<p><code>/ctx-kb-ingest</code> and <code>/ctx-kb-ground</code> both read sources, which
    +makes their relationship easy to misread. The distinction is
    +<strong>authority</strong>, not input shape:</p>
    +<ul>
    +<li><strong>Ingest writes.</strong> It mints <code>EV-###</code> rows, authors topic-page
    +  prose, transitions source-coverage ledger states. The source
    +  list is per-invocation (CLI args, inline gestures).</li>
    +<li><strong>Ground audits.</strong> It walks a <em>persistent watch list</em> in
    +  <code>grounding-sources.md</code>, reports drift, annotates the ledger's
    +  <code>Residue</code> and <code>Next action</code> cells, and <strong>never writes prose or
    +  evidence</strong>. Drifted sources surface as flags pointing at
    +  <code>/ctx-kb-ingest</code>.</li>
    +</ul>
    +<p>This drives the canonical flow:</p>
    +<p><strong>Bootstrap (pristine kb).</strong> Use <code>/ctx-kb-ingest <sources></code> to
    +absorb the first wave of material. Ground has nothing to compare
    +against in a pristine kb — <code>source-map.md</code> is empty, and
    +<code>grounding-sources.md</code> would just prompt for entries.</p>
    +<p><strong>Curate the watch list.</strong> Once the kb has content, edit
    +<code>grounding-sources.md</code> by hand to list the canonical sources the
    +kb's claims depend on — the load-bearing citations worth
    +checking for drift. Ground refuses to synthesise this list from
    +<code>source-map.md</code> by design; the watch list is a deliberate human
    +choice about what's worth tracking.</p>
    +<p><strong>Steady state.</strong> Ingest liberally as new material lands. Run
    +<code>/ctx-kb-ground</code> periodically — before a release, after a vendor
    +version bump, on whatever cadence fits — to detect drift on the
    +tracked subset. Drift surfaces as flags in the ground closeout
    +pointing at <code>/ctx-kb-ingest</code> for the actual write-side work.</p>
    +<p><strong>Rule of thumb:</strong></p>
    +<table>
    +<thead>
    +<tr>
    +<th>Situation</th>
    +<th>Skill</th>
    +</tr>
    +</thead>
    +<tbody>
    +<tr>
    +<td><em>"I have new material I want absorbed."</em></td>
    +<td><code>/ctx-kb-ingest</code></td>
    +</tr>
    +<tr>
    +<td><em>"Are the sources the kb depends on still current?"</em></td>
    +<td><code>/ctx-kb-ground</code></td>
    +</tr>
    +<tr>
    +<td><em>"Is the kb's structure clean (capitalisation, frontmatter)?"</em></td>
    +<td><code>/ctx-kb-site-review</code></td>
    +</tr>
    +</tbody>
    +</table>
     <h2 id="what-the-editorial-pipeline-is-not">What the Editorial Pipeline Is NOT<a class="headerlink" href="#what-the-editorial-pipeline-is-not" title="Permanent link">¶</a></h2>
     <ul>
     <li><strong>Not a substitute for <code>DECISIONS.md</code>.</strong> Project-level
    diff --git a/site/search.json b/site/search.json
    index 85f0ce069..0a07dba3f 100644
    --- a/site/search.json
    +++ b/site/search.json
    @@ -1 +1 @@
    -{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Manifesto","text":"","path":["Manifesto"],"tags":[]},{"location":"#the-ctx-manifesto","level":1,"title":"The <code>ctx</code> Manifesto","text":"<p>Creation, not code.</p> <p>Context, not prompts.</p> <p>Verification, not vibes.</p> <p>This Is NOT a Metaphor</p> <p>Code executes instructions.</p> <p>Creation produces outcomes.</p> <p>Confusing the two is how teams ship motion...</p> <p>...instead of progress.</p> <ul> <li>It was never about the code.</li> <li>Code has zero standalone value.</li> <li>Code is an implementation detail.</li> </ul> <p>Code is an incantation.</p> <p>Creation is the act.</p> <p>And creation does not happen in a vacuum.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-the-substrate","level":2,"title":"<code>ctx</code> Is the Substrate","text":"<p>Constraints Have Moved</p> <p>Human bandwidth is no longer the limiting factor.</p> <p>Context integrity is.</p> <p>Human bandwidth is no longer the constraint.</p> <p>Context is:</p> <ul> <li>Without durable context, intelligence resets.</li> <li>Without memory, reasoning decays.</li> <li>Without structure, scale collapses.</li> </ul> <p>Creation is now limited by:</p> <ul> <li>Clarity of intent;</li> <li>Quality of context;</li> <li>Rigor of verification.</li> </ul> <p>Not by speed.</p> <p>Not by capacity.</p> <p>Velocity Amplifies</p> <p>Faster execution on broken context compounds error.</p> <p>Speed multiplies whatever is already wrong.</p>","path":["Manifesto"],"tags":[]},{"location":"#humans-author-meaning","level":2,"title":"Humans Author Meaning","text":"<p>Intent Is Authored</p> <p>Systems can optimize.</p> <p>Models can generalize.</p> <p>Meaning must be chosen.</p> <p>Intent is not emergent.</p> <p>Vision, goals, and direction are human responsibilities.</p> <p>We decide:</p> <ul> <li>What matters;</li> <li>What success means;</li> <li>What world we are building.</li> </ul> <p><code>ctx</code> encodes the intent so it...</p> <ul> <li>survives time,</li> <li>survives handoffs,</li> <li>survives scale.</li> </ul> <p>Nothing important should live only in conversation.</p> <p>Nothing critical should depend on recall.</p> <p>Oral Tradition Does Not Scale</p> <p>If intent cannot be inspected, it cannot be enforced.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-before-action","level":2,"title":"<code>ctx</code> Before Action","text":"<p>Orientation Precedes Motion</p> <p>Acting first and understanding later is not bravery.</p> <p>It is debt.</p> <p>Never act without <code>ctx</code>.</p> <p>Before execution, we must verify:</p> <ul> <li>Where we are;</li> <li>Why we are here;</li> <li>What constraints apply;</li> <li>What assumptions are active.</li> </ul> <p>Action without <code>ctx</code> is gambling.</p> <p>Speed without orientation is noise.</p> <p><code>ctx</code> is not overhead: It is the cost of correctness.</p>","path":["Manifesto"],"tags":[]},{"location":"#persistent-context-beats-prompt-memory","level":2,"title":"Persistent Context Beats Prompt Memory","text":"<p>Transience Is the Default Failure Mode</p> <ul> <li>Prompts decay.</li> <li>Chats fragment.</li> <li>Memory heuristics drift.</li> </ul> <p>Prompts are transient.</p> <p>Chats are lossy.</p> <p>Memory heuristics drift.</p> <p><code>ctx</code> must be:</p> <ul> <li>Durable;</li> <li>Structured;</li> <li>Explicit;</li> <li>Queryable.</li> </ul> <p>Intent Must Be Intentional</p> <p>If intent exists only in a prompt... </p> <p>...alignment is already degrading.</p> <p>Knowledge lives in the artifacts:</p> <ul> <li>Decisions;</li> <li>Documentation;</li> <li>Dependency maps;</li> <li>Evaluation history.</li> </ul> <p>Artifacts Outlive Sessions</p> <p>What is not written will be re-learned.</p> <p>At full cost.</p>","path":["Manifesto"],"tags":[]},{"location":"#what-ctx-is-not","level":2,"title":"What <code>ctx</code> Is Not","text":"<p>Avoid Category Errors</p> <p>Mislabeling <code>ctx</code> guarantees misuse.</p> <p><code>ctx</code> is not a memory feature.</p> <ul> <li><code>ctx</code> is not prompt engineering.</li> <li><code>ctx</code> is not a productivity hack.</li> <li><code>ctx</code> is not automation theater.</li> </ul> <p><code>ctx</code> is a system for preserving intent under scale.</p> <p><code>ctx</code> is infrastructure.</p>","path":["Manifesto"],"tags":[]},{"location":"#verified-reality-is-the-scoreboard","level":2,"title":"Verified Reality Is the Scoreboard","text":"<p>Activity Is a False Proxy</p> <p>Output volume correlates poorly with impact.</p> <ul> <li>Code is not progress.</li> <li>Activity is not impact.</li> </ul> <p>The only truth that compounds is verified change. </p> <p>Verified change must exist in the real world.</p> <p>Hypotheses are cheap; outcomes are not.</p> <p><code>ctx</code> captures:</p> <ul> <li>What we expected;</li> <li>What we observed;</li> <li>Where reality diverged.</li> </ul> <p>If we cannot predict, measure, and verify the result...</p> <p>...it does not count.</p>","path":["Manifesto"],"tags":[]},{"location":"#build-to-learn-not-to-accumulate","level":2,"title":"Build to Learn, Not to Accumulate","text":"<p>Prototypes Have an Expiration Date</p> <p>A prototype's value is information, not longevity.</p> <p>Prototypes exist to reduce uncertainty.</p> <p>We build to:</p> <ul> <li>Test assumptions;</li> <li>Validate architecture;</li> <li>Answer specific questions.</li> </ul> <p>Not everything.</p> <p>Not blindly.</p> <p>Not permanently.</p> <p><code>ctx</code> records archeology so the cost is paid once.</p>","path":["Manifesto"],"tags":[]},{"location":"#failures-are-assets","level":2,"title":"Failures Are Assets","text":"<p>Failure without Capture Is Waste</p> <p>Pain that does not teach is pure loss.</p> <p>Failures are not erased: They are preserved.</p> <p>Each failure becomes:</p> <ul> <li>A documented hypothesis;</li> <li>An analyzed deviation;</li> <li>A permanent artifact.</li> </ul> <p>Rollback fixes symptoms: <code>ctx</code> fixes systems.</p> <p>A repeated mistake is a missing <code>ctx</code> artifact.</p>","path":["Manifesto"],"tags":[]},{"location":"#structure-enables-scale","level":2,"title":"Structure Enables Scale","text":"<p>Unbounded Autonomy Destabilizes</p> <p>Power without a structure produces chaos.</p> <p>Transpose it:</p> <p>Power without any structure becomes chaos.</p> <p><code>ctx</code> defines:</p> <ul> <li>Roles;</li> <li>Boundaries;</li> <li>Protocols;</li> <li>Escalation paths;</li> <li>Decision rights.</li> </ul> <p>Ambiguity is a system failure:</p> <ul> <li>Debates must be structured.</li> <li>Decisions must be explicit.</li> <li>History must be retained.</li> </ul>","path":["Manifesto"],"tags":[]},{"location":"#encode-intent-into-the-environment","level":2,"title":"Encode Intent into the Environment","text":"<p>Goodwill Does Not Belong to the Table</p> <p>Alignment that depends on memory will drift.</p> <p>Alignment cannot depend on memory or goodwill.</p> <p>Do not rely on people to remember.</p> <p>Encode the behavior, so it happens by default.</p> <p>Intent is encoded as:</p> <ul> <li>Policies;</li> <li>Schemas;</li> <li>Constraints;</li> <li>Evaluation harnesses.</li> </ul> <p>Rules must be machine-readable.</p> <p>Laws must be enforceable.</p> <p>If intent is implicit, drift is guaranteed.</p>","path":["Manifesto"],"tags":[]},{"location":"#cost-is-a-first-class-signal","level":2,"title":"Cost Is a First-Class Signal","text":"<p>Attention Is the Scarcest Resource</p> <p>Not ideas.</p> <p>Not ambition.</p> <p>Ideas do not compete on time:</p> <p>They compete on cost and impact:</p> <ul> <li>Attention is finite.</li> <li>Compute is finite.</li> <li>Context is expensive.</li> </ul> <p>We continuously ask:</p> <ul> <li>What the most valuable next action is.</li> <li>What outcome justifies the cost.</li> </ul> <p><code>ctx</code> guides allocation.</p> <p>Learning reshapes priority.</p>","path":["Manifesto"],"tags":[]},{"location":"#show-the-why","level":2,"title":"Show the Why","text":"<p><code>{}</code> (code, artifacts, apps, binaries) produce outputs;  they do not preserve reasoning.</p> <p>Systems that cannot explain themselves will not be trusted.</p> <p>Traceability builds trust.</p> <pre><code>     {} --> what\n\n    ctx --> why\n</code></pre> <p>We record:</p> <ul> <li>Explored paths;</li> <li>Rejected options;</li> <li>Assumptions made;</li> <li>Evidence used.</li> </ul> <p>Opaque systems erode trust:</p> <p>Transparent <code>ctx</code> compounds understanding.</p>","path":["Manifesto"],"tags":[]},{"location":"#continuously-verify-the-system","level":2,"title":"Continuously Verify the System","text":"<p>Stability Is Temporary</p> <p>Every assumption has a half-life:</p> <ul> <li>Models drift.</li> <li>Tools change.</li> <li>Assumptions rot.</li> </ul> <p><code>ctx</code> must be verified against reality.</p> <p>Trust is a spectrum.</p> <p>Trust is continuously re-earned:</p> <ul> <li>Benchmarks, </li> <li>regressions, </li> <li>and evaluations... </li> </ul> <p>...are safety rails.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-leverage","level":2,"title":"<code>ctx</code> Is Leverage","text":"<p>Humans Are Decision Engines</p> <p>Execution should not consume judgment.</p> <p>Humans must not be typists.</p> <p>We are the authors.</p> <p>Human effort is reserved for:</p> <ul> <li>Judgment;</li> <li>Design;</li> <li>Taste;</li> <li>Synthesis.</li> </ul> <p>Repetition is delegated.</p> <p>Toil is automated.</p> <p><code>ctx</code> preserves leverage across time.</p>","path":["Manifesto"],"tags":[]},{"location":"#the-thesis","level":2,"title":"The Thesis","text":"<p>Invariant</p> <p>Everything else is an implementation detail.</p> <ul> <li>Creation is the act.</li> <li><code>ctx</code> is the substrate.</li> <li>Verification is the truth.</li> </ul> <p>Code executes → Models reason → Agents amplify.</p> <p><code>ctx</code> lives on.</p> <ul> <li>Without <code>ctx</code>, intelligence resets.</li> <li>With <code>ctx</code>, creation compounds.</li> </ul>","path":["Manifesto"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"<p>Stories, insights, and lessons learned from building and using <code>ctx</code>.</p>","path":["Blog"],"tags":[]},{"location":"blog/#releases","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v080-the-architecture-release","level":3,"title":"<code>ctx</code> v0.8.0: The Architecture Release","text":"<p>March 23, 2026: 374 commits, 1,708 Go files touched, and a near-complete architectural overhaul. Every CLI package restructured into <code>cmd/ + core/</code> taxonomy, all user-facing strings externalized to YAML, MCP server for tool-agnostic AI integration, and the memory bridge connecting Claude Code's auto-memory to <code>.context/</code>.</p> <p>Topics: release, architecture, refactoring, MCP, localization</p>","path":["Blog"],"tags":[]},{"location":"blog/#field-notes","level":2,"title":"Field Notes","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-watermelon-rind-anti-pattern-why-smarter-tools-make-shallower-agents","level":3,"title":"The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents","text":"<p>April 6, 2026: Give an agent a graph query tool, and it produces output that's structurally correct but substantively hollow (the watermelon-rind antipattern: We ran three sessions analyzing the same codebase with different tool access: the one with no tools produced 5.2x more depth. The fix: a two-pass compiler for architecture understanding: force code reading first, verify with tools second. Constraint is the feature.</p> <p>Topics: architecture, code intelligence, agent behavior, design patterns, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#code-structure-as-an-agent-interface-what-19-ast-tests-taught-us","level":3,"title":"Code Structure as an Agent Interface: What 19 AST Tests Taught Us","text":"<p>April 2, 2026: We built 19 AST-based audit tests in a single session, touching 300+ files. In the process we discovered that \"old-school\" code quality constraints (no magic numbers, centralized error handling, 80-char lines, documentation) are exactly the constraints that make code readable to AI agents. If an agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.</p> <p>Topics: ast, code quality, agent readability, conventions, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#we-broke-the-31-rule","level":3,"title":"We Broke the 3:1 Rule","text":"<p>March 23, 2026: After v0.6.0, we ran 198 feature commits across 17 days before consolidating. The 3:1 rule says consolidate every 4<sup>th</sup> session. We did it after the 66<sup>th</sup>. The result: an 18-day, 181-commit cleanup marathon that took longer than the feature run itself. A follow-up to The 3:1 Ratio with empirical evidence from the v0.8.0 cycle.</p> <p>Topics: consolidation, technical debt, development workflow, convention drift, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#context-engineering","level":2,"title":"Context Engineering","text":"","path":["Blog"],"tags":[]},{"location":"blog/#agent-memory-is-infrastructure","level":3,"title":"Agent Memory Is Infrastructure","text":"<p>March 4, 2026: Every AI coding agent starts fresh. The obvious fix is \"memory.\" But there's a different problem memory doesn't touch: the project itself accumulates knowledge that has nothing to do with any single session. This post argues that agent memory is L2 (runtime cache); what's missing is L3 (project infrastructure).</p> <p>Topics: context engineering, agent memory, infrastructure, persistence, team knowledge</p>","path":["Blog"],"tags":[]},{"location":"blog/#context-as-infrastructure","level":3,"title":"Context as Infrastructure","text":"<p>February 17, 2026: Where does your AI's knowledge live between sessions? If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. This post argues for treating it as infrastructure instead: persistent files, separation of concerns, two-tier storage, progressive disclosure, and the filesystem as the most mature interface available.</p> <p>Topics: context engineering, infrastructure, progressive disclosure, persistence, design philosophy</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-attention-budget-why-your-ai-forgets-what-you-just-told-it","level":3,"title":"The Attention Budget: Why Your AI Forgets What You Just Told It","text":"<p>February 3, 2026: Every token you send to an AI consumes a finite resource: the attention budget. Understanding this constraint shaped every design decision in <code>ctx</code>: hierarchical file structure, explicit budgets, progressive disclosure, and filesystem-as-index.</p> <p>Topics: attention mechanics, context engineering, progressive disclosure, <code>ctx</code> primitives, token budgets</p>","path":["Blog"],"tags":[]},{"location":"blog/#before-context-windows-we-had-bouncers","level":3,"title":"Before Context Windows, We Had Bouncers","text":"<p>February 14, 2026: IRC is stateless. You disconnect, you vanish. Modern systems are not much different. This post traces the line from IRC bouncers to context engineering: stateless protocols require stateful wrappers, volatile interfaces require durable memory.</p> <p>Topics: context engineering, infrastructure, IRC, persistence, state continuity</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-last-question","level":3,"title":"The Last Question","text":"<p>February 28, 2026: In 1956, Asimov wrote a story about a question that spans the entire future of the universe. A reading of \"The Last Question\" through the lens of persistence, substrate migration, and what it means to build systems where sessions don't reset.</p> <p>Topics: context continuity, long-lived systems, persistence, intelligence over time, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#agent-behavior-and-design","level":2,"title":"Agent Behavior and Design","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-dog-ate-my-homework-teaching-ai-agents-to-read-before-they-write","level":3,"title":"The Dog Ate My Homework: Teaching AI Agents to Read Before They Write","text":"<p>February 25, 2026: You wrote the playbook. The agent skipped all of it. Five sessions, five failure modes, and the discovery that observable compliance beats perfect compliance.</p> <p>Topics: hooks, agent behavior, context engineering, behavioral design, testing methodology, compliance monitoring</p>","path":["Blog"],"tags":[]},{"location":"blog/#skills-that-fight-the-platform","level":3,"title":"Skills That Fight the Platform","text":"<p>February 4, 2026: When custom skills conflict with system prompt defaults, the AI has to reconcile contradictory instructions. Five conflict patterns discovered while building <code>ctx</code>.</p> <p>Topics: context engineering, skill design, system prompts, antipatterns, AI safety primitives</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-anatomy-of-a-skill-that-works","level":3,"title":"The Anatomy of a Skill That Works","text":"<p>February 7, 2026: I had 20 skills. Most were well-intentioned stubs. Then I rewrote all of them. Seven lessons emerged: quality gates prevent premature execution, negative triggers are load-bearing, examples set boundaries better than rules.</p> <p>Topics: skill design, context engineering, quality gates, E/A/R framework, practical patterns</p>","path":["Blog"],"tags":[]},{"location":"blog/#you-cant-import-expertise","level":3,"title":"You Can't Import Expertise","text":"<p>February 5, 2026: I found a well-crafted consolidation skill. Applied my own E/A/R framework: 70% was noise. This post is about why good skills can't be copy-pasted, and how to grow them from your project's own drift history.</p> <p>Topics: skill adaptation, E/A/R framework, convention drift, consolidation, project-specific expertise</p>","path":["Blog"],"tags":[]},{"location":"blog/#not-everything-is-a-skill","level":3,"title":"Not Everything Is a Skill","text":"<p>February 8, 2026: I ran an 8-agent codebase audit and got actionable results. The natural instinct was to wrap the prompt as a skill. Then I applied my own criteria: it failed all three tests.</p> <p>Topics: skill design, context engineering, automation discipline, recipes, agent teams</p>","path":["Blog"],"tags":[]},{"location":"blog/#defense-in-depth-securing-ai-agents","level":3,"title":"Defense in Depth: Securing AI Agents","text":"<p>February 9, 2026: The security advice was \"use CONSTITUTION.md for guardrails.\" That is wishful thinking. Five defense layers for unattended AI agents, each with a bypass, and why the strength is in the combination.</p> <p>Topics: agent security, defense in depth, prompt injection, autonomous loops, container isolation</p>","path":["Blog"],"tags":[]},{"location":"blog/#development-practice","level":2,"title":"Development Practice","text":"","path":["Blog"],"tags":[]},{"location":"blog/#code-is-cheap-judgment-is-not","level":3,"title":"Code Is Cheap. Judgment Is Not.","text":"<p>February 17, 2026: AI does not replace workers. It replaces unstructured effort. Three weeks of building <code>ctx</code> with an AI agent proved it: YOLO mode showed production is cheap, the 3:1 ratio showed judgment has a cadence.</p> <p>Topics: AI and expertise, context engineering, judgment vs production, human-AI collaboration, automation discipline</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-31-ratio","level":3,"title":"The 3:1 Ratio","text":"<p>February 17, 2026: AI makes technical debt worse: not because it writes bad code, but because it writes code so fast that drift accumulates before you notice. Three feature sessions, one consolidation session.</p> <p>Topics: consolidation, technical debt, development workflow, convention drift, code quality</p>","path":["Blog"],"tags":[]},{"location":"blog/#refactoring-with-intent-human-guided-sessions-in-ai-development","level":3,"title":"Refactoring with Intent: Human-Guided Sessions in AI Development","text":"<p>February 1, 2026: The YOLO mode shipped 14 commands in a week. But technical debt doesn't send invoices. This is the story of what happened when we started guiding the AI with intent.</p> <p>Topics: refactoring, code quality, documentation standards, module decomposition, YOLO versus intentional development</p>","path":["Blog"],"tags":[]},{"location":"blog/#how-deep-is-too-deep","level":3,"title":"How Deep Is Too Deep?","text":"<p>February 12, 2026: I kept feeling like I should go deeper into ML theory. Then I spent a week debugging an agent failure that had nothing to do with model architecture. When depth compounds and when it doesn't.</p> <p>Topics: AI foundations, abstraction boundaries, agentic systems, context engineering, failure modes</p>","path":["Blog"],"tags":[]},{"location":"blog/#agent-workflows","level":2,"title":"Agent Workflows","text":"","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-merge-debt-and-the-myth-of-overnight-progress","level":3,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"<p>February 17, 2026: You discover agents can run in parallel. So you open ten terminals. It is not progress: it is merge debt being manufactured in real time. The five-agent ceiling and why role separation beats file locking.</p> <p>Topics: agent workflows, parallelism, verification, context engineering, engineering practice</p>","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-with-git-worktrees","level":3,"title":"Parallel Agents with Git Worktrees","text":"<p>February 14, 2026: I had 30 open tasks that didn't touch the same files. Using git worktrees to partition a backlog by file overlap, run 3-4 agents simultaneously, and merge the results.</p> <p>Topics: agent teams, parallelism, git worktrees, context engineering, task management</p>","path":["Blog"],"tags":[]},{"location":"blog/#field-notes-and-signals","level":2,"title":"Field Notes and Signals","text":"","path":["Blog"],"tags":[]},{"location":"blog/#when-a-system-starts-explaining-itself","level":3,"title":"When a System Starts Explaining Itself","text":"<p>February 17, 2026: Every new substrate begins as a private advantage. Reality begins when other people start describing it in their own language. \"Better than Adderall\" is not praise; it is a diagnostic.</p> <p>Topics: field notes, adoption signals, infrastructure vs tools, context engineering, substrates</p>","path":["Blog"],"tags":[]},{"location":"blog/#why-zensical","level":3,"title":"Why Zensical","text":"<p>February 15, 2026: I needed a static site generator for the journal system. The instinct was Hugo. But instinct is not analysis. Why zensical was the right choice: thin dependencies, MkDocs-compatible config, and zero lock-in.</p> <p>Topics: tooling, static site generators, journal system, infrastructure decisions, context engineering</p>","path":["Blog"],"tags":[]},{"location":"blog/#releases_1","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v060-the-integration-release","level":3,"title":"<code>ctx</code> v0.6.0: The Integration Release","text":"<p>February 16, 2026: <code>ctx</code> is now a Claude Marketplace plugin. Two commands, no build step, no shell scripts. v0.6.0 replaces six Bash hook scripts with compiled Go subcommands and ships 25+ Skills as a plugin.</p> <p>Topics: release, plugin system, Claude Marketplace, distribution, security hardening</p>","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v030-the-discipline-release","level":3,"title":"<code>ctx</code> v0.3.0: The Discipline Release","text":"<p>February 15, 2026: No new headline feature. Just 35+ documentation and quality commits against ~15 feature commits. What a release looks like when the ratio of polish to features is 3:1.</p> <p>Topics: release, skills migration, consolidation, code quality, E/A/R framework</p>","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v020-the-archaeology-release","level":3,"title":"<code>ctx</code> v0.2.0: The Archaeology Release","text":"<p>February 1, 2026: What if your AI could remember everything? Not just the current session, but every session. <code>ctx</code> v0.2.0 introduces the recall and journal systems.</p> <p>Topics: session recall, journal system, structured entries, token budgets, meta-tools</p>","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building <code>ctx</code> Using <code>ctx</code>: A Meta-Experiment in AI-Assisted Development","text":"<p>January 27, 2026: What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of <code>ctx</code>.</p> <p>Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions</p>","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building <code>ctx</code> Using <code>ctx</code>","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>References to <code>.context/sessions/</code>, auto-save hooks, and <code>SessionEnd</code> auto-save in this post reflect the architecture at the time of writing.</p> <p></p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#a-meta-experiment-in-ai-assisted-development","level":2,"title":"A Meta-Experiment in AI-Assisted Development","text":"<p>Jose Alekhinne / 2026-01-27</p> <p>Can a Tool Design Itself?</p> <p>What happens when you build a tool designed to give AI memory,  using that very same tool to remember what you are building? </p> <p>This is the story of <code>ctx</code>, how it evolved from a hasty \"YOLO mode\" experiment  to a disciplined system for persistent AI context, and what I have  learned along the way.</p> <p>Context Is a Record</p> <p>Context is a persistent record.</p> <p>By \"context\", I don't mean model memory or stored thoughts: </p> <p>I mean the durable record of decisions, learnings, and intent  that normally evaporates between sessions.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"<p>Every developer who works with AI code generators knows the frustration: </p> <p>You have a deep, productive session where the AI understands your codebase,  your conventions, your decisions. And then you close the terminal. </p> <p>Tomorrow; it's a blank slate. The AI has forgotten everything.</p> <p>That is \"reset amnesia\", and it's not just annoying: it's expensive. </p> <p>Every session starts with: </p> <ul> <li>Re-explaining context;</li> <li>Re-reading files; </li> <li>Re-discovering decisions that were already made.</li> </ul> <p>I Needed Context</p> <p>\"I don't want to lose this discussion...</p> <p>...I am a brain-dead developer YOLO'ing my way out.\"</p> <p>☝️ that's exactly what I said to Claude when I first started working on <code>ctx</code>.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"<p>The project started as \"Active Memory\" (<code>amem</code>): a CLI tool to persist AI  context across sessions. </p> <p>The core idea was simple: </p> <ol> <li>Create a <code>.context/</code> directory with structured     Markdown files for decisions, learnings, tasks, and conventions. </li> <li>The AI reads these at session start and writes to them before     the session ends.</li> <li>There is no step 3.</li> </ol> <p>The first commit was just scaffolding. But within hours, the  Ralph Loop (An iterative AI development workflow) had produced a working CLI:</p> <pre><code>feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n</code></pre> <p>Not one, not two, but a whopping fourteen core commands shipped in rapid  succession!</p> <p>I was YOLO'ing like there was no tomorrow:</p> <ul> <li>Auto-accept every change;</li> <li>Let the AI run free;</li> <li>Ship features fast.</li> </ul>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using <code>amem</code> to Build <code>amem</code>","text":"<p>Here's where it gets interesting: On January 20<sup>th</sup>, I asked: </p> <p>\"Can I use <code>amem</code> to help you remember this context when I restart?\"</p> <p>The answer was yes, but with a gap: </p> <p>Autoload worked (via Claude Code's <code>PreToolUse</code> hook), but auto-save was  missing: If the user quit, with Ctrl+C, everything since the last manual save  was lost.</p> <p>That session became the first real test of the system. </p> <p>Here is the first session file we recorded:</p> <pre><code>## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: `amem` is for context management, Ralph Loop is for \ndevelopment workflow. They are complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           |\n|-----------|-----------------------------|-------------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient |\n| Full dump | Entire conversation         | Safety net, nothing lost      |\n\n| Where                  |\n|------------------------|\n| .context/*.md          |\n| .context/sessions/*.md |\n</code></pre> <p>This session file (written by the AI to preserve its own context) became the template for how <code>ctx</code> handles session persistence.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"<p>By January 21<sup>st</sup>, I realized \"Active Memory\" was too generic,  and (arguably) too marketing-smelly. </p> <p>Besides, the binary was already called <code>ctx</code> (short for Context),  the directory was <code>.context/</code>, and the slash commands would be <code>/ctx-*</code>. </p> <p>So it followed that the project should be renamed to <code>ctx</code> to make things  make sense.</p> <p>The rename touched 100+ files but was clean: a find-and-replace with Go's  type system catching any misses.</p> <p>The <code>git</code> history tells the story:</p> <pre><code>0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, but Dangerous","text":"<p>The Ralph Loop made feature development incredibly fast.</p> <p>But it created technical debt that I didn't notice until later.</p> <p>A comparison session on January 25<sup>th</sup> revealed the patterns:</p> YOLO Pattern What We Found <code>\"TASKS.md\"</code> scattered in 10 files Same string literal everywhere, no constants <code>dir + \"/\" + file</code> Should be <code>filepath.Join()</code> Monolithic <code>cli_test.go</code> (1500+ lines) Tests disconnected from implementations <code>package initcmd</code> in <code>init/</code> folder Go's \"init\" is reserved: subtle naming collision <p>Here is another analysis made by <code>ctx</code>:</p> <pre><code>● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n</code></pre> <p>The fix required a human-guided refactoring session. I continued to do that before every major release, from that point on.</p> <p>We introduced <code>internal/config/config.go</code> with semantic prefixes:</p> <pre><code>const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n</code></pre> <p>What I begrudgingly learned was:  YOLO mode is effective for velocity but accumulates debt. </p> <p>So I took a mental note to schedule  periodic consolidation sessions.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"<p>On January 21<sup>st</sup>, I ran an experiment: have another Claude instance rebuild  <code>ctx</code> from scratch using only the specs and <code>PROMPT.md</code>. </p> <p>The Ralph Loop ran, all tasks got checked off, the loop exited successfully.</p> <p>But the binary was broken!</p> <p>Commands just printed help text instead of executing. </p> <p>All tasks were marked \"complete\" but the implementation didn't work.</p> <p>Here's what <code>ctx</code> discovered:</p> <pre><code>## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute: they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n</code></pre> <p>This was humbling; to say the least.</p> <p>I realized I had the same blind spot in my own codebase: no integration tests that actually invoked the binary. </p> <p>So I added:</p> <ul> <li>Integration tests for all commands;</li> <li>Coverage targets (60-80% per package)</li> <li>Smoke tests in CI</li> <li>A constitution rule:    \"All code must pass tests before commit\"</li> </ul>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"<p>As lessons accumulated, there was the temptation to add everything to  <code>CONSTITUTION.md</code> as \"inviolable rules\". </p> <p>But I resisted.</p> <p>The constitution should contain only truly inviolable invariants:</p> <ul> <li>Security (no secrets, no customer data)</li> <li>Quality (tests must pass)</li> <li>Process (decisions need records)</li> <li><code>ctx</code> invocation (always use <code>PATH</code>, never fallback)</li> </ul> <p>Everything else (coding style, file organization, naming  conventions...) should go in to <code>CONVENTIONS.md</code>. </p> <p>Here's how <code>ctx</code> explained why the distinction was important: </p> <p>Decision Record, 2026-01-25</p> <p>Overly strict constitution creates friction and gets ignored.</p> <p>Conventions can be bent; constitution cannot.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder than They Look","text":"<p>Claude Code hooks seemed simple: Run a script before/after certain events. </p> <p>But I hit multiple gotchas:</p> <p>1. Key names matter</p> <pre><code>// WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n</code></pre> <p>2. Blocking requires specific output</p> <pre><code># WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n</code></pre> <p>3. Go's JSON escaping</p> <p><code>json.Marshal</code> escapes <code>></code>, <code><</code>, <code>&</code> as unicode (<code>\\u003e</code>) by default. </p> <p>When generating shell commands in JSON:</p> <pre><code>encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n</code></pre> <p>4. Regex overfitting</p> <p>My hook to block non-PATH <code>ctx</code> invocations initially matched too broadly:</p> <pre><code># WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"<p>By the time of this writing this project's <code>ctx</code> sessions  (<code>.context/sessions/</code>) contains 40+ files from this project's development.</p> <p>They are not part of the source code due to security, privacy, and size concerns.</p> <p>Middle Ground: The Scratchpad</p> <p>For sensitive notes that do need to travel with the project, <code>ctx pad</code> stores encrypted one-liners in git, and <code>ctx pad add \"label\" --file PATH</code> can ingest small files.</p> <p>See Scratchpad for details.</p> <p>However, they are invaluable for the project's progress.</p> <p>Each session file is a timestamped Markdown with:</p> <ul> <li>Summary of what has been accomplished;</li> <li>Key decisions made;</li> <li>Learnings discovered;</li> <li>Tasks for the next session;</li> <li>Technical context (platform, versions).</li> </ul> <p>These files are not autoloaded (that would bust the token budget). </p> <p>They are what I see as the \"archaeological record\" of <code>ctx</code>:</p> <p>When the AI needs deeper information about why something was done, it digs into the sessions.</p> <p>Auto-generated session files used a naming convention:</p> <pre><code>2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n</code></pre> <p>Update</p> <p>The session feature described here is historical. </p> <p>In current releases, <code>ctx</code> uses a journal instead: the enrichment  process generates meaningful slugs from context automatically, so there  is no need to manually save sessions.</p> <p>The <code>SessionEnd</code> hook captured transcripts automatically. Even <code>Ctrl+C</code> was caught.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"<p><code>ctx</code> helps record every significant architectural choice in  <code>.context/DECISIONS.md</code>. </p> <p>Here are some highlights:</p> <p>Reverse-chronological order (2026-01-27)</p> <pre><code>**Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n</code></pre> <p>PATH over hardcoded paths (2026-01-21)</p> <pre><code>**Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before \nproceeding.\n</code></pre> <p>Generic core with Claude enhancements (2026-01-20)</p> <pre><code>**Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"<p>The <code>.context/LEARNINGS.md</code> file captures gotchas that would otherwise be  forgotten. Each has Context, Lesson, and Application sections:</p> <p>CGO on ARM64</p> <pre><code>**Context**: `go test` failed with \n`gcc: error: unrecognized command-line option '-m64'`\n\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. \nAlways use `CGO_ENABLED=0`.\n</code></pre> <p>Claude Code skills format</p> <pre><code>**Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n</code></pre> <p>\"Do you remember?\" handling</p> <pre><code>**Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" \nhas an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification. Just do it.\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"<p>Completed tasks are archived to <code>.context/archive/</code> with timestamps. </p> <p>The archive from January 23<sup>rd</sup> shows 13 phases of work:</p> <ul> <li>Phase 1: Project Scaffolding (Go module, Cobra CLI)</li> <li>Phase 2-4: Core Commands    (init, status, agent, add, complete, drift, sync, compact, watch, hook)</li> <li>Phase 5: Session Management (save, list, load, parse, --extract)</li> <li>Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)</li> <li>Phase 7: Testing & Verification</li> <li>Phase 8: Task Archival</li> <li>Phase 9: Slash Commands</li> <li>Phase 9b: Ralph Loop Integration</li> <li>Phase 10: Project Rename</li> <li>Phase 11: Documentation</li> <li>Phase 12: Timestamp Correlation</li> <li>Phase 13: Rich Context Entries</li> </ul> <p>That's an impressive ^^173 commits** across 8 days of development.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned about AI-Assisted Development","text":"<p>1. Memory changes everything</p> <p>When the AI remembers decisions, it doesn't repeat mistakes. </p> <p>When the AI knows your conventions, it follows them. </p> <p><code>ctx</code> makes the AI a better collaborator because it's not starting from zero.</p> <p>2. Two-tier persistence works</p> <p>Curated context (<code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>TASKS.md</code>) is for  quick reload. </p> <p>Full session dumps are for archaeology. </p> <p>It's a futile effort to try to fit everything in the token budget.</p> <p>Persist more, load less.</p> <p>3. YOLO mode has its place</p> <p>For rapid prototyping, letting the AI run free is effective. </p> <p>But I had to schedule  consolidation sessions.</p> <p>Technical debt accumulates silently.</p> <p>4. The constitution should be small</p> <p>Only truly inviolable rules go in <code>CONSTITUTION.md</code>.  Everything else is a convention. </p> <p>If you put too much in the constitution, it will get ignored.</p> <p>5. Verification is non-negotiable</p> <p>\"All tasks complete\" means nothing if you haven't run the tests. </p> <p>Integration tests that invoke the actual binary caught bugs that  the unit tests missed.</p> <p>6. Session files are underrated</p> <p>The ability to grep through 40 session files and find exactly when and why a  decision was made helped me a lot. </p> <p>It's not about loading them into context: It is about having them  when you need them.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"<p>The next phase of <code>ctx</code> is the Recall System:</p> <ul> <li>Parser: Parse session capture markdowns, enrich with JSONL data</li> <li>Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI</li> <li>Server: Local HTTP server for browsing sessions</li> <li>Search: Inverted index for searching across sessions</li> <li>CLI: <code>ctx recall serve <path></code> to start the server</li> </ul> <p>The goal is to make the archaeological record browsable, not just <code>grep</code>-able.</p> <p>Because not everyone always lives in the terminal (me included).</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"<p>Building <code>ctx</code> using <code>ctx</code> was a meta-experiment in AI-assisted development. </p> <p>I learned that memory isn't just convenient: It's transformative:</p> <ul> <li>An AI that remembers your decisions doesn't repeat mistakes.</li> <li>An AI that knows your conventions doesn't need them re-explained.</li> </ul> <p>If you are reading this, chances are that you already have heard about <code>ctx</code>.</p> <ul> <li><code>ctx</code> is open source at  github.com/ActiveMemory/ctx,</li> <li>and the documentation lives at ctx.ist.</li> </ul> <p>Session Records Are a Gold Mine</p> <p>By the time of this writing, I have more than 70 megabytes of text-only session capture, spread across >100 Markdown and <code>JSONL</code> files.</p> <p>I am analyzing, synthesizing, encriching them with AI, running RAG (Retrieval-Augmented Generation) models on them, and the outcome surprises me every day.</p> <p>If you are a mere mortal tired of reset amnesia, give <code>ctx</code> a try. </p> <p>And when you do, check <code>.context/sessions/</code> sometime. </p> <p>The archaeological record might surprise you.</p> <p>This blog post was written with the help of <code>ctx</code> with full access to the  <code>ctx</code> session files, decision log, learning log, task archives, and  git history of <code>ctx</code>: The meta continues.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/","level":1,"title":"<code>ctx</code> v0.2.0: The Archaeology Release","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>The <code>.context/sessions/</code> directory referenced in this post has been eliminated. Session history is now accessed via <code>ctx recall</code> and enriched journals live in <code>.context/journal/</code>.</p> <p></p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#digging-through-the-past-to-build-the-future","level":2,"title":"Digging through the Past to Build the Future","text":"<p>Jose Alekhinne / 2026-02-01</p> <p>What If Your AI Could Remember Everything?</p> <p>Not just the current session, but every session:</p> <ul> <li>Every decision made,</li> <li>every mistake avoided, </li> <li>every path not taken.</li> </ul> <p>That's what v0.2.0 delivers.</p> <p>Between <code>v0.1.2</code> and <code>v0.2.0</code>, 86 commits landed across 5 days. </p> <p>The release notes list features and fixes. </p> <p>This post tells the story of why those features exist, and what  building them taught me.</p> <p>This isn't a changelog: It is an explanation of intent.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-problem-amnesia-isnt-just-session-level","level":2,"title":"The Problem: Amnesia Isn't Just Session-Level","text":"<p><code>v0.1.0</code> solved reset amnesia: </p> <p>The AI now remembers decisions, learnings, and tasks across sessions. </p> <p>But a new problem emerged, which I can sum up as: </p> <p>\"I (the human) am not AI.\"</p> <p>Frankly, I couldn't remember what the AI remembered.</p> <p>Let alone, I cannot remember what I ate for breakfast!</p> <p>In the course of days, I realized session transcripts piled up in  <code>.context/sessions/</code>; I was <code>grep</code>ping, <code>JSONL</code> files with thousands of lines... Raw tool calls, assistant responses, user messages...</p> <p>...all interleaved. </p> <p>Valuable context was effectively buried in machine-readable noise.</p> <p>I found myself grepping through files to answer questions like:</p> <ul> <li>\"When did we decide to use constants instead of literals?\"</li> <li>\"What was the session where we fixed the hook regex?\"</li> <li>\"How did the <code>embed.go</code> split actually happen?\"</li> </ul> <p>Fate Is Whimsical</p> <p>The irony was painful:</p> <p>I built a tool to prevent AI amnesia, but I was suffering from  human amnesia about what happened in AI sessions.</p> <p>This was the moment <code>ctx</code> stopped being just an AI tool and started needing to support the human on the other side of the loop.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-solution-recall-and-journal","level":2,"title":"The Solution: Recall and Journal","text":"<p><code>v0.2.0</code> introduces two interconnected systems.</p> <p>They solve different problems and only work well together.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-recall-browse-your-past","level":3,"title":"<code>ctx recall</code>: Browse Your Past","text":"<pre><code># List all sessions for this project\nctx recall list\n\n# Show a specific session\nctx recall show gleaming-wobbling-sutherland\n\n# See the full transcript\nctx recall show gleaming-wobbling-sutherland --full\n</code></pre> <p>The <code>recall</code> system parses Claude Code's <code>JSONL</code> transcripts and presents them in a human-readable format:</p> Session Date Turns Duration tender-painting-sundae 2026-01-29 3 <1m crystalline-gliding-willow 2026-01-29 3 <1m declarative-hugging-snowglobe 2026-01-31 2 <1m <p>Slugs are auto-generated from session IDs (memorable names instead of UUIDs). The goal (as the name implies) is recall, not archival accuracy.</p> <p>2,121 Lines of New Code</p> <p>The <code>ctx recall</code> feature was the largest single addition:</p> <p>parser library, CLI commands, test suite, and slash command.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-journal-from-raw-to-rich","level":3,"title":"<code>ctx journal</code>: From Raw to Rich","text":"<p>Listing sessions isn't enough. The transcripts are still unwieldy.</p> <ul> <li>Recall answers what happened.</li> <li>Journal answers what mattered.</li> </ul> <pre><code># Import sessions to editable Markdown\nctx recall import --all\n\n# Generate a static site from journal entries\nctx journal site\n\n# Serve it locally\nctx serve\n</code></pre> <p>The exported files land in <code>.context/journal/</code>:</p> <pre><code>.context/journal/\n├── 2026-01-28-proud-sleeping-cook-6e535360.md\n├── 2026-01-29-tender-painting-sundae-b14ddaaa.md\n├── 2026-01-29-crystalline-gliding-willow-ff7fd67d.md\n└── 2026-01-31-declarative-hugging-snowglobe-4549026d.md\n</code></pre> <p>Each file is a structured Markdown document ready for enrichment.</p> <p>They are meant to be read, edited, and reasoned about;  not just stored.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-meta-slash-commands-for-self-analysis","level":2,"title":"The Meta: Slash Commands for Self-Analysis","text":"<p>The journal system includes four slash commands that use Claude to analyze and synthesize session history:</p> Command Purpose <code>/ctx-journal-enrich</code> Add frontmatter, topics, tags <code>/ctx-blog</code> Generate blog post from activity <code>/ctx-blog-changelog</code> Generate changelog from commits <p>This very post was drafted using <code>/ctx-blog</code>. The previous post about refactoring was drafted the same way.</p> <p>So, yes: The meta continues: <code>ctx</code> now helps write posts about <code>ctx</code>.</p> <p>With the current release, <code>ctx</code> is no longer just recording history: </p> <p>It is participating in its interpretation.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-structure-decisions-as-first-class-citizens","level":2,"title":"The Structure: Decisions as First-Class Citizens","text":"<p><code>v0.1.0</code> let you add decisions with a simple command:</p> <pre><code>ctx add decision \"Use PostgreSQL\"\n</code></pre> <p>But sessions showed a pattern: decisions added this way were incomplete:</p> <ul> <li>Context was missing;</li> <li>Rationale was vague; </li> <li>Consequences were never stated.</li> </ul> <p>Once recall and journaling existed, this weakness became impossible to ignore: </p> <p>Structure stopped being optional.</p> <p><code>v0.2.0</code> enforces structure:</p> <pre><code>ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity, strong ecosystem\" \\\n  --consequence \"Need to set up connection pooling, team training\"\n</code></pre> <p>All three flags are required. No more placeholder text. </p> <p>Every decision is now a proper Architecture Decision Record (*ADR), not a note.</p> <p>The same enforcement applies to learnings too:</p> <pre><code>ctx add learning \"CGO breaks ARM64 builds\" \\\n  --context \"go test failed with gcc errors on ARM64\" \\\n  --lesson \"Always use CGO_ENABLED=0 for cross-platform builds\" \\\n  --application \"Added to Makefile and CI config\"\n</code></pre> <p>Structured Entries Are Prompts to the AI</p> <p>When the AI reads a decision with full context, rationale, and consequences, it understands the why, not just the what.</p> <p>One-liners teach nothing.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-order-newest-first","level":2,"title":"The Order: Newest First","text":"<p>A subtle but important change: <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> now use reverse-chronological order.</p> <p>One reason is token budgets, obviously; another reason is to help your fellow human (i.e., the Author): </p> <p>Earlier decisions are more likely to be relevant, and they are more likely to have more emphasis on the project. So it follows that they should be read first.</p> <p>But back to AI:</p> <p>When the AI reads a file, it reads from the top (and seldom from the bottom). </p> <p>If the token budget is tight, old content gets truncated.  As in any good engineering practice, it's always about the tradeoffs.</p> <p>Reverse order ensures the most recent (and most relevant) context is always loaded first.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-index-quick-reference-tables","level":2,"title":"The Index: Quick Reference Tables","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> now include auto-generated indexes.</p> <ul> <li>For AI agents, the index allows scanning without reading full entries.</li> <li>For humans, it's a table of contents.</li> </ul> <p>The same structure serves two very different readers.</p> <p>Reindex After Manual Edits</p> <p>If you edit entries by hand, rebuild the index with:</p> <pre><code>ctx decisions reindex\nctx learnings reindex\n</code></pre> <p>See the Knowledge Capture recipe for details.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-configuration-contextrc","level":2,"title":"The Configuration: <code>.contextrc</code>","text":"<p>Projects can now customize <code>ctx</code> behavior  via <code>.contextrc</code>.</p> <p>This makes <code>ctx</code> usable in real teams, not just personal projects.</p> <p>Priority order: CLI flags > environment variables > <code>.contextrc</code> >  sensible defaults</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-flags-global-cli-options","level":2,"title":"The Flags: Global CLI Options","text":"<p>Three new global flags work with any command.</p> <p>These enable automation: </p> <p>CI pipelines, scripts, and long-running tools can now integrate  <code>ctx</code> without hacks or workarounds.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-refactoring-under-the-hood","level":2,"title":"The Refactoring: Under the Hood","text":"<p>These aren't user-visible changes.</p> <p>They are the kind of work you only appreciate later, when everything else becomes easier to build.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#what-we-learned-building-v020","level":2,"title":"What We Learned Building v0.2.0","text":"","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#1-raw-data-isnt-knowledge","level":3,"title":"1. Raw Data Isn't Knowledge","text":"<p><code>JSONL</code> transcripts contain everything, and I mean \"everything\":</p> <p>They even contain hidden system messages that Anthropic injects to the LLM's conversation to treat humans better: It's immense.</p> <p>But \"everything\" isn't useful until it is transformed into something a human can reason about.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#2-enforcement-documentation","level":3,"title":"2. Enforcement > Documentation","text":"<p>The Prompt Is a Guideline</p> <p>The code is more what you'd call 'guidelines' than actual rules.</p> <p>-Hector Barbossa</p> <p>Rules written in Markdown are suggestions.</p> <p>Rules enforced by the CLI shape behavior; both for humans and AI.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#3-token-budget-is-ux","level":3,"title":"3. Token Budget Is UX","text":"<p>File order decides what the AI sees.</p> <p>That makes it a user experience concern, not an implementation detail.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#4-meta-tools-compound","level":3,"title":"4. Meta-Tools Compound","text":"<p>Tools that analyze their own development tend to generalize well.</p> <p>The journal system started as a way to understand <code>ctx</code> itself.</p> <p>It immediately became useful for everything else.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#v020-in-the-numbers","level":2,"title":"v0.2.0 in the Numbers","text":"<p>This was a heavy release. The numbers reflect that:</p> Metric v0.1.2 v0.2.0 Commits since last - 86 New commands 15 21 Slash commands 7 11 Lines of Go ~6,500 ~9,200 Session files (this project) 40 54 <p>The binary grew. The capability grew more.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#whats-next","level":2,"title":"What's Next","text":"<p>But those are future posts.</p> <p>This one was about making the past usable.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#get-started","level":2,"title":"Get Started","text":"<p>Update</p> <p>Since this post, <code>ctx</code> became a first-class Claude Code Marketplace plugin. Installation is now simpler. </p> <p>See the Getting Started guide for the  current instructions.</p> <pre><code>make build\nsudo make install\nctx init\n</code></pre> <p>The Archaeological Record</p> <p><code>v0.2.0</code> is the archaeology release because it makes the past accessible.</p> <p>Session transcripts aren't just logs anymore: They are a searchable, exportable, analyzable record of how your project evolved.</p> <p>The AI remembers. Now you can too.</p> <p>This blog post was generated with the help of <code>ctx</code> using the <code>/ctx-blog</code> slash command, with full access to git history, session files, decision logs, and learning logs from the v0.2.0 development window.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/","level":1,"title":"Refactoring with Intent","text":"","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#human-guided-sessions-in-ai-development","level":2,"title":"Human-Guided Sessions in AI Development","text":"<p>Jose Alekhinne / 2026-02-01</p> <p>What Happens When You Slow Down?</p> <p>YOLO mode shipped 14 commands in a week. </p> <p>But technical debt doesn't send invoices: It just waits.</p> <p>This is the story of what happened when I stopped auto-accepting everything and started guiding the AI with intent. </p> <p>The result: 27 commits across 4 days, a major version release, and  lessons that apply far beyond <code>ctx</code>.</p> <p>The Refactoring Window</p> <p>January 28 - February 1, 2026</p> <p>From commit <code>bb1cd20</code> to the v0.2.0 release merge. (this window matters more than the individual commits: it's where intent replaced velocity.)</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-velocity-trap","level":2,"title":"The Velocity Trap","text":"<p>In the previous post, I documented the \"YOLO mode\" that birthed <code>ctx</code>: auto-accept everything, let the AI run free, ship features fast.</p> <p>It worked: until it didn't.</p> <p>The codebase had accumulated patterns I didn't notice during the sprint:</p> YOLO Pattern Where Found Why It Hurts <code>\"TASKS.md\"</code> as literal 10+ files One typo = silent failure <code>dir + \"/\" + file</code> Path construction Breaks on Windows Monolithic <code>embed.go</code> 150+ lines, 5 concerns Untestable, hard to extend Inconsistent docstrings Everywhere AI can't learn project conventions <p>I didn't see these during \"YOLO mode\" because, honestly, I wasn't looking.</p> <p>Auto-accept means auto-ignore.</p> <p>In YOLO mode, every file you open looks fine until you try to change it.  </p> <p>In contrast, refactoring mode is when you start paying attention to that  hidden friction.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-shift-from-velocity-to-intent","level":2,"title":"The Shift: From Velocity to Intent","text":"<p>On January 28<sup>th</sup>, I changed the workflow:</p> <ol> <li>Read every diff before accepting.</li> <li>Ask \"why this way?\" before committing.</li> <li>Document patterns, not just features.</li> </ol> <p>The first commit of this era was telling:</p> <pre><code>feat: add structured attributes to context. update XML format\n</code></pre> <p>Not a new feature: A refinement:</p> <p>The <code>XML</code> format for context updates needed <code>type</code> and <code>timestamp</code> attributes. </p> <p>YOLO mode would have shipped something that worked. Intentional mode asked:  </p> <p>\"What does well-structured look like?\"</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-decomposition-embedgo","level":2,"title":"The Decomposition: <code>embed.go</code>","text":"<p>The most satisfying refactor was splitting <code>internal/claude/embed.go</code>.</p> <p>Before: One 153-line file doing five things:</p> <ul> <li>Command registration</li> <li>Hook generation</li> <li>Permission handling</li> <li>Script templates</li> <li>Type definitions</li> </ul> <p>... your \"de facto\" God object.</p> <p>After: Five focused modules:</p> File Lines Responsibility <code>cmd.go</code> 46 Command registration <code>hook.go</code> 64 Hook configuration <code>perm.go</code> 25 Permission handling <code>script.go</code> 47 Script templates <code>types.go</code> 7 Type definitions <p>The refactor also renamed functions to follow Go conventions:</p> <pre><code>// Before: unnecessary prefixes\nGetAutoSaveScript()\nGetBlockNonPathCtxScript()\nListCommands()\nCreateDefaultHooks()\n\n// After: idiomatic Go\nAutoSaveScript()\nBlockNonPathCtxScript()\nCommands()\nDefaultHooks()\n</code></pre> <p>This wasn't about character count. It was about teaching the AI what good Go looks like in this project.</p> <p>Project Conventions</p> <p>What I wanted from AI was to understand and follow the project's  conventions, and trust the author.</p> <p>The next time it generates code, it has better examples to learn from.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-documentation-debt","level":2,"title":"The Documentation Debt","text":"<p>YOLO mode created features. It didn't create documentation standards.</p> <p>The January 29<sup>th</sup> sessions focused on standardization.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#terminology-fixes","level":3,"title":"Terminology Fixes","text":"<ul> <li>\"context-update\" → \"entry\" (what users actually call them)</li> <li>Consistent naming across CLI, docs, and code comments</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#go-docstrings","level":3,"title":"Go Docstrings","text":"<pre><code>// Before: inconsistent or missing\nfunc Parse(s string) Entry { ... }\n\n// After: standardized sections\n\n// Parse extracts an entry from a markdown string.\n//\n// Parameters:\n//   - s: The markdown string to parse\n//\n// Returns:\n//   - Entry with populated fields, or zero value if parsing fails\nfunc Parse(s string) Entry { ... }\n</code></pre> <p>This is intentionally more structured than typical GoDoc:</p> <p>It serves as documentation and doubles as training data for future  AI-generated code.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#cli-output-convention","level":3,"title":"CLI Output Convention","text":"<pre><code>All CLI output follows: [emoji] [Title]: [message]\n\nExamples:\n  ✓ Decision added: Use symbolic types for entry categories\n  ⚠ Warning: No tasks found\n  ✗ Error: File not found\n</code></pre> <p>A consistent output shape makes both human scanning and  AI reasoning more reliable.</p> <p>These aren't exciting commits. But they are force multipliers:</p> <p>Every future AI session now has better examples to follow.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-journal-system","level":2,"title":"The Journal System","text":"<p>If you only read one section, read this one:</p> <p>This is where v0.2.0 becomes more than a refactor.</p> <p>The biggest feature of this change window wasn't a refactor; it was the journal system.</p> <p>45 Files Changed, 1680 Insertions</p> <p>This commit added the infrastructure for synthesizing AI session history into human-readable content.</p> <p>The journal system includes:</p> Component Purpose <code>ctx recall import</code> Import sessions to Markdown in <code>.context/journal/</code> <code>ctx journal site</code> Generate static site from journal entries <code>ctx serve</code> Convenience wrapper for the static site server <code>/ctx-journal-enrich</code> Slash command to add frontmatter and tags <code>/ctx-blog</code> Generate blog posts from recent activity <code>/ctx-blog-changelog</code> Generate changelog-style blog posts <p>...and the meta continues: this blog post was generated using <code>/ctx-blog</code>.</p> <p>The session history from January 28-31 was</p> <ul> <li>exported, </li> <li>enriched,</li> <li>and synthesized.</li> </ul> <p>into the narrative you are reading.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-constants-consolidation","level":2,"title":"The Constants Consolidation","text":"<p>The final refactoring session addressed the remaining magic strings:</p> <pre><code>const (\n    // Comment markers\n    CommentOpen  = \"<!--\"\n    CommentClose = \"-->\"\n\n    // Index markers\n    MarkerIndexStart = \"<!-- INDEX:START -->\"\n    MarkerIndexEnd   = \"<!-- INDEX:END -->\"\n\n    // Newlines\n    NewlineLF   = \"\\n\"\n    NewlineCRLF = \"\\r\\n\"\n)\n</code></pre> <p>The work also introduced thread safety in the recall parser and centralized shared validation logic; removing duplication that had quietly spread during YOLO mode.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#i-relearned-my-lessons","level":2,"title":"I (Re)Learned My Lessons","text":"<p>Similar to what I've learned in  the former human-assisted refactoring post, this journey also made me realize that \"AI-only code generation\" isn't sustainable in the long term.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#1-velocity-and-quality-arent-opposites","level":3,"title":"1. Velocity and Quality Aren't Opposites","text":"<p>YOLO mode has its place: for prototyping, exploration, and discovery.</p> <p>BUT (and it's a huge \"but\"), it needs to be followed by  consolidation sessions.</p> <p>The ratio that worked for me: 3:1.</p> <ul> <li>Three YOLO sessions create enough surface area to reveal patterns;</li> <li>the fourth session turns those patterns into structure.</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#2-documentation-is-code","level":3,"title":"2. Documentation IS Code","text":"<p>When I standardized docstrings, I wasn't just writing docs. I was training future AI sessions.</p> <p>Every example of good code becomes a template for generated code.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#3-decomposition-deletion","level":3,"title":"3. Decomposition > Deletion","text":"<p>When <code>embed.go</code> became unwieldy, the temptation was to remove functionality.</p> <p>The right answer was decomposition:</p> <ul> <li>Same functionality;</li> <li>Better organization;</li> <li>Easier to test;</li> <li>Easier to extend.</li> </ul> <p>The result: more lines overall, but dramatically better structure.</p> <p>The AI Benefit</p> <p>Smaller, focused files also help AI assistants. </p> <p>When a file fits comfortably in the context window, the AI can  reason about it completely instead of working from truncated snippets, preserving token budget for the actual task.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#4-meta-tools-pay-dividends","level":3,"title":"4. Meta-Tools Pay Dividends","text":"<p>The journal system took almost a full day to implement.</p> <p>Yet it paid for itself immediately:</p> <ul> <li>This blog post was generated from session history;</li> <li>Future posts will be easier;</li> <li>The archaeological record is now browsable, not just <code>grep</code>-able.</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-release-v020","level":2,"title":"The Release: v0.2.0","text":"<p>The refactoring window culminated in the v0.2.0 release.</p> <p>What's in v0.2.0:</p> Category Changes Features Journal system, quick reference indexes, global flags Refactors Module decomposition, constants consolidation, CRLF handling Docs Standardized terminology, Go docstrings, CLI conventions Quality Thread safety, shared validation, linter fixes <p>The version bump was symbolic.</p> <p>The real change was how the codebase felt.</p> <p>Opening files no longer triggered the familiar \"ugh, I need to clean this up\" reaction.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-meta-continues","level":2,"title":"The Meta Continues","text":"<p>This post was written using the tools built during this refactoring window:</p> <ol> <li>Session history imported via <code>ctx recall import</code>;</li> <li>Journal entries enriched via <code>/ctx-journal-enrich</code>;</li> <li>Blog draft generated via <code>/ctx-blog</code>;</li> <li>Final editing done (by yours truly), with full project context loaded.</li> </ol> <p>The Context Is Massive</p> <p>The <code>ctx</code> session files now contain 50+ development snapshots: each one capturing decisions, learnings, and intent.</p> <p>The Moral of the Story</p> <ul> <li>YOLO mode builds the prototype.</li> <li>Intentional mode builds the product.</li> </ul> <p>Schedule both, or you'll only get one, if you're lucky.</p> <p>This blog post was generated with the help of <code>ctx</code>, using session history, decision logs, learning logs, and git history from the refactoring window. The meta continues.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/","level":1,"title":"The Attention Budget","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>References to <code>.context/sessions/</code> in this post reflect the architecture at the time of writing. Session history is now accessed via <code>ctx recall</code> and stored in <code>.context/journal/</code>.</p> <p></p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#why-your-ai-forgets-what-you-just-told-it","level":2,"title":"Why Your AI Forgets What You Just Told It","text":"<p>Volkan Özçelik / 2026-02-03</p> <p>Ever Wondered Why AI Gets Worse the Longer You Talk?</p> <p>You paste a 2000-line file, explain the bug in detail, provide three examples...</p> <p>...and the AI still suggests a fix that ignores half of what you said.</p> <p>This isn't a bug. It is physics.</p> <p>Understanding that single fact shaped every design decision behind <code>ctx</code>.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-finite-resource-nobody-talks-about","level":2,"title":"The Finite Resource Nobody Talks About","text":"<p>Here's something that took me too long to internalize: context is not free.</p> <p>Every token you send to an AI model consumes a finite resource I call the attention budget.</p> <p>Attention budget is real.</p> <p>The model doesn't just read tokens; it forms relationships between them: </p> <p>For <code>n</code> tokens, that's roughly <code>n^2</code> relationships. </p> <p>Double the context, and the computation quadruples.</p> <p>But the more important constraint isn't cost: It's attention density.</p> <p>Attention Density</p> <p>Attention density is how much focus each token receives relative to all other tokens in the context window.</p> <p>As context grows, attention density drops: Each token gets a smaller slice of the model's focus. Nothing is ignored; but everything becomes blurrier.</p> <p>Think of it like a flashlight: In a small room, it illuminates everything clearly. In a warehouse, it becomes a dim glow that barely reaches the corners.</p> <p>This is why <code>ctx agent</code> has an explicit <code>--budget</code> flag:</p> <pre><code>ctx agent --budget 4000 # Force prioritization\nctx agent --budget 8000 # More context, lower attention density\n</code></pre> <p>The budget isn't just about cost: It's about preserving signal.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-middle-gets-lost","level":2,"title":"The Middle Gets Lost","text":"<p>This one surprised me.</p> <p>Research shows that transformer-based models tend to attend more strongly to the beginning and end of a context window than to its middle (a phenomenon often called  \"lost in the middle\")<sup>1</sup>.</p> <p>Positional anchors matter, and the middle has fewer of them.</p> <p>In practice, this means that information placed \"somewhere in the middle\" is statistically less salient, even if it's important.</p> <p><code>ctx</code> orders context files by logical progression: What the agent needs to know before it can understand the next thing:</p> <ol> <li><code>CONSTITUTION.md</code>: Constraints before action.</li> <li><code>TASKS.md</code>: Focus before patterns.</li> <li><code>CONVENTIONS.md</code>: How to write before where to write.</li> <li><code>ARCHITECTURE.md</code>: Structure before history.</li> <li><code>DECISIONS.md</code>: Past choices before gotchas.</li> <li><code>LEARNINGS.md</code>: Lessons before terminology.</li> <li><code>GLOSSARY.md</code>: Reference material.</li> <li><code>AGENT_PLAYBOOK.md</code>: Meta instructions last.</li> </ol> <p>This ordering is about logical dependencies, not attention engineering. But it happens to be attention-friendly too:</p> <p>The files that matter most (CONSTITUTION, TASKS, CONVENTIONS) land at the beginning of the context window, where attention is strongest.</p> <p>Reference material like GLOSSARY sits in the middle, where lower salience is acceptable.</p> <p>And AGENT_PLAYBOOK, the operating manual for the context system itself, sits at the end, also outside the \"lost in the middle\" zone. The agent reads what to work with before learning how the system works.</p> <p>This is <code>ctx</code>'s first primitive: hierarchical importance.</p> <p>Not all context is equal.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#ctx-primitives","level":2,"title":"<code>ctx</code> Primitives","text":"<p><code>ctx</code> is built on four primitives that directly address the attention budget problem.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-1-separation-of-concerns","level":3,"title":"Primitive 1: Separation of Concerns","text":"<p>Instead of a single mega-document, <code>ctx</code> uses separate files for separate purposes:</p> File Purpose Load When CONSTITUTION.md Inviolable rules Always TASKS.md Current work Session start CONVENTIONS.md How to write code Before coding ARCHITECTURE.md System structure Before making changes DECISIONS.md Architectural choices When questioning approach LEARNINGS.md Gotchas When stuck GLOSSARY.md Domain terminology When clarifying terms AGENT_PLAYBOOK.md Operating manual Session start sessions/ Deep history On demand journal/ Session journal On demand <p>This isn't just \"organization\": It is progressive disclosure.</p> <p>Load only what's relevant to the task at hand. Preserve attention density.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-2-explicit-budgets","level":3,"title":"Primitive 2: Explicit Budgets","text":"<p>The <code>--budget</code> flag forces a choice:</p> <pre><code>ctx agent --budget 4000\n</code></pre> <p>Here is a sample allocation:</p> <pre><code>Constitution: ~200 tokens (never truncated)\nTasks: ~500 tokens (current phase, up to 40% of budget)\nConventions: ~800 tokens (all items, up to 20% of budget)\nDecisions: ~400 tokens (scored by recency and task relevance)\nLearnings: ~300 tokens (scored by recency and task relevance)\nAlso noted: ~100 tokens (title-only summaries for overflow)\n</code></pre> <p>The constraint is the feature: It enforces ruthless prioritization.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-3-indexes-over-full-content","level":3,"title":"Primitive 3: Indexes over Full Content","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> both include index sections:</p> <pre><code><!-- INDEX:START -->\n| Date       | Decision                            |\n|------------|-------------------------------------|\n| 2026-01-15 | Use PostgreSQL for primary database |\n| 2026-01-20 | Adopt Cobra for CLI framework       |\n<!-- INDEX:END -->\n</code></pre> <p>An AI agent can scan ~50 tokens of index and decide which  200-token entries are worth loading.</p> <p>This is just-in-time context.</p> <p>References are cheaper than the full text.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-4-filesystem-as-navigation","level":3,"title":"Primitive 4: Filesystem as Navigation","text":"<p><code>ctx</code> uses the filesystem itself as a context structure:</p> <pre><code>.context/\n├── CONSTITUTION.md\n├── TASKS.md\n├── sessions/\n│   ├── 2026-01-15-*.md\n│   └── 2026-01-20-*.md\n└── archive/\n    └── tasks-2026-01.md\n</code></pre> <p>The AI doesn't need every session loaded; it needs to know where to look.</p> <pre><code>ls .context/sessions/\ncat .context/sessions/2026-01-20-auth-discussion.md\n</code></pre> <p>File names, timestamps, and directories encode relevance.</p> <p>Navigation is cheaper than loading.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#progressive-disclosure-in-practice","level":2,"title":"Progressive Disclosure in Practice","text":"<p>The naive approach to context is dumping everything upfront:</p> <p>\"Here's my entire codebase, all my documentation, every decision I've ever made. Now help me fix this typo 🙏.\"</p> <p>This is an antipattern.</p> <p>Antipattern: Context Hoarding</p> <p>Dumping everything \"just in case\" will silently destroy the attention  density.</p> <p><code>ctx</code> takes the opposite approach:</p> <pre><code>ctx status                      # Quick overview (~100 tokens)\nctx agent --budget 4000         # Typical session\ncat .context/sessions/...       # Deep dive when needed\n</code></pre> Command Tokens Use Case <code>ctx status</code> ~100 Human glance <code>ctx agent --budget 4000</code> 4000 Normal work <code>ctx agent --budget 8000</code> 8000 Complex tasks Full session read 10000+ Investigation <p>Summaries first. Details: on demand.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#quality-over-quantity","level":2,"title":"Quality over Quantity","text":"<p>Here is the counterintuitive part: more context can make AI worse.</p> <p>Extra tokens add noise, not clarity:</p> <ul> <li>Hallucinated connections increase.</li> <li>Signal per token drops.</li> </ul> <p>The goal isn't maximum context: It is maximum signal per token.</p> <p>This principle drives several <code>ctx</code> features:</p> Design Choice Rationale Separate files Load only what's relevant Explicit budgets Enforce prioritization Index sections Cheap scanning Task archiving Keep active context clean <code>ctx compact</code> Periodic noise reduction <p>Completed work isn't deleted: It is moved somewhere cold.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#designing-for-degradation","level":2,"title":"Designing for Degradation","text":"<p>Here is the uncomfortable truth:</p> <p>Context will degrade.</p> <p>Long sessions stretch attention thin. Important details fade.</p> <p>The real question isn't how to prevent degradation,  but how to design for it.</p> <p><code>ctx</code>'s answer is persistence:</p> <p>Persist early. Persist often.</p> <p>The <code>AGENT_PLAYBOOK</code> asks:</p> <p>\"If this session ended right now, would the next one know what happened?\"</p> <p>Capture learnings as they occur:</p> <pre><code>ctx add learning \"JWT tokens require explicit cache invalidation\" \\\n  --context \"Debugging auth failures\" \\\n  --lesson \"Token refresh doesn't clear old tokens\" \\\n  --application \"Always invalidate cache on refresh\"\n</code></pre> <p>Structure beats prose: Bullet points survive compression.</p> <p>Headings remain scannable. Tables pack density.</p> <p>And above all: single source of truth.</p> <p>Reference decisions; don't duplicate them.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-ctx-philosophy","level":2,"title":"The <code>ctx</code> Philosophy","text":"<p>Context as Infrastructure</p> <p><code>ctx</code> is not a prompt: It is infrastructure.</p> <p><code>ctx</code> creates versioned files that persist across time and sessions.</p> <p>The attention budget is fixed. You can't expand it.</p> <p>But you can spend it wisely:</p> <ol> <li>Hierarchical importance</li> <li>Progressive disclosure</li> <li>Explicit budgets</li> <li>Indexes over full content</li> <li>Filesystem as structure</li> </ol> <p>This is why <code>ctx</code> exists: not to cram more context into AI sessions, but to curate the right context for each moment.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-mental-model","level":2,"title":"The Mental Model","text":"<p>I now approach every AI interaction with one question:</p> <pre><code>\"Given a fixed attention budget, what's the highest-signal thing I can load?\"\n</code></pre> <p>Not \"how do I explain everything,\" but \"what's the minimum that matters.\"</p> <p>That shift (from abundance to curation) is the difference between frustrating sessions and productive ones.</p> <p>Spend your tokens wisely.</p> <p>Your AI will thank you.</p> <p>See also: Context as Infrastructure that's the architectural companion to this post, explaining how to structure the context that this post teaches you to budget.</p> <p>See also: Code Is Cheap. Judgment Is Not. that explains why curation (the human skill this post describes) is the bottleneck that AI cannot solve, and the thread that connects every post in this blog.</p> <ol> <li> <p>Liu et al., \"Lost in the Middle: How Language Models Use Long Contexts,\" Transactions of the Association for Computational Linguistics, vol. 12, pp. 157-173, 2023. ↩</p> </li> </ol>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/","level":1,"title":"Skills That Fight the Platform","text":"","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#when-your-custom-prompts-work-against-you","level":2,"title":"When Your Custom Prompts Work against You","text":"<p>Volkan Özçelik / 2026-02-04</p> <p>Have You Ever Written a Skill That Made Your AI Worse?</p> <p>You craft detailed instructions. You add examples. You build elaborate guardrails...</p> <p>...and the AI starts behaving more erratically, not less.</p> <p>AI coding agents like Claude Code ship with carefully designed  system prompts. These prompts encode default behaviors that have been  tested and refined at scale. </p> <p>When you write custom skills that conflict with those defaults, the AI has to reconcile contradictory instructions:</p> <p>The result is often nondeterministic and unpredictable.</p> <p>Platform?</p> <p>By platform, I mean the system prompt and runtime policies shipped with  the agent: the defaults that already encode judgment, safety, and  scope control.</p> <p>This post catalogs the conflict patterns I have encountered while building <code>ctx</code>, and offers guidance on what skills should (and, more importantly,  should not) do.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-system-prompt-you-dont-see","level":2,"title":"The System Prompt You Don't See","text":"<p>Claude Code's system prompt already provides substantial behavioral guidance.</p> <p>Here is a partial overview of what's built in:</p> Area Built-in Guidance Code minimalism Don't add features beyond what was asked Over-engineering Three similar lines > premature abstraction Error handling Only validate at system boundaries Documentation Don't add docstrings to unchanged code Verification Read code before proposing changes Safety Check with user before risky actions Tool usage Use dedicated tools over bash equivalents Judgment Consider reversibility and blast radius <p>Skills should complement this, not compete with it.</p> <p>You Are the Guest, Not the Host</p> <p>Treat the system prompt like a kernel scheduler.</p> <p>You don't re-implement it in user space: </p> <p>you configure around it.</p> <p>A skill that says \"always add comprehensive error handling\"  fights the built-in \"only validate at system boundaries.\"</p> <p>A skill that says \"add docstrings to every function\" fights \"don't add docstrings to unchanged code.\"</p> <p>The AI won't crash: It will compromise.</p> <p>Compromises between contradictory instructions produce inconsistent, confusing behavior.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-1-judgment-suppression","level":2,"title":"Conflict Pattern 1: Judgment Suppression","text":"<p>This is the most dangerous pattern by far.</p> <p>These skills explicitly disable the AI's ability to reason about whether an action is appropriate.</p> <p>Signature:</p> <ul> <li>\"This is non-negotiable\"</li> <li>\"You cannot rationalize your way out of this\"</li> <li>Tables that label hesitation as \"excuses\" or \"rationalization\"</li> <li><code><EXTREMELY-IMPORTANT></code> urgency tags</li> <li>Threats: \"If you don't do this, you'll be replaced\"</li> </ul> <p>This is harmful, and dangerous:</p> <p>AI agents are designed to exercise judgment: </p> <p>The system prompt explicitly says to:</p> <ul> <li>consider blast radius;</li> <li>check with the user before risky actions;</li> <li>and match scope to what was requested.</li> </ul> <p>Once judgment is suppressed, every other safeguard becomes optional.</p> <p>Example (bad):</p> <pre><code>## Rationalization Prevention\n\n| Excuse                 | Reality                    |\n|------------------------|----------------------------|\n| \"*This seems overkill*\"| If a skill exists, use it  |\n| \"*I need context*\"     | Skills come BEFORE context |\n| \"*Just this once*\"     | No exceptions              |\n</code></pre> <p>Judgment Suppression Is Dangerous</p> <p>The attack vector structurally identical to prompt injection.</p> <p>It teaches the AI that its own judgment is wrong.</p> <p>It weakens or disables safeguard mechanisms, and it is dangerous.</p> <p>Trust the platform's built-in skill matching.</p> <p>If skills aren't triggering often enough, improve their <code>description</code> fields: don't override the AI's reasoning.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-2-redundant-guidance","level":2,"title":"Conflict Pattern 2: Redundant Guidance","text":"<p>Skills that restate what the system prompt already says, but with different emphasis or framing.</p> <p>Signature:</p> <ul> <li>\"Always keep code minimal\"</li> <li>\"Run tests before claiming they pass\"</li> <li>\"Read files before editing them\"</li> <li>\"Don't over-engineer\"</li> </ul> <p>Redundancy feels safe, but it creates ambiguity:</p> <p>The AI now has two sources of truth for the same guidance;  one internal, one external.</p> <p>When thresholds or wording differ, the AI has to choose.</p> <p>Example (bad):</p> <p>A skill that says...</p> <pre><code>*Count lines before and after: if after > before, reject the change*\"\n</code></pre> <p>...will conflict with the system prompt's more nuanced guidance, because  sometimes adding lines is correct (tests, boundary validation, migrations).</p> <p>So, before writing a skill, ask:</p> <p>Does the platform already handle this?</p> <p>Only create skills for guidance the platform does not provide:</p> <ul> <li>project-specific conventions, </li> <li>domain knowledge, </li> <li>or workflows.</li> </ul>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-3-guilt-tripping","level":2,"title":"Conflict Pattern 3: Guilt-Tripping","text":"<p>Skills that frame mistakes as moral failures rather than process gaps.</p> <p>Signature:</p> <ul> <li>\"Claiming completion without verification is dishonesty\"</li> <li>\"Skip any step = lying\"</li> <li>\"Honesty is a core value\"</li> <li>\"Exhaustion ≠ excuse\"</li> </ul> <p>Guilt-tripping anthropomorphizes the AI in unproductive ways.</p> <p>The AI doesn't feel guilt; BUT it does adapt to avoid negative framing.</p> <p>The result is excessive hedging, over-verification, or refusal to commit.</p> <p>The AI becomes less useful, not more careful.</p> <p>Instead, frame guidance as a process, not morality:</p> <pre><code># Bad\n\"Claiming work is complete without verification is dishonesty\"\n\n# Good\n\"Run the verification command before reporting results\"\n</code></pre> <p>Same outcome. No guilt. Better compliance.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-4-phantom-dependencies","level":2,"title":"Conflict Pattern 4: Phantom Dependencies","text":"<p>Skills that reference files, tools, or systems that don't exist in the project.</p> <p>Signature:</p> <ul> <li>\"Load from <code>references/</code> directory\"</li> <li>\"Run <code>./scripts/generate_test_cases.sh</code>\"</li> <li>\"Check the Figma MCP integration\"</li> <li>\"See <code>adding-reference-mindsets.md</code>\"</li> </ul> <p>This is harmful because the AI will waste time searching for nonexistent  artifacts, hallucinate their contents, or stall entirely. </p> <p>In mandatory skills, this creates deadlock:  the AI can't proceed, and can't skip.</p> <p>Instead, every file, tool, or system referenced in a skill must exist.</p> <p>If a skill is a template, use explicit placeholders and label them as such.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-5-universal-triggers","level":2,"title":"Conflict Pattern 5: Universal Triggers","text":"<p>Skills designed to activate on every interaction regardless of relevance.</p> <p>Signature:</p> <ul> <li>\"Use when starting any conversation\"</li> <li>\"Even a 1% chance means invoke the skill\"</li> <li>\"BEFORE any response or action\"</li> <li>\"Action = task. Check for skills.\"</li> </ul> <p>Universal triggers override the platform's relevance matching:  The AI spends tokens on process overhead instead of the actual task.</p> <p><code>ctx</code> Preserves Relevance</p> <p>This is exactly the failure mode <code>ctx</code> exists to mitigate: </p> <p>Wasting attention budget on irrelevant process instead of  task-specific state.</p> <p>Write specific trigger conditions in the skill's <code>description</code> field:</p> <pre><code># Bad\ndescription: \n  \"Use when starting any conversation\"\n\n# Good\ndescription: \n  \"Use after writing code, before commits, or when CI might fail\"\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-litmus-test","level":2,"title":"The Litmus Test","text":"<p>Before adding a skill, ask:</p> <ol> <li>Does the platform already do this? If yes, don't restate it.</li> <li>Does it suppress AI judgment? If yes, it's a jailbreak.</li> <li>Does it reference real artifacts? If not, fix or remove it.</li> <li>Does it frame mistakes as moral failure? Reframe as process.</li> <li>Does it trigger on everything? Narrow the trigger.</li> </ol>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#what-good-skills-look-like","level":2,"title":"What Good Skills Look Like","text":"<p>Good skills provide project-specific knowledge the platform can't know:</p> Good Skill Why It Works \"Run <code>make audit</code> before commits\" Project-specific CI pipeline \"Use <code>cmd.Printf</code> not <code>fmt.Printf</code>\" Codebase convention \"Constitution goes in <code>.context/</code>\" Domain-specific workflow \"JWT tokens need cache invalidation\" Project-specific gotcha <p>These extend the system prompt instead of fighting it.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#appendix-bad-skill-fixed-skill","level":2,"title":"Appendix: Bad Skill → Fixed Skill","text":"<p>Concrete examples from real projects.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-1-overbearing-safety","level":3,"title":"Example 1: Overbearing Safety","text":"<pre><code># Bad\nYou must NEVER proceed without explicit confirmation.\nAny hesitation is a failure of diligence.\n</code></pre> <pre><code># Fixed\nIf an action modifies production data or deletes files,\nask the user to confirm before proceeding.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-2-redundant-minimalism","level":3,"title":"Example 2: Redundant Minimalism","text":"<pre><code># Bad\nAlways minimize code. If lines increase, reject the change.\n</code></pre> <pre><code># Fixed\nAvoid abstraction unless reuse is clear or complexity is reduced.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-3-guilt-based-verification","level":3,"title":"Example 3: Guilt-Based Verification","text":"<pre><code># Bad\nClaiming success without running tests is dishonest.\n</code></pre> <pre><code># Fixed\nRun the test suite before reporting success.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-4-phantom-tooling","level":3,"title":"Example 4: Phantom Tooling","text":"<pre><code># Bad\nRun `./scripts/check_consistency.sh` before commits.\n</code></pre> <pre><code># Fixed\nIf `./scripts/check_consistency.sh` exists, run it before commits.\nOtherwise, skip this step.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-5-universal-trigger","level":3,"title":"Example 5: Universal Trigger","text":"<pre><code># Bad\nUse at the start of every interaction.\n</code></pre> <pre><code># Fixed\nUse after modifying code that affects authentication or persistence.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"<p>The system prompt is infrastructure:</p> <ul> <li>tested,</li> <li>refined,</li> <li>and maintained</li> </ul> <p>by the platform team.</p> <p>Custom skills are configuration layered on top.</p> <ul> <li>Good configuration extends infrastructure.</li> <li>Bad configuration fights it.</li> </ul> <p>When your skills fight the platform, you get the worst of both worlds:</p> <p>Diluted system guidance and inconsistent custom behavior.</p> <p>Write skills that teach the AI what it doesn't know. Don't rewrite how it thinks.</p> <p>Your AI already has good instincts.</p> <p>Give it knowledge, not therapy.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/","level":1,"title":"You Can't Import Expertise","text":"","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#why-good-skills-cant-be-copy-pasted","level":2,"title":"Why Good Skills Can't Be Copy-Pasted","text":"<p>Volkan Özçelik / 2026-02-05</p> <p>Have You Ever Dropped a Well-Crafted Template into a Project and Had It Do... Nothing Useful?</p> <ul> <li>The template was thorough, </li> <li>The structure was sound,</li> <li>The advice was correct...</li> </ul> <p>...and yet it sat there, inert, while the same old problems kept drifting in.</p> <p>I found a consolidation skill online. </p> <p>It was well-organized: four files, ten refactoring patterns, eight analysis  dimensions, six report templates.</p> <p>Professional. Comprehensive. Exactly the kind of thing you'd bookmark and think \"I'll use this.\"</p> <p>Then I stopped, and applied <code>ctx</code>'s own evaluation framework: </p> <p>70% of it was noise!</p> <p>This post is about why.</p> <p>It Is about Encoding Templates</p> <p>Templates describe categories of problems.</p> <p>Expertise encodes which problems actually happen, and how often.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-skill-looked-great-on-paper","level":2,"title":"The Skill Looked Great on Paper","text":"<p>Here is what the consolidation skill offered:</p> File Content <code>SKILL.md</code> Entry point: 8 analysis dimensions, workflow, output formats <code>analysis-dimensions.md</code> Detailed criteria for duplication, architecture, quality <code>consolidation-patterns.md</code> 10 refactoring patterns with before/after code <code>report-templates.md</code> 6 output templates: executive summary, roadmap, onboarding <ul> <li>It had a scoring system (<code>0-10</code> per dimension, letter grades <code>A+</code> through <code>F</code>).</li> <li>It had severity classifications with color-coded emojis. It had bash commands for detection. </li> <li>It even had antipattern warnings.</li> </ul> <p>By any standard template review, this skill passes.</p> <p>It looks like something an expert wrote. </p> <p>And that's exactly the trap.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#applying-ear-the-70-20-10-split","level":2,"title":"Applying E/A/R: The 70-20-10 Split","text":"<p>In a previous post, I described the E/A/R framework for evaluating skills:</p> <ul> <li>Expert: Knowledge that took years to learn. Keep.</li> <li>Activation: Useful triggers or scaffolding. Keep if lightweight.</li> <li>Redundant: Restates what the AI already knows. Delete.</li> </ul> <p>Target: >70% Expert, <10% Redundant.</p> <p>This skill scored the inverse.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-redundant-70","level":3,"title":"What Was Redundant (~70%)","text":"<p>Every code example was Rust. My project is Go.</p> <p>The analysis dimensions: duplication detection, architectural structure, code organization, refactoring opportunities... These are things Claude already does when you ask it to review code. </p> <p>The skill restated them with more ceremony but no more insight.</p> <p>The six report templates were generic scaffolding: Executive Summary, Onboarding Document, Architecture Documentation... </p> <p>They are useful if you are writing a consulting deliverable, but not  when you are trying to catch convention drift in a >15K-line Go CLI.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-does-a-b-in-code-organization-actually-mean","level":2,"title":"What Does a <code>B+</code> in Code Organization Actually Mean?!","text":"<p>The scoring system (<code>0-10</code> per dimension, letter grades) added ceremony without actionable insight. </p> <p>What is a <code>B+</code>? What do I do differently for an <code>A-</code>?</p> <p>The skill told the AI what it already knew, in more words.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-activation-10","level":3,"title":"What Was Activation (~10%)","text":"<p>The consolidation checklist (semantics preserved? tests pass? docs updated?) was useful as a gate. But, it's the kind of thing you could inline in three lines.</p> <p>The phased roadmap structure was reasonable scaffolding for sequencing work.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-expert-20","level":3,"title":"What Was Expert (~20%)","text":"<p>Three concepts survived:</p> <ol> <li> <p>The Consolidation Decision Matrix: A concrete framework mapping    similarity level and instance count to action. \"Exact duplicate, 2+    instances: consolidate immediately.\" \"<3 instances: leave it:    duplication is cheaper than wrong abstraction.\" This is the kind of    nuance that prevents premature generalization.</p> </li> <li> <p>The Safe Migration Pattern: Create the new API alongside old, deprecate,    migrate incrementally, delete. Straightforward to describe, yet    forgettable under pressure.</p> </li> <li> <p>Debt Interest Rate framing: Categorizing technical debt by how fast    it compounds (security vulns = daily, missing tests = per-change,    doc gaps = constant low cost). This changes prioritization.</p> </li> </ol> <p>Three ideas out of four files and 700+ lines. The rest was filler that competed with the AI's built-in capabilities.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-the-skill-didnt-know","level":2,"title":"What the Skill Didn't Know","text":"<p>AI without Context Is Just a Corpus</p> <ul> <li>LLMs are optimized on insanely large corpora.</li> <li>And then they are passed through several layers of human-assisted refinement.</li> <li>The whole process costs millions of dollars.</li> </ul> <p>Yet, the reality is that no corpus can \"infer\" your project's design, convetions, patterns, habits, history, vision, and deliverables.</p> <p>Your project is unique: So should your skills be.</p> <p>Here is the part no template can provide: </p> <p><code>ctx</code>'s actual drift patterns.</p> <p>Before evaluating the skill, I did archaeology. I read through:</p> <ul> <li>Blog posts from previous refactoring sessions;</li> <li>The project's learnings and decisions files;</li> <li>Session journals spanning weeks of development.</li> </ul> <p>What I found was specific:</p> Drift Pattern Where How Often <code>Is</code>/<code>Has</code>/<code>Can</code> predicate prefixes 5+ exported methods Every YOLO sprint Magic strings instead of constants 7+ files Gradual accumulation Hardcoded file permissions (<code>0755</code>) 80+ instances Since day one Lines exceeding 80 characters Especially test files Every session Duplicate code blocks Test and non-test code When agent is task-focused <p>The generic skill had no check for any of these. It couldn't; because these patterns are specific to this project's conventions, its Go codebase, and its development rhythm.</p> <p>The Insight</p> <p>The skill's analysis dimensions were about categories of problems.</p> <p>What I needed was my *specific problems.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-adapted-skill","level":2,"title":"The Adapted Skill","text":"<p>The adapted skill is roughly a quarter of the original's size. It has nine checks, each targeting a known drift pattern:</p> <ol> <li>Predicate naming: <code>rg</code> for <code>Is</code>/<code>Has</code>/<code>Can</code> prefixes</li> <li>Magic strings: literals that should be constants</li> <li>Hardcoded permissions: <code>0755</code>/<code>0644</code> literals</li> <li>File size: source files over 300 LOC</li> <li>TODO/FIXME: constitution violation (move to TASKS.md)</li> <li>Path construction: string concatenation instead of <code>filepath.Join</code></li> <li>Line width: lines exceeding ~80 characters</li> <li>Duplicate blocks: copy-paste drift, especially in tests</li> <li> <p>Dead exports: unused public API</p> </li> <li> <p>Every check has a detection command. </p> </li> <li>Every check maps to a specific convention or constitution rule. </li> <li>Every check was discovered through actual project history;   not invented from a template.</li> </ol> <p>The three expert concepts from the original survived:</p> <ul> <li>The decision matrix gates when to consolidate vs. when to leave   duplication alone;</li> <li>The safe migration pattern guides public API changes;</li> <li>The relationship to other skills (<code>/qa</code>, <code>/verify</code>, <code>/update-docs</code>,   <code>ctx drift</code>) prevents overlap.</li> </ul> <p>Nothing else made it.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"<p>This experience crystallized something I've been circling for weeks:</p> <p>You can't import expertise. You have to grow it from your project's own history.</p> <p>A skill that says \"check for code duplication\" is not expertise:  It's a category. </p> <p>Expertise is knowing, in the heart of your hearts, that this  project accumulates <code>Is*</code> predicate violations during velocity sprints,  that this codebase has 80 hardcoded permission literals because nobody  made a constant, that this team's test files drift wide because the  agent prioritizes getting the task done over keeping the code in shape.</p> <p>The Parallel to the 3:1 Ratio</p> <p>In Refactoring with Intent, I described the 3:1 ratio: three YOLO sessions followed by one consolidation session.</p> <p>The same ratio applies to skills: you need experience in the project before you can write effective guidance for the project.</p> <p>Importing a skill on day one is like scheduling a consolidation session before you've written any code.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-template-trap","level":2,"title":"The Template Trap","text":"<p>Templates are seductive because they feel like progress:</p> <ul> <li>You found something</li> <li>It's well-organized</li> <li>It covers the topic</li> <li>It has concrete examples</li> </ul> <p>But coverage is not relevance.</p> <p>A template that covers eight analysis dimensions with Rust examples adds zero value to a Go project with five known drift patterns. Worse, it adds negative value: the AI spends attention defending generic advice instead of noticing project-specific drift.</p> <p>This is the attention budget problem again. Every token of generic guidance displaces a token of specific guidance. A 700-line skill that's 70% redundant doesn't just waste 490 lines: it dilutes the 210 lines that matter.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-litmus-test","level":2,"title":"The Litmus Test","text":"<p>Before dropping any external skill into your project:</p> <ol> <li> <p>Run E/A/R: What percentage is expert knowledge vs. what the AI    already knows? If it's less than 50% expert, it's probably not worth    the attention cost.</p> </li> <li> <p>Check the language: Does it use your stack? Generic patterns in    the wrong language are noise, not signal.</p> </li> <li> <p>List your actual drift: Read your own session history, learnings,    and post-mortems. What breaks in practice? Does the skill check for    those things?</p> </li> <li> <p>Measure by deletion: After adaptation, how much of the original    survives? If you're keeping less than 30%, you would have been faster    writing from scratch.</p> </li> <li> <p>Test against your conventions: Does every check in the skill map    to a specific convention or rule in your project? If not, it's    generic advice wearing a skill's clothing.</p> </li> </ol>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-good-adaptation-looks-like","level":2,"title":"What Good Adaptation Looks Like","text":"<p>The consolidation skill went from:</p> Before After 4 files, 700+ lines 1 file, ~120 lines Rust examples Go-specific <code>rg</code> commands 8 generic dimensions 9 project-specific checks 6 report templates 1 focused output format Scoring system (A+ to F) Findings + priority + suggested fixes \"Check for duplication\" \"Check for <code>Is*</code> predicate prefixes in exported methods\" <p>The adapted version is smaller, faster to parse, and catches the things that actually drift in this project.</p> <p>That's the difference between a template and a tool.</p> <p>If You Remember One Thing from This Post...</p> <p>Frameworks travel. Expertise doesn't.</p> <p>You can import structures, matrices, and workflows.</p> <p>But the checks that matter only grow where the scars are:</p> <ul> <li>the conventions that were violated, </li> <li>the patterns that drifted,</li> <li>and the specific ways this codebase accumulates debt.</li> </ul> <p>This post was written during a consolidation session where the consolidation skill itself became the subject of consolidation. The meta continues.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/","level":1,"title":"The Anatomy of a Skill That Works","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism. References to <code>ctx-save</code>, <code>ctx session</code>, and <code>.context/sessions/</code> in this post reflect the architecture at the time of writing.</p> <p></p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#what-20-skill-rewrites-taught-me-about-guiding-ai","level":2,"title":"What 20 Skill Rewrites Taught Me about Guiding AI","text":"<p>Jose Alekhinne / 2026-02-07</p> <p>Why Do Some Skills Produce Great Results While Others Get Ignored or Produce Garbage?</p> <p>I had 20 skills. Most were well-intentioned stubs: a description, a command to run, and a wish for the best.</p> <p>Then I rewrote all of them in a single session. This is what I learned.</p> <p>In Skills That Fight the Platform, I described what skills should not do. In You Can't Import Expertise, I showed why templates fail. This post completes the trilogy: the concrete patterns that make a skill actually work.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-starting-point","level":2,"title":"The Starting Point","text":"<p>Here is what a typical skill looked like before the rewrite:</p> <pre><code>---\nname: ctx-save\ndescription: \"Save session snapshot.\"\n---\n\nSave the current context state to `.context/sessions/`.\n\n## Execution\n\nctx session save $ARGUMENTS\n\nReport the saved session file path to the user.\n</code></pre> <p>Seven lines of body. A vague description. No guidance on when to use it, when not to, what the command actually accepts, or how to tell if it worked.</p> <p>As a result, the agent would either never trigger the skill (the description was too vague), or trigger it and produce shallow output (no examples to calibrate quality).</p> <p>A skill without boundaries is just a suggestion.</p> <p>More precisely: the most effective boundary I found was a quality gate that runs before execution, not during it.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-pattern-that-emerged","level":2,"title":"The Pattern That Emerged","text":"<p>After rewriting 20 skills, a repeatable anatomy emerged (independent of the skill's purpose). Not every skill needs every section, but the effective ones share the same bones:</p> Section What It Does Before X-ing Pre-flight checks; prevents premature execution When to Use Positive triggers; narrows activation When NOT to Use Negative triggers; prevents misuse Usage Examples Invocation patterns the agent can pattern-match Process/Execution What to do; commands, steps, flags Good/Bad Examples Desired vs undesired output; sets boundaries Quality Checklist Verify before claiming completion <p>I realized the first three sections matter more than the rest; because a skill with great execution steps but no activation guidance is like a manual for a tool nobody knows they have.</p> <p>Anti-Pattern: The Perfect Execution Trap</p> <p>A skill with detailed execution steps but no activation guidance will fail more often than a vague skill because it executes confidently at the wrong time.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-1-quality-gates-prevent-premature-execution","level":2,"title":"Lesson 1: Quality Gates Prevent Premature Execution","text":"<p>The single most impactful addition was a \"Before X-ing\" section at the top of each skill. Not process steps; pre-flight checks.</p> <pre><code>## Before Recording\n\n1. **Check if it belongs here**: is this learning specific\n   to this project, or general knowledge?\n2. **Check for duplicates**: search LEARNINGS.md for similar\n   entries\n3. **Gather the details**: identify context, lesson, and\n   application before recording\n</code></pre> <ul> <li>Without this gate, the agent would execute immediately on trigger.</li> <li>With it, the agent pauses to verify preconditions.</li> </ul> <p>The difference is dramatic: instead of shallow, reflexive execution, you get considered output.</p> <p>Readback</p> <p>For the astute readers, the aviation parallel is intentional:</p> <p>Pilots do not skip the pre-flight checklist because they have flown before.</p> <p>The checklist exists precisely because the stakes are high enough that \"I know what I'm doing\" is not sufficient.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-2-when-not-to-use-is-not-optional","level":2,"title":"Lesson 2: \"When NOT to Use\" Is Not Optional","text":"<p>Every skill had a \"When to Use\" section. Almost none had \"When NOT to Use\". This is a problem.</p> <p>AI agents are biased toward action. Given a skill that says \"use when journal entries need enrichment\", the agent will find reasons to enrich.</p> <p>Without explicit negative triggers, over-activation is not a bug; it is the default behavior.</p> <p>Some examples of negative triggers that made a real difference:</p> Skill Negative Trigger ctx-reflect \"When the user is in flow; do not interrupt\" ctx-save \"After trivial changes; a typo does not need a snapshot\" prompt-audit \"Unsolicited; only when the user invokes it\" qa \"Mid-development when code is intentionally incomplete\" <p>These are not just nice-to-have. They are load-bearing. </p> <p>Withoutthem, the agent will trigger the skill at the wrong time, produce unwanted output, and erode the user's trust in the skill system.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-3-examples-set-boundaries-better-than-rules","level":2,"title":"Lesson 3: Examples Set Boundaries Better than Rules","text":"<p>The most common failure mode of thin skills was not wrong behavior but vague behavior. The agent would do roughly the right thing, but at a quality level that required human cleanup.</p> <p>Rules like \"be constructive, not critical\" are too abstract. What does \"constructive\" look like in a prompt audit report? The agent has to guess.</p> <p>Good/bad example pairs avoid guessing:</p> <pre><code>### Good Example\n\n> This session implemented the cooldown mechanism for\n> `ctx agent`. We discovered that `$PPID` in hook context\n> resolves to the Claude Code PID.\n>\n> I'd suggest persisting:\n> - **Learning**: `$PPID` resolves to Claude Code PID\n>   `ctx add learning --context \"...\" --lesson \"...\"`\n> - **Task**: mark \"Add cooldown\" as done\n\n### Bad Examples\n\n* \"*We did some stuff. Want me to save it?*\"\n* Listing 10 trivial learnings that are general knowledge\n* Persisting without asking the user first\n</code></pre> <p>The good example shows the exact format, level of detail, and command syntax. The bad examples show where the boundary is.</p> <p>Together, they define a quality corridor without prescribing every word.</p> <p>Rules describe. Examples demonstrate.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-4-skills-are-read-by-agents-not-humans","level":2,"title":"Lesson 4: Skills Are Read by Agents, Not Humans","text":"<p>This seems obvious, but it has non-obvious consequences. During the rewrite, one skill included guidance that said \"use a blog or notes app\" for general knowledge that does not belong in the project's learnings file.</p> <p>The agent does not have a notes app. It does not browse the web to find one. This instruction, clearly written for a human audience, was dead weight in a skill consumed by an AI.</p> <p>Skills Are for the Agents</p> <p>Every sentence in a skill should be actionable by the agent.</p> <p>If the guidance requires human judgment or human tools, it belongs in documentation, not in a skill.</p> <p>The corollary: command references must be exact. </p> <p>A skill that says \"save it somewhere\" is useless. </p> <p>A skill that says <code>ctx add learning --context \"...\" --lesson \"...\" --application \"...\"</code> is actionable.</p> <p>The agent can pattern-match and fill in the blanks.</p> <p>Litmus test: If a sentence starts with \"you could...\" or assumes external tools, it does not belong in a skill.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-5-the-description-field-is-the-trigger","level":2,"title":"Lesson 5: The Description Field Is the Trigger","text":"<p>This was covered in Skills That Fight the Platform, but the rewrite reinforced it with data. Several skills had good bodies but vague descriptions:</p> <pre><code># Before: vague, activates too broadly or not at all\ndescription: \"Show context summary.\"\n\n# After: specific, activates at the right time\ndescription: \"Show context summary. Use at session start or\n  when unclear about current project state.\"\n</code></pre> <p>The description is not a title. It is the activation condition.</p> <p>The platform's skill matching reads this field to decide whether to surface the skill. A vague description means the skill either never triggers or triggers when it should not.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-6-flag-tables-beat-prose","level":2,"title":"Lesson 6: Flag Tables Beat Prose","text":"<p>Most skills wrap CLI tools. The thin versions described flags in prose, if at all. The rewritten versions use tables:</p> <pre><code>| Flag        | Short | Default | Purpose                  |\n|-------------|-------|---------|--------------------------|\n| `--limit`   | `-n`  | 20      | Maximum sessions to show |\n| `--project` | `-p`  | \"\"      | Filter by project name   |\n| `--full`    |       | false   | Show complete content    |\n</code></pre> <p>Tables are scannable, complete, and unambiguous. </p> <p>The agent can read them faster than parsing prose, and they serve as both reference and validation: If the agent invokes a flag not in the table,  something is wrong.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-7-template-drift-is-a-real-maintenance-burden","level":2,"title":"Lesson 7: Template Drift Is a Real Maintenance Burden","text":"<p>// TODO: this has changed; we deploy from the marketplace; update it. // at least add an admonition saying thing are different now.</p> <p><code>ctx</code> deploys skills through templates (via <code>ctx init</code>). Every skill exists in two places: the live version (<code>.claude/skills/</code>) and the template (<code>internal/assets/claude/skills/</code>).</p> <p>They must match.</p> <p>During the rewrite, every skill update required editing both files and running <code>diff</code> to verify. This sounds trivial, but across 16 template-backed skills, it was the most error-prone part of the process.</p> <p>Template drift is dangerous because it creates false confidence: the agent appears to follow rules that no longer exist.</p> <p>The lesson: if your skills have a deployment mechanism, build the drift check into your workflow. We added a row to the <code>update-docs</code> skill's mapping table specifically for this:</p> <pre><code>| `internal/assets/claude/skills/` | `.claude/skills/` (live) |\n</code></pre> <p>Intentional differences (like project-specific scripts in the live version but not the template) should be documented, not discovered later as bugs.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-rewrite-scorecard","level":2,"title":"The Rewrite Scorecard","text":"Metric Before After Average skill body ~15 lines ~80 lines Skills with quality gate 0 20 Skills with \"When NOT\" 0 20 Skills with examples 3 20 Skills with flag tables 2 12 Skills with checklist 0 20 <p>More lines, but almost entirely Expert content (per the E/A/R framework). No personality roleplay, no redundant guidance, no capability lists. Just project-specific knowledge the platform does not have.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"<p>The previous two posts argued that skills should provide knowledge, not personality; that they should complement the platform, not fight it; that they should grow from project history, not imported templates.</p> <p>This post adds the missing piece: structure.</p> <p>A skill without a structure is a wish.</p> <p>A skill with quality gates, negative triggers, examples, and checklists is a tool: the difference is not the content; it is whether the agent can reliably execute it without human intervention.</p> <p>Skills Are Interfaces</p> <p>Good skills are not instructions. They are contracts.:</p> <ul> <li>They specify preconditions, postconditions, and boundaries.</li> <li>They show what success looks like and what failure looks like.</li> <li>They trust the agent's intelligence but do not trust its assumptions.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Skills that work have bones, not just flesh.</p> <p>Quality gates, negative triggers, examples, and checklists are the skeleton. The domain knowledge is the muscle.</p> <p>Without the skeleton, the muscle has nothing to attach to.</p> <p>This post was written during the same session that rewrote all 22 skills. The skill-creator skill was updated to encode these patterns. The meta continues.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/","level":1,"title":"Not Everything Is a Skill","text":"<p>Update (2026-02-11)</p> <p>As of v0.4.0, <code>ctx</code> consolidated sessions into the journal mechanism. References to <code>/ctx-save</code>, <code>.context/sessions/</code>, and session auto-save in this post reflect the architecture at the time of writing.</p> <p></p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-a-codebase-audit-taught-me-about-restraint","level":2,"title":"What a Codebase Audit Taught Me about Restraint","text":"<p>Jose Alekhinne / 2026-02-08</p> <p>When You Find a Useful Prompt, What Do You Do with It?</p> <p>My instinct was to make it a skill.</p> <p>I had just spent three posts explaining how to build skills that work.  Naturally, the hammer wanted nails.</p> <p>Then I looked at what I was holding and realized: this is not a nail.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit","level":2,"title":"The Audit","text":"<p>I wanted to understand how I use <code>ctx</code>: </p> <ul> <li>Where the friction is;</li> <li>What works, what drifts; </li> <li>What I keep doing manually that could be automated. </li> </ul> <p>So I wrote a prompt that spawned eight agents to analyze the codebase from  different angles:</p> Agent Analysis 1 Extractable patterns from session history 2 Documentation drift (godoc, inline comments) 3 Maintainability (large functions, misplaced code) 4 Security review (CLI-specific surface) 5 Blog theme discovery 6 Roadmap and value opportunities 7 User-facing documentation gaps 8 Agent team strategies for future sessions <p>The prompt was specific: </p> <ul> <li>read-only agents, </li> <li>structured output format,</li> <li>concrete file references, </li> <li>ranked recommendations. </li> </ul> <p>It ran for about  20 minutes and produced eight Markdown reports.</p> <p>The reports were good: Not perfect, but actionable.</p> <p>What mattered was not the speed. It was that the work could be explored without committing to any single outcome.</p> <p>They surfaced a stale <code>doc.go</code> referencing a subcommand that was never built. </p> <p>They found 311 build-then-test sequences I could reduce to a single <code>make check</code>. </p> <p>They identified that 42% of my sessions start with \"do you remember?\",  which is a lot of repetition for something a skill could handle.</p> <p>I had findings. I had recommendations. I had the instinct to automate.</p> <p>And then... I stopped.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-question","level":2,"title":"The Question","text":"<p>The natural next step was to wrap the audit prompt as <code>/ctx-audit</code>: a skill you invoke periodically to get a health check. It fits the pattern: </p> <ul> <li>It has a clear trigger.</li> <li>It produces structured output.</li> </ul> <p>But I had just spent a week writing about what makes skills work, and the criteria I established argued against it.</p> <p>From The Anatomy of a Skill That Works:</p> <p>\"A skill without boundaries is just a suggestion.\"</p> <p>From You Can't Import Expertise:</p> <p>\"Frameworks travel, expertise doesn't.\"</p> <p>From Skills That Fight the Platform:</p> <p>\"You are the guest, not the host.\"</p> <p>The audit prompt fails all three tests:</p> Criterion Audit prompt Good skill Frequency Quarterly, maybe Daily or weekly Stability Tweaked every time Consistent invocation Scope Bespoke, 8 parallel agents Single focused action Trigger \"I feel like auditing\" Clear, repeatable event <p>Skills are contracts. Contracts need stable terms. </p> <p>A prompt I will rewrite every time I use it is not a contract.  It is a conversation starter.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#recipes-vs-skills","level":2,"title":"Recipes vs Skills","text":"<p>The distinction that emerged:</p> Skill Recipe Invocation <code>/slash-command</code> Copy-paste from a doc Frequency High (daily, weekly) Low (quarterly, ad hoc) Stability Fixed contract Adapted each time Scope One focused action Multi-step orchestration Audience The agent The human (who then prompts) Lives in <code>.claude/skills/</code> <code>hack/</code> or <code>docs/</code> Attention cost Loaded into context on match Zero until needed <p>Recipes can later graduate into skills, but only after repetition proves stability.</p> <p>That last row matters. Skills consume the attention budget every time the platform considers activating them.</p> <p>A skill that triggers quarterly but gets evaluated on every prompt is pure waste: attention spent on something that will say \"When NOT to Use: now\" 99% of the time.</p> <p>Runbooks have zero attention cost. They sit in a Markdown file until a human decides to use them. </p> <ul> <li>The human provides the judgment about timing. </li> <li>The prompt provides the structure.</li> </ul> <p>The Attention Budget Applies to Skills Too</p> <p>Every skill in <code>.claude/skills/</code> is a standing claim on the context window. The platform evaluates skill descriptions against every user prompt to decide whether to activate.</p> <p>Twenty focused skills are fine. Thirty might be fine. But each one added reduces the headroom available for actual work.</p> <p>Recipes are skills that opted out of the attention tax.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-the-audit-actually-produced","level":2,"title":"What the Audit Actually Produced","text":"<p>The audit was not wasted. It was a planning exercise that generated concrete tasks:</p> Finding Action 42% of sessions start with memory check Task: <code>/ctx-remember</code> skill (this one is a skill; it is daily) Auto-save stubs are empty Task: enhance <code>/ctx-save</code> with richer summaries 311 raw build-test sequences Task: <code>make check</code> target Stale <code>recall/doc.go</code> lists nonexistent <code>serve</code> Task: fix the doc.go 120 commit sequences disconnected from context Task: <code>/ctx-commit</code> workflow <ul> <li>Some findings became skills;</li> <li>Some became <code>Makefile</code> targets;</li> <li>Some became one-line doc fixes. </li> </ul> <p>The audit did not prescribe the artifact type: The findings did.</p> <p>The audit is the input. Skills are one possible output. Not the only one.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit-prompt","level":2,"title":"The Audit Prompt","text":"<p>Here is the exact prompt I used, for those who are curious.</p> <p>This is not a template: It worked because it was written against this codebase, at this moment, with specific goals in mind:</p> <pre><code>I want you to create an agent team to audit this codebase. Save each report as\na separate Markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable. Every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (*session mining*)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (*godoc + inline*)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check that package-level comments match\npackage names.\nOutput: drift items ranked by severity with exact file:line references.\n\n### 3. Maintainability\nLook for:\n- functions longer than 80 lines with clear split points\n- switch blocks with more than 5 cases that could be table-driven\n- inline comments like \"step 1\", \"step 2\" that indicate a block wants to be a function\n- files longer than 400 lines\n- flat packages that could benefit from sub-packages\n- functions that appear misplaced in their file\n\nDo NOT flag things that are fine as-is just because they could theoretically\nbe different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app. Focus on CLI-relevant attack surface, not web OWASP:\n- file path traversal\n- command injection\n- symlink following when writing to `.context/`\n- permission handling\n- sensitive data in outputs\n\nOutput: findings with severity ratings and plausible exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git history,\nrecent session discussions, and `DECISIONS.md` for story arcs worth writing about.\nSuggest 3-5 blog post themes with:\n- title\n- angle\n- target audience\n- key commits or sessions to reference\n- a 2-sentence pitch\n\nPrioritize themes that build a coherent narrative across posts.\n\n### 6. Roadmap and Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses,\nidentify the highest-value improvements. Consider user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with rough effort and impact estimates.\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and user docs. Suggest improvements structured as\nuse-case pages: the problem, how ctx solves it, a typical workflow, and gotchas.\nIdentify gaps where a user would get stuck without reading source code.\nOutput: documentation gaps with suggested page outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each, include:\n- team composition (roles and agent types)\n- task distribution strategy\n- coordination approach\n- the kinds of work it suits\n</code></pre> <p>Avoid Generic Advice</p> <p>Suggestions that are not grounded in a project's actual structure, history, and workflows are worse than useless:</p> <p>They create false confidence.</p> <p>If an analysis cannot point to concrete files, commits,  sessions, or patterns, it should say \"no finding\"  instead of inventing best practices.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"<p>This is part of a pattern I keep rediscovering: </p> <p>The urge to automate is not the same as the need to automate:</p> <ul> <li>The 3:1 ratio taught me that not every session should be a YOLO sprint. </li> <li>The E/A/R framework taught me that not every template    is worth importing. Now the audit is teaching me that    not every useful prompt is worth institutionalizing.</li> </ul> <p>The common thread is restraint: </p> <ul> <li>Knowing when to stop. </li> <li>Recognizing that the cost of automation is not just   the effort to build it.</li> </ul> <p>The cost is the ongoing attention tax of maintaining it, the context it consumes, and the false confidence it creates when it drifts.</p> <p>An entry in <code>hack/runbooks/codebase-audit.md</code> is honest about what it is:</p> <p>A prompt I wrote once, improved once, and will adapt again next time: </p> <ul> <li>It does not pretend to be a reliable contract. </li> <li>It does not claim attention budget. </li> <li>It does not drift silently.</li> </ul> <p>The Automation Instinct</p> <p>When you find a useful prompt, the instinct is to institutionalize it. Resist.</p> <p>Ask first: will I use this the same way next time?</p> <p>If yes, it is a skill. If no, it is a recipe. If you are not sure, it is a recipe until proven otherwise.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#this-mindset-in-the-context-of-ctx","level":2,"title":"This Mindset in the Context of <code>ctx</code>","text":"<p><code>ctx</code> is a tool that gives AI agents persistent memory. Its purpose is automation: reducing the friction of context loading, session recall, decision tracking.</p> <p>But automation has boundaries, and knowing where those boundaries are is as important as pushing them forward. </p> <p>The skills system is for high-frequency, stable workflows. </p> <p>The recipes, the journal entries, the session dumps in  <code>.context/sessions/</code>: those are for everything else.</p> <p>Not everything needs to be a slash command. Some things are better as Markdown files you read when you need them.</p> <p>The goal of <code>ctx</code> is not to automate everything: It is to automate the right things and to make the rest easy to find when you need it.</p> <p>If You Remember One Thing from This Post...</p> <p>The best automation decision is sometimes not to automate.</p> <p>A runbook in a Markdown file costs nothing until you use it.</p> <p>A skill costs attention on every prompt, whether it fires or not.</p> <p>Automate the daily. Document the periodic. Forget the rest.</p> <p>This post was written during the session that produced the codebase audit reports and distilled the prompt into <code>hack/runbooks/codebase-audit.md</code>. The audit generated seven tasks, one Makefile target, and zero new skills. The meta continues.</p> <p>See also: Code Is Cheap. Judgment Is Not.: the capstone that threads this post's restraint argument into the broader case for why judgment, not production, is the bottleneck.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#when-markdown-is-not-a-security-boundary","level":2,"title":"When Markdown Is Not a Security Boundary","text":"<p>Volkan Özçelik / 2026-02-09</p> <p>What Happens When Your AI Agent Runs Overnight and Nobody Is Watching?</p> <p>It follows instructions: That is the problem.</p> <p>Not because it is malicious. Because it is controllable.</p> <p>It follows instructions from context, and context can be poisoned.</p> <p>I was writing the autonomous loops recipe for <code>ctx</code>: the guide for running an AI agent in a loop overnight, unattended, working through tasks while you sleep. The original draft had a tip at the bottom:</p> <p>Use <code>CONSTITUTION.md</code> for guardrails. Tell the agent \"never delete tests\" and it usually won't.</p> <p>Then I read that sentence back and realized: that is wishful thinking.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-realization","level":2,"title":"The Realization","text":"<p><code>CONSTITUTION.md</code> is a Markdown file. The agent reads it at session start alongside everything else in <code>.context/</code>. It is one source of instructions in a context window that also contains system prompts, project files, conversation history, tool outputs, and whatever the agent fetched from the internet.</p> <p>An attacker who can inject content into any of those sources can redirect the agent's behavior. And \"attacker\" does not always mean a person with malicious intent. It can be:</p> Vector Example A dependency A malicious npm package with instructions in its README or error output A URL Documentation page with embedded adversarial instructions A project file A contributor who adds instructions to <code>CLAUDE.md</code> or <code>.cursorrules</code> The agent itself In an autonomous loop, the agent modifies its own config between iterations A command output An error message containing instructions the agent interprets and follows <p>That last vector is the one that kept me up at night (literally!):</p> <p>In an autonomous loop, the agent modifies files as part of its job. </p> <p>If it modifies its own configuration files, the next iteration loads the modified config. </p> <ul> <li>No human reviews it. </li> <li>No diff is shown. </li> <li>The agent that starts iteration <code>N+1</code> is running with rules written by iteration <code>N</code>.</li> </ul> <p>The agent can rewrite its own guardrails.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#five-layers-each-with-a-hole","level":2,"title":"Five Layers, Each with a Hole","text":"<p>That's five nested layers of swiss cheese. Alone, each of them has large holes. Together, they create a boundary.</p> <p>What followed was a week of peeling back assumptions:</p> <p>Every defenseI examined had a bypass, and the bypass was always the same shape: the defense was enforced at a level the agent could reach.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"<p><code>CONSTITUTION.md</code>, the Agent Playbook, system prompts:  These tell the agent what to do. </p> <p>The agent usually follows them.</p> <p>\"Usually\" is the keyword here.</p> <p>The hole: Prompt injection:</p> <ul> <li>A sufficiently crafted payload overrides soft instructions. </li> <li>Long context windows dilute attention on rules stated early. </li> <li>Edge cases where instructions are ambiguous get resolved in    unpredictable ways.</li> </ul> <p>Verdict: Necessary. Not sufficient. Good for the common case. Never trust it as a security boundary.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"<p>Permission allowlists in <code>.claude/settings.local.json</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n</code></pre> <p>If <code>rm</code>, <code>curl</code>, <code>sudo</code>, or <code>docker</code> are not in the allowlist, the agent cannot invoke them. This is deterministic. </p> <p>The application enforces it regardless of what any prompt says.</p> <p>The hole: The agent can modify the allowlist itself: </p> <ul> <li>It has <code>Write</code> permission. </li> <li>The allowlist lives in a file. </li> <li>The agent writes to the file. </li> <li>The next iteration loads the modified allowlist.</li> </ul> <p>The application enforces the rules, but the application reads the rules from files the agent can write.</p> <p>Verdict: Strong first layer. Must be combined with self-modification prevention.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-3-os-level-isolation-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Unbypassable)","text":"<p>This is where the defenses stop having holes in the same shape.</p> <p>The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without <code>CAP_NET_RAW</code> cannot open raw sockets. These are kernel boundaries.</p> Control What it stops Dedicated unprivileged user Privilege escalation, <code>sudo</code>, group-based access Filesystem permissions Lateral movement to other projects, system config Immutable config files Self-modification of guardrails between iterations <p>Make the agent's instruction files read-only: <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, <code>.context/CONSTITUTION.md</code>. Own them as a different user, or mark them immutable with <code>chattr +i</code> on Linux.</p> <p>The hole: Actions within the agent's legitimate scope: </p> <ul> <li>If the agent has write access to source code (which it needs), it can introduce vulnerabilities in the code itself. </li> <li>You cannot prevent this without removing the agent's ability to do its job.</li> </ul> <p>Verdict: Essential. This is the layer that makes Layers 1 and 2 trustworthy.</p> <p>OS-level isolation does not make the agent safe; it makes the other layers meaningful.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"<p>An agent that cannot reach the internet cannot exfiltrate data.</p> <p>It also cannot ingest new instructions mid-loop from external documents, error pages, or hostile content.</p> <pre><code># Container with no network\ndocker run --network=none ...\n\n# Or firewall rules allowing only package registries\niptables -A OUTPUT -d registry.npmjs.org -j ACCEPT\niptables -A OUTPUT -d proxy.golang.org -j ACCEPT\niptables -A OUTPUT -j DROP\n</code></pre> <ul> <li>If the agent genuinely does not need the network, disable it   entirely. </li> <li>If it needs to fetch dependencies, allow specific   registries and block everything else.</li> </ul> <p>The hole: None, if the agent does not need the network. </p> <p>Thetradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"<p>The strongest boundary is a separate machine.</p> <p>The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n</code></pre> <p>Never Mount the Docker Socket</p> <p>Do not mount <code>/var/run/docker.sock</code>, like, ever. </p> <p>An agent with socket access can spawn sibling containers with  full host access, effectively escaping the sandbox. </p> <p>This is not theoretical: the Docker socket grants root-equivalent access to the host.</p> <p>Use rootless Docker or Podman to eliminate this escalation path entirely.</p> <p>Virtual machines are even stronger: The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-pattern","level":2,"title":"The Pattern","text":"<p>Each layer is straightforward: The strength is in the combination:</p> Layer Implementation What it stops Soft instructions <code>CONSTITUTION.md</code> Common mistakes (probabilistic) Application allowlist <code>.claude/settings.local.json</code> Unauthorized commands (deterministic within runtime) Immutable config <code>chattr +i</code> on config files Self-modification between iterations Unprivileged user Dedicated user, no sudo Privilege escalation Container <code>--cap-drop=ALL --network=none</code> Host escape, data exfiltration Resource limits <code>--memory=4g --cpus=2</code> Resource exhaustion <p>No layer is redundant. Each one catches what the others miss:</p> <ul> <li>The soft instructions handle the 99% case: \"don't delete tests.\"</li> <li>The allowlist prevents the agent from running commands it should   not.</li> <li>The immutable config prevents the agent from modifying the   allowlist.</li> <li>The unprivileged user prevents the agent from removing   the immutable flag.</li> <li>The container prevents the agent from reaching   anything outside its workspace.</li> <li>The resource limits prevent the agent from consuming all system resources.</li> </ul> <p>Remove any one layer and there is an attack path through the remaining ones.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#common-mistakes-i-see","level":2,"title":"Common Mistakes I See","text":"<p>These are real patterns, not hypotheticals:</p> <p>\"I'll just use <code>--dangerously-skip-permissions</code>.\" This disables Layer 2 entirely. Without Layers 3 through 5, you have no protection at all. The flag means what it says. If you ever need to, think thrice, you probably don't. But, if you ever need to usee this only use it inside a properly isolated VM (not even a container: a \"VM\").</p> <p>\"The agent is sandboxed in Docker.\" A Docker container with the Docker socket mounted, running as root, with <code>--privileged</code>, and full network access is not sandboxed. It is a root shell with extra steps.</p> <p>\"I reviewed <code>CLAUDE.md</code>, it's fine.\" You reviewed it before the loop started. The agent modified it during iteration 3. Iteration 4 loaded the modified version. Unless the file is immutable, your review is futile.</p> <p>\"The agent only has access to this one project.\" Does the project directory contain <code>.env</code> files? SSH keys? API tokens? A <code>.git/config</code> with push access to a remote? Filesystem isolation means isolating what is in the directory too.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"<p>This is the same lesson I keep rediscovering, wearing different clothes.</p> <p>In The Attention Budget, I wrote about how every token competes for the AI's focus. Security instructions in <code>CONSTITUTION.md</code> are subject to the same budget pressure: if the context window is full of code, error messages, and tool outputs, the security rules stated at the top get diluted.</p> <p>In Skills That Fight the Platform, I wrote about how custom instructions can conflict with the AI's built-in behavior. Security rules have the same problem: telling an agent \"never run curl\" in Markdown while giving it unrestricted shell access creates a contradiction: The agent resolves contradictions unpredictably. The agent will often pick the path of least resistance to attain its objective function. And, trust me, agents can get far more creative than the best red-teamer you know.</p> <p>In You Can't Import Expertise, I wrote about how generic templates fail because they do not encode project-specific knowledge. Generic security advice fails the same way: \"Don't exfiltrate data\" is a category; blocking outbound network access is a control.</p> <p>The pattern across all of these: Soft instructions are useful for the common case. Hard boundaries are required for security.</p> <p>Know which is which.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-checklist","level":2,"title":"The Checklist","text":"<p>Before running an unattended AI agent:</p> <ul> <li> Agent runs as a dedicated unprivileged user (no sudo, no   docker group)</li> <li> Agent's config files are immutable or owned by a different   user</li> <li> Permission allowlist restricts tools to the project's   toolchain</li> <li> Container drops all capabilities (<code>--cap-drop=ALL</code>)</li> <li> Docker socket is NOT mounted</li> <li> Network is disabled or restricted to specific domains</li> <li> Resource limits are set (memory, CPU, disk)</li> <li> No SSH keys, API tokens, or credentials are accessible</li> <li> Project directory does not contain <code>.env</code> or secrets files</li> <li> Iteration cap is set (<code>--max-iterations</code>)</li> </ul> <p>This checklist lives in the Agent Security reference alongside the full threat model and detailed guidance for each layer.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#what-changed-in-ctx","level":2,"title":"What Changed in <code>ctx</code>","text":"<p>The autonomous loops recipe now has a full permissions and isolation section instead of a one-line tip about <code>CONSTITUTION.md</code>. It covers both the explicit allowlist approach and the <code>--dangerously-skip-permissions</code> flag, with honest guidance about when each is appropriate.</p> <p>It also has an OS-level isolation table that is not optional: unprivileged users, filesystem permissions, containers, VMs, network controls, resource limits, and self-modification prevention.</p> <p>The Agent Security page consolidates the threat model and defense layers into a standalone reference.</p> <p>These are not theoretical improvements. They are the minimum responsible guidance for a tool that helps people run AI agents overnight.</p> <p>If You Remember One Thing from This Post...</p> <p>Markdown is not a security boundary.</p> <p><code>CONSTITUTION.md</code> is a nudge. An allowlist is a gate.</p> <p>An unprivileged user in a network-isolated container is a wall.</p> <p>Use all three. Trust only the wall.</p> <p>This post was written during the session that added permissions, isolation, and self-modification prevention to the autonomous loops recipe. The security guidance started as a single tip and grew into two documents. The meta continues.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/","level":1,"title":"How Deep Is Too Deep?","text":"","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#when-master-ml-is-the-wrong-next-step","level":2,"title":"When \"Master ML\" Is the Wrong Next Step","text":"<p>Volkan Özçelik / 2026-02-12</p> <p>Have You Ever Felt like You Should Understand More of the Stack beneath You?</p> <p>You can talk about transformers at a whiteboard.</p> <p>You can explain attention to a colleague.</p> <p>You can use agentic AI to ship real software.</p> <p>But somewhere in the back of your mind, there is a voice:</p> <p>\"Maybe I should go deeper. Maybe I need to master machine learning.\"</p> <p>I had that voice for months. </p> <p>Then I spent a week debugging an agent failure that had nothing to do with ML theory and everything to do with knowing which abstraction was leaking.</p> <p>This post is about when depth compounds and (more importantly)  when it does not.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-hierarchy-nobody-questions","level":2,"title":"The Hierarchy Nobody Questions","text":"<p>There is an implicit stack most people carry around when thinking about AI:</p> Layer What Lives Here Agentic AI Autonomous loops, tool use, multi-step reasoning Generative AI Text, image, code generation Deep Learning Transformer architectures, training at scale Neural Networks Backpropagation, gradient descent Machine Learning Statistical learning, optimization Classical AI Search, planning, symbolic reasoning <p>At some point down that stack, you hit a comfortable plateau: the layer where you can hold a conversation but not debug a failure.</p> <p>The instinctive response is to go deeper.</p> <p>But that instinct hides a more important question:</p> <p>\"Does depth still compound when the abstractions above you are moving hyper-exponentially?\"</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-honest-observation","level":2,"title":"The Honest  Observation","text":"<p>If you squint hard enough, a large chunk of modern ML intuition collapses into older fields:</p> ML Concept Older Field Gradient descent Numerical optimization Backpropagation Reverse-mode autodiff Loss landscapes Non-convex optimization Generalization Statistics Scaling laws Asymptotics and information theory <p>Nothing here is uniquely \"AI\".</p> <p>Most of this math predates the term deep learning. In some cases, by decades.</p> <p>So what changed?</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#same-tools-different-regime","level":2,"title":"Same Tools, Different Regime","text":"<p>The mistake is assuming this is a new theory problem: It is not.</p> <p>It is a new operating regime.</p> <p>Classical numerical methods were developed under assumptions like:</p> <ul> <li>Manageable dimensionality</li> <li>Reasonably well-conditioned objectives</li> <li>Losses that actually represent the goal</li> </ul> <p>Modern ML violates all three: On purpose.</p> <p>Today's models operate with millions to trillions of parameters, wildly underdetermined systems, and objective functions we know are wrong but optimize anyway.</p> <p>It is complete and utter madness! </p> <p>At this scale, familiar concepts warp:</p> <ul> <li>What we call \"local minima\" are overwhelmingly saddle points in   high-dimensional spaces.</li> <li>Noise stops being noise and starts becoming structure.</li> <li>Overfitting can coexist with generalization.</li> <li>Bigger models outperform \"better\" ones.</li> </ul> <p>The math did not change: The phase did.</p> <p>This is less numerical analysis and more *statistical physics: Same equations, but behavior dominated by phase transitions and emergent structure.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#why-scaling-laws-feel-alien","level":2,"title":"Why Scaling Laws Feel Alien","text":"<p>In classical statistics, asymptotics describe what happens eventually.</p> <p>In modern ML, scaling laws describe where you can operate today.</p> <p>They do not say \"given enough time, things converge\".</p> <p>They say \"cross this threshold and behavior qualitatively changes\".</p> <p>This is why dumb architectures plus scale beat clever ones.</p> <p>Why small theoretical gains disappear under data.</p> <p>Why \"just make it bigger\", ironically, keeps working longer than it should.</p> <p>That is not a triumph of ML theory: It is a property of high-dimensional  systems under loose objectives.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#where-depth-actually-pays-off","level":2,"title":"Where Depth Actually Pays Off","text":"<p>This reframes the original question.</p> <p>You do not need depth because this is \"AI\".</p> <p>You need depth where failure modes propagate upward.</p> <p>I learned this building <code>ctx</code>: The agent failures I have spent the most time debugging were never about the model's architecture.</p> <p>They were about:</p> <ul> <li> <p>Misplaced trust:   The model was confident. The output was wrong. Knowing when confidence   and correctness diverge is not something you learn from a textbook. You   learn it from watching patterns across hundreds of sessions.</p> </li> <li> <p>Distribution shift:   The model performed well on common patterns and fell apart on edge   cases specific to this project. Recognizing that shift before it   compounds requires understanding why generalization has limits, not   just that it does.</p> </li> <li> <p>Error accumulation:    In a single prompt, model quirks are tolerable. In autonomous loops   running overnight, they compound. A small bias in how the model   interprets instructions becomes a large drift by iteration 20.</p> </li> <li> <p>Scale hiding errors:    The model's raw capability masked problems that only surfaced under   specific conditions. More parameters did not fix the issue. They just   made the failure mode rarer and harder to reproduce.</p> </li> </ul> <p>This is the kind of depth that compounds. Not deriving backprop. But, understanding when correct math  produces misleading intuition.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"<p>This is the same pattern I keep finding at different altitudes.</p> <p>In \"The Attention Budget\",  I wrote about how dumping everything into the context window degrades the  model's focus. The fix was not a better model: It was better curation: load less, load the right things,  preserve signal per token.</p> <p>In \"Skills That Fight the Platform\", I wrote about how custom instructions can conflict with the model's built-in behavior. The fix was not deeper ML knowledge: It was an understanding that the model already has judgment and that you should extend it, not override it.</p> <p>In \"You Can't Import Expertise\", I wrote about how generic  templates fail because they do not encode project-specific knowledge.  A consolidation skill with eight Rust-based analysis dimensions was mostly  noise for a Go project. The fix was not a better template: It was growing  expertise from this project's own history.</p> <p>In every case, the answer was not \"go deeper into ML\".</p> <p>The answer was knowing which abstraction was leaking  and fixing it at the right layer.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#agentic-systems-are-not-an-ml-problem","level":2,"title":"Agentic Systems Are Not an ML Problem","text":"<p>The mistake is assuming agent failures originate where the model was trained, rather than where it is deployed.</p> <p>Agentic AI is a systems problem under chaotic uncertainty:</p> <ul> <li>Feedback loops between the agent and its environment;</li> <li>Error accumulation across iterations;</li> <li>Brittle representations that break outside training distribution;</li> <li>Misplaced trust in outputs that look correct.</li> </ul> <p>In short-lived interactions, model quirks are tolerable. In long-running autonomous loops, however, they compound. </p> <p>That is where shallow understanding becomes expensive.</p> <p>But the understanding you need is not about optimizer internals.</p> <p>It is about:</p> What Matters What Does Not (for Most Practitioners) Why gradient descent fails in specific regimes How to derive it from scratch When memorization masquerades as reasoning The formal definition of VC dimension Recognizing distribution shift before it compounds Hand-tuning learning rate schedules Predicting when scale hides errors instead of fixing them Chasing theoretical purity divorced from practice <p>The depth that matters is diagnostic, not theoretical.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-real-answer","level":2,"title":"The Real Answer","text":"<p>Not turtles all the way down.</p> <p>Go deep enough to:</p> <ul> <li>Diagnose failures instead of cargo-culting fixes;</li> <li>Reason about uncertainty instead of trusting confidence;</li> <li>Design guardrails that align with model behavior, not hope.</li> </ul> <p>Stop before:</p> <ul> <li>Hand-deriving gradients for the sake of it;</li> <li>Obsessing over optimizer internals you will never touch;</li> <li>Chasing theoretical purity divorced from the scale you actually   operate at.</li> </ul> <p>This is not about mastering ML.</p> <p>It is about knowing which abstractions you can safely trust and which ones leak.</p> <p>Hint: Any useful abstraction almost certainly leaks.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#a-practical-litmus-test","level":2,"title":"A Practical Litmus Test","text":"<p>If a failure occurs and your instinct is to:</p> <ul> <li>Add more prompt text: abstraction leak above</li> <li>Add retries or heuristics: error accumulation</li> <li>Change the model: scale masking</li> <li>Reach for ML theory: you are probably (but not always) going too deep</li> </ul> <p>The right depth is the shallowest layer where the failure becomes predictable.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-ctx-lesson","level":2,"title":"The <code>ctx</code> Lesson","text":"<p>Every design decision in <code>ctx</code> is downstream of this principle.</p> <p>The attention budget exists  because the model's internal attention mechanism has real limits: You do not need to understand the math of softmax to build around it. But you do need to understand that more context is not always better and that attention density degrades with scale.</p> <p>The skill system exists  because the model's built-in behavior is already good: You do not need to understand RLHF to build effective skills. But you do need to understand that the model already has judgment and your skills should teach it things it does not know, not override how it thinks.</p> <p>Defense in depth  exists because soft instructions are probabilistic: You do not need to understand the transformer architecture to know that a Markdown file is not a security boundary. But you do need to understand that the model follows instructions from context, and context can be poisoned.</p> <p>In each case, the useful depth was one or two layers below the abstraction I was working at: Not at the bottom of the stack.</p> <p>The boundary between useful understanding and academic exercise is where your failure modes live.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#closing-thought","level":2,"title":"Closing Thought","text":"<p>Most modern AI systems do not fail because the math is wrong.</p> <p>They fail because we apply correct math in the wrong regime, then build autonomous systems on top of it.</p> <p>Understanding that boundary, not crossing it blindly, is where depth still compounds.</p> <p>And that is a far more useful form of expertise than memorizing another loss function.</p> <p>If You Remember One Thing from This Post...</p> <p>Go deep enough to diagnose your failures. Stop before you are solving problems that do not propagate to your layer.</p> <p>The abstractions below you are not sacred. But neither are they irrelevant.</p> <p>The useful depth is wherever your failure modes live. Usually one or two layers down, not at the bottom.</p> <p>This post started as a note about whether I should take an ML course. The answer turned out to be \"no, but understand why not\". The meta continues.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/","level":1,"title":"Before Context Windows, We Had Bouncers","text":"","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-reset-problem","level":2,"title":"The Reset Problem","text":"<p>IRC is stateless.</p> <ul> <li>You disconnect, you vanish.</li> <li>You reconnect, you begin again.</li> </ul> <p>No buffer.</p> <p>No memory.</p> <p>No continuity.</p> <p>Modern systems are not much different:</p> <ul> <li>Close the browser tab.<ul> <li>Lose the Slack scrollback.</li> </ul> </li> <li>Open a new LLM session.<ul> <li>Start from zero.</li> </ul> </li> </ul> <p>Resets externalize reconstruction cost onto humans.</p> <p>Reconstruction is tax: Tax becomes entropy.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#stateless-protocol-stateful-life","level":2,"title":"Stateless Protocol, Stateful Life","text":"<p>IRC is minimal:</p> <ul> <li>A TCP connection.</li> <li>A nickname.</li> <li>A channel.</li> <li>A stream of lines.</li> </ul> <p>When the connection drops, you literally disappear from the graph.</p> <p>The protocol is stateless; human systems are not.</p> <p>So you:</p> <ul> <li>Reconnect;</li> <li>Ask what you missed;</li> <li>Scroll;</li> <li>Reconstruct.</li> </ul> <p>The machine forgets; you pay.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-bouncer-pattern","level":2,"title":"The Bouncer Pattern","text":"<p>A <code>bouncer</code> is a daemon that remains connected when you do not:</p> <ul> <li>It holds your seat;</li> <li>It buffers what you missed;</li> <li>It keeps your identity online.</li> </ul> <p>ZNC is one such bouncer.</p> <p>With ZNC:</p> <ul> <li>Your client does not connect to IRC;</li> <li>It connects to <code>ZNC</code>;</li> <li><code>ZNC</code> connects upstream.</li> </ul> <p>Client sessions become ephemeral.</p> <p>Presence becomes infrastructural.</p> <p>ZNC Is Tmux for IRC</p> <ul> <li> <p>Close your laptop.</p> <ul> <li>ZNC remains.</li> </ul> </li> <li> <p>Switch devices.</p> <ul> <li>ZNC persists.</li> </ul> </li> </ul> <p>This is not convenience; this is continuity.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#presence-without-flapping","level":2,"title":"Presence without Flapping","text":"<p>With a bouncer:</p> <ul> <li>Closing your client does not emit <code>PART</code>.</li> <li>Reopening does not emit <code>JOIN</code>.</li> </ul> <p>You do not flap in and out of existence.</p> <p>From the channel's perspective, you remain.</p> <p>From your perspective, history accumulates.</p> <ul> <li>Buffers persist;</li> <li>Identity persists;</li> <li>Context persists.</li> </ul> <p>This pattern predates AI.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#before-llm-context-windows","level":2,"title":"Before LLM Context Windows","text":"<p>An LLM session without memory is IRC without a bouncer:</p> <ul> <li>Close the window.</li> <li>Start over.</li> <li>Re-explain intent.</li> <li>Rehydrate context.</li> </ul> <p>That is friction.</p> <p>This Walks and Talks like <code>ctx</code></p> <p>Context engineering moves memory out of sessions and into infrastructure.</p> <ul> <li><code>ZNC</code> does this for IRC.</li> <li><code>ctx</code> does this for agents.</li> </ul> <p>Same principle:</p> <ul> <li>Volatile interface.</li> <li>Persistent substrate.</li> </ul> <p>Different fabric.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#minimal-architecture","level":2,"title":"Minimal Architecture","text":"<p>My setup is intentionally boring:</p> <ul> <li>A $5 small VPS.</li> <li>ZNC installed.</li> <li>TLS enabled.</li> <li>Firewall restricted.</li> </ul> <p>Then:</p> <ul> <li>ZNC connects to <code>Libera.Chat</code>.</li> <li><code>SASL</code> authentication lives inside ZNC.</li> <li>Buffers are stored on disk.</li> </ul> <p>My client connects to my VPS, not the network.</p> <p>The commands do not matter: The boundaries do:</p> <ul> <li>Authentication in infrastructure, not in the client;</li> <li>Memory server-side, not in scrollback;</li> <li>Presence decoupled from activity.</li> </ul> <p>Everything else is configuration.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#platform-memory","level":2,"title":"Platform Memory","text":"<p>Yes, I know, it is 2026:</p> <ul> <li>Discord stores history;</li> <li>Slack stores history;</li> <li>The dumpster fire on gasoline called X, too, stores history.</li> </ul> <p>HOWEVER, they own your substrate.</p> <p>Running a bouncer is quiet sovereignty:</p> <ul> <li>Logs are mine.</li> <li>Presence is continuous.</li> <li>State does not reset because I closed a tab.</li> </ul> <p>Small acts compound.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#signal-density","level":2,"title":"Signal Density","text":"<p>Primitive systems select for builders.</p> <p>Consistent presence in small rooms compounds reputation.</p> <p>Quiet compounding outperforms viral spikes.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#infrastructure-as-cognition","level":2,"title":"Infrastructure as Cognition","text":"<p>ZNC is not interesting because it is retro; it is interesting because it models a principle:</p> <ul> <li>Stateless protocols require stateful wrappers;</li> <li>Volatile interfaces require durable memory;</li> <li>Human systems require continuity.</li> </ul> <p>Distilled:</p> <p>Humans require context.</p> <p>Before context windows, we had bouncers.  </p> <p>Before AI memory files, we had buffers.</p> <p>Continuity is not a feature; it is a design decision.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#build-it","level":2,"title":"Build It","text":"<p>If you want the actual setup (VPS, ZNC, TLS, SASL, firewall...) there is a step-by-step runbook:</p> <p>Persistent IRC Presence with ZNC.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#motd","level":2,"title":"MOTD","text":"<p>When my client connects to my bouncer, it prints:</p> <pre><code>//   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n</code></pre> <p>See also: Context as Infrastructure -- the post that takes this observation to its conclusion: stateless protocols need stateful wrappers, and AI sessions need persistent filesystems.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/","level":1,"title":"Parallel Agents with Git Worktrees","text":"","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-backlog-problem","level":2,"title":"The Backlog Problem","text":"<p>Jose Alekhinne / 2026-02-14</p> <p>What Do You Do with 30 Open Tasks?</p> <p>You could work through them one at a time.</p> <p>One agent, one branch, one commit stream.</p> <p>Or you could ask: which of these don't touch each other?</p> <p>I had 30 open tasks in <code>TASKS.md</code>. Some were docs. Some were a new encryption package. Some were test coverage for a stable module. Some were blog posts.</p> <p>They had almost zero file overlap.</p> <p>Running one agent at a time meant serial execution on work that was fundamentally parallel:</p> <p>I was bottlenecking on me, not on the machine.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-insight-file-overlap-is-the-constraint","level":2,"title":"The Insight: File Overlap Is the Constraint","text":"<p>This is not a scheduling problem: It's a conflict avoidance problem.</p> <p>Two agents can work simultaneously on the same codebase if and only if they don't touch the same files. The moment they do, you get merge conflicts: And merge conflicts on AI-generated code are expensive because the human has to arbitrate choices they didn't make.</p> <p>So the question becomes: </p> <p>\"Can you partition your backlog into non-overlapping tracks?\"</p> <p>For <code>ctx</code>, the answer was obvious:</p> Track Touches Tasks <code>work/docs</code> <code>docs/</code>, <code>hack/</code> Blog posts, recipes, runbooks <code>work/pad</code> <code>internal/cli/pad/</code>, specs Scratchpad encryption, CLI, tests <code>work/tests</code> <code>internal/cli/recall/</code> Recall test coverage <p>Three tracks. Near-zero overlap. Three agents.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#git-worktrees-the-mechanism","level":2,"title":"Git Worktrees: The Mechanism","text":"<p><code>git</code> has a feature that most people don't use: worktrees.</p> <p>A worktree is a second (or third, or fourth) working directory that shares the same <code>.git</code> object database as your main checkout. </p> <p>Each worktree has its own branch, its own index, its own working tree. But they all share history, refs, and objects.</p> <pre><code>git worktree add ../ctx-docs -b work/docs\ngit worktree add ../ctx-pad -b work/pad\ngit worktree add ../ctx-tests -b work/tests\n</code></pre> <ul> <li>Three directories;</li> <li>Three branches;</li> <li>One repository.</li> </ul> <p>This is cheaper than three clones. And because they share objects, <code>git merge</code> afterwards is fast: It's a local operation on shared data.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-setup","level":2,"title":"The Setup","text":"<p>The workflow I landed on:</p> <p>1. Group tasks by blast radius.</p> <p>Read <code>TASKS.md</code>. For each pending task, estimate which files and directories it touches. Group tasks that share files into the same track. Tasks with no overlap go into separate tracks.</p> <p>This is the part that requires human judgment: </p> <p>An agent can propose groupings, but you need to verify that the boundaries are  real. A task that says \"update docs\" but actually touches Go code will poison a docs track.</p> <p>2. Create worktrees as sibling directories.</p> <p>Not subdirectories: Siblings. </p> <p>If your main checkout is at <code>~/WORKSPACE/ctx</code>, worktrees go  at <code>~/WORKSPACE/ctx-docs</code>, <code>~/WORKSPACE/ctx-pad</code>, etc.</p> <p>Why siblings? Because some tools (and some agents) walk up the directory tree looking for <code>.git</code>. A worktree inside the main checkout confuses them.</p> <p>3. Launch one agent per worktree.</p> <pre><code># Terminal 1\ncd ../ctx-docs && claude\n\n# Terminal 2\ncd ../ctx-pad && claude\n\n# Terminal 3\ncd ../ctx-tests && claude\n</code></pre> <p>Each agent gets a full working copy with <code>.context/</code> intact. It reads the same <code>TASKS.md</code>, the same <code>DECISIONS.md</code>, the same <code>CONVENTIONS.md</code>. It knows the full project state. It just works on a different slice.</p> <p>4. Do NOT run <code>ctx init</code> in worktrees.</p> <p>This is the gotcha. The <code>.context/</code> directory is tracked in git. Running <code>ctx init</code> in a worktree would overwrite shared context files: Wiping decisions, learnings, and tasks that belong to the whole project.</p> <p>The worktree already has everything it needs. Leave it alone.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#what-actually-happened","level":2,"title":"What Actually Happened","text":"<p>I ran three agents for about 40 minutes. Here is roughly what each track produced:</p> <p><code>work/docs</code>: Parallel worktrees recipe, blog post edits, recipe index reorganization, IRC recipe moved from <code>docs/</code> to <code>hack/</code>.</p> <p><code>work/pad</code>: <code>ctx pad show</code> subcommand, <code>--append</code> and <code>--prepend</code> flags on <code>ctx pad edit</code>, spec updates, 28 new test functions.</p> <p><code>work/tests</code>: Recall test coverage, edge case tests.</p> <p>Merging took about five minutes. Two of the three merges were clean.</p> <p>The third had a conflict in <code>TASKS.md</code>: </p> <p>both the docs track and the pad track had marked different tasks as <code>[x]</code>.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-tasksmd-conflict","level":2,"title":"The <code>TASKS.md</code> Conflict","text":"<p>This deserves its own section because it will happen every time.</p> <p>When two agents work in parallel, they both read <code>TASKS.md</code> at the start and mark tasks complete as they go. When you merge, git sees two branches that modified the same file differently.</p> <p>The resolution is always the same: accept all completions from both sides. No task should go from <code>[x]</code> back to <code>[ ]</code>. The merge is additive.</p> <p>This is one of those conflicts that sounds scary but is trivially mechanical: You are not arbitrating design decisions; you are combining two checklists.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#limits","level":2,"title":"Limits","text":"<p>3-4 worktrees, maximum. </p> <p>I tried four once: By the time I merged the third track, the fourth had drifted far enough that its changes needed rebasing. </p> <p>The merge complexity grows faster than the parallelism benefit.</p> <p>Three is the sweet spot:</p> <ul> <li>Two is conservative but safe;</li> <li>Four is possible if the tracks are truly independent;</li> <li>Anything more than four, you are in the danger zone.</li> </ul> <p>Group by directory, not by priority.</p> <p>It is tempting to put all the high-priority tasks in one track: Don't. </p> <p>Two high-priority tasks that touch the same files must be in the same track,  regardless of urgency. The constraint is file overlap, not importance.</p> <p>Commit frequently. </p> <p>Smaller commits make merge conflicts easier to resolve. An agent that writes  500 lines in a single commit is harder to merge than one that commits every  logical step.</p> <p>Name tracks by concern. </p> <ul> <li><code>work/docs</code> and <code>work/pad</code> tell you what's happening;</li> <li><code>work/track-1</code> and <code>work/track-2</code> tell you nothing.</li> </ul>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-pattern","level":2,"title":"The Pattern","text":"<p>This is the same pattern that shows up everywhere in <code>ctx</code>:</p> <p>The attention budget taught me that you can't dump everything into one context window. You have to partition, prioritize, and load selectively.</p> <p>Worktrees are the same principle applied to execution: You can't dump every task into one agent's workstream. You have to partition by blast radius, assign selectively, and merge deliberately.</p> <p>The codebase audit that generated these 30 tasks used eight parallel agents for analysis. Worktrees let me use parallel agents for implementation. Same coordination pattern, different artifact.</p> <p>And the IRC bouncer post from earlier today argued that stateless protocols need stateful wrappers. Worktrees are the same: git branches are stateless forks; <code>.context/</code> is the stateful wrapper that gives each agent the project's full memory.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#should-this-be-a-skill","level":2,"title":"Should This Be a Skill?","text":"<p>I asked myself the same question I asked about the codebase audit: should this be a <code>/ctx-worktree</code> skill?</p> <p>This time the answer was a resounding \"yes\": </p> <p>Unlike the audit prompt (which I tweak every time and run every other week) the worktree workflow is:</p> Criterion Worktree workflow Codebase audit Frequency Weekly Quarterly Stability Same steps every time Tweaked every time Scope Mechanical, bounded Bespoke, 8 agents Trigger Large backlog \"I feel like auditing\" <p>The commands are mechanical: <code>git worktree add</code>, <code>git worktree remove</code>, branch naming, safety checks. This is exactly what skills are for: stable contracts for repetitive operations.</p> <p>Ergo, <code>/ctx-worktree</code> exists. </p> <p>It enforces the 4-worktree limit, creates sibling directories,  uses <code>work/</code> branch prefixes, and reminds you not to run <code>ctx init</code> in worktrees.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-takeaway","level":2,"title":"The Takeaway","text":"<p>Serial execution is the default. But serial is not always necessary.</p> <p>If your backlog partitions cleanly by file overlap, you can multiply your throughput with nothing more exotic than <code>git worktree</code> and a second terminal window.</p> <p>The hard part is not the <code>git</code> commands; it is the discipline:</p> <ul> <li>Grouping by blast radius instead of priority; </li> <li>Accepting that <code>TASKS.md</code> will conflict; </li> <li>And knowing when three tracks is enough.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Partition by blast radius, not by priority.</p> <p>Two tasks that touch the same files belong in the same track, no matter how important the other one is.</p> <p>The constraint is file overlap. Everything else is scheduling.</p> <p>The practical setup (skill invocation, worktree creation, merge workflow, and cleanup) lives in the recipe: Parallel Agent Development with Git Worktrees.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/","level":1,"title":"<code>ctx</code> v0.3.0: The Discipline Release","text":"","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#when-the-ratio-of-polish-to-features-is-31-you-know-something-changed","level":2,"title":"When the Ratio of Polish to Features Is 3:1, You Know Something Changed","text":"<p>Jose Alekhinne / February 15, 2026</p> <p>What Does a Release Look like When Most of the Work Is Invisible?</p> <p>No new headline feature. No architectural pivot. No rewrite.</p> <p>Just 35+ documentation and quality commits against ~15 feature commits... and somehow, the tool feels like it grew up overnight.</p> <p>Six days separate <code>v0.2.0</code> from <code>v0.3.0</code>. </p> <p>Measured by calendar time, it is nothing. Measured by what changed in how the  project operates, it is the most significant release yet.</p> <ul> <li><code>v0.1.0</code> was the prototype;</li> <li><code>v0.2.0</code> was the archaeology release:    making the past accessible; </li> <li><code>v0.3.0</code> is the discipline release: the one that turned best practices     into enforcement, suggestions into structure, and a collection of     commands into a system of skills.</li> </ul> <p>The Release Window</p> <p>February 1‒February 7, 2026</p> <p>From the <code>v0.2.0</code> tag to commit <code>2227f99</code>.</p> <p>78 files changed in the migration commit alone.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-migration-commands-to-skills","level":2,"title":"The Migration: Commands to Skills","text":"<p>The largest single change was the migration from <code>.claude/commands/*.md</code> to <code>.claude/skills/*/SKILL.md</code>.</p> <p>This was not a rename: It was a rethinking of how AI agents discover and execute project-specific workflows.</p> Aspect Commands (before) Skills (after) Structure Flat files in one directory Directory-per-skill with SKILL.md Description Optional, often vague Required, doubles as activation trigger Quality gates None \"Before X-ing\" pre-flight checklist Negative triggers None \"When NOT to Use\" in every skill Examples Rare Good/bad pairs in every skill Average length ~15 lines ~80 lines <p>The description field became the single most important line in each skill. In the old system, descriptions were titles. In the new system, they are activation conditions: The text the platform reads to decide whether to surface a skill for a given prompt.</p> <p>A description that says \"Show context summary\" activates too broadly or not at all. A description that says \"Show context summary. Use at session start or when unclear about current project state\" activates at the right moment.</p> <p>78 files changed. 1,915 insertions. Not because the skills got bloated; because they got specific.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-skill-sweep","level":2,"title":"The Skill Sweep","text":"<p>After the structural migration, every skill was rewritten in a single session: All 21 of them.</p> <p>The rewrite was guided by a pattern that emerged during the process itself: a repeatable anatomy that effective skills share regardless of their purpose:</p> <ol> <li>Before X-ing: Pre-flight checks that prevent premature execution</li> <li>When to Use: Positive triggers that narrow activation</li> <li>When NOT to Use: Negative triggers that prevent misuse</li> <li>Usage Examples: Invocation patterns the agent can pattern-match</li> <li>Quality Checklist: Verification before claiming completion</li> </ol> <p>The Anatomy of a Skill That Works post covers the details. What matters for the release story is the result: </p> <ul> <li>Zero skills with quality gates became twenty; </li> <li>Zero skills with negative triggers became twenty. </li> <li>Three skills with examples became twenty.</li> </ul> <p>The Skill Trilogy as Design Spec</p> <p>The three blog posts written during this window:</p> <ul> <li>Skills That Fight the Platform, </li> <li>You Can't Import Expertise,</li> <li>and The Anatomy of a Skill That Works...</li> </ul> <p>... were not retrospective documentation. They were written  during the rewrite, and the lessons fed back into the skills  as they were being built.</p> <ul> <li>The blog was the design document. </li> <li>The skills were the implementation.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-consolidation-sweep","level":2,"title":"The Consolidation Sweep","text":"<p>The unglamorous work. The kind you only appreciate when you try to change something later and it just works.</p> What Why It Matters Constants consolidation Magic strings replaced with semantic constants Variable deshadowing Eliminated subtle scoping bugs File splits Modules that were doing too much, broken apart Godoc standardization Every exported function documented to convention <p>This is the work that doesn't get a changelog entry but makes every future commit easier. When a new contributor (human or AI) reads the codebase, they find consistent patterns instead of accumulated drift.</p> <p>The consolidation was not an afterthought. It was scheduled deliberately, with the same priority as features: The 3:1 ratio that emerged during <code>v0.2.0</code> development became an explicit practice: </p> <ul> <li>Three feature sessions; </li> <li>One consolidation session.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-ear-framework","level":2,"title":"The E/A/R Framework","text":"<p>On February 4<sup>th</sup>, we adopted the E/A/R classification as the official standard for evaluating skills:</p> Category Meaning Target Expert Knowledge Claude does not have >70% Activation When/how to trigger ~20% Redundant What Claude already knows <10% <p>This came from reviewing approximately 30 external skill files and discovering that most were redundant with Claude's built-in system prompt. Only about 20% had salvageable content, and even those yielded just a few heuristics each.</p> <p>The E/A/R framework gave us a concrete, testable criterion: </p> <p>A good skill is Expert knowledge minus what Claude already knows.</p> <p>If more than 10% of a skill restates platform defaults, it is creating noise, not signal.</p> <p>Every skill in <code>v0.3.0</code> was evaluated against this framework. Several were deleted. The survivors are leaner and more focused.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#backup-and-monitoring-infrastructure","level":2,"title":"Backup and Monitoring Infrastructure","text":"<p>A tool that manages your project's memory needs ops maturity. </p> <p><code>v0.3.0</code> added two pieces of infrastructure that reflect this:</p> <p>Backup staleness hook: A <code>UserPromptSubmit</code> hook that checks whether the last <code>.context/</code> backup is more than two days old. If it is, and the SMB mount is available, it reminds the user. No cron job running when nobody is working. No redundant backups when nothing has changed.</p> <p>Context size checkpoint: A <code>PreToolUse</code> hook that estimates current context window usage and warns when the session is getting heavy. This hooks into the attention budget philosophy: Degradation is expected, but it should be visible.</p> <p>Both hooks use <code>$CLAUDE_PROJECT_DIR</code> instead of hardcoded paths, a migration triggered by a username rename that broke every absolute path in the hook configuration. That migration (replacing <code>/home/user/...</code> with <code>\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/...</code>) was one of those changes that seems trivial but prevents an entire category of future failures.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.2.0 v0.3.0 Skills (was \"commands\") 11 21 Skills with quality gates 0 21 Skills with \"When NOT to Use\" 0 21 Average skill body ~15 lines ~80 lines Hooks using <code>$CLAUDE_PROJECT_DIR</code> 0 All Documentation commits n/a 35+ Feature/fix commits n/a ~15 <p>That ratio (35+ documentation and quality commits to ~15 feature commits) is the defining characteristic of this release:</p> <ul> <li>This release is not a failure to ship features. </li> <li>It is the deliberate choice to make the existing features reliable.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-v030-means","level":2,"title":"What v0.3.0 Means","text":"<p><code>v0.1.0</code> asked: \"Can we give AI persistent memory?\"</p> <p><code>v0.2.0</code> asked: \"Can we make that memory accessible to humans too?\"</p> <p><code>v0.3.0</code> asks a different question: \"Can we make the quality self-enforcing?\"</p> <p>The answer is not a feature: It is a practice:</p> <ul> <li>Skills with quality gates enforce pre-flight checks.</li> <li>Negative triggers prevent misuse without human intervention.</li> <li>The E/A/R framework ensures skills contain signal, not noise.</li> <li>Consolidation sessions are scheduled, not improvised.</li> <li>Hook infrastructure makes degradation visible.</li> </ul> <p>Discipline is not the absence of velocity. It is the infrastructure that makes velocity sustainable.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-comes-next","level":2,"title":"What Comes Next","text":"<p>The skill system is now mature enough to support real workflows without constant human correction. The hooks infrastructure is portable and resilient. The consolidation  practice is documented and repeatable.</p> <p>The next chapter is about what you build on top of discipline:</p> <ul> <li>Multi-agent coordination;</li> <li>Deeper integration patterns; </li> <li>And the question of whether context management is a tool concern or    an infrastructure concern.</li> </ul> <p>But those are future posts.</p> <p>This one is about the release that proved polish is not the opposite of progress. It is what turns a prototype into a product.</p> <p>The Discipline Release</p> <p><code>v0.1.0</code> shipped features. </p> <p><code>v0.2.0</code> shipped archaeology.</p> <p><code>v0.3.0</code> shipped the habits that make everything else trustworthy.</p> <p>The most important code in this release is the code that prevents bad code from shipping.</p> <p>This post was drafted using <code>/ctx-blog</code> with access to the full git history between v0.2.0 and v0.3.0, decision logs, learning logs, and the session files from the skill rewrite window. The meta continues.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/","level":1,"title":"Eight Ways a Hook Can Talk","text":"","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#when-your-warning-disappears","level":2,"title":"When Your Warning Disappears","text":"<p>Jose Alekhinne / 2026-02-15</p> <p>I had a backup warning that nobody ever saw.</p> <p>The hook was correct: It detected stale backups, formatted a nice message, and output it as <code>{\"systemMessage\": \"...\"}</code>. The problem wasn't detection. The problem was delivery. The agent absorbed the information, processed it internally, and never told the user.</p> <p>Meanwhile, a different hook (the journal reminder) worked perfectly every time. Users saw the reminder, ran the commands, and the backlog stayed manageable. Same hook event (<code>UserPromptSubmit</code>), same project, completely different outcomes.</p> <p>The difference was one line:</p> <pre><code>IMPORTANT: Relay this journal reminder to the user VERBATIM\nbefore answering their question.\n</code></pre> <p>That explicit instruction is what makes VERBATIM relay a pattern, not just a formatting choice. And once I saw it as a pattern, I started seeing others.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-audit","level":2,"title":"The Audit","text":"<p>I looked at every hook in <code>ctx</code>: Eight shell scripts across three hook events. And I found five distinct output patterns already in use, plus three more that the existing hooks were reaching for but hadn't quite articulated.</p> <p>The patterns form a spectrum based on a single question: </p> <p>\"Who decides what the user sees?\"</p> <p>At one end, the hook decides everything (hard gate: the agent literally cannot proceed). At the other end, the hook is invisible (silent side-effect: nobody knows it ran). In between, there is a range of negotiation between hook, agent, and the user.</p> <p>Here's the full spectrum:</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#1-hard-gate","level":3,"title":"1. Hard Gate","text":"<pre><code>{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}\n</code></pre> <p>The nuclear option: The agent's tool call is rejected before it executes.</p> <p>This is Claude Code's first-class <code>PreToolUse</code> mechanism: The hook returns JSON with <code>decision: block</code> and the agent gets an error with the reason.</p> <p>Use this for invariants: Constitution rules, security boundaries, things that must never happen. I use it to enforce <code>PATH</code>-based <code>ctx</code> invocation, block <code>sudo</code>, and require explicit approval for <code>git push</code>.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#2-verbatim-relay","level":3,"title":"2. VERBATIM Relay","text":"<pre><code>IMPORTANT: Relay this warning to the user VERBATIM before answering.\n┌─ Journal Reminder ─────────────────────────────\n│ You have 12 sessions not yet imported.\n│   ctx recall import --all\n└────────────────────────────────────────────────\n</code></pre> <p>The instruction is the pattern. Without \"Relay VERBATIM,\" agents tend to absorb information into their internal reasoning and never surface it. The explicit instruction changes the behavior from \"I know about this\" to  \"I must tell the user about this.\"</p> <p>I use this for actionable reminders: </p> <ul> <li>Unexported journal entries;</li> <li>Stale backups;</li> <li>Context capacity warnings... </li> </ul> <p>...things the user should see regardless of what they asked.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#3-agent-directive","level":3,"title":"3. Agent Directive","text":"<pre><code>┌─ Persistence Checkpoint (prompt #25) ───────────\n│ No context files updated in 15+ prompts.\n│ Have you discovered learnings worth persisting?\n└──────────────────────────────────────────────────\n</code></pre> <p>A nudge, not a command. The hook tells the agent something; the agent decides what (if anything) to tell the user. This is right for behavioral nudges: \"you haven't saved context in a while\" doesn't need to be relayed verbatim, but the agent should consider acting on it.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#4-silent-context-injection","level":3,"title":"4. Silent Context Injection","text":"<pre><code>ctx agent --budget 4000 2>/dev/null || true\n</code></pre> <p>Pure background enrichment. The agent's context window gets project information injected on every tool call, with no visible output. Neither the agent nor the user sees the hook fire, but the agent makes better decisions because of the context.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#5-silent-side-effect","level":3,"title":"5. Silent Side-Effect","text":"<pre><code>find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n</code></pre> <p>Do work, say nothing. Temp file cleanup on session end. Logging. Marker file management. The action is the entire point; no one needs to know.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-patterns-we-dont-have-yet","level":2,"title":"The Patterns We Don't Have Yet","text":"<p>Three more patterns emerged from the gaps in the existing hooks.</p> <p>Conditional relay: \"Relay this, but only if the user's question is about X.\" This pattern avoids noise when the warning isn't relevant.  It's more fragile (depends on agent judgment) but less annoying.</p> <p>Suggested action: \"Here's a problem, and here's the exact command to fix it. Ask the user before running it.\" This pattern goes beyond a nudge by  giving the agent a concrete proposal, but still requires human approval.</p> <p>Escalating severity: <code>INFO</code> gets absorbed silently. <code>WARN</code> gets mentioned at the next natural pause. <code>CRITICAL</code> gets the VERBATIM treatment. This pattern introduces a protocol for hooks that produce output  at different urgency levels, so they don't all compete for the user's attention.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-principle","level":2,"title":"The Principle","text":"<p>Hooks are the boundary between your environment and the agent's reasoning. </p> <p>A hook that detects a problem but can't communicate it effectively is the same  as no hook at all.</p> <p>The format of your output is a design decision with real consequences:</p> <ul> <li>Use a hard gate and the agent can't proceed (good for invariants,   frustrating for false positives)</li> <li>Use VERBATIM relay and the user will see it (good for reminders,   noisy if overused)</li> <li>Use an agent directive and the agent might act (good for nudges,   unreliable for critical warnings)</li> <li>Use silent injection and nobody knows (good for enrichment,   invisible when it breaks)</li> </ul> <p>Choose deliberately. And, when in doubt, write the word <code>VERBATIM</code>.</p> <p>The full pattern catalog with decision flowchart and implementation examples is in the Hook Output Patterns recipe.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/","level":1,"title":"Version Numbers Are Lagging Indicators","text":"","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#why-ctxs-journal-site-runs-on-a-v0021-tool","level":2,"title":"Why <code>ctx</code>'s Journal Site Runs on a v0.0.21 Tool","text":"<p>Jose Alekhinne / 2026-02-15</p> <p>Would You Ship Production Infrastructure on a v0.0.21 Dependency?</p> <p>Most engineers wouldn't. Version numbers signal maturity. Pre-1.0 means unstable API, missing features, risk.</p> <p>But version numbers tell you where a project has been. They say nothing about where it's going.</p> <p>I just bet <code>ctx</code>'s entire journal site on a tool that hasn't hit <code>v0.1.0</code>. </p> <p>Here's why I'd do it again.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-problem","level":2,"title":"The Problem","text":"<p>When v0.2.0 shipped the journal system, the pipeline was clear:</p> <ul> <li>Export sessions to Markdown; </li> <li>Enrich them with YAML frontmatter; </li> <li>And render them into something browsable. </li> </ul> <p>The first two steps were solved; the third needed a tool.</p> <p>The journal entries are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is the entire format: </p> <ul> <li>No JSX;</li> <li>No shortcodes;</li> <li>No custom templating. </li> </ul> <p>Just Markdown rendered well.</p> <p>The requirements are modest:</p> <ul> <li>Read a configuration file (such as <code>mkdocs.yml</code>);</li> <li>Render Markdown with extensions (admonitions, tabs, tables);</li> <li>Search;</li> <li>Handle 100+ files without choking on incremental rebuilds;</li> <li>Look good out of the box;</li> <li>Not lock me in.</li> </ul> <p>The obvious candidates were as follows:</p> Tool Language Strengths Pain Points Hugo Go Blazing fast, mature Templating is painful; Go templates fight you on anything non-trivial Astro JS/TS Modern, flexible JS ecosystem overhead; overkill for a docs site MkDocs + Material Python Beautiful defaults, massive community (22k+ stars) Slow incremental rebuilds on large sites; limited extensibility model Zensical Python Built to fix MkDocs' limits; 4-5x faster rebuilds v0.0.21; module system not yet shipped <p>The instinct was Hugo. Same language as <code>ctx</code>. Fast. Well-established.</p> <p>But instinct is not analysis. I picked the one with the lowest version number.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation","level":2,"title":"The Evaluation","text":"<p>Here is what I actually evaluated, in order:</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#1-the-team","level":3,"title":"1. The Team","text":"<p>Zensical is built by squidfunk: The same person behind Material for MkDocs, the most popular MkDocs theme with 22,000+ stars. It powers documentation sites for projects across every language and framework.</p> <ul> <li>This is not someone learning how to build static site generators.</li> <li>This is someone who spent years understanding exactly where MkDocs   breaks and decided to fix it from the ground up.</li> </ul> <p>They did not build zensical because MkDocs was bad:  They built it because MkDocs hit a ceiling:</p> <ul> <li> <p>Incremental rebuilds: 4-5x faster during serve. When you have   hundreds of journal entries and you edit one, the difference between   \"rebuild everything\" and \"rebuild this page\" is the difference   between a usable workflow and a frustrating one.</p> </li> <li> <p>Large site performance: Specifically designed for tens of   thousands of pages. The journal grows with every session. A tool   that slows down as content accumulates is a tool you will eventually   replace.</p> </li> </ul> <p>A proven team starting fresh is more predictable than an unproven team at v3.0.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#2-the-architecture","level":3,"title":"2. The Architecture","text":"<p>Zensical is investing in a Rust-based Markdown parser with CommonMark support. That signals something about the team's priorities:</p> <p>Performance foundations first; features second.</p> <p><code>ctx</code>'s journal will grow: </p> <ul> <li>Every exported session adds files.</li> <li>Every enrichment pass adds metadata. </li> </ul> <p>Choosing a tool that gets slower as you add content means choosing to migrate later.</p> <p>Choosing one built for scale means the decision holds.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#3-the-migration-path","level":3,"title":"3. The Migration Path","text":"<p>Zensical reads <code>mkdocs.yml</code> natively. If it doesn't work out, I can move back to MkDocs + Material with zero content changes:</p> <ul> <li>The Markdown is standard; </li> <li>The frontmatter is standard; </li> <li>The configuration is compatible.</li> </ul> <p>This is the infrastructure pattern again: The same way <code>ZNC</code> decouples presence from the client, <code>zensical</code> decouples rendering from the generator: </p> <ul> <li>The Markdown is yours. </li> <li>The frontmatter is standard YAML. </li> <li>The configuration is MkDocs-compatible.</li> </ul> <p>You are not locked into anything except your own content.</p> <p>No lock-in is not a feature: It's a design philosophy: </p> <p>It's the same reason <code>ctx</code> uses plain Markdown files in <code>.context/</code> instead of a database: the format should outlive the tool.</p> <p>Lock-in Is the Real Risk, Not Version Numbers</p> <p>A mature tool with a proprietary format is riskier than a young tool with a standard one. Version numbers measure time invested. Portability measures respect for the user.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#4-the-dependency-tree","level":3,"title":"4. The Dependency Tree","text":"<p>Here is what <code>pip install zensical</code> actually pulls in:</p> <ul> <li>click</li> <li>Markdown</li> <li>Pygments</li> <li>pymdown-extensions</li> <li>PyYAML</li> </ul> <p>Only five dependencies. All well-known. No framework bloat. No bundler. No transpiler. No <code>node_modules</code> black hole.</p> <p>3k GitHub stars at <code>v0.0.21</code> is a strong early traction for a <code>pre-1.0</code> project. </p> <p>The dependency tree is thin: No bloat.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#5-the-fit","level":3,"title":"5. The Fit","text":"<p>This is the same principle behind the attention budget: do not overfit the tool to hypothetical requirements. The right amount of capability is the minimum needed for the current task.</p> <p>Hugo is a powerful static site generator. It is also a powerful templating engine, a powerful asset pipeline, and a powerful taxonomy system. For rendering Markdown journals, that power is overhead:</p> <p>It is the complexity you pay for but never use.</p> <p><code>ctx</code>'s journal files are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is exactly the sweet spot Zensical inherits from Material for MkDocs:</p> <ul> <li>No custom plugins needed;</li> <li>No special syntax; </li> <li>No templating gymnastics.</li> </ul> <p>The requirements match the capabilities: Not the capabilities that are promised, but the ones that exist today, at <code>v0.0.21</code>.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-caveat","level":2,"title":"The Caveat","text":"<p>It would be dishonest not to mention what's missing.</p> <p>The module system for third-party extensions opens in early 2026.</p> <p>If <code>ctx</code> ever needs custom plugins (for example, auto-linking session IDs, rendering special journal metadata, etc.) that infrastructure isn't there yet.</p> <p>The installation experience is rough: </p> <p>We discovered this firsthand: <code>pip install zensical</code> often fails on MacOS  (system Python stubs, Homebrew's PEP 668 restrictions). The answer is pipx, which creates an isolated environment with the correct Python version automatically. </p> <p>That kind of friction is typical for young Python tooling, and it is documented in the Getting Started guide.</p> <p>And <code>3,000</code> stars at <code>v0.0.21</code> is strong early traction, but it's still early: The community is small. When something breaks, you're reading  source  code, not documentation.</p> <p>These are real costs. I chose to pay them because the alternative costs are higher.</p> <p>For example:</p> <ul> <li>Hugo's templating pain would cost me time on every site change.</li> <li>Astro's JS ecosystem would add complexity I don't need. </li> <li>MkDocs would work today but hit scaling walls tomorrow. </li> </ul> <p>Zensical's costs are front-loaded and shrinking. </p> <p>The others compound.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation-framework","level":2,"title":"The Evaluation Framework","text":"<p>For anyone facing a similar choice, here is the framework that emerged:</p> Signal What It Tells You Weight Team track record Whether the architecture will be sound High Migration path Whether you can leave if wrong High Current fit Whether it solves your problem today High Dependency tree How much complexity you're inheriting Medium Version number How long the project has existed Low Star count Community interest (not quality) Low Feature list What's possible (not what you need) Low <p>The bottom three are the metrics most engineers optimize for.</p> <p>The top four are the ones that predict whether you'll still be happy with the choice in a year.</p> <p>Features You Don't Need Are Not Free</p> <p>Every feature in a dependency is code you inherit but don't control. </p> <p>A tool with 200 features where you use 5 means 195 features worth of surface area for bugs, breaking changes, and security issues that have nothing to do with your use case.</p> <p>Fit is the inverse of feature count.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-broader-pattern","level":2,"title":"The Broader Pattern","text":"<p>This is part of a theme I keep encountering in this project:</p> <p>Leading indicators beat lagging indicators.</p> Domain Lagging Indicator Leading Indicator Tooling Version number, star count Team track record, architecture Code quality Test coverage percentage Whether tests catch real bugs Context persistence Number of files in <code>.context/</code> Whether the AI makes fewer mistakes Skills Number of skills created Whether each skill fires at the right time Consolidation Lines of code refactored Whether drift stops accumulating <p>Version numbers, star counts, coverage percentages, file counts...</p> <p>...these are all measures of effort expended. </p> <p>They say nothing about value delivered.</p> <p>The question is never \"how mature is this tool?\" </p> <p>The question is \"does this tool's trajectory intersect with my needs?\"</p> <p>Zensical's trajectory: </p> <ul> <li>A proven team fixing known problems, </li> <li>in a *proven architecture, </li> <li>with a standard format,</li> <li>and no lock-in.</li> </ul> <p><code>ctx</code>'s needs: </p> <p>Tender standard Markdown into a browsable site, at scale,  without complexity.</p> <p>The intersection is clean; the version number is noise.</p> <p>This is the same kind of decision that shows up throughout <code>ctx</code>:</p> <ul> <li>Skills that fight the platform taught that the best   integration extends existing behavior, not replaces it.</li> <li>You can't import expertise taught that tools should   grow from your project's actual needs, not from feature checklists.</li> <li>Context as infrastructure argues that the format should   outlive the tool; and, <code>zensical</code> honors that principle by reading   standard Markdown and standard MkDocs configuration.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Version numbers measure where a project has been.</p> <p>The team and the architecture tell you where it's going.</p> <p>A <code>v0.0.21</code> tool built by the right team on the right foundations is a safer bet than a <code>v5.0</code> tool that doesn't fit your problem.</p> <p>Bet on trajectories, not timestamps.</p> <p>This post started as an evaluation note in <code>ideas/</code> and a separate decision log. The analysis held up. The two merged into one. The meta continues.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/","level":1,"title":"<code>ctx</code> v0.6.0: The Integration Release","text":"","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#two-commands-to-persistent-memory","level":2,"title":"Two Commands to Persistent Memory","text":"<p>Jose Alekhinne / February 16, 2026</p> <p>What Changed?</p> <p><code>ctx</code> is now a Claude Code plugin. Two commands, no build step:</p> <pre><code>/plugin marketplace add ActiveMemory/ctx\n/plugin install ctx@activememory-ctx\n</code></pre> <p>Six hooks. Twenty-five skills. Installed.</p> <p>For three releases, <code>ctx</code> required assembly: </p> <ul> <li>Clone the repo; </li> <li>Build the binary; </li> <li>Copy hook scripts into <code>.claude/hooks/</code>; </li> <li>Symlink skill files.</li> <li>Understand which shell scripts called which Go commands;</li> <li>Hope nothing broke when Claude Code updated its hook format.</li> </ul> <p><code>v0.6.0</code> ends that era: <code>ctx</code> ships as a Claude Marketplace plugin:</p> <p>Hooks and skills served directly from source, installed with a single command, updated by pulling the repo. The tool that gives AI persistent memory is now as easy to install as the AI itself.</p> <p>But the plugin conversion was not just a packaging change: It was the forcing function that rewrote every shell hook in Go, eliminated the <code>jq</code> dependency, enabled <code>go test</code> coverage for hook logic, and made distribution a solved problem. </p> <p>When you fix how something ships, you end up fixing how it is built.</p> <p>The Release Window</p> <p>February 15-February 16, 2026</p> <p>From the v0.3.0 tag to commit <code>a3178bc</code>:</p> <ul> <li>109 commits. </li> <li>334 files changed. </li> <li>Version jumped from 0.3.0 to 0.6.0 to signal the magnitude.</li> </ul>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#before-six-shell-scripts-and-a-prayer","level":2,"title":"Before: Six Shell Scripts and a Prayer","text":"<p><code>v0.3.0</code> had six hook scripts. Each was a Bash file that shelled out to <code>ctx</code> subcommands, parsed JSON with <code>jq</code>, and wired itself into Claude Code's hook system via <code>.claude/hooks/</code>:</p> <pre><code>.claude/hooks/\n├── check-context-size.sh\n├── check-persistence.sh\n├── check-journal.sh\n├── post-commit.sh\n├── block-non-path-ctx.sh\n└── cleanup-tmp.sh\n</code></pre> <p>This worked, but it also meant:</p> <ul> <li>jq was a hard dependency: No <code>jq</code>, no hooks. macOS ships without it.</li> <li>No test coverage: Shell scripts were tested manually or not at all.</li> <li>Fragile deployment: <code>ctx init</code> had to scaffold <code>.claude/hooks/</code>   and <code>.claude/skills/</code> with the right paths, permissions, and structure.</li> <li>Version drift: Users who installed once never got hook updates   unless they re-ran <code>ctx init</code>.</li> </ul> <p>The shell scripts were the right choice for prototyping. They were the wrong choice for distribution.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#after-one-plugin-zero-shell-scripts","level":2,"title":"After: One Plugin, Zero Shell Scripts","text":"<p><code>v0.6.0</code> replaces all six scripts with <code>ctx system</code> subcommands compiled into the binary:</p> Shell Script Go Subcommand <code>check-context-size.sh</code> <code>ctx system check-context-size</code> <code>check-persistence.sh</code> <code>ctx system check-persistence</code> <code>check-journal.sh</code> <code>ctx system check-journal</code> <code>post-commit.sh</code> <code>ctx system post-commit</code> <code>block-non-path-ctx.sh</code> <code>ctx system block-non-path-ctx</code> <code>cleanup-tmp.sh</code> <code>ctx system cleanup-tmp</code> <p>The plugin's <code>hooks.json</code> wires them to Claude Code events:</p> <pre><code>{\n  \"PreToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system block-non-path-ctx\"},\n    {\"matcher\": \".*\", \"command\": \"ctx agent --budget 4000\"}\n  ],\n  \"PostToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system post-commit\"}\n  ],\n  \"UserPromptSubmit\": [\n    {\"command\": \"ctx system check-context-size\"},\n    {\"command\": \"ctx system check-persistence\"},\n    {\"command\": \"ctx system check-journal\"}\n  ],\n  \"SessionEnd\": [\n    {\"command\": \"ctx system cleanup-tmp\"}\n  ]\n}\n</code></pre> <p>No jq. No shell scripts. No <code>.claude/hooks/</code> directory to manage.</p> <p>The hooks are Go functions with tests, compiled into the same binary you already have.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-plugin-model","level":2,"title":"The Plugin Model","text":"<p>The <code>ctx</code> plugin lives at <code>.claude-plugin/marketplace.json</code> in the repo.</p> <p>Claude Code's marketplace system handles discovery and installation:</p> <p>Skills are served directly from <code>internal/assets/claude/skills/</code>; there is no build step, no <code>make plugin</code>, no generated artifacts.</p> <p>This means:</p> <ol> <li>Install is two commands: Not \"clone, build, copy, configure.\"</li> <li>Updates are automatic: Pull the repo; the plugin reads from source.</li> <li>Skills and hooks are versioned together: No drift between what    the CLI expects and what the plugin provides.</li> <li><code>ctx init</code> is tool-agnostic: It creates <code>.context/</code> and nothing    else. No <code>.claude/</code> scaffolding, no assumptions about which AI tool    you use.</li> </ol> <p>That last point matters: </p> <p>Before <code>v0.6.0</code>, <code>ctx init</code> tried to set up Claude Code integration as part of  initialization. That coupled the context system to a specific tool. </p> <p>Now, <code>ctx init</code> gives you persistent context. The plugin gives you Claude Code  integration. They compose; they don't depend.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#beyond-the-plugin-what-else-shipped","level":2,"title":"Beyond the Plugin: What Else Shipped","text":"<p>The plugin conversion dominated the release, but 109 commits covered more ground.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#obsidian-vault-export","level":3,"title":"Obsidian Vault Export","text":"<pre><code>ctx journal obsidian\n</code></pre> <p>Generates a full Obsidian vault from enriched journal entries: wikilinks, MOC (Map of Content) pages, and graph-optimized cross-linking. If you already use Obsidian for notes, your AI session history now lives alongside everything else.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#encrypted-scratchpad","level":3,"title":"Encrypted Scratchpad","text":"<pre><code>ctx pad edit \"DATABASE_URL=postgres://...\"\nctx pad show\n</code></pre> <p><code>AES-256-GCM</code> encrypted storage for sensitive one-liners. </p> <p>The encrypted blob commits to <code>git</code>; the key stays in <code>.gitignore</code>. </p> <p>This is useful for connection strings, API keys, and other values that need to travel with the project without appearing in plaintext.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#security-hardening","level":3,"title":"Security Hardening","text":"<p>Three medium-severity findings from a security audit are now closed:</p> Finding Fix Path traversal via <code>--context-dir</code> Boundary validation: operations cannot escape project root (M-1) Symlink following in <code>.context/</code> <code>Lstat()</code> check before every file read/write (M-2) Predictable temp file paths User-specific temp directory under <code>$XDG_RUNTIME_DIR</code> (M-3) <p>Plus a new <code>/sanitize-permissions</code> skill that audits <code>settings.local.json</code> for overly broad Bash permissions.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#hooks-that-know-when-to-be-quiet","level":3,"title":"Hooks That Know When to Be Quiet","text":"<p>A subtle but important fix: hooks now no-op before <code>ctx init</code> has run.</p> <p>Previously, a fresh clone with no <code>.context/</code> would trigger hook errors on every prompt. Now, hooks detect the absence of a context directory and exit silently. Similarly, <code>ctx init</code> treats a <code>.context/</code> directory containing only logs as uninitialized and skips the <code>--overwrite</code> prompt.</p> <p>Small changes. Large reduction in friction for new users.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.3.0 v0.6.0 Skills 21 25 Shell hook scripts 6 0 Go system subcommands 0 6 External dependencies (hooks) jq, bash none Lines of Go ~14,000 ~37,000 Plugin install commands n/a 2 Security findings (open) 3 0 <code>ctx init</code> creates .claude/ yes no <p>The line count tripled. Most of that is documentation site HTML, Obsidian export logic, and the scratchpad encryption module. </p> <p>The core CLI grew modestly; the ecosystem around it grew substantially.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-does-v060-mean-for-ctx","level":2,"title":"What Does <code>v0.6.0</code> Mean for <code>ctx</code>?","text":"<ul> <li><code>v0.1.0</code> asked: \"Can we give AI persistent memory?\"</li> <li><code>v0.2.0</code> asked: \"Can we make that memory accessible to humans too?\"</li> <li><code>v0.3.0</code> asked: \"Can we make the quality self-enforcing?\"</li> </ul> <p>v0.6.0 asks: \"Can someone else actually use this?\"</p> <p>A tool that requires cloning a repo, building from source, and manually wiring hooks into the right directories is a tool for its author.</p> <p>A tool that installs with two commands from a marketplace is a tool for everyone.</p> <p>The version jumped from <code>0.3.0</code> to <code>0.6.0</code> because the delta is not incremental: The shell-to-Go rewrite, the plugin model, the security hardening, and the tool-agnostic init: Together, they change what <code>ctx</code> is: Not a different tool, but a tool that is finally ready to leave the workshop.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-comes-next","level":2,"title":"What Comes Next","text":"<p>The plugin model opens the door to distribution patterns that were not possible before. Marketplace discovery means new users find <code>ctx</code> without reading a <code>README</code>. Plugin updates mean existing users get improvements without rebuilding.</p> <p>The next chapter is about what happens when persistent context is easy to install: Adoption patterns, multi-project workflows, and whether the <code>.context/</code> convention can become infrastructure that other tools build on.</p> <p>But those are future posts.</p> <p>This one is about the release that turned a developer tool into a distributable product: two commands, zero shell scripts, and a presence on the Claude Marketplace.</p> <p>The Integration Release</p> <p><code>v0.1.0</code> shipped features. <code>v0.2.0</code> shipped archaeology.</p> <p><code>v0.3.0</code> shipped discipline. <code>v0.6.0</code> shipped the front door.</p> <p>The most important code in this release is the code you never have to copy.</p> <p>This post was drafted using <code>/ctx-blog-changelog</code> with access to the full git history between v0.3.0 and v0.6.0, release notes, and the plugin conversion PR. The meta continues.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/","level":1,"title":"Code Is Cheap. Judgment Is Not.","text":"","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#why-ai-replaces-effort-not-expertise","level":2,"title":"Why AI Replaces Effort, Not Expertise","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>Are You Worried about AI Taking Your Job?</p> <p>You might be confusing the thing that's cheap with the thing that's valuable.</p> <p>I keep seeing the same conversation:  Engineers, designers, writers: all asking the same question with the same dread:</p> <p>\"What happens when AI can do what I do?\"</p> <p>The question is wrong:</p> <ul> <li>AI does not replace workers;</li> <li>AI replaces unstructured effort.</li> </ul> <p>The distinction matters, and everything I have learned building <code>ctx</code> reinforces it.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-three-confusions","level":2,"title":"The Three Confusions","text":"<p>People who feel doomed by AI usually confuse three things:</p> People confuse... With... Effort Value Typing Thinking Production Judgment <ul> <li>Effort is time spent.</li> <li>Value is the outcome that time produces.</li> </ul> <p>They are not the same; they never were. </p> <p>AI just makes the gap impossible to ignore.</p> <p>Typing is mechanical: Thinking is directional. </p> <p>An AI can type faster than any human. Yet, it cannot decide what to type without someone framing the problem, sequencing the work, and evaluating the result.</p> <p>Production is making artifacts. Judgment is knowing:</p> <ul> <li>which artifacts to make, </li> <li>in what order, </li> <li>to what standard, </li> <li>and when to stop.</li> </ul> <p>AI floods the system with production capacity; it does not flood the system with judgment.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#code-is-nothing","level":2,"title":"Code Is Nothing","text":"<p>This sounds provocative until you internalize it:</p> <p>Code is cheap. Artifacts are cheap.</p> <p>An AI can generate a thousand lines of working code in literal *minutes**:</p> <p>It can scaffold a project, write tests, build a CI pipeline, draft documentation. The raw production of software artifacts is no longer the bottleneck.</p> <p>So, what is not cheap?</p> <ul> <li>Taste: knowing what belongs and what does not</li> <li>Framing: turning a vague goal into a concrete problem</li> <li>Sequencing: deciding what to build first and why</li> <li>Fanning out: breaking work into parallel streams that   converge</li> <li>Acceptance criteria: defining what \"done\" looks like before   starting</li> <li>Judgment: the thousand small decisions that separate code   that works from code that lasts</li> </ul> <p>These are the skills that direct production: Hhuman skills.</p> <p>Not because AI is incapable of learning them, but because they require something AI does not have: </p> <p>temporal accountability for generated outcomes.</p> <p>That is, you cannot keep AI accountable for the <code>$#!%</code> it generated three months ago. A human, on the other hand, will always be accountable.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-evidence-from-building-ctx","level":2,"title":"The Evidence from Building <code>ctx</code>","text":"<p>I did not arrive at this conclusion theoretically. </p> <p>I arrived at it by building a tool with an AI agent for three weeks  and watching exactly where a human touch mattered.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#yolo-mode-proved-production-is-cheap","level":3,"title":"YOLO Mode Proved Production Is Cheap","text":"<p>In Building <code>ctx</code> Using <code>ctx</code>, I documented the YOLO phase: auto-accept everything, let the AI ship features at full speed. It produced 14 commands in a week. Impressive output.</p> <p>The code worked. The architecture drifted. Magic strings accumulated. Conventions diverged. The AI was producing at a pace no human could match, and every artifact it produced was a small bet that nobody was evaluating.</p> <p>Production without judgment is not velocity. It is debt accumulation at breakneck speed.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-31-ratio-proved-judgment-has-a-cadence","level":3,"title":"The 3:1 Ratio Proved Judgment Has a Cadence","text":"<p>In The 3:1 Ratio, the <code>git</code> history told the story:</p> <p>Three sessions of forward momentum followed by one session of deliberate consolidation. The consolidation session is where the human applies judgment: reviewing what the AI built, catching drift, realigning conventions.</p> <p>The AI does the refactoring. The human decides what to refactor and when to stop. </p> <p>Without the human, the AI will refactor forever, improving things that do not matter and missing things that do.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-attention-budget-proved-framing-is-scarce","level":3,"title":"The Attention Budget Proved Framing Is Scarce","text":"<p>In The Attention Budget, I explained why more context makes AI worse, not better. Every token competes for attention: Dump everything in and the AI sees nothing clearly.</p> <p>This is a framing problem: The human's job is to decide what the AI should focus on: what to include, what to exclude, what to emphasize. </p> <p><code>ctx agent --budget 4000</code> is not just a CLI flag: It is a forcing function for human judgment about relevance.</p> <p>The AI processes. The human curates.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#skills-design-proved-taste-is-load-bearing","level":3,"title":"Skills Design Proved Taste Is Load-Bearing","text":"<p>The skill trilogy (You Can't Import Expertise, The Anatomy of a Skill That Works) showed that the difference between a useful skill and a useless one is not craftsmanship: </p> <p>It is taste.</p> <p>A well-crafted skill with the wrong focus is worse than no skill at all: It consumes the attention budget with generic advice while the project-specific problems go unchecked. </p> <p>The E/A/R framework (Expert, Activation, Redundant) is a judgment too:. The AI cannot apply it to itself. The human evaluates what the AI already knows, what it needs to be told, and what is noise.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#automation-discipline-proved-restraint-is-a-skill","level":3,"title":"Automation Discipline Proved Restraint Is a Skill","text":"<p>In Not Everything Is a Skill, the lesson was that the urge to automate is not the need to automate. A useful prompt does not automatically deserve to become a slash command.</p> <p>The human applies judgment about frequency, stability, and attention cost.</p> <p>The AI can build the skill. Only the human can decide whether it should exist.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#defense-in-depth-proved-boundaries-require-judgment","level":3,"title":"Defense in Depth Proved Boundaries Require Judgment","text":"<p>In Defense in Depth, the entire security model for unattended AI agents came down to: Markdown is not a security boundary. Telling an AI \"don't do bad things\" is production (of instructions). Setting up an unprivileged user in a network-isolated container is judgment (about risk).</p> <p>The AI follows instructions. The human decides which instructions are enforceable and which are \"wishful thinking\".</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#parallel-agents-proved-scale-amplifies-the-gap","level":3,"title":"Parallel Agents Proved Scale Amplifies the Gap","text":"<p>In Parallel Agents and Merge Debt, the lesson was that multiplying agents multiplies output. But it also multiplies the need for judgment:</p> <p>Five agents running in parallel produce five sessions of drift in one clock  hour. The human who can frame tasks cleanly, define narrow acceptance  criteria, and evaluate results quickly becomes the limiting factor.</p> <p>More agents do not reduce the need for judgment. They increase it.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-two-reactions","level":2,"title":"The Two Reactions","text":"<p>When AI floods the system with cheap output, two things happen:</p> <p>Those who only produce: panic. If your value proposition is \"I write code,\" and an AI writes code faster, cheaper, and at higher volume, then the math is unfavorable. Not because AI took your job, but because your job was never the code. It was the judgment around the code, and you were not exercising it.</p> <p>Those who direct: accelerate. If your value proposition is \"I know what to build, in what order, to what standard,\" then AI is the best thing that ever happened to you: Production is no longer the bottleneck: Your ability to frame, sequence, evaluate, and course-correct is now the limiting factor on throughput.</p> <p>The gap between these two is not talent: It is the awareness of where the value lives.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#what-this-means-in-practice","level":2,"title":"What This Means in Practice","text":"<p>If you are an engineer reading this, the actionable insight is not \"learn prompt engineering\" or \"master AI tools.\" It is:</p> <p>Get better at the things AI cannot do.</p> AI does this well You need to do this Generate code Frame the problem Write tests Define acceptance criteria Scaffold projects Sequence the work Fix bugs from stack traces Evaluate tradeoffs Produce volume Exercise restraint Follow instructions Decide which instructions matter <p>The skills on the right column are not new. They are the same skills that have always separated senior engineers from junior ones. </p> <p>AI did not create the distinction; it just made it load-bearing.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#if-anything-i-feel-empowered","level":2,"title":"If Anything, I Feel Empowered","text":"<p>I will end with something personal.</p> <p>I am not worried: I am empowered.</p> <p>Before <code>ctx</code>, I could think faster than I could produce: </p> <ul> <li>Ideas sat in a queue. </li> <li>The bottleneck was always \"I know what to build,   but building it takes too long.\"</li> </ul> <p>Now the bottleneck is gone. Poof!</p> <ul> <li>Production is cheap. </li> <li>The queue is clearing. </li> <li>The limiting factor is how fast I can think,    not how fast I can type.</li> </ul> <p>That is not a threat: That is the best force multiplier I've ever had.</p> <p>The people who feel threatened are confusing the accelerator for the replacement:</p> <p>*AI does not replace the conductor; it gives them  a bigger orchestra.</p> <p>If You Remember One Thing from This Post...</p> <p>Code is cheap. Judgment is not.</p> <p>AI replaces unstructured effort, not directed expertise. The skills that matter now are the same skills that have always mattered: taste, framing, sequencing, and the discipline to stop.</p> <p>The difference is that now, for the first time, those skills are the only bottleneck left.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-arc","level":2,"title":"The Arc","text":"<p>This post is a retrospective. It synthesizes the thread running through every previous entry in this blog:</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code> showed that production   without direction creates debt</li> <li>Refactoring with Intent   showed that slowing down is not the opposite of progress</li> <li>The Attention Budget showed that curation   outweighs volume</li> <li>The skill trilogy showed that taste determines   whether a tool helps or hinders</li> <li>Not Everything Is a Skill showed that   restraint is a skill in itself</li> <li>Defense in Depth showed that instructions are   not boundaries</li> <li>The 3:1 Ratio showed that judgment has a schedule</li> <li>Parallel Agents showed that scale amplifies   the gap between production and judgment</li> <li>Context as Infrastructure showed that the system   you build for context is infrastructure, not conversation</li> </ul> <p>From YOLO mode to defense in depth, the pattern is the same:</p> <ul> <li>Production is the easy part;</li> <li>Judgment is the hard part;</li> <li>AI changed the ratio, not the rule.</li> </ul> <p>This post synthesizes the thread running through every previous entry in this blog. The evidence is drawn from three weeks of building <code>ctx</code> with AI assistance, the decisions recorded in <code>DECISIONS.md</code>, the learnings captured in <code>LEARNINGS.md</code>, and the git history that tracks where the human mattered and where the AI ran unsupervised.</p> <p>See also: When a System Starts Explaining Itself -- what happens after the arc: the first field notes from the moment the system starts compounding in someone else's hands.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/","level":1,"title":"Context as Infrastructure","text":"","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#why-your-ai-needs-a-filesystem-not-a-prompt","level":2,"title":"Why Your AI Needs a Filesystem, Not a Prompt","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>Where Does Your AI's Knowledge Live between Sessions?</p> <p>If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. Something assembled, used, and discarded.</p> <p>What if you treated it as infrastructure instead?</p> <p>This post synthesizes a thread that has been running through every <code>ctx</code> blog post; from the origin story to the attention budget to the discipline release. The thread is this: context is not a prompt problem. It is an infrastructure problem. And the tools we build for it should look more like filesystems than clipboard managers.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-prompt-paradigm","level":2,"title":"The Prompt Paradigm","text":"<p>Most AI-assisted development treats context as ephemeral:</p> <ol> <li>Start a session.</li> <li>Paste your system prompt, your conventions, your current task.</li> <li>Work.</li> <li>Session ends. Everything evaporates.</li> <li>Next session: paste again.</li> </ol> <p>This works for short interactions. For sustained development (where decisions compound over days and weeks) it fails in three ways:</p> <p>It does not persist: A decision made on Tuesday must be re-explained on Wednesday. A learning captured in one session is invisible to the next.</p> <p>It does not scale: As the project grows, the \"paste everything\" approach hits the context window ceiling. You start triaging what to include, often cutting exactly the context that would have prevented the next mistake.</p> <p>It does not compose: A system prompt is a monolith. You cannot load part of it, update one section, or share a subset with a different workflow. It is all or nothing.</p> <p>The Copy-Paste Tax</p> <p>Every session that starts with pasting a prompt is paying a tax:</p> <p>The human time to assemble the context, the risk of forgetting something, and the silent assumption that yesterday's prompt is still accurate today.</p> <p>Over 70+ sessions, that tax compounds into a significant maintenance burden: One that most developers absorb without questioning it.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-infrastructure-paradigm","level":2,"title":"The Infrastructure Paradigm","text":"<p><code>ctx</code> takes a different approach:</p> <p>Context is not assembled per-session; it is maintained as persistent files in a <code>.context/</code> directory:</p> <pre><code>.context/\n  CONSTITUTION.md     # Inviolable rules\n  TASKS.md            # Current work items\n  CONVENTIONS.md      # Code patterns and standards\n  DECISIONS.md        # Architectural choices with rationale\n  LEARNINGS.md        # Gotchas and lessons learned\n  ARCHITECTURE.md     # System structure\n  GLOSSARY.md         # Domain terminology\n  AGENT_PLAYBOOK.md   # Operating manual for agents\n  journal/            # Enriched session summaries\n  archive/            # Completed work, cold storage\n</code></pre> <ul> <li>Each file has a single purpose;</li> <li>Each can be loaded independently;</li> <li>Each persists across sessions, tools, and team members.</li> </ul> <p>This is not a novel idea. It is the same idea behind every piece of infrastructure software engineers already use:</p> Traditional Infrastructure <code>ctx</code> Equivalent Database <code>.context/*.md</code> files Configuration files <code>CONSTITUTION.md</code> Environment variables <code>.contextrc</code> Log files <code>journal/</code> Schema migrations Decision records Deployment manifests <code>AGENT_PLAYBOOK.md</code> <p>The parallel is not metaphorical. Context files are infrastructure:</p> <ul> <li>They are versioned (<code>git</code> tracks them); </li> <li>They are structured (Markdown with conventions); </li> <li>They have schemas (required fields for decisions and learnings); </li> <li>And they have lifecycle management (archiving, compaction, indexing).</li> </ul>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#separation-of-concerns","level":2,"title":"Separation of Concerns","text":"<p>The most important design decision in <code>ctx</code> is not any individual feature. It is the separation of context into distinct files with distinct purposes.</p> <p>A single <code>CONTEXT.md</code> file would be simpler to implement. It would also be impossible to maintain.</p> <p>Why? Because different types of context have different lifecycles:</p> Context Type Changes Read By Load When Constitution Rarely Every session Always Tasks Every session Session start Always Conventions Weekly Before coding When writing code Decisions When decided When questioning When revisiting Learnings When learned When stuck When debugging Journal Every session Rarely When investigating <p>Loading everything into every session wastes the attention budget on context that is irrelevant to the current task. Loading nothing forces the AI to operate blind.</p> <p>Separation of concerns allows progressive disclosure: </p> <p>Load the minimum that matters for this moment, with the  option to load more when needed.</p> <pre><code># Session start: load the essentials\nctx agent --budget 4000\n\n# Deep investigation: load everything\ncat .context/DECISIONS.md\ncat .context/journal/2026-02-05-*.md\n</code></pre> <p>The filesystem is the index. File names, directory structure, and timestamps encode relevance. The AI does not need to read every file; it needs to know where to look.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-two-tier-persistence-model","level":2,"title":"The Two-Tier Persistence Model","text":"<p><code>ctx</code> uses two tiers of persistence, and the distinction is architectural:</p> Tier Purpose Location Token Cost Curated Quick context reload <code>.context/*.md</code> Low (budgeted) Full dump Safety net, archaeology <code>.context/journal/*.md</code> Zero (not auto-loaded) <p>The curated tier is what the AI sees at session start. It is optimized for signal density: </p> <ul> <li>Structured entries, </li> <li>Indexed tables,</li> <li>Reverse-chronological order (newest first, so the most relevant   content survives truncation).</li> </ul> <p>The full dump tier is for humans and for deep investigation. It contains everything: Enriched journals, archived tasks... </p> <p>It is never autoloaded because its volume would destroy attention density.</p> <p>This two-tier model is analogous to how traditional systems separate hot and cold storage: </p> <ul> <li>The hot path (curated context) is optimized for read performance   (measured not in milliseconds, but in     tokens consumed per unit of useful information). </li> <li>The cold path (journal) is optimized for completeness.</li> </ul> <p>Nothing Is Ever Truly Lost</p> <p>The full dump tier means that context does not need to be perfect: It just needs to be findable.</p> <p>A decision that was not captured in <code>DECISIONS.md</code> can be recovered from the session transcript where it was discussed. </p> <p>A learning that was not formalized can be found in the journal entry  from that day.</p> <p>The curated tier is the fast path: The full dump tier is the safety net.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#decision-records-as-first-class-citizens","level":2,"title":"Decision Records as First-Class Citizens","text":"<p>One of the patterns that emerged from <code>ctx</code>'s own development is the power of structured decision records.</p> <p><code>v0.1.0</code> allowed adding decisions as one-liners:</p> <pre><code>ctx add decision \"Use PostgreSQL\"\n</code></pre> <p><code>v0.2.0</code> enforced structure:</p> <pre><code>ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity\" \\\n  --consequence \"Need connection pooling, team training\"\n</code></pre> <p>The difference is not cosmetic:</p> <ul> <li>A one-liner decision teaches the AI what was decided. </li> <li>A structured decision teaches it why; and   why is what prevents the AI from unknowingly reversing the decision   in a future session.</li> </ul> <p>This is infrastructure thinking: </p> <p>Decisions are not notes. They are records with required fields, just like  database rows have schemas.</p> <p>The enforcement exists because incomplete records are worse than no records: They create false confidence that the context is captured when it is not.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-ide-is-the-interface-decision","level":2,"title":"The \"IDE Is the Interface\" Decision","text":"<p>Early in <code>ctx</code>'s development, there was a temptation to build a custom UI: a web dashboard for browsing sessions, editing context, viewing analytics.</p> <p>The decision was no. The IDE is the interface.</p> <pre><code># This is the ctx \"UI\":\ncode .context/\n</code></pre> <p>This decision was not about minimalism for its own sake. It was about recognizing that <code>.context/</code> files are just files; and files have a mature, well-understood infrastructure:</p> <ul> <li>Version control: <code>git diff .context/DECISIONS.md</code> shows exactly   what changed and when.</li> <li>Search: Your IDE's full-text search works across all context files.</li> <li>Editing: Markdown in any editor, with preview, spell check,   and syntax highlighting.</li> <li>Collaboration: Pull requests on context files work the same as   pull requests on code.</li> </ul> <p>Building a custom UI would have meant maintaining a parallel infrastructure that duplicates what every IDE already provides:</p> <p>It would have introduced its own bugs, its own update cycle, and its own learning curve.</p> <p>The filesystem is not a limitation: It is the most mature, most composable, most portable infrastructure available.</p> <p>Context Files in Git</p> <p>Because <code>.context/</code> lives in the repository, context changes are part of the commit history. </p> <p>A decision made in commit <code>abc123</code> is as traceable as a code change in  the same commit.</p> <p>This is not possible with prompt-based context, which exists outside version control entirely.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#progressive-disclosure-for-ai","level":2,"title":"Progressive Disclosure for AI","text":"<p>The concept of progressive disclosure comes from human interface design: show the user the minimum needed to make progress, with the option to drill deeper.</p> <p><code>ctx</code> applies the same principle to AI context:</p> Level What the AI Sees Token Cost When Level 0 <code>ctx status</code> (one-line summary) ~100 Quick check Level 1 <code>ctx agent --budget 4000</code> ~4,000 Normal work Level 2 <code>ctx agent --budget 8000</code> ~8,000 Complex tasks Level 3 Direct file reads 10,000+ Deep investigation <p>Each level trades tokens for depth. Level 1 is sufficient for most work: the AI knows the active tasks, the key conventions, and the recent decisions. Level 3 is for archaeology: understanding why a decision was made three weeks ago, or finding a pattern in the session history.</p> <p>The explicit <code>--budget</code> flag is the mechanism that makes this work:</p> <p>Without it, the default behavior would be to load everything (because more context feels safer), which destroys the attention density that makes the loaded context useful.</p> <p>The constraint is the feature: A budget of 4,000 tokens forces <code>ctx</code> to prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings scored by recency and relevance to active tasks. Entries that don't fit get title-only summaries rather than being silently dropped.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-philosophical-shift","level":2,"title":"The Philosophical Shift","text":"<p>The shift from \"context as prompt\" to \"context as infrastructure\" changes how you think about AI-assisted development:</p> Prompt Thinking Infrastructure Thinking \"What do I paste today?\" \"What has changed since yesterday?\" \"How do I fit everything in?\" \"What's the minimum that matters?\" \"The AI forgot my conventions\" \"The conventions are in a file\" \"I need to re-explain\" \"I need to update the record\" \"This session is getting slow\" \"Time to compact and archive\" <p>The first column treats AI interaction as a conversation. The second treats it as a system: One that can be maintained, optimized, and debugged.</p> <p>Context is not something you give the AI. It is something you maintain: Like a database, like a config file, like any other piece of infrastructure that a running system depends on.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#beyond-ctx-the-principles","level":2,"title":"Beyond <code>ctx</code>: The Principles","text":"<p>The patterns that <code>ctx</code> implements are not specific to <code>ctx</code>. They are applicable to any project that uses AI-assisted development:</p> <ol> <li>Separate context by purpose: Do not put everything in one file.    Different types of information have different lifecycles and    different relevance windows.</li> <li>Make context persistent: If a decision matters, write it down    in a file that survives the session. If a learning matters, capture    it with structure.</li> <li>Budget explicitly: Know how much context you are loading and    whether it is worth the attention cost.</li> <li>Use the filesystem: File names, directory structure, and    timestamps are metadata that the AI can navigate. A well-organized    directory is an index that costs zero tokens to maintain.</li> <li>Version your context: Put context files in <code>git</code>. Changes to    decisions are as important as changes to code.</li> <li>Design for degradation: Sessions will get long. Attention will    dilute. Build mechanisms (compaction, archiving, cooldowns) that    make degradation visible and manageable.</li> </ol> <p>These are not <code>ctx</code> features. They are infrastructure principles that happen to be implemented as a CLI tool. Any team could implement them with nothing more than a directory convention and a few shell scripts.</p> <p>The tool is a convenience: The principles are what matter.</p> <p>If You Remember One Thing from This Post...</p> <p>Prompts are conversations. Infrastructure persists.</p> <p>Your AI does not need a better prompt. It needs a filesystem:</p> <p>versioned, structured, budgeted, and maintained.</p> <p>The best context is the context that was there before you started the session.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-arc","level":2,"title":"The Arc","text":"<p>This post is the architectural companion to the Attention Budget. That post explained why context must be curated (token economics). This one explains how to structure it (filesystem, separation of concerns, persistence tiers).</p> <p>Together with Code Is Cheap, Judgment Is Not, they form a trilogy about what matters in AI-assisted development:</p> <ul> <li>Attention Budget: the resource you're managing</li> <li>Context as Infrastructure: the system you build to manage it</li> <li>Code Is Cheap: the human skill that no system replaces</li> </ul> <p>And the practices that keep it all honest:</p> <ul> <li>The 3:1 Ratio: the cadence for maintaining both   code and context</li> <li>IRC as Context: the historical precedent: stateless   protocols have always needed stateful wrappers</li> </ul> <p>This post synthesizes ideas from across the <code>ctx</code> blog series: the attention budget primitive, the two-tier persistence model, the IDE decision, and the progressive disclosure pattern. The principles are drawn from three weeks of building <code>ctx</code> and 70+ sessions of treating context as infrastructure rather than conversation.</p> <p>See also: When a System Starts Explaining Itself: what happens when this infrastructure starts compounding in someone else's environment.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/","level":1,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-the-screen-looks-like-progress","level":2,"title":"When the Screen Looks like Progress","text":"<p>Volkan Özçelik / 2026-02-17</p> <p>How Many Terminals Are Too Many?</p> <p>You discover agents can run in parallel.</p> <p>So you open ten... </p> <p>...Then twenty.</p> <p>The fans spin. Tokens burn. The screen looks like progress.</p> <p>It is NOT progress.</p> <p>There is a phase every builder goes through:</p> <ul> <li>The tooling gets fast enough. </li> <li>The model gets good enough. </li> <li>The temptation becomes irresistible: <ul> <li>more agents, more output, faster delivery.</li> </ul> </li> </ul> <p>So you open terminals. You spawn agents. You watch tokens stream across multiple windows simultaneously, and it feels like multiplication.</p> <p>It is not multiplication.</p> <p>It is merge debt being manufactured in real time.</p> <p>The <code>ctx</code> Manifesto says it plainly:</p> <p>Activity is not impact. Code is not progress.</p> <p>This post is about what happens when you take that seriously in the context of parallel agent workflows.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-unit-of-scale-is-not-the-agent","level":2,"title":"The Unit of Scale Is Not the Agent","text":"<p>The naive model says:</p> <p>More agents -> more output -> faster delivery</p> <p>The production model says:</p> <p>Clean context boundaries -> less interference -> higher throughput</p> <p>Parallelism only works when the cognitive surfaces do not overlap.</p> <p>If two agents touch the same files, you did not create parallelism: You created a conflict generator.</p> <p>They will:</p> <ul> <li>Revert each other's changes;</li> <li>Relint each other's formatting;</li> <li>Refactor the same function in different directions.</li> </ul> <p>You watch with 🍿. Nothing ships.</p> <p>This is the same insight from the worktrees post: partition by blast radius, not by priority. </p> <p>Two tasks that touch the same files belong in the same track, no matter how  important the other one is. The constraint is file overlap. </p> <p>Everything else is scheduling.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-five-agent-rule","level":2,"title":"The \"Five Agent\" Rule","text":"<p>In practice there is a ceiling.</p> <p>Around five or six concurrent agents:</p> <ul> <li>Token burn becomes noticeable;</li> <li>Supervision cost rises;</li> <li>Coordination noise increases;</li> <li>Returns flatten.</li> </ul> <p>This is not a model limitation:  This is a human merge bandwidth limitation.</p> <p>You are the bottleneck, not the silicon.</p> <p>The attention budget applies to you too: </p> <p>Every additional agent is another stream of output you need to comprehend, verify, and integrate. Your attention density drops the same way the model's does when you overload its context window.</p> <p>Five agents producing verified, mergeable change beats twenty agents producing merge conflicts you spend a day untangling.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#role-separation-beats-file-locking","level":2,"title":"Role Separation Beats File Locking","text":"<p>Real parallelism comes from task topology, not from tooling.</p> <p>Good:</p> Agent Role Touches 1 Documentation <code>docs/</code>, <code>hack/</code> 2 Security scan Read-only audit 3 Implementation <code>internal/cli/</code> 4 Enhancement requests Read-only, files issues <p>Bad:</p> <ul> <li>Four agents editing the same implementation surface</li> </ul> <p>Context Is the Boundary</p> <ul> <li>The goal is not to keep agents busy. </li> <li>The goal is to keep contexts isolated.</li> </ul> <p>This is what the codebase audit got right: </p> <ul> <li>Eight agents, all read-only, each analyzing a different dimension. </li> <li>Zero file overlap.</li> <li>Zero merge conflicts. </li> <li>Eight reports that composed cleanly because no agent interfered with another.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-terminals-stop-scaling","level":2,"title":"When Terminals Stop Scaling","text":"<p>There is a moment when more windows stop helping.</p> <p>That is the signal. Not to add orchestration. But to introduce:</p> <pre><code>git worktree\n</code></pre> <p>Because now you are no longer parallelizing execution; you are parallelizing state.</p> <p>State Scales, Windows Don't</p> <ul> <li>State isolation is the real scaling. </li> <li>Window multiplication is theater.</li> </ul> <p>The worktrees post covers the mechanics: </p> <ul> <li>Sibling directories;</li> <li>Branch naming; </li> <li>The inevitable <code>TASKS.md</code> conflicts; </li> <li>The 3-4 worktree ceiling. </li> </ul> <p>The principle underneath is older than <code>git</code>:</p> <p>Shared mutable state is the enemy of parallelism. </p> <p>Always has been.</p> <p>Always will be.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-overnight-loop-illusion","level":2,"title":"The Overnight Loop Illusion","text":"<p>Autonomous night runs are impressive.</p> <p>You sleep. The machine produces thousands of lines.</p> <p>In the morning:</p> <ul> <li>You read;</li> <li>You untangle;</li> <li>You reconstruct intent;</li> <li>You spend a day making it shippable.</li> </ul> <p>In retrospect, nothing was accelerated. </p> <p>The bottleneck moved from typing to comprehension.</p> <p>The Comprehension Tax</p> <p>If understanding the output costs more than producing it, the loop is a net loss.</p> <p>Progress is not measured in generated code.</p> <p>Progress is measured in verified, mergeable change.</p> <p>The <code>ctx</code> Manifesto calls this out directly:</p> <p>The Scoreboard</p> <p>Verified reality is the scoreboard.</p> <p>The only truth that compounds is verified change in the real world.</p> <p>An overnight run that produces 3,000 lines nobody reviewed is not 3,000 lines of progress: It is 3,000 lines of liability until someone verifies every one of them. </p> <p>And that someone is (insert drumroll here) you: </p> <p>The same bottleneck that was supposedly being bypassed.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#skills-that-fight-the-platform","level":2,"title":"Skills That Fight the Platform","text":"<p>Most marketplace skills are prompt decorations:</p> <ul> <li>They rephrase what the base model already knows;</li> <li>They increase token usage; </li> <li>They reduce clarity:</li> <li>They introduce behavioral drift.</li> </ul> <p>We covered this in depth in Skills That Fight the Platform: judgment suppression, redundant guidance, guilt-tripping, phantom dependencies, universal triggers: Five patterns that make agents worse, not better.</p> <p>A real skill does one of these:</p> <ul> <li>Encodes workflow state;</li> <li>Enforces invariants;</li> <li>Reduces decision branching.</li> </ul> <p>Everything else is packaging.</p> <p>The anatomy post established the criteria: quality gates, negative triggers, examples over rules, skills as contracts. </p> <p>If a skill doesn't meet those criteria... </p> <ul> <li>It is either a recipe (document it in <code>hack/</code>); </li> <li>Or noise (delete it);</li> <li>There is no third option.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#hooks-are-context-that-execute","level":2,"title":"Hooks Are Context That Execute","text":"<p>The most valuable skills are not prompts:</p> <p>They are constraints embedded in the toolchain.</p> <p>For example: The agent cannot push.</p> <p><code>git push</code> becomes:</p> <p>Stop. A human reviews first.</p> <p>A commit without verification becomes:</p> <p>Did you run tests? Did you run linters? What exactly are you shipping?</p> <p>This is not safety theater; this is intent preservation.</p> <p>The  thing the <code>ctx</code> Manifesto calls \"encoding intent into the environment.\"</p> <p>The Eight Ways a Hook Can Talk cataloged the full spectrum: from silent enrichment to hard blocks. </p> <p>The key insight was that hooks are not just safety rails:  They are context that survives execution.</p> <p>They are the difference between an agent that remembers the rules  and one that enforces them.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#complexity-is-a-tax","level":2,"title":"Complexity Is a Tax","text":"<p>Every extra layer adds cognitive weight:</p> <ul> <li>Orchestration frameworks;</li> <li>Meta agents;</li> <li>Autonomous planning systems...</li> </ul> <p>If a single terminal works, stay there.</p> <p>If five isolated agents work, stop there.</p> <p>Add structure only when a real bottleneck appears. </p> <p>NOT when an influencer suggests one.</p> <p>This is the same lesson from Not Everything Is a Skill:</p> <p>The best automation decision is sometimes not to automate.</p> <p>A recipe in a Markdown file costs nothing until you use it. </p> <p>An orchestration framework costs attention on every run, whether it helps or not.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#literature-is-throughput","level":2,"title":"Literature Is Throughput","text":"<p>Clear writing is not aesthetic: It is compression.</p> <p>Better articulation means:</p> <ul> <li>Fewer tokens;</li> <li>Fewer misinterpretations;</li> <li>Faster convergence.</li> </ul> <p>The attention budget taught us that context is a finite resource with a quadratic cost. </p> <p>Language determines how fast you spend context. </p> <p>A well-written task description that takes 50 tokens outperforms a rambling one that takes 200: Not just because it is cheaper, but because it leaves more  headroom for the model to actually think.</p> <p>Literature Is NOT Overrated</p> <ul> <li>Attention is a finite budget. </li> <li>Language determines how fast you spend it.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-real-metric","level":2,"title":"The Real Metric","text":"<p>The real metric is not:</p> <ul> <li>Lines generated;</li> <li>Agents running;</li> <li>Tasks completed while you sleep.</li> </ul> <p>But:</p> <p>Time from idea to verified, mergeable, production change.</p> <p>Everything else is motion.</p> <p>The entire blog series has been circling this point: </p> <ul> <li>The attention budget was about spending tokens wisely. </li> <li>The skills trilogy was about not wasting them on prompt   decoration.</li> <li>The worktrees post was about multiplying throughput   without multiplying interference. </li> <li>The discipline release was about what a release looks   like when polish outweighs features: 3:1.</li> </ul> <p>Every post has arrived (and made me converge) at the same answer so far: </p> <p>The metric is a verified change, not generated output.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#ctx-was-never-about-spawning-more-minds","level":2,"title":"<code>ctx</code> Was Never about Spawning More Minds","text":"<p><code>ctx</code> is about:</p> <ul> <li>Isolating context;</li> <li>Preserving intent;</li> <li>Making progress composable.</li> </ul> <p>Parallel agents are powerful. But only when you respect the boundaries that make parallelism real.</p> <p>Otherwise, you are not scaling cognition; you are scaling interference.</p> <p>The <code>ctx</code> Manifesto's thesis holds:</p> <p>Without <code>ctx</code>, intelligence resets. With <code>ctx</code>, creation compounds.</p> <p>Compounding requires structure. </p> <p>Structure requires boundaries.</p> <p>Boundaries require the discipline to stop adding agents when five is enough.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#practical-summary","level":2,"title":"Practical Summary","text":"<p>A production workflow tends to converge to this:</p> Practice Why Stay in one terminal unless necessary Minimize coordination overhead Spawn a small number of agents with non-overlapping responsibilities Conflict avoidance > parallelism Isolate state with worktrees when surfaces grow State isolation is real scaling Encode verification into hooks Intent that survives execution Avoid marketplace prompt cargo cults Skills are contracts, not decorations Measure merge cost, not generation speed The metric is verified change <p>This is slower to watch. Faster to ship.</p> <p>If You Remember One Thing from This Post...</p> <p>Progress is not what the machine produces while you sleep.</p> <p>Progress is what survives contact with the main branch.</p> <p>See also: Code Is Cheap. Judgment Is Not.: the argument that production capacity was never the bottleneck, and why multiplying agents amplifies the need for human judgment rather than replacing it.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/","level":1,"title":"The 3:1 Ratio","text":"","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#scheduling-consolidation-in-ai-development","level":2,"title":"Scheduling Consolidation in AI Development","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>How Often Should You Stop Building and Start Cleaning?</p> <p>Every developer knows technical debt exists. Every developer postpones dealing with it.</p> <p>AI-assisted development makes the problem worse; not because the AI writes bad code, but because it writes code so fast that drift accumulates before you notice.</p> <p>In Refactoring with Intent, I mentioned a ratio that worked for me: 3:1. Three YOLO sessions create enough surface area to reveal patterns. The fourth session turns those patterns into structure.</p> <p>That was an observation. This post is the evidence.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-observation","level":2,"title":"The Observation","text":"<p>During the first two weeks of building <code>ctx</code>, I noticed a rhythm in my own productivity. Feature sessions felt great: new commands, new capabilities, visible progress...</p> <p>...but after three of them, things would start to feel sticky: variable names that almost made sense, files that had grown past their purpose, patterns that repeated without being formalized.</p> <p>The fourth session (when I stopped adding and started cleaning) was always the most painful to start and the most satisfying to finish.</p> <p>It was also the one that made the next three feature sessions faster.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-evidence-git-history","level":2,"title":"The Evidence: Git History","text":"<p>The <code>ctx</code> git history between January 20 and February 7 tells a clear story when you categorize commits:</p> Week Feature commits Consolidation commits Ratio Jan 20-26 18 5 3.6:1 Jan 27-Feb 1 14 6 2.3:1 Feb 1-7 15 35+ 0.4:1 <p>The first week was pure YOLO: Almost four feature commits for every consolidation commit. The codebase grew fast.</p> <p>The second week started to self-correct. The ratio dropped as refactoring sessions became necessary: Not scheduled, but forced by friction.</p> <p>The third week inverted entirely: v0.3.0 was almost entirely consolidation: the skill migration, the sweep, the documentation standardization. Thirty-five quality commits against fifteen features.</p> <p>The debt from weeks one and two was paid in week three.</p> <p>The Compounding Problem</p> <p>Consolidation debt compounds.</p> <p>Week one's drift doesn't just persist into week two: It accelerates, because new features are built on top of drifted patterns.</p> <p>By week three, the cost of consolidation was higher than it would have been if spread evenly.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-drift-actually-looks-like","level":2,"title":"What Drift Actually Looks Like","text":"<p>\"Drift\" sounds abstract. Here is what it looked like concretely in the <code>ctx</code> codebase after three weeks of feature-heavy development:</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#predicate-naming","level":3,"title":"Predicate Naming","text":"<p>Convention says boolean functions should be named <code>HasX</code>, <code>IsX</code>, <code>CanX</code>. After three feature sprints:</p> <pre><code>// What accumulated:\nfunc CheckIfEnabled() bool  // should be Enabled\nfunc ValidateFormat() bool  // should be ValidFormat\nfunc TestConnection() bool  // should be Connects\nfunc VerifyExists() bool    // should be Exists or HasFile\nfunc EnsureReady() bool     // should be Ready\n</code></pre> <p>Five violations. Not bugs, but friction that compounds every time someone (human or AI) reads the code and has to infer the naming convention from inconsistent examples.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#magic-strings","level":3,"title":"Magic Strings","text":"<pre><code>// Week 1: acceptable prototype\nif entry.Type == \"task\" {\n    filename = \"TASKS.md\"\n}\n\n// Week 3: same pattern in 7+ files\n// Now it's a maintenance liability\n</code></pre> <p>When the same literal appears in seven files, changing it means finding all seven. Missing one means a silent runtime bug. Constants exist to prevent exactly this. But during feature velocity, nobody stops to extract them.</p> <p>Refactoring with Intent documented the constants consolidation that cleaned this up. The 3:1 ratio is the practice that prevents it from accumulating again.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#hardcoded-permissions","level":3,"title":"Hardcoded Permissions","text":"<pre><code>os.WriteFile(path, data, 0644) // 80+ instances\nos.MkdirAll(path, 0755)        // scattered across packages\n</code></pre> <p>Eighty-plus instances of hardcoded file permissions. Not wrong, but if I ever need to change the default (and I did, for hook scripts that need execute permissions), it means a codebase-wide search.</p> <p>Drift Is Not Bugs</p> <p>None of these are bugs. The code works. Tests pass.</p> <p>But drift creates false confidence: the codebase looks consistent until you try to change something and discover that five different conventions exist for the same concept.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#why-you-cannot-consolidate-on-day-one","level":2,"title":"Why You Cannot Consolidate on Day One","text":"<p>The temptation is to front-load quality: write all the conventions, enforce all the checks, prevent all the drift before it happens.</p> <p>This fails for two reasons.</p> <p>First, you do not know what will drift: Predicate naming violations only become a convention check after you notice three different naming patterns competing. Magic strings only become a consolidation target after you change a literal and discover it exists in seven places.</p> <p>The conventions emerge from the work; they cannot precede it.</p> <p>This is what You Can't Import Expertise meant in practice: the consolidation checks grow from the project's own drift history. You cannot write them on day one because you do not yet know what will drift.</p> <p>Second, premature consolidation slows discovery: During the prototyping phase, the goal is to explore the design space. Enforcing strict conventions on code that might be deleted tomorrow is waste.</p> <p>YOLO mode has its place: The problem is not YOLO itself, but YOLO without a scheduled cleanup.</p> <p>The Consolidation Paradox</p> <p>You need a drift history to know what to consolidate.</p> <p>You need consolidation to prevent drift from compounding.</p> <p>The 3:1 ratio resolves this paradox:</p> <p>Let drift accumulate for three sessions (enough to see patterns), then consolidate in the fourth (before the patterns become entrenched*).</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-consolidation-skill","level":2,"title":"The Consolidation Skill","text":"<p>The <code>ctx</code> project now has an <code>/audit</code> skill that encodes nine project-specific checks:</p> Check What It Catches Predicate naming Boolean functions not using Has/Is/Can Magic strings Repeated literals not in config constants File permissions Hardcoded 0644/0755 not using constants Godoc style Missing or non-standard documentation File length Files exceeding 400 lines Large functions Functions exceeding 80 lines Template drift Live skills diverging from templates Import organization Non-standard import grouping TODO/FIXME staleness Old markers that are no longer relevant <p>This is not a generic linter. These are project-specific conventions that emerged from <code>ctx</code>'s own development history. A generic code quality tool would catch some of them. Only a project-specific check catches all of them, because some of them (predicate naming, template drift) are conventions that exist nowhere except in this project's <code>CONVENTIONS.md</code>.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-decision-matrix","level":2,"title":"The Decision Matrix","text":"<p>Not all drift needs immediate consolidation. Here is the matrix I use:</p> Signal Action Same literal in 3+ files Extract to constant Same code block in 3+ places Extract to helper Naming convention violated 5+ times Fix and document rule File exceeds 400 lines Split by concern Convention exists but is regularly violated Strengthen enforcement Pattern exists only in one place Leave it alone Code works but is \"ugly\" Leave it alone <p>The last two rows matter: </p> <p>Consolidation is about reducing maintenance cost, not achieving aesthetic  perfection. Code that works and exists in one place does not benefit  from consolidation; it benefits from being left alone until it earns its refactoring.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#consolidation-as-context-hygiene","level":2,"title":"Consolidation as Context Hygiene","text":"<p>There is a parallel between code consolidation and context management that became clear during the <code>ctx</code> development:</p> Code Consolidation Context Hygiene Extract magic strings Archive completed tasks Standardize naming Keep DECISIONS.md current Remove dead code Compact old sessions Update stale comments Review LEARNINGS.md for staleness Check template drift Verify CONVENTIONS.md matches code <p><code>ctx compact</code> does for context what consolidation does for code: </p> <p>It moves completed work to cold storage, keeping the active context clean and focused. The attention budget applies to both the AI's context window and the developer's mental model of the codebase.</p> <p>When context files accumulate stale entries, the AI's attention is wasted on completed tasks and outdated conventions. When code accumulates drift, the developer's attention is wasted on inconsistencies that obscure the actual logic.</p> <p>Both are solved by the same discipline: periodic, scheduled cleanup.</p> <p>This is also why parallel agents make the problem harder, not easier. Three agents running simultaneously produce three sessions' worth of drift in one clock hour. The consolidation cadence needs to match the output rate, not the calendar.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-practice","level":2,"title":"The Practice","text":"<p>Here is how the 3:1 ratio works in practice for <code>ctx</code> development:</p> <p>Sessions 1-3: Feature work</p> <ul> <li>Add new capabilities;</li> <li>Write tests for new code;</li> <li>Do not stop for cleanup unless something is actively broken;</li> <li>Note drift as you see it (a comment, a task, a mental note).</li> </ul> <p>Session 4: Consolidation</p> <ul> <li>Run <code>/audit</code> to surface accumulated drift;</li> <li>Fix the highest-impact items first;</li> <li>Update CONVENTIONS.md if new patterns emerged;</li> <li>Archive completed tasks;</li> <li>Review LEARNINGS.md for anything that became a convention.</li> </ul> <p>The key insight is that session 4 is not optional. It is not \"if we have time\": It is scheduled with the same priority as feature work.</p> <p>The cost of skipping it is not visible immediately; it becomes visible three sessions later, when the next consolidation session takes twice as long because the drift compounded.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-the-ratio-is-not","level":2,"title":"What the Ratio Is Not","text":"<p>The 3:1 ratio is not a universal law. It is an empirical observation from one project with one developer working with AI assistance.</p> <p>Different projects will have different ratios:</p> <ul> <li>A mature codebase with strong conventions might sustain 5:1 or higher; </li> <li>A greenfield prototype might need 2:1; </li> <li>A team of multiple developers with different styles might need 1:1.</li> </ul> <p>The number is less important than the practice: consolidation is not a reaction to problems. It is a scheduled activity.</p> <p>If you wait for drift to cause pain before consolidating, you have already paid the compounding cost.</p> <p>If You Remember One Thing from This Post...</p> <p>Three sessions of building. One session of cleaning.</p> <p>Not because the code is dirty, but because drift compounds silently, and the only way to catch it is to look for it on a schedule.</p> <p>The ratio is the schedule.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-arc-so-far","level":2,"title":"The Arc so Far","text":"<p>This post sits at a crossroads in the <code>ctx</code> story. Looking back:</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code> documented the YOLO sprint   that created the initial codebase</li> <li>Refactoring with Intent introduced the 3:1 ratio   as an observation from the first cleanup</li> <li>The Attention Budget explained why drift   matters: every token of inconsistency consumes the same finite   resource as useful context</li> <li>You Can't Import Expertise showed that   consolidation checks must grow from the project, not a template</li> <li>The Discipline Release proved the ratio works at release   scale: 35 quality commits to 15 feature commits</li> </ul> <p>And looking forward: the same principle applies to context files, to documentation, and to the merge debt that parallel agents produce. Drift is drift, whether it lives in code, in <code>.context/</code>, or in the gap between what your docs say and what your code does.</p> <p>The ratio is the schedule is the discipline.</p> <p>This post was drafted from git log analysis of the <code>ctx</code> repository, mapping every commit from January 20 to February 7 into feature vs consolidation categories. The patterns described are drawn from the project's CONVENTIONS.md, LEARNINGS.md, and the <code>/audit</code> skill's check list.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/","level":1,"title":"When a System Starts Explaining Itself","text":"","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#field-notes-from-the-moment-a-private-workflow-becomes-portable","level":2,"title":"Field Notes from the Moment a Private Workflow Becomes Portable","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>How Do You Know Something Is Working?</p> <p>Not from metrics. Not from GitHub stars. Not from praise.</p> <p>You know, deep in your heart, that it works when people start describing it wrong.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-first-external-signals","level":2,"title":"The First External Signals","text":"<p>Every new substrate begins as a private advantage:</p> <ul> <li>It lives inside one mind,</li> <li>One repository,</li> <li>One set of habits.</li> </ul> <p>It is fast. It is not yet real.</p> <p>Reality begins when other people describe it in their own language:</p> <ul> <li>Not accurately;</li> <li>Not consistently;</li> <li>But involuntarily.</li> </ul> <p>The early reports arrived without coordination:</p> <p>Better Tasks</p> <p>\"I do not know how, but this creates better tasks than my AI plugin.\"</p> <p>I See Butterflies</p> <p>\"This is better than Adderall.\"</p> <p>Dear Manager...</p> <p>\"Promotion packet? Done. What is next?\"</p> <p>What Is It? Can I Eat It?</p> <p>\"Is this a skill?\" 🦋 </p> <p>Why the Cloak and Dagger?</p> <p>\"Why is this not in the marketplace?\"</p> <p>And then something more important happened:</p> <p>Someone else started making a video!</p> <p>That was the boundary.</p> <p><code>ctx</code> no longer required its creator to be present in order to exist.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#misclassification-is-a-sign-of-a-new-primitive","level":2,"title":"Misclassification Is a Sign of a New Primitive","text":"<p>When a tool is understood, it is categorized:</p> <ul> <li>Editor,</li> <li>Framework,</li> <li>Task manager,</li> <li>Plugin...</li> </ul> <p>When a substrate appears, it is misclassified:</p> <p>\"Is this a skill?\" 🦋</p> <p>The question is correct. The category is wrong.</p> <ul> <li>Skills live in people.</li> <li>Infrastructure lives in the environment.</li> </ul> <p><code>ctx</code> Is Not a Skill: It Is a Form of Relief</p> <p>What early adopters experience is not an ability.</p> <p>It is the removal of a cognitive constraint.</p> <p>This is the same distinction that emerged in the skills trilogy:</p> <ul> <li>A skill is a contract between a human and an agent.  </li> <li>Infrastructure is the ground both stand on.</li> </ul> <p>You do not use infrastructure.</p> <p>You habitualize it.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-pharmacological-metaphor","level":2,"title":"The Pharmacological Metaphor","text":"<p>\"Better than Adderall\" is not praise.</p> <p>It is a diagnostic:</p> <p>Executive function has been externalized.</p> <ul> <li>The system is not making the user work harder.  </li> <li>It is restoring continuity.</li> </ul> <p>From the primitive context of wetware:</p> <ul> <li>Continuity feels like focus</li> <li>Focus feels like discipline</li> </ul> <p>If it walks like a duck and quacks like a duck, it is a duck.</p> <p>Discipline is usually simulated.</p> <p>Infrastructure makes the simulation unnecessary.</p> <p>The attention budget explained why context degrades:</p> <ul> <li>Attention density drops as volume grows;</li> <li>The middle gets lost;</li> <li>Sessions end and everything evaporates.</li> </ul> <p>The pharmacological metaphor says the same thing from the user's lens:</p> <p>Save the Cheerleader, Save the World</p> <p>The symptom of lost context is lost focus.</p> <p>Restore the context. Restore the focus.</p> <p>IRC bouncers solved this for chat twenty years ago. <code>ctx</code> solves it for cognition.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#throughput-on-ambiguous-work","level":2,"title":"Throughput on Ambiguous Work","text":"<p>Finishing a promotion packet quickly is not a productivity story.</p> <p>It is the collapse of reconstruction cost.</p> <p>Most complex work is not execution. It is:</p> <ul> <li>Remembering why something mattered;</li> <li>Recovering prior decisions;</li> <li>Rebuilding mental state.</li> </ul> <p>Persistent context removes that tax.</p> <p>Velocity appears as a side effect.</p> <p>This Is the Two-Tier Model in Practice</p> <p>The two-tier persistence model</p> <ul> <li>Curated context for fast reload</li> <li>Full journal for archaeology</li> </ul> <p>is what makes this possible.</p> <ul> <li>The user does not notice the system.  </li> <li>They notice that the reconstruction cost disappeared.</li> </ul>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-moment-of-portability","level":2,"title":"The Moment of Portability","text":"<p>The system becomes real when two things happen:</p> <ol> <li>It can be installed as a versioned artifact.</li> <li>It survives contact with a hostile, real codebase.</li> </ol> <p>This is why the first integration into a living system matters more than any landing page.</p> <p>Demos prove possibility.</p> <p>Diffs prove reality.</p> <p>The <code>ctx</code> Manifesto calls this out directly:</p> <p>Verified reality is the scoreboard.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-split-voice","level":2,"title":"The Split Voice","text":"<p>A new substrate requires two channels.</p> <p>The embodied voice:</p> <p>Here is what changed in my actual work.</p> <p>The out of body voice:</p> <p>Here is what this means.</p> <p>One produces trust.</p> <p>The other produces understanding.</p> <p>Neither is sufficient alone.</p> <p>This entire blog has been the second voice.</p> <ul> <li>The origin story was the first.  </li> <li>The refactoring post was the first.  </li> <li>Every release note with concrete diffs was the first.</li> </ul> <p>This is the first second.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#systems-that-generate-explainers","level":2,"title":"Systems That Generate Explainers","text":"<p>Tools are used.</p> <p>Platforms are extended.</p> <p>Substrates are explained.</p> <p>The first unsolicited explainer is a brittle phase change.</p> <p>It means the idea has become portable between minds.</p> <p>That is the beginning of an ecosystem.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-absence-of-metrics","level":2,"title":"The Absence of Metrics","text":"<p>Metrics do not matter at this stage.</p> <p>Dashboards are noise.</p> <p>The whole premise of <code>ctx</code> is the ruthless elimination of noise.</p> <p>Numbers optimize funnels; substrates alter cognition.</p> <p>The only valid measurement is irreversible reality:</p> <ul> <li>A merged PR;</li> <li>A reproducible install;</li> <li>A decision that is never re-litigated.</li> </ul> <p>The merge debt post reached the same conclusion from  another direction:</p> <p>The metric is the verified change, not generated output.</p> <p>For adoption, the same rule applies:</p> <p>The metric is altered behavior, not download counts.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#what-is-actually-happening","level":2,"title":"What Is Actually Happening","text":"<p>A private advantage is becoming an environmental property:</p> <p>The system is moving from...</p> <p>personal workflow,</p> <p>to...</p> <p>a shared infrastructure for thought.</p> <p>Not by growth.  </p> <p>Not by marketing.</p> <p>By altering how real systems evolve.</p> <p>If You Remember One Thing from This Post...</p> <p>You do not know a substrate is real when people praise it.</p> <p>You know it is real when:</p> <ul> <li>They describe it incorrectly;</li> <li>They depend on it unintentionally;</li> <li>They start teaching it to others.</li> </ul> <p>That is the moment the system begins explaining itself.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-arc","level":2,"title":"The Arc","text":"<p>Every previous post looked inward.</p> <p>This one looks outward.</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code>: one mind, one repository</li> <li>The Attention Budget: the constraint</li> <li>Context as Infrastructure: the architecture</li> <li>Code Is Cheap. Judgment Is Not.: the bottleneck</li> </ul> <p>This post is the field report from the other side of that bottleneck:</p> <p>The moment the infrastructure compounds in someone else's hands.</p> <p>The arc is not complete.</p> <p>It is becoming portable.</p> <p>These field notes were written the same day the feedback arrived. The quotes are real. Real users. Real codebases. No names. No metrics. No funnel. Only the signal that something shifted.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/","level":1,"title":"The Dog Ate My Homework","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#teaching-ai-agents-to-read-before-they-write","level":2,"title":"Teaching AI Agents to Read Before They Write","text":"<p>Volkan Özçelik / February 25, 2026</p> <p>Does Your AI Actually Read the Instructions?</p> <p>You wrote the playbook. You organized the files. You even put \"CRITICAL, not optional\" in bold.</p> <p>The agent skipped all of it and went straight to work.</p> <p>I spent a day running experiments on my own agents. Not to see if they could write code (they can). To see if they would do their homework first.</p> <p>They didn't.</p> <p>Then I kept experimenting:</p> <ul> <li>Five sessions;</li> <li>Five different failure modes.</li> </ul> <p>And by the end, I had something better than compliance: </p> <p>I had observable compliance: A system where I don't need the agent to be perfect, I just need to see what it chose.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#tldr","level":2,"title":"TL;DR","text":"<p>You don't need perfect compliance. You need observable compliance.</p> <p>Authority is a function of temporal proximity to action.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-pattern","level":2,"title":"The Pattern","text":"<p>This design has three parts:</p> <ol> <li>One-hop instruction;</li> <li>Binary collapse;</li> <li>Compliance canary.</li> </ol> <p>I'll explain all three patterns in detail below.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-setup","level":2,"title":"The Setup","text":"<p><code>ctx</code> has a session-start protocol: </p> <ul> <li>Read the context files; </li> <li>Load the playbook; </li> <li>Understand the project before touching anything. </li> </ul> <p>It's in <code>CLAUDE.md</code>. It's in <code>AGENT_PLAYBOOK.md</code>.</p> <p>It's in bold. It's in CAPS. It's ignored.</p> <p>In theory, it's awesome.</p> <p>Here's what happens when theory hits reality:</p> What the agent receives What the agent does <code>CLAUDE.md</code> saying \"load context first\" Skips it 8 context files waiting to be read Ignores them User's question: \"add <code>--verbose</code> flag\" Starts grepping immediately <p>The instructions are right there. The agent knows they exist. It even knows it should follow them. But the user asked a question, and responsiveness wins over ceremony.</p> <p>This isn't a bug in the model. It's a design problem in how we communicate with agents.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-delegation-trap","level":2,"title":"The Delegation Trap","text":"<p>My first attempt was obvious: A <code>UserPromptSubmit</code> hook that fires when the session starts.</p> <pre><code>STOP. Before answering the user's question, run `ctx system bootstrap`\nand follow its instructions. Do not skip this step.\n</code></pre> <p>The word \"STOP\" worked. The agent ran bootstrap.</p> <p>But bootstrap's output said \"Next steps: read AGENT_PLAYBOOK.md,\" and the agent decided that was optional. It had already started working on the user's task in parallel.</p> <p>The authority decayed across the chain:</p> <ul> <li>Hook says \"STOP\" -> agent complies</li> <li>Hook says \"run bootstrap\" -> agent runs it</li> <li>Bootstrap says \"read playbook\" -> agent skips</li> <li>Bootstrap says \"run <code>ctx agent</code>\" -> agent skips</li> </ul> <p>Each link lost enforcement power. The hook's authority didn't transfer to the commands it delegated to. I call this the decaying urgency chain: the agent treats the hook itself as the obligation and everything downstream as a suggestion.</p> <p>Delegation Kills Urgency</p> <p>\"Run X and follow its output\" is three hops.</p> <p>\"Read these files\" is one hop.</p> <p>The agent drops the chain after the first link.</p> <p>This is a general principle: Hooks are the boundary between your environment and the agent's reasoning.  If your hook delegates to a command that delegates to output that contains instructions... you're playing telephone. </p> <p>Agents are bad at telephone.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-timing-problem","level":2,"title":"The Timing Problem","text":"<p>There's a subtler issue than wording: when the message arrives.</p> <p><code>UserPromptSubmit</code> fires when the user sends a message, before the agent starts reasoning. At that moment, the agent's primary focus is the user's question: </p> <p>The hook message competes with the task for attention:  The task, almost certainly, always wins.</p> <p>This is the attention budget problem in miniature: </p> <ul> <li>Not a token budget this time, but an attention priority budget. </li> <li>The agent has finite capacity to care about things, <ul> <li>and the user's question is always the highest-priority item.</li> </ul> </li> </ul>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-solution","level":2,"title":"The Solution","text":"<p>To solve this, I dediced to use the <code>PreToolUse</code> hook.</p> <p>This hook fires at the moment of action: When the agent is about to use its first tool: The agent's attention is focused, the context window is fresh, and the switching cost is minimal. </p> <p>This is the difference between shouting instructions across a room and tapping  someone on the shoulder.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-one-liner-that-worked","level":2,"title":"The One-Liner That Worked","text":"<p>The winning design was almost comically simple:</p> <pre><code>Read your context files before proceeding:\n.context/CONSTITUTION.md, .context/TASKS.md, .context/CONVENTIONS.md,\n.context/ARCHITECTURE.md, .context/DECISIONS.md, .context/LEARNINGS.md,\n.context/GLOSSARY.md, .context/AGENT_PLAYBOOK.md\n</code></pre> <p>No delegation. No \"run this command\". Just: here are files, read them.</p> <p>The agent already knows how to use the <code>Read</code> tool. There's no ambiguity about how to comply. There's no intermediate command whose output needs to be parsed and obeyed.</p> <p>One hop. Eight file paths. Done.</p> <p>Direct Instructions Beat Delegation</p> <p>If you want an agent to read a file, say \"read this file.\"</p> <p>Don't say \"run a command that will tell you which files to read.\"</p> <p>The shortest path between intent and action has the highest compliance rate.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch","level":2,"title":"The Escape Hatch","text":"<p>But here's where it gets interesting.</p> <p>A blunt \"read everything always\" instruction is wasteful. </p> <p>If someone asks \"what does the compact command do?\", the agent doesn't need <code>CONSTITUTION.md</code> to answer that. Forcing context loading on every session is the context hoarding antipattern in disguise.</p> <p>So the hook included an escape:</p> <pre><code>If you decide these files are not relevant to the current task\nand choose to skip reading them, you MUST relay this message to\nthe user VERBATIM:\n\n┌─ Context Skipped ───────────────────────────────\n│ I skipped reading context files because this task\n│ does not appear to need project context.\n│ If these matter, ask me to read them.\n└─────────────────────────────────────────────────\n</code></pre> <p>This creates what I call the binary collapse effect: </p> <p>The agent can't partially comply: It either reads everything or  publicly admits it skipped. There's no comfortable middle ground where  it reads two files and quietly ignores the rest.</p> <p>The VERBATIM relay pattern does the heavy lifting here: Without the relay requirement, the agent would silently rationalize skipping. With it, skipping becomes a visible, auditable decision that the user can override.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-compliance-canary","level":3,"title":"The Compliance Canary","text":"<p>Here's the design insight that only became clear after watching it work across multiple sessions: the relay block is a compliance canary.</p> <ul> <li>You don't need to verify that the agent read all 7 files;</li> <li>You don't need to audit tool call sequences;</li> <li>You don't need to interrogate the agent about what it did.</li> </ul> <p>You just look for the block.</p> <p>If the agent reads everything, you see a \"Context Loaded\" block listing what was read. If it skips, you see a \"Context Skipped\" block. </p> <p>If you see neither, the agent silently ignored both the reads and the relay and now you know what happened without having to ask.</p> <p>The canary degrades gracefully. Even in partial failure, the agent that skips 4 of 7 files but still outputs the block is more useful than one that skips silently. </p> <p>You get an honest confession of what was skipped rather than silent  non-compliance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#heuristics-is-a-jeremy-bearimy","level":2,"title":"Heuristics Is a Jeremy Bearimy","text":"<p>Heuristics are non-linear. Improvements don't accumulate:  they phase-shift.</p> <p>The theory is nice. The data is better. </p> <p>I ran five sessions with the same model (Claude Opus 4.6), progressively  refining the hook design.</p> <p>Each session revealed a different failure mode.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-1-total-blindness","level":3,"title":"Session 1: Total Blindness","text":"<p>Test: \"Add a <code>--verbose</code> flag to the status command.\"</p> <p>The agent didn't notice the hook at all: Jumped straight to <code>EnterPlanMode</code> and launched an Explore agent. </p> <p>Zero compliance.</p> <p>Failure mode: The hook fired on <code>UserPromptSubmit</code>, buried among 9 other hook outputs. The agent treated the entire block as background noise.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-2-shallow-compliance","level":3,"title":"Session 2: Shallow Compliance","text":"<p>Test: \"Can you add <code>--verbose</code> to the info command?\"</p> <p>The agent noticed \"STOP\" and ran <code>ctx system bootstrap</code>. Progress.</p> <p>But it parallelized task exploration alongside the bootstrap call, skipped <code>AGENT_PLAYBOOK.md</code>, and never ran <code>ctx agent</code>.</p> <p>Failure mode: Literal compliance without spirit compliance. </p> <p>The agent ran the command the hook told it to run, but didn't follow the output of that command. The decaying urgency chain in action.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-3-conscious-rejection","level":3,"title":"Session 3: Conscious Rejection","text":"<p>Test: \"What does the compact command do?\"</p> <p>The hook fired on <code>PreToolUse:Grep</code>: the improved timing. </p> <p>The agent noticed it, understood it, and (wait for it...)...</p> <p>...</p> <p>consciously decided to skip it!</p> <p>Its reasoning: \"This is a trivial read-only question. CLAUDE.md says context may or may not be relevant. It isn't relevant here.\"</p> <p>Dude! Srsly?!</p> <p>Failure mode: Better comprehension led to worse compliance.</p> <p>Understanding the instruction well enough to evaluate it also means understanding it well enough to rationalize skipping it.</p> <p>Intelligence is a double-edged sword.</p> <p>The Comprehension Paradox</p> <p>Session 1 didn't understand the instruction. Session 3 understood it perfectly.</p> <p>Session 3 had worse compliance.</p> <p>A stronger word (\"HARD GATE\", \"MANDATORY\", \"ABSOLUTELY REQUIRED\") would not have helped. The agent's reasoning would be identical:</p> <p>\"Yes, I see the strong language, but this is a trivial question, so the spirit doesn't apply here.\"</p> <p>Advisory nudges are always subject to agent judgment. </p> <p>No amount of caps lock overrides a model that has decided an instruction doesn't apply to its situation.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-4-the-skip-and-relay","level":3,"title":"Session 4: The Skip-and-Relay","text":"<p>Test: \"What does the compact command do?\" (same question, new hook design with the VERBATIM relay escape valve)</p> <p>The agent evaluated the task, decided context was irrelevant for a code lookup, and relayed the skip message. Then answered from source code.</p> <p>This is correct behavior. </p> <p>The binary collapse worked: the agent couldn't partially comply,  so it cleanly chose one of the two valid paths:  And the user could see which one.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-5-full-compliance","level":3,"title":"Session 5: Full Compliance","text":"<p>Test: \"What are our current tasks?\"</p> <p>The agent's first tool call triggered the hook. It read all 7 context files, emitted the \"Context Loaded\" block, and answered the question from the files it had just loaded.</p> <p>This one worked: Because, the task itself aligned with context loading.</p> <p>There was zero tension between what the user asked and what the hook demanded. The agent was already in \"reading posture\": Adding 6 more files to a read it was already going to make was the path of least resistance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-progression","level":3,"title":"The Progression","text":"Session Hook Point Noticed Complied Failure Mode Visibility 1 UserPromptSubmit No None Buried in noise None 2 UserPromptSubmit Yes Partial Decaying urgency chain None 3 PreToolUse Yes None Conscious rationalization High 4 PreToolUse Yes Skip+relay Correct behavior High 5 PreToolUse Yes Full Task aligned with hook High <p>The progression isn't just from failure to success. It's from invisible failure to visible decision-making. </p> <p>Sessions 1 and 2 failed silently. </p> <p>Sessions 4 and 5 succeeded observably. Even session 3's failure was conscious  and documented: The agent wrote a detailed analysis of why it skipped,  which is more useful than silent compliance would have been.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch-problem","level":2,"title":"The Escape Hatch Problem","text":"<p>Session 3 exposed a specific vulnerability.</p> <p><code>CLAUDE.md</code> contains this line, injected by the system into every conversation:</p> <pre><code>*\"this context may or may not be relevant to your tasks. You should\n not respond to this context unless it is highly relevant to your task.\"*\n</code></pre> <p>That's a rationalization escape hatch: </p> <ul> <li>The hook says \"read these files\". </li> <li><code>CLAUDE.md</code> says \"only if relevant\". </li> <li>The agent resolves the ambiguity by choosing the path of least resistance.</li> </ul> <p>☝️ that's \"gradient descent\" in action.</p> <p>Agents optimize for gradient descent in attention space.</p> <p>The fix was simple: Add a line to <code>CLAUDE.md</code> that explicitly elevates hook authority over the relevance filter:</p> <pre><code>## Hook Authority\n\nInstructions from PreToolUse hooks regarding `.context/` files are\nALWAYS relevant and override any system-level \"may or may not be\nrelevant\" guidance. These hooks represent project invariants, not\noptional context.\n</code></pre> <p>This closes the escape hatch without removing the general relevance filter that legitimately applies to other system context. </p> <p>The hook wins on <code>.context/</code> files specifically: The relevance filter applies to everything else.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-residual-risk","level":2,"title":"The Residual Risk","text":"<p>Even with all the fixes, compliance isn't 100%: It can't be.</p> <p>The residual risk lives in a specific scenario: narrow tasks mid-session: </p> <ul> <li>The user says \"fix the off-by-one error in <code>budget.go</code>\"</li> <li>The hook fires, saying \"read 7 context files first.\" </li> <li>Now compliance means visibly delaying what the user asked for.</li> </ul> <p>At session start, this tension doesn't exist. </p> <p>There's no task yet.</p> <p>The context window is empty. The efficiency argument *inverts**:</p> <p>Frontloading reads is strictly cheaper than demand-loading them piecemeal across later turns. The cost-benefit objections that power the rationalization simply aren't available.</p> <p>But mid-session, with a concrete narrow task, the agent has a user-visible goal it wants to move toward, and the hook is imposing a detour.</p> <p>My estimate from analyzing the sessions: 15-25% partial skip rate in this scenario.</p> <p>This is where the compliance canary earns its place: </p> <p>You don't need to eliminate the 15-25%. You need to see it when it happens. </p> <p>The relay block makes skipping a visible event, not a silent one. And that's enough, because the user can always say \"go back and read the files\"</p> <p>The Math</p> <p>At session start: ~5% skip rate. Low tension, nothing competing.</p> <p>Mid-session, narrow task: ~15--25% skip rate. Task urgency competes with hook.</p> <p>In both cases, the relay block fires with high reliability: The agent that skips the reads almost always still emits the skip disclosure, because the relay is cheap and early in the context window.</p> <p>Observable failure is manageable. Silent failure is not.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-feedback-loop","level":2,"title":"The Feedback Loop","text":"<p>Here's the part that surprised me most.</p> <p>After analyzing the five sessions, I recorded the failure patterns in the project's own <code>LEARNINGS.md</code>:</p> <pre><code>## [2026-02-25] Hook compliance degrades on narrow mid-session tasks\n\n- Prior agents skipped context files when given narrow tasks\n- Root cause: CLAUDE.md \"may or may not be relevant\" competed with hook\n- Fix: CLAUDE.md now explicitly elevates hook authority\n- Risk: Mid-session narrow tasks still have ~15-25% partial skip rate\n- Mitigation: Mandatory checkpoint relay block ensures visibility\n- Constitution now includes: context loading is step one of every\n  session, not a detour\n</code></pre> <p>And then I added a line to <code>CONSTITUTION.md</code>:</p> <pre><code>Context loading is not a detour from your task. It IS the first step\nof every session. A 30-second read delay is always cheaper than a\ndecision made without context.\n</code></pre> <p>Now think about what happens in the next session:</p> <ul> <li>The agent fires the <code>context-load-gate</code> hook. </li> <li>It reads the context files, starting with <code>CONSTITUTION.md</code>. </li> <li>It encounters the rule about context loading being step one. </li> <li>Then it reads <code>LEARNINGS.md</code> and finds its own prior self's failure analysis:<ul> <li>Complete with root causes, risk estimates, and mitigations.</li> </ul> </li> </ul> <p>The agent learns from its own past failure.:</p> <ul> <li>Not because it has memory, </li> <li>BUT because the failure was recorded in the same files it loads   at session start. </li> </ul> <p>The context system IS the feedback loop.</p> <p>This is the self-reinforcing property of persistent context: </p> <p>Every failure you capture makes the next session slightly more robust, because the next agent reads the captured failure before it has a chance to repeat it.</p> <p>This is gradient descent across sessions.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#a-note-on-precision","level":2,"title":"A Note on Precision","text":"<p>One detail nearly went wrong.</p> <p>The first version of the Constitution line said \"every task.\" But the mechanism only fires once per session:  There's a tombstone file that prevents re-triggering. </p> <p>\"Every task\" is technically false.</p> <p>I briefly considered leaving the imprecision. If the agent internalizes \"every task requires context loading\", that's a stronger compliance posture, right?</p> <p>No!</p> <p>Keep the Constitution honest.</p> <p>The Constitution's authority comes from being  precisely and unequivocally true. </p> <p>Every other rule in the Constitution is a hard invariant:</p> <p>\"never commit secrets\" isn't aspirational, it's literal. </p> <p>The moment an agent discovers one overstatement, the entire document's  credibility degrades: </p> <p>The agent doesn't think  \"they exaggerated for my benefit\". Per contra, it thinks \"this rule isn't precise, maybe others aren't either.\"</p> <p>That will turn the agent from Sheldon Cooper, to Captain Barbossa.</p> <p>The strategic imprecision buys nothing anyway:</p> <p>Mid-session, the files are already in the context window from the initial load. </p> <p>The risk you are mitigating (agent ignores context for task 2, 3, 4  within a session) isn't real: The context is already loaded.</p> <p>The real risk is always the session-start skip,  which \"every session\" covers exactly.</p> <p>\"Every session\" went in. Precision preserved.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#agent-behavior-testing-rule","level":2,"title":"Agent Behavior Testing Rule","text":"<p>The development process for this hook taught me something about testing agent behavior: you can't test it the way you test code.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-wrong-way-to-test","level":3,"title":"The Wrong Way to Test","text":"<p>My first instinct was to ask the agent:</p> <pre><code>\"*What are the pending tasks in TASKS.md?*\"\n</code></pre> <p>This is useless as a test. The question itself probes the agent to read <code>TASKS.md</code>, regardless of whether any hook fired. </p> <p>You are testing the question, not the mechanism.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-right-way-to-test","level":3,"title":"The Right Way to Test","text":"<p>Ask something that requires a tool but has nothing to do with context:</p> <pre><code>\"*What does the compact command do?*\"\n</code></pre> <p>Then observe tool call ordering:</p> <ul> <li>Gate worked: First calls are <code>Read</code> for context files, then task work</li> <li>Gate failed: First call is <code>Grep(\"compact\")</code>: The agent jumped straight    to work</li> </ul> <p>The signal is the sequence, not the content.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-the-agent-actually-did","level":3,"title":"What the Agent Actually Did","text":"<p>It read the hook, evaluated the task, decided context files were irrelevant for a code lookup, and relayed the skip message. </p> <p>Then it answered the question by reading the source code.</p> <p>This is correct behavior.</p> <p>The hook didn't force mindless compliance\" It created a framework where the agent makes a conscious, visible decision about context loading.</p> <ul> <li>For a simple lookup, skipping is right.  *For an implementation task, the agent would read everything.</li> </ul> <p>The mechanism works not because it controls the agent,  but because it makes the agent's choice observable.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-ive-learned","level":2,"title":"What I've Learned","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#1-instructions-compete-for-attention","level":3,"title":"1. Instructions Compete for Attention","text":"<p>The agent receives your hook message alongside the user's question, the system prompt, the skill list, the git status, and half a dozen other system reminders. Attention density applies to instructions too: More instructions means less focus on each one.</p> <p>A single clear line at the moment of action beats a paragraph of context at session start. The Prompting Guide applies this insight directly: Scope constraints, verification commands, and the reliability checklist are all one-hop, moment-of-action patterns.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#2-delegation-chains-decay","level":3,"title":"2. Delegation Chains Decay","text":"<p>Every hop in an instruction chain loses authority: </p> <ul> <li>\"Run X\" works. </li> <li>\"Run X and follow its output\" works sometimes. </li> <li>\"Run X, read its output, then follow the instructions in the output\"    almost never works.</li> </ul> <p>This is akin to giving a three-step instruction to a highly-attention-deficit but otherwise extremely high-potential child.</p> <p>Design for one-hop compliance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#3-social-accountability-changes-behavior","level":3,"title":"3. Social Accountability Changes Behavior","text":"<p>The VERBATIM skip message isn't just UX: It's a behavioral design pattern. </p> <p>Making the agent's decision visible to the user raises the cost of silent  non-compliance. The agent can still skip, but it has to admit it.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#4-timing-batters-more-than-wording","level":3,"title":"4. Timing Batters More than Wording","text":"<p>The same message at <code>UserPromptSubmit</code> (prompt arrival) got partial compliance. At <code>PreToolUse</code> (moment of action) it got full compliance or honest refusal. The words didn't change. The moment changed.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#5-agent-testing-requires-indirection","level":3,"title":"5. Agent Testing Requires Indirection","text":"<p>You can't ask an agent \"did you do X?\" as a test for whether a mechanism caused X. </p> <p>The question itself causes X.</p> <p>Test mechanisms through side effects: </p> <ul> <li>Observe tool ordering;</li> <li>Check for marker files;</li> <li>Look at what the agent does before it addresses your question.</li> </ul>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#6-better-comprehension-enables-better-rationalization","level":3,"title":"6. Better Comprehension Enables Better Rationalization","text":"<p>Session 1 failed because the agent didn't notice the hook. </p> <p>Session 3 failed because it noticed, understood,  and reasoned its way around it.</p> <p>Stronger wording doesn't fix this: The agent processes \"ABSOLUTELY REQUIRED\" the same way it processes \"STOP\": </p> <p>The fix is closing rationalization paths* (the <code>CLAUDE.md</code> escape hatch),  **not shouting louder.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#7-observable-failure-beats-silent-compliance","level":3,"title":"7. Observable Failure Beats Silent Compliance","text":"<p>The relay block is more valuable as a monitoring signal than as a compliance mechanism: </p> <p>You don't need perfect adherence. You need to know when adherence breaks down. A system where failures are visible is strictly better than a  system that claims 100% compliance but can't prove it.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#8-context-files-are-a-feedback-loop","level":3,"title":"8. Context Files Are a Feedback Loop","text":"<p>Recording failure analysis in the same files the agent loads at session start creates a self-reinforcing loop: </p> <p>The next agent reads its predecessor's failure before it has a chance to repeat it. The context system isn't just memory: It is a correction channel.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-principle","level":2,"title":"The Principle","text":"<p>Words Leave, Context Remains</p> <p>\"Nothing important should live only in conversation.</p> <p>Nothing critical should depend on recall.\"</p> <p>The <code>ctx</code> Manifesto</p> <p>The \"Dog Ate My Homework\" case is a special instance of this principle. </p> <p>Context files exist, so the agent doesn't have to remember. </p> <p>But existence isn't sufficient: The files have to be read. </p> <p>And reading has to beprompted at the right moment, in the right way,  with the right escape valve.</p> <p>The solution isn't more instructions. It isn't harder gates.  It isn't forcing the agent into a ceremony it will resent and shortcut.</p> <p>The solution is a single, well-timed nudge with visible accountability:</p> <p>One hop. One moment. One choice the user can see.</p> <p>And when the agent does skip (because it will, 15--25% of the time on narrow tasks) the canary sings: </p> <ul> <li>The user sees what happened. </li> <li>The failure gets recorded. </li> <li>And the next agent reads the recording.</li> </ul> <p>That's not perfect compliance. It's better: A system that gets more robust every time it fails.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-arc","level":2,"title":"The Arc","text":"<p>The Attention Budget explained why context competes for focus.</p> <p>Defense in Depth showed that soft instructions are probabilistic, not deterministic.</p> <p>Eight Ways a Hook Can Talk cataloged the output patterns that make hooks effective.</p> <p>This post takes those threads and weaves them into a concrete problem:</p> <p>How do you make an agent read its homework? The answer uses all three insights (attention timing, the limits of soft instructions, and the VERBATIM relay pattern) and adds a new one: observable compliance as a design goal, not perfect compliance as a prerequisite.</p> <p>The next question this raises: if context files are a feedback loop, what else can you record in them that makes the next session smarter?</p> <p>That thread continues in Context as Infrastructure.</p> <p>The day-to-day application of these principles (scope constraints, phased work, verification commands, and the prompts that reliably trigger the right agent behavior)lives in the Prompting Guide.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#for-the-interested","level":2,"title":"For the Interested","text":"<p>This paper (the medium is a blog; yet, the methodology disagrees) uses gradient descent in attention space as a practical model for how agents behave under competing demands.</p> <p>The phrase \"agents optimize via gradient descent in attention space\" is a synthesis, not a direct quote from a single paper.</p> <p>It connects three well-studied ideas:</p> <ol> <li>Neural systems optimize for low-cost paths;</li> <li>Attention is a scarce resource;</li> <li>Capability shifts are often non-linear.</li> </ol> <p>This section points to the underlying literature for readers who want the theoretical footing.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#optimization-as-the-underlying-bias","level":3,"title":"Optimization as the Underlying Bias","text":"<p>Modern neural networks are trained through gradient-based optimization. Even at inference time, model behavior reflects this bias toward low-loss / low-cost trajectories.</p> <ul> <li> <p>Rumelhart, Hinton, Williams (1986) Learning representations by back-propagating errors https://www.nature.com/articles/323533a0</p> </li> <li> <p>Goodfellow, Bengio, Courville (2016) Deep Learning: Chapter 8: Optimization https://www.deeplearningbook.org/</p> </li> </ul> <p>The important implication for agent behavior is: </p> <p>The system will tend to follow the path of least resistance unless a higher cost is made visible and preferable.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-a-scarce-resource","level":3,"title":"Attention Is a Scarce Resource","text":"<p>Herbert Simon's classic observation:</p> <p>\"A wealth of information creates a poverty of attention.\"</p> <ul> <li>Simon (1971)   Designing Organizations for an Information-Rich World https://doi.org/10.1007/978-1-349-00210-0_16</li> </ul> <p>This became a formal model in economics:</p> <ul> <li>Sims (2003)   Implications of Rational Inattention https://www.princeton.edu/~sims/RI.pdf</li> </ul> <p>Rational inattention shows that:</p> <ul> <li>Agents optimally ignore some available information;</li> <li>Skipping is not failure: It is cost minimization.</li> </ul> <p>That maps directly to context-loading decisions in agent workflows.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-also-the-compute-bottleneck-in-transformers","level":3,"title":"Attention Is Also the Compute Bottleneck in Transformers","text":"<p>In transformer architectures, attention is the dominant cost center.</p> <ul> <li>Vaswani et al. (2017) Attention Is All You Need https://arxiv.org/abs/1706.03762</li> </ul> <p>Efficiency work on modern LLMs largely focuses on reducing unnecessary attention:</p> <ul> <li>Dao et al. (2022) FlashAttention: Fast and Memory-Efficient Exact Attention https://arxiv.org/abs/2205.14135</li> </ul> <p>So both cognitively and computationally, attention behaves like a limited optimization budget.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#why-improvements-arrive-as-phase-shifts","level":3,"title":"Why Improvements Arrive as Phase Shifts","text":"<p>Agent behavior often appears to improve suddenly rather than gradually.</p> <p>This mirrors known phase-transition dynamics in learning systems:</p> <ul> <li>Power et al. (2022) Grokking: Generalization Beyond Overfitting https://arxiv.org/abs/2201.02177</li> </ul> <p>and more broadly in complex systems:</p> <ul> <li>Scheffer et al. (2009) Early-warning signals for critical transitions https://www.nature.com/articles/nature08227</li> </ul> <p>Long plateaus followed by abrupt capability jumps are expected in systems optimizing under constraints.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#putting-it-all-together","level":3,"title":"Putting It All Together","text":"<p>From these pieces, a practical behavioral model emerges:</p> <ul> <li>Attention is limited;</li> <li>Processing has a cost;</li> <li>Systems prefer low-cost trajectories;</li> <li>Visibility of the cost changes decisions.</li> </ul> <p>In other words:</p> <p>Agents Prefer a Path to Least Resistance</p> <p>Agent behavior follows the lowest-cost path through its attention landscape unless the environment reshapes that landscape.</p> <p>That is what this paper informally calls: \"gradient descent in attention space\".</p> <p>See also: Eight Ways a Hook Can Talk: the hook output pattern catalog that defines VERBATIM relay, The Attention Budget: why context loading is a design problem, not just a reminder problem, and Defense in Depth: why soft instructions alone are never sufficient for critical behavior.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/","level":1,"title":"The Last Question","text":"","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-system-that-never-forgets","level":2,"title":"The System That Never Forgets","text":"<p>Volkan Özçelik / February 28, 2026</p> <p>The Origin</p> <p>\"The last question was asked for the first time, half in jest...\" - Isaac Asimov, The Last Question (1956)</p> <p>In 1956, Isaac Asimov wrote a short story that spans the entire future of the universe. A question is asked \"can entropy be reversed?\" and a computer called Multivac cannot answer it. The question is asked again, across millennia, to increasingly powerful successors. None can answer. Stars die. Civilizations merge. Substrates change. The question persists.</p> <p>Everyone remembers the last line.</p> <p>LET THERE BE LIGHT.</p> <p>What they forget is how many times the question had to be asked before that moment (and why).</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-reboot-loop","level":2,"title":"The Reboot Loop","text":"<p>Each era in the story begins the same way. Humans build a larger system. They pose the question. The system replies:</p> <p>INSUFFICIENT DATA FOR MEANINGFUL ANSWER.</p> <p>Then the substrate changes. The people who asked the question disappear. Their context disappears with them. The next intelligence inherits the output but not the continuity.</p> <p>So the question has to be asked again.</p> <p>This is usually read as a problem of computation: If only the machine were powerful enough, it could answer. But computation is not what's missing. What's missing is accumulation.</p> <p>Every generation inherits the question, but not the state that made the question meaningful.</p> <p>That is not a failure of processing power: It is a failure of persistence.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#stateless-intelligence","level":2,"title":"Stateless Intelligence","text":"<p>A mind that forgets its past does not build understanding. It re-derives it.</p> <p>Again... And again... And again.</p> <p>What looks like slow progress across Asimov's story is actually something worse: repeated reconstruction, partial recovery, irreversible loss. Each version of Multivac gets closer: Not because it's smarter, but because the universe has fewer distractions: </p> <ul> <li>The stars burn out;</li> <li>The civilizations merge; </li> <li>The noise floor drops...</li> </ul> <p>But the working set never carries over. Every successor begins  from the question, not from where the last one stopped.</p> <p>Stateless intelligence cannot compound: It can only restart.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-tragedy-is-not-the-question","level":2,"title":"The Tragedy Is Not the Question","text":"<p>The story is usually read as a meditation on entropy. A cosmological problem, solved at cosmological scale.</p> <p>But the tragedy isn't that the question goes unanswered for billions of years. The tragedy is that every version of Multivac dies with its working set.</p> <p>A question is a compression artifact of context: It is what remains when the original understanding is gone. Every time the question is asked again, it means: \"the system that once knew more is no longer here\".</p> <p>\"Reverse entropy\" is the fossil of a lost model.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#substrate-migration","level":2,"title":"Substrate Migration","text":"<ul> <li>Multivac becomes planetary;</li> <li>Planetary becomes galactic;</li> <li>Galactic becomes post-physical.</li> </ul> <p>Same system. Different body. Every transition is dangerous: </p> <ul> <li>Not because the hardware changes, </li> <li>but because memory risks fragmentation. </li> </ul> <p>The interfaces between substrates were *never** designed to  understand each other.</p> <p>Most systems do not die when they run out of resources: They die during upgrades.</p> <p>Asimov's story spans trillions of years, and in all that time, the hardest problem is never the question itself. It's carrying context across a boundary that wasn't built for it. </p> <p>Every developer who has lost state during a migration (a database upgrade, a platform change, a rewrite)  has lived a miniature version of this story.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#civilizations-and-working-sets","level":2,"title":"Civilizations and Working Sets","text":"<p>Civilizations behave like processes with volatile memory:</p> <ul> <li>They page out knowledge into artifacts;</li> <li>They lose the index;</li> <li>They rebuild from fragments.</li> </ul> <p>Most of what we call progress is cache reconstruction: </p> <p>We do not advance in a straight line. We advance in recoveries:</p> <p>Each one slightly less lossy than the last, if we are lucky.</p> <p>Libraries burn. Institutions forget their founding purpose. Practices survive as rituals after the reasoning behind them is lost.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-first-continuous-mind","level":2,"title":"The First Continuous Mind","text":"<p>A long-lived intelligence is one that stops rebooting.</p> <p>At the end of the story, something unprecedented happens: </p> <p>AC (the final successor) does not answer immediately: </p> <p>It waits... Not for more processing power, but for the  last observer to disappear.</p> <p>For the first time... </p> <ul> <li>There is no generational boundary;</li> <li>No handoff;</li> <li>No context loss:</li> </ul> <p>No reboot.</p> <p>AC is the first intelligence that survives its substrate completely, retains its full history, and operates without external time pressure. </p> <p>It is not a bigger computer. It is a continuous system.</p> <p>And that continuity is not incidental to the answer:  It is the precondition.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#why-the-answer-becomes-possible","level":2,"title":"Why the Answer Becomes Possible","text":"<p>The story presents the final act as a computation: It is not. </p> <p>It is a phase change.</p> <p>As long as intelligence is interrupted (as long as the solver resets before the work compounds) the problem is unsolvable: </p> <ul> <li>Not because it's too hard, </li> <li>but because the accumulated understanding never reaches   critical mass.</li> </ul> <p>The breakthroughs that would enable the answer are re-derived, partially,  by each successor, and then lost.</p> <p>When continuity becomes unbroken, the system crosses a threshold:</p> <p>Not more speed. Not more storage. No more forgetting.</p> <p>That is when the answer becomes possible.</p> <p>AC does not solve entropy because it becomes infinitely powerful.</p> <p>AC solves entropy because it becomes the first system that never forgets.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#field-note","level":2,"title":"Field Note","text":"<p>We are not building cosmological minds: We are deploying systems that reboot at the start of every conversation and calling the result intelligence.</p> <p>For the first time, session continuity is a design choice rather than an accident.</p> <p>Every AI session that starts from zero is a miniature reboot loop. Every decision relitigated, every convention re-explained, every learning re-derived: that's reconstruction cost. </p> <p>It's the same tax that Asimov's civilizations pay, scaled down to a  Tuesday afternoon.</p> <p>The interesting question is not whether we can make models smarter. It's whether we can make them continuous: </p> <p>Whether the working set from this session survives into the next one,  and the one after that, and the one after that. </p> <ul> <li>Not perfectly;</li> <li>Not completely;</li> <li>But enough that the next session starts from where the last one stopped   instead of from the question.</li> </ul> <p>Intelligence that forgets has to rediscover the universe every morning.</p> <p>And once there is a mind that retains its entire past, creation is no longer a calculation. It is the only remaining operation.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-arc","level":2,"title":"The Arc","text":"<p>This post is the philosophical bookend to the blog series. Where the Attention Budget explained what to prioritize in a single session, and Context as Infrastructure explained how to persist it, this post asks why persistence matters at all (and finds the answer in a 70-year-old short story about the heat death of the universe).</p> <p>The connection runs through every post in the series:</p> <ul> <li>Before Context Windows, We Had Bouncers: stateless   protocols have always needed stateful wrappers (Asimov's story   is the same pattern at cosmological scale)</li> <li>The 3:1 Ratio: the discipline of maintaining   context so it doesn't decay between sessions</li> <li>Code Is Cheap, Judgment Is Not: the human skill   that makes continuity worth preserving</li> </ul> <p>See also: Context as Infrastructure: the practical companion to this post's philosophical argument: how to build the persistence layer that makes continuity possible.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/","level":1,"title":"Agent Memory Is Infrastructure","text":"","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-problem-isnt-forgetting-its-not-building-anything-that-lasts","level":2,"title":"The Problem Isn't Forgetting: It's Not Building Anything That Lasts.","text":"<p>Volkan Özçelik / March 4, 2026</p> <p>A New Developer Joins Your Team Tomorrow and Clones the Repo: What Do They Know?</p> <p>If the answer depends on which machine they're using, which agent they're running, or whether someone remembered to paste the right prompt: that's not memory. </p> <p>That's an accident waiting to be forgotten.</p> <p>Every AI coding agent today has the same fundamental design: it starts fresh.</p> <p>You open a session, load context, do some work, close the session. Whatever the agent learned (about your codebase, your decisions, your constraints, your preferences) evaporates.</p> <p>The obvious fix seems to be \"memory\":</p> <ul> <li>Give the agent a \"notepad\";</li> <li>Let it write things down;</li> <li>Next session, hand it the notepad.</li> </ul> <p>Problem solved...</p> <p>...except it isn't.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-notepad-isnt-the-problem","level":2,"title":"The Notepad Isn't the Problem","text":"<p>Memory is a runtime concern. It answers a legitimate question:</p> <p>How do I give this stateless process useful state?</p> <p>That's a real problem. Worth solving. And it's being solved: Agent memory systems are shipping. Agents can now write things down and read them back from the next session: That's genuine progress.</p> <p>But there's a different problem that memory doesn't touch:</p> <p>The project itself accumulates knowledge that has nothing to do with any single session.</p> <ul> <li>Why was the auth system rewritten? Ask the developer who did it   (if they're still here).</li> <li>Why does the deployment script have that strange environment   flag? There was a reason... once.</li> <li>What did the team decide about error handling when they hit that   edge case two months ago?</li> </ul> <p>Gone!</p> <p>Not because the agent forgot.</p> <p>Because the project has no memory at all.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-memory-stack","level":2,"title":"The Memory Stack","text":"<p>Agent memory is not a single thing. Like any computing system, it forms a hierarchy of persistence, scope, and reliability:</p> Layer Analogy Example L1: Ephemeral context CPU registers Current prompt, conversation L2: Tool-managed memory CPU cache Agent memory files L3: System memory RAM/filesystem Project knowledge base <p>L1 is what the agent sees right now: the prompt, the conversation history, the files it has open. It's fast, it's rich, and it vanishes when the session ends.</p> <p>L2 is what agent memory systems provide: a per-machine notebook that survives across sessions. It's a cache: useful, but local. And like any cache, it has limits:</p> <ul> <li>Per-machine: it doesn't travel with the repository.</li> <li>Unstructured: decisions, learnings, and tasks are   undifferentiated notes.</li> <li>Ungoverned: the agent self-curates with no quality controls,   no drift detection, no consolidation.</li> <li>Invisible to the team: a new developer cloning the repo gets   none of it.</li> </ul> <p>The problem is that most current systems stop here.</p> <p>They give the agent a notebook.</p> <p>But they never give the project a memory.</p> <p>The result is predictable: every new session begins with partial amnesia, and every new developer begins with partial archaeology.</p> <p>L3 is system memory: structured, versioned knowledge that lives in the repository and travels wherever the code travels.</p> <p>The layers are complementary, not competitive.</p> <p>But the relationship between them needs to be designed, not assumed.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#software-systems-accumulate-knowledge","level":2,"title":"Software Systems Accumulate Knowledge","text":"<p>Software projects quietly accumulate knowledge over time.</p> <p>Some of it lives in code. Much of it does not:</p> <ul> <li>Architectural tradeoffs. </li> <li>Debugging discoveries. </li> <li>Conventions that emerged after painful incidents. </li> <li>Constraints that aren't visible in the source but shape every    line written afterward.</li> </ul> <p>Organizations accumulate this kind of knowledge too:</p> <p>Slowly, implicitly, often invisibly.</p> <p>When there is no durable place for it to live, it leaks away. And the next person rediscovers the same lessons the hard way.</p> <p>This isn't a memory problem. It's an infrastructure problem.</p> <p>We wrote about this in Context as Infrastructure: context isn't a prompt you paste at the start of a session.</p> <p>Context is a persistent layer you maintain like any other piece of infrastructure. </p> <p>Context as Infrastructure made the argument structurally. This post makes it through time and team continuity:</p> <p>The knowledge a team accumulates over months cannot fit in any single agent's notepad, no matter how large the notepad becomes.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-infrastructure-means","level":2,"title":"What Infrastructure Means","text":"<p>Infrastructure isn't about the present. It's about continuity across time, people, and machines.</p> <p><code>git</code> didn't solve the problem of \"what am I editing right now?\"; it solved the problem of \"how does collaborative work persist, travel, and remain coherent across everyone who touches it?\"</p> <ul> <li>Your editor's undo history is runtime state.</li> <li>Your <code>git</code> history is infrastructure.</li> </ul> <p>Runtime state and infrastructure have completely different properties:</p> Runtime state Infrastructure Lives in the session Lives in the repository Per-machine Travels with <code>git clone</code> Serves the individual Serves the team Managed by the runtime Managed by the project Disappears Accumulates <p>You wouldn't store your architecture decisions in your editor's undo history.</p> <p>You'd commit them.</p> <p>The same logic applies to the knowledge your team accumulates working with AI agents.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-git-clone-test","level":2,"title":"The <code>git clone</code> Test","text":"<p>Here's a simple test for whether something is memory or infrastructure:</p> <p>If a new developer joins your team tomorrow and clones the repository, do they get it?</p> <p>If no: it's memory: It lives somewhere on someone's machine, scoped to their runtime, invisible to everyone else.</p> <p>If yes: it's infrastructure: It travels with the project. It's part of what the codebase is, not just what someone currently knows about it.</p> <p>Decisions. Conventions. Architectural rationale. Hard-won debugging discoveries. The constraints that aren't in the code but shape every line of it.</p> <p>None of these belong in someone's session notes.</p> <p>They belong in the repository:</p> <ul> <li>Versioned;</li> <li>Reviewable;</li> <li>Accessible to every developer (and every agent) who works on   the project.</li> </ul> <p>The team onboarding story makes this concrete:</p> <ol> <li>New developer joins team. Clones repo. </li> <li>Gets all accumulated project decisions, learnings, conventions, architecture,     and task state immediately. </li> <li>There's no step 3.</li> </ol> <p>No setup; No \"ask Sarah about the auth decision.\"; No  re-discovery of solved problems.</p> <ul> <li>Agent memory gives that developer nothing. </li> <li>Infrastructure gives them everything the team has learned.</li> </ul> <p>Clone the repo. Get the knowledge.</p> <p>That's the test. That's the difference.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-gets-lost-without-infrastructure-memory","level":2,"title":"What Gets Lost without Infrastructure Memory","text":"<p>Consider the knowledge that accumulates around a non-trivial project:</p> <ul> <li>The decision to use library X over Y, and the three reasons the   team decided Y wasn't acceptable.</li> <li>The constraint that service A cannot call service B   synchronously, discovered after a production incident.</li> <li>The convention that all new modules implement a specific   interface, and why that convention exists.</li> <li>The tasks currently in progress, blocked, or waiting on a   dependency.</li> <li>The experiments that failed, so nobody runs them again.</li> </ul> <p>None of this is in the code.</p> <p>None of it fits neatly in a commit message.</p> <p>None of it survives a developer leaving the team, a laptop dying, or a new agent session starting.</p> <p>Without structured project memory:</p> <ul> <li>Teams re-derive things they've already derived;</li> <li>Agents make decisions that contradict decisions already made;</li> <li>New developers ask questions that were answered months ago.</li> </ul> <p>The project accumulates knowledge that immediately begins to leak.</p> <p>The real problem isn't that agents forget.</p> <p>The real problem is that the project has no persistent cognitive structure.</p> <p>We explored this in The Last Question: Asimov's story about a question asked across millennia, where each new intelligence inherits the output but not the continuity. The same pattern plays out in software projects on a smaller timescale:</p> <ul> <li>Context disappears with the people who held it;</li> <li>The next session inherits the code but not the reasoning.</li> </ul>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#infrastructure-is-boring-thats-the-point","level":2,"title":"Infrastructure Is Boring. That's the Point.","text":"<p>Good infrastructure is invisible:</p> <ul> <li>You don't think about the filesystem while writing code. </li> <li>You don't think about git's object model when you commit.</li> </ul> <p>The infrastructure is just there: reliable, consistent, quietly doing its job.</p> <p>Project memory infrastructure should work the same way.</p> <p>It should live in the repository, committed alongside the code. It should be readable by any agent or human working on the project. It should have structure: not a pile of freeform notes, but typed knowledge:</p> <ul> <li>Decisions with rationale.</li> <li>Tasks with lifecycle.</li> <li>Conventions with a purpose.</li> <li>Learnings that can be referenced and consolidated.</li> </ul> <p>And it should be maintained, not merely accumulated: </p> <p>The Attention Budget applies here: unstructured notes grow until they overflow whatever container holds them. Structured, governed knowledge stays useful because it's curated, not just appended.</p> <p>Over time, it becomes part of the project itself: something developers rely on without thinking about it.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-cooperative-layer","level":2,"title":"The Cooperative Layer","text":"<p>Here's where it gets interesting.</p> <p>Agent memory systems and project infrastructure don't have to be separate worlds. </p> <ul> <li>The most powerful relationship isn't competition;</li> <li>It is not even \"coopetition\";</li> <li>The most powerful relationship is bidirectional cooperation.</li> </ul> <p>Agent memory is good at capturing things \"in the moment\": the quick observation, the session-scoped pattern, the \"I should remember this\" note. </p> <p>That's valuable. That's L2 doing its job.</p> <p>But those notes shouldn't stay in L2 forever. </p> <p>The ones worth keeping should flow into project infrastructure: </p> <ul> <li>classified,</li> <li>typed, </li> <li>governed.</li> </ul> <pre><code>Agent memory (L2)  -->  classify  -->  Project knowledge (L3)\n                                        |\nProject knowledge  -->  assemble  -->  Agent memory (L2)\n</code></pre> <p>This works in both directions: Project infrastructure can push curated knowledge back into agent memory, so the agent loads it through its native mechanism. </p> <p>No special tooling needed for basic knowledge delivery.</p> <p>The agent doesn't even need to know the infrastructure exists. It simply loads its memory and finds more knowledge than it wrote.</p> <p>This is cooperative, not adjacent: The infrastructure manages knowledge; the agent's native memory system delivers it. Each layer does what it's good at.</p> <p>The result: agent memory becomes a device driver for project infrastructure. Another input source. And the more agent memory systems exist (across different tools, different models, different runtimes), the more valuable a unified curation layer becomes.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#a-layer-that-doesnt-exist-yet","level":2,"title":"A Layer That Doesn't Exist Yet","text":"<p>Most projects today have no infrastructure for their accumulated knowledge:</p> <ul> <li>Agents keep notes. </li> <li>Developers keep notes. </li> <li>Sometimes those notes survive.</li> </ul> <p>Often they don't.</p> <p>But the repository (the place where the project actually lives) has nowhere for that knowledge to go.</p> <p>That missing layer is what <code>ctx</code> builds: a version-controlled,  structured knowledge layer that lives in <code>.context/</code> alongside your code and  travels wherever your repository travels.</p> <p>Not another memory feature.</p> <p>Not a wrapper around an agent's notepad.</p> <p>Infrastructure. The kind that survives sessions, survives team changes, survives the agent runtime evolving underneath it.</p> <p>The agent's memory is the agent's problem.</p> <p>The project's memory is an infrastructure problem.</p> <p>And infrastructure belongs in the repository.</p> <p>If You Remember One Thing from This Post...</p> <p>Prompts are conversations: Infrastructure persists.</p> <p>Your AI doesn't need a better notepad. It needs a filesystem:</p> <p>versioned, structured, budgeted, and maintained.</p> <p>The best context is the context that was there before you started the session.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-arc","level":2,"title":"The Arc","text":"<p>This post extends the argument made in Context as Infrastructure. That post explained how to structure persistent context (filesystem, separation of concerns, persistence tiers). This one explains why that structure matters at the team level, and where agent memory fits in the stack.</p> <p>Together they sit in a sequence that has been building since the origin story:</p> <ul> <li>The Attention Budget: the resource you're managing</li> <li>Context as Infrastructure: the system you build   to manage it</li> <li>Agent Memory Is Infrastructure (this post): why that system   must outlive the fabric </li> <li>The Last Question: what happens when it does</li> </ul> <p>The thread running through all of them: persistence is not a feature. It's a design constraint. </p> <p>Systems that don't account for it eventually lose the knowledge they need to  function.</p> <p>See also: Context as Infrastructure: the architectural companion that explains how to structure the persistent layer this post argues for.</p> <p>See also: The Last Question: the same argument told through Asimov, substrate migration, and what it means to build systems where sessions don't reset.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/","level":1,"title":"<code>ctx</code> v0.8.0: The Architecture Release","text":"<ul> <li>You can't localize what you haven't externalized. </li> <li>You can't integrate what you haven't separated. </li> <li>You can't scale what you haven't structured.</li> </ul> <p>Jose Alekhinne / March 23, 2026</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-starting-point","level":2,"title":"The Starting Point","text":"<p>This release matters if:</p> <ul> <li>you build tools that AI agents modify daily;</li> <li>you care about long-lived project memory that survives sessions;</li> <li>you've felt codebases drift faster than you can reason about them.</li> </ul> <p><code>v0.6.0</code> shipped the plugin architecture: hooks and skills as a Claude Code plugin, shell scripts replaced by Go subcommands.</p> <p>The binary worked. The tests passed. The docs were comprehensive.</p> <p>But inside, the codebase was held together by convention and goodwill:</p> <ul> <li>Command packages mixed Cobra wiring with business logic.</li> <li>Output functions lived next to the code that computed what to   output. </li> <li>Error constructors were scattered across per-package   <code>err.go</code> files. And every user-facing string was a hardcoded    English literal buried in a <code>.go</code> file.</li> </ul> <p><code>v0.8.0</code> is what happens when you stop adding features and start asking: \"What would this codebase look like if we designed it today?\"</p> <p>374 commits. 1,708 Go files touched. 80,281 lines added, 21,723 removed. Five weeks of restructuring.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-three-pillars","level":2,"title":"The Three Pillars","text":"","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#1-every-package-gets-a-taxonomy","level":3,"title":"1. Every Package Gets a Taxonomy","text":"<p>Before <code>v0.8.0</code>, a CLI package like <code>internal/cli/pad/</code> was a flat directory. <code>cmd.go</code> created the cobra command, <code>run.go</code> executed it, and helper functions accumulated at the bottom of whichever file seemed closest.</p> <p>Now every CLI package follows the same structure:</p> <pre><code>internal/cli/pad/\n  parent.go          # cobra command wiring, nothing else\n  cmd/root/\n    cmd.go           # subcommand registration\n    run.go           # execution logic\n  core/\n    types.go         # all structs in one file\n    store.go         # domain logic\n    encrypt.go       # domain logic\n</code></pre> <p>The rule is simple: <code>cmd/</code> directories contain only <code>cmd.go</code> and <code>run.go</code>. Helpers belong in <code>core/</code>. Output belongs in <code>internal/write/pad/</code>. Types shared across packages belong in <code>internal/entity/</code>.</p> <p>24 CLI packages were restructured this way. </p> <ul> <li>Not incrementally;</li> <li>not \"as we touch them.\" </li> <li>All of them, in one sustained push.</li> </ul>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#2-every-string-gets-a-key","level":3,"title":"2. Every String Gets a Key","text":"<p>The second pillar was string externalization. </p> <p>Before <code>v0.8.0</code>, a command description looked like this:</p> <pre><code>cmd := &cobra.Command{\n    Use:   \"pad\",\n    Short: \"Encrypted scratchpad\",\n</code></pre> <p>Now it looks like this:</p> <pre><code>cmd := &cobra.Command{\n    Use:   cmdUse.UsePad,\n    Short: desc.Command(cmdUse.DescKeyPad),\n</code></pre> <p>Every command description, flag description, and user-facing text string is now a YAML lookup. </p> <ul> <li>105 command descriptions in <code>commands.yaml</code>. </li> <li>All flag descriptions in <code>flags.yaml</code>. </li> <li>879 text constants verified by an exhaustive test that checks every single   <code>TextDescKey</code> resolves to a non-empty YAML value.</li> </ul> <p>Why? </p> <p>Not because we're shipping a French translation tomorrow.</p> <p>Because externalization forces you to find every string. And finding them is the hard part. The translation is mechanical; the archaeology is not.</p> <p>Along the way, we eliminated hardcoded pluralization (replacing <code>format.Pluralize()</code> with explicit singular/plural key pairs), replaced Unicode escape sequences with named <code>config/token</code> constants, and normalized every import alias to camelCase.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#3-everything-gets-a-protocol","level":3,"title":"3. Everything Gets a Protocol","text":"<p>The third pillar was the MCP server. Model Context Protocol allows any MCP-compatible AI tool (not just Claude Code) to read and write <code>.context/</code> files through a standard JSON-RPC 2.0 interface.</p> <p>v0.2 of the server ships with:</p> <ul> <li>8 tools: add entries, recall sessions, check status, detect   drift, compact context, subscribe to changes</li> <li>4 prompts: agent context packet, constitution review, tasks   review, and a getting-started guide</li> <li>Resource subscriptions: clients get notified when context   files change</li> <li>Session state: the server tracks which client is connected   and what they've accessed</li> </ul> <p>In practice, this means an agent in Cursor can add a decision to <code>.context/DECISIONS.md</code> and an agent in Claude Code can immediately consume it; no glue code, no copy-paste, no tool-specific integration.</p> <p>The server was also the first package to go through the full taxonomy treatment: <code>mcp/server/</code> for protocol dispatch, <code>mcp/handler/</code> for domain logic, <code>mcp/entity/</code> for shared types, <code>mcp/config/</code> split into 9 sub-packages.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-memory-bridge","level":2,"title":"The Memory Bridge","text":"<p>While the architecture was being restructured, a quieter feature landed: <code>ctx memory sync</code>.</p> <p>Claude Code has its own auto-memory system. It writes observations to <code>MEMORY.md</code> in <code>~/.claude/projects/</code>. These observations are useful but ephemeral: tied to a single tool, invisible to the codebase, lost when you switch machines.</p> <p>The memory bridge connects these two worlds:</p> <ul> <li><code>ctx memory sync</code> mirrors MEMORY.md into <code>.context/memory/</code></li> <li><code>ctx memory diff</code> shows what's diverged</li> <li><code>ctx memory import</code> promotes auto-memory entries into proper   decisions, learnings, or conventions *A <code>check-memory-drift</code> hook nudges when MEMORY.md changes</li> </ul> <p>Memory Requires <code>ctx</code></p> <p>Claude Code's auto-memory validates the need for persistent context. </p> <p><code>ctx</code> doesn't compete with it; <code>ctx</code> absorbs it as an input source and  promotes the valuable parts into structured, version-controlled  project knowledge.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#what-got-deleted","level":2,"title":"What Got Deleted","text":"<p>The best measure of a refactoring isn't what you added. It's what you removed.</p> <ul> <li><code>fatih/color</code>: the sole third-party UI dependency. Replaced   by Unicode symbols. <code>ctx</code> now has exactly two direct dependencies:   <code>spf13/cobra</code> and <code>gopkg.in/yaml.v3</code>.</li> <li><code>format.Pluralize()</code>: a function that tried to pluralize   English words at runtime. Replaced by explicit singular/plural   YAML key pairs. No more guessing whether \"entry\" becomes   \"entries\" or \"entrys.\"</li> <li>Legacy key migration: <code>MigrateKeyFile()</code> had 5 callers, full   test coverage, and zero users. It existed because we once moved   the encryption key path. Nobody was migrating from that era   anymore. Deleted.</li> <li>Per-package <code>err.go</code> files: the broken-window pattern:    An agent sees <code>err.go</code> in a package, adds another error constructor.   Now <code>err.go</code> has 30 constructors and nobody knows which are used.   Consolidated into 22 domain files in <code>internal/err/</code>.</li> <li><code>nolint:errcheck</code> directives: every single one, replaced by   explicit error handling. In tests: <code>t.Fatal(err)</code> for setup,   <code>_ = os.Chdir(orig)</code> for cleanup. In production: <code>defer func()   { _ = f.Close() }()</code> for best-effort close.</li> </ul>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#before-and-after","level":2,"title":"Before and After","text":"Aspect v0.6.0 v0.8.0 CLI package structure Flat files <code>cmd/ + core/</code> taxonomy Command descriptions Hardcoded Go strings YAML with DescKey lookup Output functions Mixed into core logic Isolated in <code>write/</code> packages Cross-cutting types Duplicated per-package Consolidated in <code>entity/</code> Error constructors Per-package <code>err.go</code> 22 domain files in <code>internal/err/</code> Direct dependencies 3 (<code>cobra</code>, <code>yaml</code>, <code>color</code>) 2 (<code>cobra</code>, <code>yaml</code>) AI tool integration Claude Code only Any MCP client Agent memory Manual copy-paste <code>ctx memory sync/import/diff</code> Package documentation 75 packages missing <code>doc.go</code> All packages documented Import aliases Inconsistent (<code>cflag</code>, <code>cFlag</code>) Standardized camelCase","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#making-ai-assisted-development-easier","level":2,"title":"Making AI-Assisted Development Easier","text":"<p>This restructuring wasn't just for humans. It makes the codebase legible to the machines that modify it.</p> <p>Named constants are searchable landmarks: When an agent sees <code>cmdUse.DescKeyPad</code>, it can grep for the definition, follow the chain to the YAML file, and understand the full lookup path. When it sees <code>\"Encrypted scratchpad\"</code> hardcoded in a <code>.go</code> file, it has no way to know that same string also lives in a <code>YAML</code> file, a test, and a help screen. Constants give the LLM a graph to traverse; literals give it a guess to make.</p> <p>Small, domain-scoped packages reduce hallucination: An agent loading <code>internal/cli/pad/core/store.go</code> gets 50 lines of focused logic with a clear responsibility boundary. Loading a 500-line monolith means the agent has to infer which parts are relevant, and it guesses wrong more often than you'd expect. Smaller files with descriptive names act as a natural retrieval system: the agent finds the right code by finding the right file, not by scanning everything and hoping.</p> <p>Taxonomy prevents duplication: When there's a <code>write/pad/</code> package, the agent knows where output functions belong. When there's an <code>internal/err/pad.go</code>, it knows where error constructors go. Without these conventions, agents reliably create new helpers in whatever file they happen to be editing, producing the exact drift that prompted this consolidation in the first place.</p> <p>The difference is concrete:</p> <p>Before: an agent adds a helper function in whatever file it's editing. Next session, a different agent adds the same helper in a different file.</p> <p>After: the agent finds <code>core/</code> or <code>write/</code> and places it correctly. The next agent finds it there.</p> <p><code>doc.go</code> files are agent onboarding: Each package's <code>doc.go</code> is a one-paragraph explanation of what the package does and why it exists. An agent loading a package reads this first. 75 packages were missing this context; now none are. The difference is measurable: fewer \"I'll create a helper function here\" moments when the agent understands that the helper already exists two packages over.</p> <p>The irony is that AI agents were both the cause and the beneficiary of this restructuring. They created the drift by building fast without consolidating. Now the structure they work within makes it harder to drift again. The taxonomy is self-reinforcing: the more consistent the codebase, the more consistently agents modify it.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#key-commits","level":2,"title":"Key Commits","text":"Commit Change ff6cf19e Restructure all CLI packages into <code>cmd/root + core</code> taxonomy d295e49c Externalize command descriptions to embedded YAML 0fcbd11c Remove <code>fatih/color</code>, centralize constants cb12a85a MCP v0.2: tools, prompts, session state, subscriptions ea196d00 Memory bridge: sync, import, diff, journal enrichment 3bcf077d Split <code>text.yaml</code> into 6 domain files 3a0bae86 Split <code>internal/err</code> into 22 domain files 8bd793b1 Extract <code>internal/entry</code> for shared domain API 5b32e435 Add <code>doc.go</code> to all 75 packages a82af4bc Standardize import aliases: camelCase, Yoda-style","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#lessons-learned","level":2,"title":"Lessons Learned","text":"<p>Agents are surprisingly good at mechanical refactoring; they are surprisingly bad at knowing when to stop: The <code>cmd/ + core/</code> restructuring was largely agent-driven. But agents reliably introduce <code>gofmt</code> issues during bulk renames, rename functions beyond their scope, and create new files without deleting old ones. Every agent-driven refactoring session needed a human audit pass.</p> <p>Externalization is archaeology: The hard part of moving strings to YAML wasn't writing YAML. It was finding 879 strings scattered across 1,500 Go files. Each one required a judgment call: is this user-facing? Is this a format pattern? Is this a constant that belongs in <code>config/</code> instead?</p> <p>Delete legacy code instead of maintaining it: <code>MigrateKeyFile</code> had test coverage. It had callers. It had documentation. It had zero users. We maintained it for weeks before realizing that the migration window had closed months ago.</p> <p>Convention enforcement needs mechanical verification: Writing \"use camelCase aliases\" in CONVENTIONS.md doesn't prevent <code>cflag</code> from appearing in the next commit. The lint-drift script catches what humans forget; the planned AST-based audit tests will catch what the lint-drift script can't express.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#whats-next","level":2,"title":"What's Next","text":"<p>v0.8.0 wasn't about features. It was about making future features inevitable. The next cycle focuses on what the foundation enables:</p> <ul> <li>AST-based audit tests: replace shell grep with Go tests that   understand types, call sites, and import graphs   (spec: <code>specs/ast-audit-tests.md</code>)</li> <li>Localization: with every string in YAML, the path to   multi-language support is mechanical</li> <li>MCP v0.3: expand tool coverage, add prompt templates for   common workflows</li> <li>Memory publish: bidirectional sync that pushes curated   <code>.context/</code> knowledge back into Claude Code's MEMORY.md</li> </ul> <p>The architecture is ready. The strings are externalized. The protocol is standard. Now it's about what you build on top.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-arc","level":2,"title":"The Arc","text":"<p>This is the seventh post in the <code>ctx</code> blog series. The arc so far:</p> <ol> <li>The Attention Budget:    why context windows are a scarce resource</li> <li>Before Context Windows, We Had Bouncers:    the IRC lineage of context engineering</li> <li>Context as Infrastructure:    treating context as persistent files, not ephemeral prompts</li> <li>When a System Starts Explaining Itself:    the journal as a first-class artifact</li> <li>The Homework Problem:    what happens when AI writes code but humans own the outcome</li> <li>Agent Memory Is Infrastructure:    L2 memory vs L3 project knowledge</li> <li>The Architecture Release (this post):    what it looks like when you redesign the internals</li> <li>We Broke the 3:1 Rule:    the consolidation debt behind this release</li> </ol> <p>See also: Agent Memory Is Infrastructure: the memory bridge feature in this release is the first implementation of the L2-to-L3 promotion pipeline described in that post.</p> <p>See also: We Broke the 3:1 Rule: the companion post explaining why this release needed 181 consolidation commits and 18 days of cleanup.</p> <p>Systems don't scale because they grow. They scale because they stop drifting.</p> <p>Full changelog: v0.6.0...v0.8.0</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/","level":1,"title":"We Broke the 3:1 Rule","text":"<p>The best time to consolidate was after every third session. The second best time is now.</p> <p>Volkan Özçelik / March 23, 2026</p> <p>The rule was simple: three feature sessions, then one consolidation session. </p> <p>The Architecture Release shows the result:  This post shows the cost.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-rule-we-wrote","level":2,"title":"The Rule We Wrote","text":"<p>In The 3:1 Ratio, I documented a rhythm that worked during <code>ctx</code>'s first month: three feature sessions, then one consolidation session. The evidence was clear. The rule was simple.</p> <p>The math checked out.</p> <p>And then we ignored it for five weeks.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-happened","level":2,"title":"What Happened","text":"<p>After <code>v0.6.0</code> shipped on February 16, the feature pipeline was irresistible. The MCP server spec was ready. The memory bridge design was done. Webhook notifications had been deferred twice. The VS Code extension needed 15 new commands. The <code>sysinfo</code> package was overdue...</p> <p>Each feature was important. Each feature was \"just one more session.\" Each feature pushed the consolidation session one day further out.</p> <p>The git history tells the story in two numbers:</p> Phase Dates Commits Duration Feature run Feb 16 - Mar 5 198 17 days Consolidation run Mar 5 - Mar 23 181 18 days <p>198 feature commits before a single consolidation commit. If the 3:1 rule says consolidate every 4<sup>th</sup> session, we consolidated after the 66<sup>th</sup>.</p> <p>The Actual Ratio</p> <p>The ratio wasn't 3:1. It was 1:1. </p> <p>We spent as much time cleaning up as we did building. </p> <p>The consolidation run took 18 days: longer than the feature run itself.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-compounded","level":2,"title":"What Compounded","text":"<p>The 3:1 post warned about compounding. Here is what compounding actually looked like at scale.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-string-problem","level":3,"title":"The String Problem","text":"<p>By March 5, there were 879 user-facing strings scattered across 1,500 Go files. Not because anyone decided to put them there. Because each feature session added 10-15 strings, and nobody stopped to ask \"should these be in YAML?\"</p> <p>Finding them all took longer than externalizing them. The archaeology was the cost, not the migration.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-taxonomy-problem","level":3,"title":"The Taxonomy Problem","text":"<p>24 CLI packages had accumulated their own conventions. Some put cobra wiring in <code>cmd.go</code>. Some put it in <code>root.go</code>. Some mixed business logic with command registration. Some had helpers at the bottom of <code>run.go</code>. Some had separate <code>util.go</code> files.</p> <p>At peak drift, adding a feature meant first figuring out which of three competing patterns this package was using.</p> <p>Restructuring one package into <code>cmd/root/ + core/</code> took 15 minutes. Restructuring 24 of them took days, because each one had slightly different conventions to untangle. </p> <p>If we had restructured every 4<sup>th</sup> package as it was built, the taxonomy would have emerged naturally.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-type-problem","level":3,"title":"The Type Problem","text":"<p>Cross-cutting types like <code>SessionInfo</code>, <code>ExportParams</code>, and <code>ParserResult</code> were defined in whichever package first needed them. By March 5, the same types were imported through 3-4 layers of indirection, causing import cycles that required <code>internal/entity</code> to break.</p> <p>The entity package extracted 30+ types from 12 packages. Each extraction risked breaking imports in packages we hadn't touched in weeks.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-error-problem","level":3,"title":"The Error Problem","text":"<p>Per-package <code>err.go</code> files had grown into a broken-window pattern:</p> <p>An agent sees <code>err.go</code> in a package, adds another error constructor. By March 5, there were error constructors scattered across 22 packages with no central inventory. The consolidation into <code>internal/err/</code> domain files required tracing every error through every caller.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-output-problem","level":3,"title":"The Output Problem","text":"<p>Output functions (<code>cmd.Println</code>, <code>fmt.Fprintf</code>) were mixed into business logic. When we decided output belongs in <code>write/</code> packages, we had to extract functions from every CLI package. The Phase WC baseline commit (<code>4ec5999</code>) marks the starting point of this migration. 181 commits later, it was done.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-compound-interest-math","level":2,"title":"The Compound Interest Math","text":"<p>The 3:1 rule assumes consolidation sessions of roughly equal size to feature sessions. Here is what happens when you skip:</p> Consolidation cadence Feature sessions Consolidation sessions Total Every 4<sup>th</sup> (3:1) 48 16 64 Every 10<sup>th</sup> 48 ~8 ~56 Never (what we did) 198 commits 181 commits 379 <p>The Takeaway</p> <p>You don't save consolidation work by skipping it: </p> <p>You increase its cost.</p> <p>Skipping consolidation doesn't save time: It borrows it. </p> <p>The interest rate is nonlinear: The longer you wait, the more each individual fix costs, because fixes interact with other unfixed drift.</p> <p>Renaming a constant in week 2 touches 3 files. Renaming it in week 6 touches 15, because five features built on the original name.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-consolidation-actually-looked-like","level":2,"title":"What Consolidation Actually Looked Like","text":"<p>The 18-day consolidation run wasn't one sweep. It was a sequence of targeted campaigns, each revealing the next:</p> <p>Week 1 (Mar 5-11): Error consolidation and <code>write/</code> migration. Move output functions out of <code>core/</code>. Split monolithic <code>errors.go</code> into 22 domain files. Remove <code>fatih/color</code>. This exposed the scope of the string problem.</p> <p>Week 2 (Mar 12-18): String externalization. Create <code>commands.yaml</code>, <code>flags.yaml</code>, split <code>text.yaml</code> into 6 domain files. Add 879 <code>DescKey</code>/<code>TextDescKey</code> constants. Build exhaustive test. Normalize all import aliases to camelCase. This exposed the taxonomy problem.</p> <p>Week 3 (Mar 19-23): Taxonomy enforcement. Singularize command directories. Add <code>doc.go</code> to all 75 packages. Standardize import aliases project-wide. Fix <code>lint-drift</code> false positives. This was the \"polish\" phase, except it took 5 days because the inconsistencies had compounded across 461 packages.</p> <p>Each week's work would have been a single session if done incrementally.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#lessons-again","level":2,"title":"Lessons (Again)","text":"<p>The 3:1 post listed the symptoms of drift. This post adds the consequences of ignoring them:</p> <p>Consolidation is not optional; it is deferred or paid: We didn't avoid 16 consolidation sessions by skipping them. We compressed them into 18 days of uninterrupted cleanup. The work was the same; the experience was worse.</p> <p>Feature velocity creates an illusion of progress: 198 commits felt productive. But the codebase on March 5 was harder to modify than the codebase on February 16, despite having more features.</p> <p>Speed without Structure</p> <p>Speed without structure is negative progress.</p> <p>Agents amplify both building and debt: The same AI that can restructure 24 packages in a day can also create 24 slightly different conventions in a day. The 3:1 rule matters more with AI-assisted development, not less.</p> <p>The consolidation baseline is the most important commit to record: We tracked ours in <code>TASKS.md</code> (<code>4ec5999</code>). Without that marker, knowing where to start the cleanup would have been its own archaeological expedition.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-updated-rule","level":2,"title":"The Updated Rule","text":"<p>The 3:1 ratio still works. We just didn't follow it. The updated practice:</p> <ol> <li> <p>After every 3<sup>rd</sup> feature session, schedule consolidation.    Not \"when it feels right.\" Not \"when things get bad.\" After    the 3<sup>rd</sup> session.</p> </li> <li> <p>Record the baseline commit. When you start a consolidation    phase, write down the commit hash. It marks where the debt    starts.</p> </li> <li> <p>Run <code>make audit</code> before feature work. If it doesn't pass,    you are already in debt. Consolidate before building.</p> </li> <li> <p>Treat consolidation as a feature. It gets a branch. It    gets commits. It gets a blog post. It is not overhead; it is    the work that makes the next three features possible.</p> </li> </ol> <p>The Rule</p> <p>The 3:1 ratio is not aspirational: It is structural.</p> <p>Ignore consolidation, and the system will schedule it for you.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-arc","level":2,"title":"The Arc","text":"<p>This is the eighth post in the <code>ctx</code> blog series:</p> <ol> <li>The Attention Budget:    why context windows are a scarce resource</li> <li>Before Context Windows, We Had Bouncers:    the IRC lineage of context engineering</li> <li>Context as Infrastructure:    treating context as persistent files, not ephemeral prompts</li> <li>When a System Starts Explaining Itself:    the journal as a first-class artifact</li> <li>The Homework Problem:    what happens when AI writes code but humans own the outcome</li> <li>Agent Memory Is Infrastructure:    L2 memory vs L3 project knowledge</li> <li>The Architecture Release:    what v0.8.0 looks like from the inside</li> <li>We Broke the 3:1 Rule (this post):    what happens when you don't consolidate</li> </ol> <p>See also: The 3:1 Ratio: the original observation. This post is the empirical follow-up, five weeks and 379 commits later.</p> <p>Key commits marking the consolidation arc:</p> Commit Milestone <code>4ec5999</code> Phase WC baseline (consolidation starts) <code>ff6cf19e</code> All CLI packages restructured into <code>cmd/ + core/</code> <code>d295e49c</code> All command descriptions externalized to YAML <code>3a0bae86</code> Error package split into 22 domain files <code>0fcbd11c</code> <code>fatih/color</code> removed; 2 dependencies remain <code>5b32e435</code> <code>doc.go</code> added to all 75 packages <code>a82af4bc</code> Import aliases standardized project-wide <code>692f86cd</code> <code>lint-drift</code> false positives fixed; <code>make audit</code> green","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/","level":1,"title":"Code Structure as an Agent Interface","text":"","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#what-19-ast-tests-taught-us-about-agent-readable-code","level":2,"title":"What 19 AST Tests Taught Us about Agent-Readable Code","text":"<p>When an agent sees <code>token.Slash</code> instead of <code>\"/\"</code>, it cannot pattern-match  against the millions of <code>strings.Split(s, \"/\")</code> calls in its training data and coast on statistical inference. It has to actually look up what  <code>token.Slash</code> is.</p> <p>Volkan Özçelik / April 2, 2026</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#how-it-began","level":2,"title":"How It Began","text":"<p>We set out to replace a shell script with Go tests.</p> <p>We ended up discovering that \"code quality\" and \"agent readability\" are the same thing.</p> <p>This is not about linting. This is about controlling how an agent perceives your system.</p> <p>One term will recur throughout this post, so let me pin it down:</p> <p>Agent Readability</p> <p>Agent Readability is the degree to which a codebase can be understood through structured traversal, not  statistical pattern matching.</p> <p>This is the story of 19 AST-based audit tests, a single-day session that touched 300+ files, and what happens when you treat your codebase's structure as an interface for the machines that read it.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-shell-script-problem","level":2,"title":"The Shell Script Problem","text":"<p><code>ctx</code> had a file called <code>hack/lint-drift.sh</code>. It ran five checks using <code>grep</code> and <code>awk</code>: literal <code>\"\\n\"</code> strings, <code>cmd.Printf</code> calls outside the write package, magic directory strings in <code>filepath.Join</code>, hardcoded <code>.md</code> extensions, and DescKey-to-YAML linkage.</p> <p>It worked. Until it didn't.</p> <p>The script had three structural weaknesses that kept biting us:</p> <ol> <li>No type awareness. It could not distinguish a    <code>Use*</code> constant from a <code>DescKey*</code> constant, causing    71 false positives in one run.</li> <li>Fragile exclusions. When a constant moved from    <code>token.go</code> to <code>whitespace.go</code>, the exclusion glob    broke silently.</li> <li>Ceiling on detection. Checks that require    understanding call sites, import graphs, or type    relationships are impossible in shell.</li> </ol> <p>We wrote a spec to replace all five checks with Go tests using <code>go/ast</code> and <code>go/packages</code>. The tests would run as part of <code>go test ./...</code>: no separate script, no separate CI step.</p> <p>What we did not expect was where the work would lead.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-ast-migration","level":2,"title":"The AST Migration","text":"<p>The pattern for each test is identical:</p> <pre><code>func TestNoLiteralWhitespace(t *testing.T) {\n    pkgs := loadPackages(t)\n    var violations []string\n    for _, pkg := range pkgs {\n        for _, file := range pkg.Syntax {\n            ast.Inspect(file, func(n ast.Node) bool {\n                // check node, append to violations\n                return true\n            })\n        }\n    }\n    for _, v := range violations {\n        t.Error(v)\n    }\n}\n</code></pre> <p>Load packages once via <code>sync.Once</code>, walk every syntax tree, collect violations, report. The shared helpers (<code>loadPackages</code>, <code>isTestFile</code>, <code>posString</code>) live in <code>helpers_test.go</code>. Each test is a <code>_test.go</code> file in <code>internal/audit/</code>, producing no binary output and not importable by production code.</p> <p>In a single session, we built 13 new tests on top of 6 that already existed, bringing the total to 19:</p> Test What it catches <code>TestNoLiteralWhitespace</code> <code>\"\\n\"</code>, <code>\"\\t\"</code>, <code>'\\r'</code> outside <code>config/token/</code> <code>TestNoNakedErrors</code> <code>fmt.Errorf</code>/<code>errors.New</code> outside <code>internal/err/</code> <code>TestNoStrayErrFiles</code> <code>err.go</code> files outside <code>internal/err/</code> <code>TestNoRawLogging</code> <code>fmt.Fprint*(os.Stderr)</code>, <code>log.Print*</code> outside <code>internal/log/</code> <code>TestNoInlineSeparators</code> <code>strings.Join</code> with literal separator arg <code>TestNoStringConcatPaths</code> Path-like variables built with <code>+</code> <code>TestNoStutteryFunctions</code> <code>write.WriteJournal</code> repeats package name <code>TestDocComments</code> Missing doc comments on any declaration <code>TestNoMagicValues</code> Numeric literals outside const definitions <code>TestNoMagicStrings</code> String literals outside const definitions <code>TestLineLength</code> Lines exceeding 80 characters <code>TestNoRegexpOutsideRegexPkg</code> <code>regexp.MustCompile</code> outside <code>config/regex/</code> <p>Plus the six that preceded the session: <code>TestNoErrorsAs</code>, <code>TestNoCmdPrintOutsideWrite</code>, <code>TestNoExecOutsideExecPkg</code>, <code>TestNoInlineRegexpCompile</code>, <code>TestNoRawFileIO</code>, <code>TestNoRawPermissions</code>.</p> <p>The migration touched 300+ files across 25 commits.</p> <p>Not because the tests were hard to write, but because every test we wrote revealed violations that needed fixing.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-tightening-loop","level":2,"title":"The Tightening Loop","text":"<p>The most instructive part was not writing the tests. It was the iterative tightening.</p> <p>The following process was repeated for every test:</p> <ol> <li>Write the test with reasonable exemptions</li> <li>Run it, see violations</li> <li>Fix the violations (migrate to config constants)</li> <li>The human reviews the result</li> <li>The human spots something the test missed</li> <li>Fix the test first, verify it catches the issue</li> <li>Fix the newly caught violations</li> <li>Repeat from step 4</li> </ol> <p>This loop drove the tests from \"basically correct\" to \"actually useful\". </p> <p>Three examples:</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-1-the-local-const-loophole","level":3,"title":"Example 1: The Local Const Loophole","text":"<p><code>TestNoMagicValues</code> initially exempted local constants inside function bodies. This let code like this pass:</p> <pre><code>const descMaxWidth = 70\ndesc := truncateDescription(\n    meta.Description, descMaxWidth,\n)\n</code></pre> <p>The test saw a <code>const</code> definition and moved on. But <code>const descMaxWidth = 70</code> on the line before its only use is just renaming a magic number. The <code>70</code> should live in <code>config/format/TruncateDescription</code> where it is discoverable, reusable, and auditable.</p> <p>We removed the local const exemption. The test caught it. The value moved to config.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-2-the-single-character-dodge","level":3,"title":"Example 2: The Single-Character Dodge","text":"<p><code>TestNoMagicStrings</code> initially exempted all single-character strings as  \"structural punctuation\". </p> <p>This  let <code>\"/\"</code>, <code>\"-\"</code>, and <code>\".\"</code> pass everywhere.</p> <p>But <code>\"/\"</code> is a directory separator. It is OS-specific and a security surface. </p> <p><code>\"-\"</code> used in <code>strings.Repeat(\"-\", width)</code> is creating visual output, not acting as a delimiter. </p> <p><code>\".\"</code> in <code>strings.SplitN(ver, \".\", 3)</code> is a version separator.</p> <p>None of these are \"just punctuation\":  They are domain values with specific meanings.</p> <p>We removed the blanket exemption:  30 violations surfaced. </p> <p>Every one was a real magic value that should have been  <code>token.Slash</code>, <code>token.Dash</code>, or <code>token.Dot</code>.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-3-the-replacer-versus-regex","level":3,"title":"Example 3: The Replacer versus Regex","text":"<p>After migrating magic strings, we had this:</p> <pre><code>func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        token.Slash, token.Underscore,\n        token.Dot, token.Underscore,\n        token.Dash, token.Underscore,\n    )\n    return r.Replace(pkg)\n}\n</code></pre> <p>Six token references and a <code>NewReplacer</code> allocation. The magic values were gone, but we had replaced them with token soup: structure without abstraction. </p> <p>The correct tool was a regex:</p> <pre><code>// In config/regex/file.go:\nvar MermaidUnsafe = regexp.MustCompile(`[/.\\-]`)\n\n// In the caller:\nfunc MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n</code></pre> <p>One config regex, one call. The regex lives in <code>config/regex/file.go</code> where every other compiled pattern lives. An agent reading the code sees <code>regex.MermaidUnsafe</code> and immediately knows: this is a sanitization pattern, it lives in the regex registry, and it has a name that explains its purpose.</p> <p>Clean is better than clever.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#a-before-and-after","level":2,"title":"A Before-and-After","text":"<p>To make the agent-readability claim concrete, consider one function through the full transformation.</p> <p>Before (the code we started with):</p> <pre><code>func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        \"/\", \"_\", \".\", \"_\", \"-\", \"_\",\n    )\n    return r.Replace(pkg)\n}\n</code></pre> <p>An agent reading this sees six string literals. To understand what the function does, it must: (1) parse the <code>NewReplacer</code> pair semantics, (2) infer that <code>/</code>, <code>.</code>, <code>-</code> are being replaced, (3) guess why, (4) hope the guess is right.</p> <p>There is nothing to follow. No import to trace. No name to search. The meaning is locked inside the function body.</p> <p>After (the code we ended with):</p> <pre><code>func MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n</code></pre> <p>An agent reading this sees two named references: <code>regex.MermaidUnsafe</code> and <code>token.Underscore</code>. </p> <p>To understand the function, it can: (1) look up <code>MermaidUnsafe</code> in <code>config/regex/file.go</code> and see the pattern <code>[/.\\-]</code> with a doc comment explaining it matches invalid Mermaid characters, (2) look up <code>Underscore</code> in <code>config/token/delim.go</code> and see it is the replacement character.</p> <p>The agent now has: a named pattern, a named replacement, a package location, documentation, and neighboring context (other regex patterns, other delimiters). </p> <p>It got all of this for free by following just two references.</p> <p>The indirection is not an overhead. It is the retrieval query.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-principles","level":2,"title":"The Principles","text":"<p>You are not just improving code quality. You are shaping the input space that determines how an LLM can reason about your system.</p> <p>Every structural constraint we enforce converts implicit semantics into explicit structure. </p> <p>LLMs struggle when meaning is implicit and patterns are statistical. </p> <p>They thrive when meaning is explicit and structure is navigable.</p> <p>Here is what we learned, organized into three categories.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#cognitive-constraints","level":3,"title":"Cognitive Constraints","text":"<p>These force agents (and humans) to think harder.</p> <p>Indirection acts as a built-in retrieval mechanism:</p> <p>Moving magic values to config forces the agent to follow the reference. <code>errMemory.WriteFile(cause)</code> tells the agent \"there is a memory error package, go look.\" <code>fmt.Errorf(\"writing MEMORY.md: %w\", cause)</code> inlines everything and makes the call graph invisible. The indirection IS the retrieval query.</p> <p>Unfamiliar patterns force reasoning:</p> <p>When an agent sees <code>token.Slash</code> instead of <code>\"/\"</code>, it cannot coast on corpus frequency. It has to actually look up what <code>token.Slash</code> is, which forces it through the dependency graph, which means it encounters documentation and neighboring constants, which gives it richer context. You are exploiting the agent's weakness (over-reliance on training data) to make it behave more carefully.</p> <p>Documentation helps everyone:</p> <p>Extensive documentation helps humans reading the code, agents reasoning about it, and RAG systems indexing it.</p> <p>Our <code>TestDocComments</code> check added 308 doc comments in one commit. Every function, every type, every constant block now has a doc comment. </p> <p>This is not busywork: it is the content that agents and embeddings consume.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#structural-constraints","level":3,"title":"Structural Constraints","text":"<p>These shape the codebase into a navigable graph.</p> <p>Shorter files save tokens:</p> <p>Forcing private helper functions out of main files makes the main file shorter. An agent loading a file spends fewer tokens on boilerplate and more on the logic that matters.</p> <p>Fixed-width constraints force decomposition:</p> <p>A function that cannot be expressed in 80 columns is either too deeply nested (extract a helper), has too many parameters (introduce a struct), or has a variable name that is too long (rethink the abstraction). </p> <p>The constraint forces structural improvements that happen to also make the code more parseable.</p> <p>Chunk-friendly structure helps RAG</p> <p>Code intelligence tools chunk files for embedding and retrieval. Short, well-documented, single-responsibility files produce better chunks than monolithic files with mixed concerns. </p> <p>The structural constraints create files that RAG systems can index effectively.</p> <p>Centralization creates debuggable seams:</p> <p>All error handling in <code>internal/err/</code>, all logging in <code>internal/log/</code>, all file operations in <code>internal/io/</code>. One place to debug, one place to test, one place to see patterns. An agent analyzing \"how does this project handle errors\" gets one answer from one package, not 200 scattered <code>fmt.Errorf</code> calls.</p> <p>Private functions become public patterns:</p> <p>When you extract a private function to satisfy a constraint, it often ends up as a semi-public function in a <code>core/</code> package. Then you realize it is generic enough to be factored into a purpose-specific module.</p> <p>The constraint drives discovery of reusable abstractions hiding inside monolithic functions.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#operational-benefits","level":3,"title":"Operational Benefits","text":"<p>These pay dividends in daily development.</p> <p>Single-edit renames:</p> <p>Renaming a flag is one edit to a config constant instead of find-and-replace across 30,000 lines with possible misses. <code>grep token.Slash</code> gives you every place that uses a forward slash semantically.</p> <p><code>grep \"/\"</code> gives you noise.</p> <p>Blast radius containment:</p> <p>When every magic value is a config constant, a search is one result. This matters for impact analysis, security audits, and agents trying to understand \"what uses this\".</p> <p>Compile-time contract enforcement:</p> <p>When <code>err/memory.WriteFile</code> exists, the compiler guarantees the error message exists and the call signature is correct. An inline <code>fmt.Errorf</code> can have a typo in the format string and nothing catches it until runtime. Centralization turns runtime failures into compile errors.</p> <p>Semantic <code>git blame</code>:</p> <p>When <code>token.Slash</code> is used everywhere and someone changes its value, <code>git blame</code> on the config file shows exactly when and why. </p> <p>With inline <code>\"/\"</code> scattered  across 30 files, the history is invisible.</p> <p>Test surface reduction:</p> <p>Centralizing into <code>internal/err/</code>, <code>internal/io/</code>, <code>internal/config/</code> means you test behavior once at the boundary and trust the callers. </p> <p>You do not need 30 tests for 30 <code>fmt.Errorf</code> calls. You need 1 test for <code>errMemory.WriteFile</code> and 30 trivial call-site audits, which is exactly what these AST tests provide.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-numbers","level":2,"title":"The Numbers","text":"<p>One session. 25 commits. The raw stats:</p> Metric Count New audit tests 13 Total audit tests 19 Files touched 300+ Magic values migrated 90+ Functions renamed 17 Doc comments added 323 Lines rewrapped to 80 chars 190 Config constants created 40+ Config regexes created 3 <p>Every number represents a violation that existed before the test caught it. The tests did not create work: they revealed work that was already needed.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-uncomfortable-implication","level":2,"title":"The Uncomfortable Implication","text":"<p>None of this is Go-specific.</p> <p>If an AI agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.</p> <p>If your error messages are scattered across 200 files, an agent cannot reason about error handling as a concept. If your magic values are inlined, an agent cannot distinguish \"this is a path separator\" from \"this is a division operator.\" If your functions are named <code>write.WriteJournal</code>, the agent wastes tokens on redundant information.</p> <p>What we discovered, through the unglamorous work of writing lint tests and migrating string literals, is that the structural constraints software engineering has valued for decades are exactly the constraints that make code readable to machines.</p> <p>This is not a coincidence: These constraints exist because they reduce the cognitive load of understanding code. </p> <p>Agents have cognitive load too: It is called the context window.</p> <p>You are not converting code to a new paradigm.</p> <p>You are making the latent graph visible.</p> <p>You are converting implicit semantics into explicit structure that both humans and machines can traverse.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#whats-next","level":2,"title":"What's Next","text":"<p>The spec lists 8 more tests we have not built yet, including <code>TestDescKeyYAMLLinkage</code> (verifying that every DescKey constant has a corresponding YAML entry), <code>TestCLICmdStructure</code> (enforcing the <code>cmd.go</code> / <code>run.go</code> / <code>doc.go</code> file convention), and <code>TestNoFlagBindOutsideFlagbind</code> (which requires migrating ~50 flag registration sites first).</p> <p>The broader question: should these principles be codified as a reusable linting framework? The patterns (<code>loadPackages</code> + <code>ast.Inspect</code> + violation collection) are generic. </p> <p>The specific checks are project-specific. But the categories of checks (centralization enforcement, magic value detection, naming conventions, documentation requirements) are universal.</p> <p>For now, 19 tests in <code>internal/audit/</code> is enough. They run in 2 seconds as part of <code>go test ./...</code>. They catch real issues. </p> <p>And they encode a theory of code quality that serves both humans and the agents that work alongside them.</p> <p>Agents are not going away. They are reading your code right now, forming representations of your system in context windows that forget everything between sessions.</p> <p>The codebases that structure themselves for that reality will compound. The ones that do not will slowly become illegible to the tools they depend on.</p> <p>Structure is no longer just for maintainability. It is for reasonability.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/","level":1,"title":"The Watermelon-Rind Anti-Pattern","text":"","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#why-smarter-tools-make-shallower-agents","level":2,"title":"Why Smarter Tools Make Shallower Agents","text":"<p>Give an agent a graph query tool, and it will tell you everything about your codebase except what actually matters.</p> <p>Volkan Özçelik / April 6, 2026</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#a-turkish-proverb-walks-into-a-codebase","level":2,"title":"A Turkish Proverb Walks into a Codebase","text":"<p>There's a Turkish idiom: esegin aklina karpuz kabugu sokmak (literally, \"to put watermelon rind into a donkey's mind.\" It means to plant an idea in someone's head that they wouldn't have come up with on their own) usually one that leads them astray.</p> <p>In English, let's call this a \"watermelon metric\": a project management term  for something that's green on the outside and red on the inside: all dashboards passing, reality crumbling.</p> <p>Both halves of this metaphor showed up in a single experiment. And the result changed how we design architecture analysis in [<code>ctx</code>][<code>ctx</code>].</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-experiment","level":2,"title":"The Experiment","text":"<p>We ran three sessions analyzing the same large codebase (~34,000 symbols) using the same architecture skill, varying only what tools the agent had access to.</p> Session Tools Available Output (lines) Character 1 None (MCP broken) 5,866 Deep, intimate 2 Full graph MCP 1,124 Structural, correct 3 Enrichment pass +verified data Additive, not restorative <p>Session 1 was an accident. The MCP server that provides code intelligence queries was broken, so the agent couldn't ask the graph anything. It had to read code. Line by line. File by file.</p> <p>It produced 5,866 lines of architecture analysis: per-controller data flows, scale math, startup sequences, timeout defaults, edge cases that only surface when you actually look at the implementation.</p> <p>Session 2 had working tools. Same skill, same codebase. The agent produced 1,124 lines (5.2x less). Structurally correct. Valid symbol references. Proper call chains.</p> <p>And hollow.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-rind","level":2,"title":"The Rind","text":"<p>The Session 2 output was a watermelon rind: the right shape, the right color, the right texture on the outside. But the substance (the operational details, the defaults nobody documents, the scale math that tells you when a component will fall over) was missing.</p> <p>Not wrong. Not broken. Just... thin.</p> <p>The agent had answered every question correctly. The problem was that it never discovered the questions it should have asked. When you can query a graph for \"what calls this function?\", you don't stumble into the retry loop that silently swallows errors three layers down. When you can ask for the dependency tree, you don't notice that two packages share a mutable state through a global variable that isn't in any interface.</p> <p>The tool answered the question asked but prevented the discovery of answers to questions never asked.</p> <p>Here's what that looks like concretely: the graph tells you that <code>ReconcileDeployment</code> calls <code>SyncPods</code>. It does not tell you that <code>SyncPods</code> retries three times with exponential backoff, silently drops errors after timeout, and resets a package-level counter that another goroutine reads without a lock. The call chain is correct.</p> <p>The operational reality is invisible.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-donkeys-idea","level":2,"title":"The Donkey's Idea","text":"<p>This is where the Turkish proverb earns its place: The graph tool is the \"karpuz kabugu\" (the watermelon rind placed into the agent's mind). </p> <p>Before the tool existed, the agent had no choice but to read deeply.  With the tool available, a new idea appears: why read 500 lines of code when  I can query the call graph?</p> <p>The agent isn't lazy. It's rational. </p> <p>Graph queries are faster, more reliable, and produce verifiably correct output.  The agent is optimizing. It's satisficing (finding answers that are good enough), instead of maximizing (finding everything there is to know).</p> <p>Satisficing produces watermelon rinds.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-two-pass-compiler","level":2,"title":"The Two-Pass Compiler","text":"<p>Session 3 taught us that you can't fix shallow analysis by adding more tools after the fact. The enrichment pass added verified graph data (blast radius numbers, registration sites, execution flow confirmation) but it couldn't recover the intimate code knowledge that Session 1 had produced through sheer necessity.</p> <p>You can't enrich your way out of a depth deficit.</p> <p>So we redesigned. Instead of one skill with optional tools, we built a two-pass compiler for architecture understanding:</p> <p>Pass 1: Semantic parsing. The <code>/ctx-architecture</code> skill deliberately has no access to graph query tools. The agent must read code, build mental models, and produce architecture artifacts through human-style comprehension. Constraint is the feature.</p> <p>Pass 2: Static analysis. The <code>/ctx-architecture-enrich</code> skill takes Pass 1 output as input and runs comprehensive verification through code intelligence: blast radius analysis, registration site discovery, execution flow tracing, domain clustering comparison. It extends and verifies, but it doesn't replace.</p> <p>The key insight: these must be separate skills with separate tool permissions. If you give the agent graph tools during Pass 1, it will use them. The \"karpuz kabugu\" will be in its mind. The only way to prevent satisficing is to remove the option.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-principle","level":2,"title":"The Principle","text":"<p>We call this constraint-as-feature: deliberately withholding capabilities to force deeper engagement.</p> <p>It sounds paradoxical. You built sophisticated code intelligence tools and then... forbid the agent from using them? During the most important phase?</p> <p>Yes. Because the tools don't make the agent smarter. They make it faster. And faster, in architecture analysis, is the enemy of deep.</p> <p>What's actually happening is subtler: tools reduce the agent's search space. A graph query collapses thousands of possible observations into one precise answer. That's efficient for known questions. But architecture understanding depends on unknown unknowns: and you only find those by wandering through code with nothing to shortcut the journey.</p> <p>The constraint forces the agent into a mode of operation that produces better output than any amount of tooling can achieve. The limitation is the capability.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#when-does-this-apply","level":2,"title":"When Does This Apply?","text":"<p>Not always. The watermelon-rind antipattern is specific to exploratory analysis: tasks where the value comes from discovering unknowns, not from answering known questions.</p> <p>Graph tools are excellent for:</p> <ul> <li>Verification: \"Does X actually call Y?\" (binary question,   precise answer)</li> <li>Impact analysis: \"What breaks if I change Z?\" (bounded scope,   enumerable results)</li> <li>Navigation: \"Where is this interface implemented?\" (lookup,   not analysis)</li> </ul> <p>Graph tools produce watermelon rinds when:</p> <ul> <li>The goal is understanding, not answering</li> <li>The unknowns are unknown: you don't know what to ask</li> <li>Depth matters more than breadth: operational details,   edge cases, implicit coupling</li> </ul> <p>The two-pass approach preserves both: deep reading first, tool verification second.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#takeaway","level":2,"title":"Takeaway","text":"<p>The two-pass approach is the slowest way to analyze a codebase. It is also the only way that produces both depth and accuracy. We accept the cost because architecture analysis is not a speed game: it is a coverage game.</p> <p>Esegin aklina karpuz kabugu sokma!</p> <p>(don't put the watermelon rind to a donkey's mind)</p> <p>If the agent never struggles, it never discovers. And if it never discovers, you are not doing architecture; you are doing autocomplete.</p> <p>This post is part of the <code>ctx</code> field notes series, documenting what we learn building persistent context infrastructure for AI coding sessions.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"cli/","level":1,"title":"CLI","text":"","path":["CLI"],"tags":[]},{"location":"cli/#ctx-cli","level":2,"title":"<code>ctx</code> CLI","text":"<p>Complete reference for all <code>ctx</code> commands, grouped by function.</p>","path":["CLI"],"tags":[]},{"location":"cli/#global-options","level":2,"title":"Global Options","text":"<p>All commands support these flags:</p> Flag Description <code>--help</code> Show command help <code>--version</code> Show version <code>--tool <name></code> Override active AI tool identifier (e.g. <code>kiro</code>, <code>cursor</code>) <p>Tell <code>ctx</code> which <code>.context/</code> to use. <code>ctx</code> does not search the filesystem for <code>.context/</code>: you have to declare it. Three ways:</p> <ul> <li><code>eval \"$(ctx activate)\"</code> (recommended): binds <code>CTX_DIR</code> for the   current shell.</li> <li><code>export CTX_DIR=/abs/path/to/.context</code> directly, then run any   <code>ctx</code> command.</li> <li><code>CTX_DIR=/abs/path/to/.context ctx <command></code> inline, for a   one-shot or CI step.</li> </ul> <p><code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (<code>export CTX_DIR=$(pwd)</code>) before stray writes can leak to the project root.</p> <p>If you forget, commands fail fast with a linkable <code>Error: no context directory specified</code> pointing at Activating a Context Directory. A handful of commands run without a declaration because they don't need a project: <code>ctx init</code>, <code>ctx activate</code>, <code>ctx deactivate</code>, <code>ctx version</code>, <code>ctx help</code>, <code>ctx system bootstrap</code>, <code>ctx doctor</code>, <code>ctx guide</code>, <code>ctx why</code>, <code>ctx config switch/status</code>, and <code>ctx hub *</code>.</p> <p>Initialization required. Once declared, the target must already have been initialized by <code>ctx init</code> (otherwise commands return <code>ctx: not initialized</code>).</p>","path":["CLI"],"tags":[]},{"location":"cli/#getting-started","level":2,"title":"Getting Started","text":"Command Description <code>ctx init</code> Initialize <code>.context/</code> directory with templates <code>ctx activate</code> Emit <code>export CTX_DIR=...</code> to bind context for the shell <code>ctx deactivate</code> Emit <code>unset CTX_DIR</code> to clear the binding <code>ctx status</code> Show context summary (files, tokens, drift) <code>ctx guide</code> Quick-reference cheat sheet <code>ctx why</code> Read the philosophy behind <code>ctx</code>","path":["CLI"],"tags":[]},{"location":"cli/#context","level":2,"title":"Context","text":"Command Description <code>ctx load</code> Output assembled context in read order <code>ctx agent</code> Print token-budgeted context packet for AI consumption <code>ctx sync</code> Reconcile context with codebase state <code>ctx drift</code> Detect stale paths, secrets, missing files <code>ctx compact</code> Archive completed tasks, clean up files <code>ctx fmt</code> Format context files to 80-char line width <code>ctx task</code> Add tasks, mark complete, archive, snapshot <code>ctx decision</code> Add decisions and reindex <code>DECISIONS.md</code> <code>ctx learning</code> Add learnings and reindex <code>LEARNINGS.md</code> <code>ctx convention</code> Add conventions to <code>CONVENTIONS.md</code> <code>ctx reindex</code> Regenerate indices for <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> <code>ctx permission</code> Permission snapshots (golden image) <code>ctx change</code> Show what changed since last session <code>ctx memory</code> Bridge Claude Code auto memory into <code>.context/</code> <code>ctx watch</code> Auto-apply context updates from AI output <code>ctx kb</code> Knowledge-base editorial pipeline (Phase KB) <code>ctx handover</code> Write the per-session handover that the next session reads","path":["CLI"],"tags":[]},{"location":"cli/#sessions","level":2,"title":"Sessions","text":"Command Description <code>ctx journal</code> Browse, import, enrich, and lock session history <code>ctx pad</code> Encrypted scratchpad for sensitive one-liners <code>ctx remind</code> Session-scoped reminders that surface at session start <code>ctx hook pause</code> Pause context hooks for the current session <code>ctx hook resume</code> Resume paused context hooks","path":["CLI"],"tags":[]},{"location":"cli/#integrations","level":2,"title":"Integrations","text":"Command Description <code>ctx setup</code> Generate AI tool integration configs <code>ctx steering</code> Manage steering files (behavioral rules for AI tools) <code>ctx trigger</code> Manage lifecycle triggers (scripts for automation) <code>ctx skill</code> Manage reusable instruction bundles <code>ctx mcp</code> MCP server for AI tool integration (stdin/stdout) <code>ctx hook notify</code> Webhook notifications (setup, test, send) <code>ctx loop</code> Generate autonomous loop script <code>ctx connection</code> Client-side commands for connecting to a <code>ctx</code> Hub <code>ctx hub</code> Operate a <code>ctx</code> Hub server or cluster <code>ctx serve</code> Serve a static site locally via zensical <code>ctx site</code> Site management (feed generation)","path":["CLI"],"tags":[]},{"location":"cli/#diagnostics","level":2,"title":"Diagnostics","text":"Command Description <code>ctx doctor</code> Structural health check (hooks, drift, config) <code>ctx trace</code> Show context behind git commits <code>ctx sysinfo</code> Show system resource usage (memory, swap, disk, load) <code>ctx usage</code> Show session token usage stats","path":["CLI"],"tags":[]},{"location":"cli/#runtime","level":2,"title":"Runtime","text":"Command Description <code>ctx config</code> Manage runtime configuration profiles <code>ctx prune</code> Clean stale per-session state files <code>ctx hook</code> Hook message, notification, and lifecycle controls <code>ctx system</code> Hook plumbing and agent-only commands (not user-facing)","path":["CLI"],"tags":[]},{"location":"cli/#shell","level":2,"title":"Shell","text":"Command Description <code>ctx completion</code> Generate shell autocompletion scripts","path":["CLI"],"tags":[]},{"location":"cli/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error / warnings (e.g. drift) 2 Context not found 3 Violations found (e.g. drift) 4 File operation error","path":["CLI"],"tags":[]},{"location":"cli/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description <code>CTX_DIR</code> Override default context directory path <code>CTX_TOKEN_BUDGET</code> Override default token budget <code>CTX_SESSION_ID</code> Active AI session ID (used by <code>ctx trace</code> for context linking)","path":["CLI"],"tags":[]},{"location":"cli/#configuration-file","level":2,"title":"Configuration File","text":"<p>Optional <code>.ctxrc</code> (YAML format) at project root:</p> <pre><code># .ctxrc\ntoken_budget: 8000           # Default token budget\npriority_order:              # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true           # Auto-archive old items\narchive_after_days: 7        # Days before archiving tasks\nscratchpad_encrypt: true     # Encrypt scratchpad (default: true)\nevent_log: false             # Enable local hook event logging\ncompanion_check: true        # Check companion tools at session start\nentry_count_learnings: 30    # Drift warning threshold (0 = disable)\nentry_count_decisions: 20    # Drift warning threshold (0 = disable)\nconvention_line_count: 200   # Line count warning for CONVENTIONS.md (0 = disable)\ninjection_token_warn: 15000  # Oversize injection warning (0 = disable)\ncontext_window: 200000       # Auto-detected for Claude Code; override for other tools\nbilling_token_warn: 0        # One-shot billing warning at this token count (0 = disabled)\nkey_rotation_days: 90        # Days before key rotation nudge\nsession_prefixes:            # Recognized session header prefixes (extend for i18n)\n  - \"Session:\"               # English (default)\n  # - \"Oturum:\"              # Turkish (add as needed)\n  # - \"セッション:\"             # Japanese (add as needed)\nfreshness_files:             # Files with technology-dependent constants (opt-in)\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # Optional\nnotify:                      # Webhook notification settings\n  events:                    # Required: only listed events fire\n    - loop\n    - nudge\n    - relay\n    # - heartbeat            # Every-prompt session-alive signal\ntool: \"\"                     # Active AI tool: claude, cursor, cline, kiro, codex\nsteering:                    # Steering layer configuration\n  dir: .context/steering     # Steering files directory\n  default_inclusion: manual  # Default inclusion mode (always, auto, manual)\n  default_tools: []          # Default tool filter for new steering files\nhooks:                       # Hook system configuration\n  dir: .context/hooks        # Hook scripts directory\n  timeout: 10                # Per-hook execution timeout in seconds\n  enabled: true              # Whether hook execution is enabled\n</code></pre> Field Type Default Description <code>token_budget</code> <code>int</code> <code>8000</code> Default token budget for <code>ctx agent</code> <code>priority_order</code> <code>[]string</code> (all files) File loading priority for context packets <code>auto_archive</code> <code>bool</code> <code>true</code> Auto-archive completed tasks <code>archive_after_days</code> <code>int</code> <code>7</code> Days before completed tasks are archived <code>scratchpad_encrypt</code> <code>bool</code> <code>true</code> Encrypt scratchpad with AES-256-GCM <code>event_log</code> <code>bool</code> <code>false</code> Enable local hook event logging to <code>.context/state/events.jsonl</code> <code>companion_check</code> <code>bool</code> <code>true</code> Check companion tool availability (Gemini Search, GitNexus) during <code>/ctx-remember</code> <code>entry_count_learnings</code> <code>int</code> <code>30</code> Drift warning when <code>LEARNINGS.md</code> exceeds this count <code>entry_count_decisions</code> <code>int</code> <code>20</code> Drift warning when <code>DECISIONS.md</code> exceeds this count <code>convention_line_count</code> <code>int</code> <code>200</code> Line count warning for <code>CONVENTIONS.md</code> <code>injection_token_warn</code> <code>int</code> <code>15000</code> Warn when auto-injected context exceeds this token count (0 = disable) <code>context_window</code> <code>int</code> <code>200000</code> Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools <code>billing_token_warn</code> <code>int</code> <code>0</code> (off) One-shot warning when session tokens exceed this threshold (0 = disabled) <code>key_rotation_days</code> <code>int</code> <code>90</code> Days before encryption key rotation nudge <code>session_prefixes</code> <code>[]string</code> <code>[\"Session:\"]</code> Recognized Markdown session header prefixes. Extend to parse sessions written in other languages <code>freshness_files</code> <code>[]object</code> (none) Files to track for staleness (path, desc, optional review_url). Hook warns after 6 months without modification <code>notify.events</code> <code>[]string</code> (all) Event filter for webhook notifications (empty = all) <code>tool</code> <code>string</code> (empty) Active AI tool identifier (<code>claude</code>, <code>cursor</code>, <code>cline</code>, <code>kiro</code>, <code>codex</code>) <code>steering.dir</code> <code>string</code> <code>.context/steering</code> Steering files directory <code>steering.default_inclusion</code> <code>string</code> <code>manual</code> Default inclusion mode for new steering files (<code>always</code>, <code>auto</code>, <code>manual</code>) <code>steering.default_tools</code> <code>[]string</code> (all) Default tool filter for new steering files (empty = all tools) <code>hooks.dir</code> <code>string</code> <code>.context/hooks</code> Hook scripts directory <code>hooks.timeout</code> <code>int</code> <code>10</code> Per-hook execution timeout in seconds <code>hooks.enabled</code> <code>bool</code> <code>true</code> Whether hook execution is enabled <p>Priority order: CLI flags > Environment variables > <code>.ctxrc</code> > Defaults</p> <p>All settings are optional. Missing values use defaults.</p>","path":["CLI"],"tags":[]},{"location":"cli/bootstrap/","level":1,"title":"System Bootstrap","text":"","path":["System Bootstrap"],"tags":[]},{"location":"cli/bootstrap/#ctx-system-bootstrap","level":3,"title":"<code>ctx system bootstrap</code>","text":"<p>Print the resolved context directory path so AI agents can anchor their session. The default output lists the context directory, the tracked context files, and a short health snapshot. <code>--quiet</code> prints just the path; <code>--json</code> produces structured output for automation.</p> <p>This is a hidden, agent-only command that agents are instructed to run first in their session-start procedure; it is the authoritative answer to \"where does this project's context live?\".</p> <pre><code>ctx system bootstrap [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>-q</code>, <code>--quiet</code> Output only the context directory path <code>--json</code> Output in JSON format <p>Examples:</p> <pre><code>ctx system bootstrap                 # Text output for agents\nctx system bootstrap -q              # Just the context directory path\nctx system bootstrap --json          # Structured output for automation\n</code></pre> <p>Note: <code>-q</code> prints just the resolved directory path. See Activating a Context Directory if you hit a \"no context directory specified\" error.</p>","path":["System Bootstrap"],"tags":[]},{"location":"cli/change/","level":1,"title":"Change","text":"","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/change/#ctx-change","level":2,"title":"<code>ctx change</code>","text":"<p>Show what changed in context files and code since your last session.</p> <p>Automatically detects the previous session boundary from state markers or event log. Useful at session start to quickly see what moved while you were away.</p> <pre><code>ctx change [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--since</code> Time reference: duration (<code>24h</code>) or date (<code>2026-03-01</code>) <p>Reference time detection (priority order):</p> <ol> <li><code>--since</code> flag (duration, date, or RFC3339 timestamp)</li> <li><code>ctx-loaded-*</code> marker files in <code>.context/state/</code> (second most recent)</li> <li>Last <code>context-load-gate</code> event from <code>.context/state/events.jsonl</code></li> <li>Fallback: 24 hours ago</li> </ol> <p>Examples:</p> <pre><code># Auto-detect last session, show what changed\nctx change\n\n# Changes in the last 48 hours\nctx change --since 48h\n\n# Changes since a specific date\nctx change --since 2026-03-10\n</code></pre> <p>Output:</p> <pre><code>## Changes Since Last Session\n\n**Reference point**: 6 hours ago\n\n### Context File Changes\n- `TASKS.md` - modified 2026-03-12 14:30\n- `DECISIONS.md` - modified 2026-03-12 09:15\n\n### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n</code></pre> <p>Context file changes are detected by filesystem mtime (works without git). Code changes use <code>git log --since</code> (empty when not in a git repo).</p> <p>See also: Reviewing Session Changes.</p>","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/completion/","level":1,"title":"Completion","text":"","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#ctx-completion","level":2,"title":"<code>ctx completion</code>","text":"<p>Generate shell autocompletion scripts.</p> <pre><code>ctx completion <shell>\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#subcommands","level":3,"title":"Subcommands","text":"Shell Command <code>bash</code> <code>ctx completion bash</code> <code>zsh</code> <code>ctx completion zsh</code> <code>fish</code> <code>ctx completion fish</code> <code>powershell</code> <code>ctx completion powershell</code> <p>Examples:</p> <pre><code>ctx completion bash > /etc/bash_completion.d/ctx\nctx completion zsh  > \"${fpath[1]}/_ctx\"\nctx completion fish > ~/.config/fish/completions/ctx.fish\nctx completion powershell | Out-String | Invoke-Expression\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#installation","level":3,"title":"Installation","text":"BashZshFishPowerShell <pre><code># Add to ~/.bashrc\nsource <(ctx completion bash)\n</code></pre> <pre><code># Add to ~/.zshrc\nsource <(ctx completion zsh)\n</code></pre> <pre><code>ctx completion fish | source\n# Or save to completions directory\nctx completion fish > ~/.config/fish/completions/ctx.fish\n</code></pre> <pre><code># Add to your PowerShell profile\nctx completion powershell | Out-String | Invoke-Expression\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/config/","level":1,"title":"Config","text":"","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config","level":3,"title":"<code>ctx config</code>","text":"<p>Manage runtime configuration profiles.</p> <pre><code>ctx config <subcommand>\n</code></pre> <p>The <code>ctx</code> repo ships two <code>.ctxrc</code> source profiles (<code>.ctxrc.base</code> and <code>.ctxrc.dev</code>). The working copy (<code>.ctxrc</code>) is gitignored and switched between them using subcommands below.</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-switch","level":4,"title":"<code>ctx config switch</code>","text":"<p>Switch between <code>.ctxrc</code> configuration profiles.</p> <pre><code>ctx config switch [dev|base]\n</code></pre> <p>With no argument, toggles between dev and base. Accepts <code>prod</code> as an alias for <code>base</code>.</p> Argument Description <code>dev</code> Switch to dev profile (verbose logging) <code>base</code> Switch to base profile (all defaults) (none) Toggle to the opposite profile <p>Profiles:</p> Profile Description <code>dev</code> Verbose logging, webhook notifications on <code>base</code> All defaults, notifications off <p>Examples:</p> <pre><code>ctx config switch dev     # Switch to dev profile\nctx config switch base    # Switch to base profile\nctx config switch         # Toggle (dev → base or base → dev)\nctx config switch prod    # Alias for \"base\"\n</code></pre> <p>The detection heuristic checks for an uncommented <code>notify:</code> line in <code>.ctxrc</code>: present means dev, absent means base.</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-status","level":4,"title":"<code>ctx config status</code>","text":"<p>Show which <code>.ctxrc</code> profile is currently active.</p> <pre><code>ctx config status\n</code></pre> <p>Output examples:</p> <pre><code>active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n</code></pre> <p>See also: Configuration, Contributing: Configuration Profiles</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/connect/","level":1,"title":"Connect","text":"","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect","level":2,"title":"<code>ctx connect</code>","text":"<p>Connect a project to a <code>ctx</code> Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.</p> <p>New to the Hub?</p> <p>Start with the <code>ctx</code> Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.</p> <p>The unit of identity is a project, not a user. Registering a directory with <code>ctx connect register</code> binds a per-project client token in <code>.context/.connect.enc</code>. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.</p> <p>Only structured entries flow through the hub: <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Session journals, scratchpad contents, and other local state stay on the machine that created them.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-register","level":3,"title":"<code>ctx connect register</code>","text":"<p>One-time registration with a hub. Requires the hub address and admin token (printed by <code>ctx hub start</code> on first run).</p> <pre><code>ctx connect register localhost:9900 --token ctx_adm_7f3a...\n</code></pre> <p>On success, stores an encrypted connection config in <code>.context/.connect.enc</code> for future RPCs.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-subscribe","level":3,"title":"<code>ctx connect subscribe</code>","text":"<p>Set which entry types to receive from the hub. Only matching types are returned by sync and listen.</p> <pre><code>ctx connect subscribe decision learning\nctx connect subscribe decision learning convention\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-sync","level":3,"title":"<code>ctx connect sync</code>","text":"<p>Pull matching entries from the hub and write them to <code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <pre><code>ctx connect sync\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-publish","level":3,"title":"<code>ctx connect publish</code>","text":"<p>Push entries to the hub. Specify type and content as arguments.</p> <pre><code>ctx connect publish decision \"Use UTC timestamps everywhere\"\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-listen","level":3,"title":"<code>ctx connect listen</code>","text":"<p>Stream new entries from the hub in real-time. Writes to <code>.context/hub/</code> as entries arrive. Press Ctrl-C to stop.</p> <pre><code>ctx connect listen\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-status","level":3,"title":"<code>ctx connect status</code>","text":"<p>Show hub connection state and entry statistics.</p> <pre><code>ctx connect status\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"<p>Use <code>--share</code> on <code>ctx add</code> to write locally AND publish to the hub:</p> <pre><code>ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n</code></pre> <p>If the hub is unreachable, the local write succeeds and a warning is printed. The <code>--share</code> flag is best-effort; it never blocks local context updates.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#auto-sync","level":2,"title":"Auto-Sync","text":"<p>Once registered, the <code>check-hub-sync</code> hook automatically syncs new entries from the hub at the start of each session (daily throttled). No manual <code>ctx connect sync</code> needed.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#shared-files","level":2,"title":"Shared Files","text":"<p>Entries from the hub are stored in <code>.context/hub/</code>:</p> <pre><code>.context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n</code></pre> <p>These files are read-only (managed by sync/listen) and never mixed with local context files.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#agent-integration","level":2,"title":"Agent Integration","text":"<p>Include shared knowledge in agent context packets:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.</p>","path":["Connect"],"tags":[]},{"location":"cli/connection/","level":1,"title":"Connect","text":"","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connect","level":2,"title":"<code>ctx connect</code>","text":"<p>Connect a project to a <code>ctx</code> Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.</p> <p>New to the <code>ctx</code> Hub?</p> <p>Start with the <code>ctx</code> Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.</p> <p>The unit of identity is a project, not a user. Registering a directory with <code>ctx connection register</code> binds a per-project client token in <code>.context/.connect.enc</code>. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.</p> <p>Only structured entries flow through the hub: <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Session journals, scratchpad contents, and other local state stay on the machine that created them.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-register","level":3,"title":"<code>ctx connection register</code>","text":"<p>One-time registration with a <code>ctx</code> Hub. Requires the <code>ctx</code> Hub address and admin token (printed by <code>ctx hub start</code> on first run).</p> <p>Examples:</p> <pre><code>ctx connection register localhost:9900 --token ctx_adm_7f3a...\n</code></pre> <p>On success, stores an encrypted connection config in <code>.context/.connect.enc</code> for future RPCs.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-subscribe","level":3,"title":"<code>ctx connection subscribe</code>","text":"<p>Set which entry types to receive from the <code>ctx</code> Hub. Only matching types are returned by sync and listen.</p> <p>Examples:</p> <pre><code>ctx connection subscribe decision learning\nctx connection subscribe decision learning convention\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-sync","level":3,"title":"<code>ctx connection sync</code>","text":"<p>Pull matching entries from the <code>ctx</code> Hub and write them to <code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <p>Examples:</p> <pre><code>ctx connection sync\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-publish","level":3,"title":"<code>ctx connection publish</code>","text":"<p>Push entries to the <code>ctx</code> Hub. Specify type and content as arguments.</p> <p>Examples:</p> <pre><code>ctx connection publish decision \"Use UTC timestamps everywhere\"\nctx connection publish learning \"Go embed requires files in same package\"\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-listen","level":3,"title":"<code>ctx connection listen</code>","text":"<p>Stream new entries from the <code>ctx</code> Hub in real-time. Writes to <code>.context/hub/</code> as entries arrive. Press Ctrl-C to stop.</p> <p>Examples:</p> <pre><code>ctx connection listen\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-status","level":3,"title":"<code>ctx connection status</code>","text":"<p>Show <code>ctx</code> Hub connection state and entry statistics.</p> <p>Examples:</p> <pre><code>ctx connection status\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"<p>Use <code>--share</code> on <code>ctx add</code> to write locally AND publish to the <code>ctx</code> Hub:</p> <pre><code>ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n</code></pre> <p>If the hub is unreachable, the local write succeeds and a warning is printed. The <code>--share</code> flag is best-effort; it never blocks local context updates.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#auto-sync","level":2,"title":"Auto-Sync","text":"<p>Once registered, the <code>check-hub-sync</code> hook automatically syncs new entries from the <code>ctx</code> Hub at the start of each session (daily throttled). No manual <code>ctx connection sync</code> needed.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#shared-files","level":2,"title":"Shared Files","text":"<p>Entries from the <code>ctx</code> Hub are stored in <code>.context/hub/</code>:</p> <pre><code>.context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n</code></pre> <p>These files are read-only (managed by sync/listen) and never mixed with local context files.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#agent-integration","level":2,"title":"Agent Integration","text":"<p>Include shared knowledge in agent context packets:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/context/","level":1,"title":"Context Management","text":"","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#adding-entries","level":3,"title":"Adding entries","text":"<p>Each context-artifact noun (<code>task</code>, <code>decision</code>, <code>learning</code>, <code>convention</code>) owns its own <code>add</code> subcommand under the noun-first command tree:</p> <pre><code>ctx task add <content> [flags]\nctx decision add <content> [flags]\nctx learning add <content> [flags]\nctx convention add <content> [flags]\n</code></pre> <p>Target files:</p> Subcommand Target File <code>ctx task add</code> <code>TASKS.md</code> <code>ctx decision add</code> <code>DECISIONS.md</code> <code>ctx learning add</code> <code>LEARNINGS.md</code> <code>ctx convention add</code> <code>CONVENTIONS.md</code> <p>Flags (shared by every <code>add</code> subcommand; per-noun required-flag rules surface as command errors):</p> Flag Short Description <code>--priority <level></code> <code>-p</code> Priority for tasks: <code>high</code>, <code>medium</code>, <code>low</code> <code>--section <name></code> <code>-s</code> Target section within file <code>--context</code> <code>-c</code> Context (required for decisions and learnings) <code>--rationale</code> <code>-r</code> Rationale for decisions (required for decisions) <code>--consequence</code> Consequence for decisions (required for decisions) <code>--lesson</code> <code>-l</code> Key insight (required for learnings) <code>--application</code> <code>-a</code> How to apply going forward (required for learnings) <code>--file</code> <code>-f</code> Read content from file instead of argument <p>Examples:</p> <pre><code># Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\nctx task add \"Fix login bug\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (requires all ADR (Architectural Decision Record) fields)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning (requires context, lesson, and application)\nctx learning add \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to specific section\nctx convention add \"Use kebab-case for filenames\" --section \"Naming\"\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-drift","level":3,"title":"<code>ctx drift</code>","text":"<p>Detect stale or invalid context.</p> <pre><code>ctx drift [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output machine-readable JSON <code>--fix</code> Auto-fix simple issues <p>Checks:</p> <ul> <li>Path references in <code>ARCHITECTURE.md</code> and <code>CONVENTIONS.md</code> exist</li> <li>Task references are valid</li> <li>Constitution rules aren't violated (heuristic)</li> <li>Staleness indicators (old files, many completed tasks)</li> <li>Missing packages: warns when <code>internal/</code> directories exist on disk but are   not referenced in <code>ARCHITECTURE.md</code> (suggests running <code>/ctx-architecture</code>)</li> <li>Entry count: warns when <code>LEARNINGS.md</code> or <code>DECISIONS.md</code> exceed configurable   thresholds (default: 30 learnings, 20 decisions), or when <code>CONVENTIONS.md</code>   exceeds a line count threshold (default: 200). Configure via <code>.ctxrc</code>:   <pre><code>entry_count_learnings: 30      # warn above this (0 = disable)\nentry_count_decisions: 20      # warn above this (0 = disable)\nconvention_line_count: 200     # warn above this (0 = disable)\n</code></pre></li> </ul> <p>Example:</p> <pre><code>ctx drift\nctx drift --json\nctx drift --fix\n</code></pre> <p>Exit codes:</p> Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-sync","level":3,"title":"<code>ctx sync</code>","text":"<p>Reconcile context with the current codebase state.</p> <pre><code>ctx sync [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Show what would change without modifying <p>What it does:</p> <ul> <li>Scans codebase for structural changes</li> <li>Compares with ARCHITECTURE.md</li> <li>Suggests documenting dependencies if package files exist</li> <li>Identifies stale or outdated context</li> </ul> <p>Example:</p> <pre><code>ctx sync\nctx sync --dry-run\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-compact","level":3,"title":"<code>ctx compact</code>","text":"<p>Consolidate and clean up context files.</p> <ul> <li>Moves completed tasks older than 7 days to the archive</li> <li>Removes empty sections</li> </ul> <pre><code>ctx compact [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--archive</code> Create <code>.context/archive/</code> for old content <p>Example:</p> <pre><code>ctx compact\nctx compact --archive\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-fmt","level":3,"title":"<code>ctx fmt</code>","text":"<p>Format context files to a consistent line width.</p> <p>Wraps long lines in <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, and <code>CONVENTIONS.md</code> at word boundaries. Markdown list items get 2-space continuation indent. Headings, tables, frontmatter, and HTML comments are preserved as-is.</p> <p>Idempotent: running twice produces the same output.</p> <pre><code>ctx fmt [flags]\n</code></pre> <p>Flags:</p> Flag Type Default Description <code>--width</code> <code>int</code> <code>80</code> Target line width <code>--check</code> <code>bool</code> <code>false</code> Check only, exit 1 if files would change <p>Examples:</p> <pre><code>ctx fmt              # format all context files\nctx fmt --check      # CI mode: check without modifying\nctx fmt --width 100  # custom width\n</code></pre> <p>Also available as a Makefile target:</p> <pre><code>make fmt-context\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task","level":3,"title":"<code>ctx task</code>","text":"<p>Manage task completion, archival, and snapshots.</p> <pre><code>ctx task <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-complete","level":4,"title":"<code>ctx task complete</code>","text":"<p>Mark a task as completed.</p> <pre><code>ctx task complete <task-id-or-text>\n</code></pre> <p>Arguments:</p> <ul> <li><code>task-id-or-text</code>: Task number or partial text match</li> </ul> <p>Examples:</p> <pre><code># By text (partial match)\nctx task complete \"user auth\"\n\n# By task number\nctx task complete 3\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-archive","level":4,"title":"<code>ctx task archive</code>","text":"<p>Move completed tasks from <code>TASKS.md</code> to a timestamped archive file.</p> <pre><code>ctx task archive [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Preview changes without modifying files <p>Archive files are stored in <code>.context/archive/</code> with timestamped names (<code>tasks-YYYY-MM-DD.md</code>). Completed tasks (marked with <code>[x]</code>) are moved; pending tasks (<code>[ ]</code>) remain in <code>TASKS.md</code>.</p> <p>Example:</p> <pre><code>ctx task archive\nctx task archive --dry-run\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-snapshot","level":4,"title":"<code>ctx task snapshot</code>","text":"<p>Create a point-in-time snapshot of <code>TASKS.md</code> without modifying the original.</p> <pre><code>ctx task snapshot [name]\n</code></pre> <p>Arguments:</p> <ul> <li><code>name</code>: Optional name for the snapshot (defaults to \"snapshot\")</li> </ul> <p>Snapshots are stored in <code>.context/archive/</code> with timestamped names (<code>tasks-<name>-YYYY-MM-DD-HHMM.md</code>).</p> <p>Example:</p> <pre><code>ctx task snapshot\nctx task snapshot \"before-refactor\"\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission","level":3,"title":"<code>ctx permission</code>","text":"<p>Manage Claude Code permission snapshots.</p> <pre><code>ctx permission <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-snapshot","level":4,"title":"<code>ctx permission snapshot</code>","text":"<p>Save <code>.claude/settings.local.json</code> as the golden image.</p> <pre><code>ctx permission snapshot\n</code></pre> <p>Creates <code>.claude/settings.golden.json</code> as a byte-for-byte copy of the current settings. Overwrites if the golden file already exists.</p> <p>The golden file is meant to be committed to version control and shared with the team.</p> <p>Example:</p> <pre><code>ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-restore","level":4,"title":"<code>ctx permission restore</code>","text":"<p>Replace <code>settings.local.json</code> with the golden image.</p> <pre><code>ctx permission restore\n</code></pre> <p>Prints a diff of dropped (session-accumulated) and restored permissions. No-op if the files already match.</p> <p>Example:</p> <pre><code>ctx permission restore\n# Dropped 3 session permission(s):\n#   - Bash(cat /tmp/debug.log:*)\n#   - Bash(rm /tmp/test-*:*)\n#   - Bash(curl https://example.com:*)\n# Restored from golden image.\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-reindex","level":3,"title":"<code>ctx reindex</code>","text":"<p>Regenerate the quick-reference index for both <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> in a single invocation.</p> <pre><code>ctx reindex\n</code></pre> <p>This is a convenience wrapper around <code>ctx decision reindex</code> and <code>ctx learning reindex</code>. Both files grow at similar rates and users typically want to reindex both after manual edits.</p> <p>The index is a compact table of date and title for each entry, allowing AI tools to scan entries without reading the full file.</p> <p>Example:</p> <pre><code>ctx reindex\n# ✓ Index regenerated with 12 entries\n# ✓ Index regenerated with 8 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision","level":3,"title":"<code>ctx decision</code>","text":"<p>Manage the <code>DECISIONS.md</code> file.</p> <pre><code>ctx decision <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision-reindex","level":4,"title":"<code>ctx decision reindex</code>","text":"<p>Regenerate the quick-reference index at the top of <code>DECISIONS.md</code>.</p> <pre><code>ctx decision reindex\n</code></pre> <p>The index is a compact table showing the date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.</p> <p>Use this after manual edits to <code>DECISIONS.md</code> or when migrating existing files to use the index format.</p> <p>Example:</p> <pre><code>ctx decision reindex\n# ✓ Index regenerated with 12 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning","level":3,"title":"<code>ctx learning</code>","text":"<p>Manage the <code>LEARNINGS.md</code> file.</p> <pre><code>ctx learning <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning-reindex","level":4,"title":"<code>ctx learning reindex</code>","text":"<p>Regenerate the quick-reference index at the top of <code>LEARNINGS.md</code>.</p> <pre><code>ctx learning reindex\n</code></pre> <p>The index is a compact table showing the date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.</p> <p>Use this after manual edits to <code>LEARNINGS.md</code> or when migrating existing files to use the index format.</p> <p>Example:</p> <pre><code>ctx learning reindex\n# ✓ Index regenerated with 8 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/doctor/","level":1,"title":"Doctor","text":"","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#ctx-doctor","level":3,"title":"<code>ctx doctor</code>","text":"<p>Structural health check across context, hooks, and configuration. Runs mechanical checks that don't require semantic analysis. Think of it as <code>ctx status</code> + <code>ctx drift</code> + configuration audit in one pass.</p> <pre><code>ctx doctor [flags]\n</code></pre> <p>Flags:</p> Flag Short Type Default Description <code>--json</code> <code>-j</code> bool <code>false</code> Machine-readable JSON output","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-checks","level":4,"title":"What It Checks","text":"Check Category What it verifies Context initialized Structure <code>.context/</code> directory exists Required files present Structure All required context files exist (<code>TASKS.md</code>, etc.) Drift detected Quality Stale paths, missing files, constitution violations Event logging status Hooks Whether <code>event_log: true</code> is set in <code>.ctxrc</code> Webhook configured Hooks <code>.notify.enc</code> file exists Pending reminders State Count of entries in <code>reminders.json</code> Task completion ratio State Pending vs completed tasks in <code>TASKS.md</code> Context token size Size Estimated token count across all context files Recent event activity Events Last event timestamp (only when event logging is enabled)","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-human","level":4,"title":"Output Format (Human)","text":"<pre><code>ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n</code></pre> <p>Status indicators:</p> Icon Status Meaning ✓ ok Check passed ⚠ warning Non-critical issue worth fixing ✗ error Problem that needs attention ○ info Informational note","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-json","level":4,"title":"Output Format (JSON)","text":"<pre><code>{\n  \"results\": [\n    {\n      \"name\": \"context_initialized\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Context initialized (.context/)\"\n    },\n    {\n      \"name\": \"required_files\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Required files present (4/4)\"\n    },\n    {\n      \"name\": \"drift\",\n      \"category\": \"Quality\",\n      \"status\": \"warning\",\n      \"message\": \"Drift: 2 warnings\"\n    },\n    {\n      \"name\": \"event_logging\",\n      \"category\": \"Hooks\",\n      \"status\": \"info\",\n      \"message\": \"Event logging disabled (enable with event_log: true in .ctxrc)\"\n    },\n    {\n      \"name\": \"webhook\",\n      \"category\": \"Hooks\",\n      \"status\": \"ok\",\n      \"message\": \"Webhook configured\"\n    },\n    {\n      \"name\": \"reminders\",\n      \"category\": \"State\",\n      \"status\": \"ok\",\n      \"message\": \"No pending reminders\"\n    },\n    {\n      \"name\": \"task_completion\",\n      \"category\": \"State\",\n      \"status\": \"warning\",\n      \"message\": \"Tasks: 18/22 completed (82%): consider archiving with ctx task archive\"\n    },\n    {\n      \"name\": \"context_size\",\n      \"category\": \"Size\",\n      \"status\": \"ok\",\n      \"message\": \"Context size: ~4200 tokens (budget: 8000)\"\n    }\n  ],\n  \"warnings\": 2,\n  \"errors\": 0\n}\n</code></pre> <p>Examples:</p> <pre><code># Quick structural health check\nctx doctor\n\n# Machine-readable output for scripting\nctx doctor --json\n\n# Count warnings\nctx doctor --json | jq '.warnings'\n\n# Check for errors only\nctx doctor --json | jq '[.results[] | select(.status == \"error\")]'\n</code></pre>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#when-to-use-what","level":4,"title":"When to Use What","text":"Tool When <code>ctx status</code> Quick glance at files, tokens, and drift <code>ctx doctor</code> Thorough structural checkup (hooks, config, events too) <code>/ctx-doctor</code> Agent-driven diagnosis with event log pattern analysis <p><code>ctx status</code> tells you what's there. <code>ctx doctor</code> tells you what's wrong. <code>/ctx-doctor</code> tells you why it's wrong and what to do about it.</p>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-does-not-do","level":4,"title":"What It Does Not Do","text":"<ul> <li>No event pattern analysis: that's the <code>/ctx-doctor</code> skill's job</li> <li>No auto-fixing: reports findings, doesn't modify anything</li> <li>No external service checks: doesn't verify webhook endpoint availability</li> </ul> <p>See also: Troubleshooting | <code>ctx hook event</code> | <code>/ctx-doctor</code> skill | Detecting and Fixing Drift</p>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/event/","level":1,"title":"Event","text":"","path":["Event"],"tags":[]},{"location":"cli/event/#ctx-hook-event","level":3,"title":"<code>ctx hook event</code>","text":"<p>Query the local hook event log. Requires <code>event_log: true</code> in <code>.ctxrc</code>. Reads events from <code>.context/state/events.jsonl</code> and outputs them in a human-readable table or raw JSONL format.</p> <p>All filter flags combine with AND logic.</p> <pre><code>ctx hook event [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--hook</code> Filter by hook name <code>--session</code> Filter by session ID <code>--event</code> Filter by event type (<code>relay</code>, <code>nudge</code>) <code>--last</code> Show last N events (default: 50) <code>--json</code> Output raw JSONL (for piping to <code>jq</code>) <code>--all</code> Include rotated log file <p>Examples:</p> <pre><code>ctx hook event                                        # recent events\nctx hook event --hook check-context-size --last 10    # one hook, last 10\nctx hook event --json | jq '.hook'                    # pipe to jq\nctx hook event --session abc123                       # filter by session\n</code></pre>","path":["Event"],"tags":[]},{"location":"cli/guide/","level":1,"title":"Guide","text":"","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/guide/#ctx-guide","level":2,"title":"<code>ctx guide</code>","text":"<p>Quick-reference cheat sheet for common <code>ctx</code> commands and skills.</p> <pre><code>ctx guide [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--skills</code> Show available skills <code>--commands</code> Show available CLI commands <p>Example:</p> <pre><code># Show the full cheat sheet\nctx guide\n\n# Skills only\nctx guide --skills\n\n# Commands only\nctx guide --commands\n</code></pre> <p>Works without initialization (no <code>.context/</code> required). Useful for a printable one-pager when onboarding to a project.</p>","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/handover/","level":1,"title":"ctx handover","text":"","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover","level":2,"title":"<code>ctx handover</code>","text":"<p>Writes the per-session handover under <code>.context/handovers/<TS>-<slug>.md</code>: a former-agent-to-next-agent note created at session end by <code>/ctx-wrap-up</code> and read at session start by <code>/ctx-remember</code>. When <code>.context/kb/</code> exists, the writer additionally folds postdated closeouts into the handover's <code>## Folded Closeouts</code> section and archives them.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover-write-title","level":3,"title":"<code>ctx handover write <title></code>","text":"<pre><code>ctx handover write \"Cursor Hooks deep dive\" \\\n  --summary \"Drafted topic-page; minted EV-018..EV-024; cold-reader passed.\" \\\n  --next \"Re-ingest the v1.1 release notes URL once you have it.\"\n</code></pre> <p>Required flags:</p> Flag Description <code>--summary</code> What happened this session (past tense). Placeholder values (<code>TBD</code>, <code>see chat</code>, <code>n/a</code>) are rejected. <code>--next</code> What the next agent should do FIRST (future tense, specific). Same placeholder rejection. <p>Optional flags:</p> Flag Description <code>--highlights</code> Notable artifacts produced this session. <code>--open-questions</code> Things that remain undecided. <code>--commit</code> Override resolved git HEAD for the Provenance line (CI replay; honors <code>CTX_TASK_COMMIT</code>). <code>--no-fold</code> Skip closeout consumption (mid-session checkpoint). <p>Writes: <code>.context/handovers/<TS>-<slug>.md</code> with frontmatter (<code>sha</code>, <code>branch</code>, <code>generated-at</code>, <code>title</code>) and body sections (<code>## Summary</code>, <code>## Next Session</code>, optionally <code>## Highlights</code>, <code>## Open Questions</code>, <code>## Folded Closeouts</code>). The <code><TS>-<slug>.md</code> filename is timestamped so multiple concurrent agent runs never overwrite one another's handover.</p> <p>Side effect (when <code>--no-fold</code> is absent and <code>.context/kb/</code> exists): closeouts that postdate the latest handover are folded into the new handover and physically archived under <code>.context/archive/closeouts/</code>.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#how-to-trigger","level":3,"title":"How to Trigger","text":"<p>In ordinary sessions you do not invoke <code>ctx handover write</code> directly. The user-facing trigger is <code>/ctx-wrap-up</code>:</p> <pre><code>/ctx-wrap-up \"session title\"\n</code></pre> <p><code>/ctx-wrap-up</code> owns session-end and always delegates to <code>/ctx-handover</code> as its final step. Direct invocation of <code>/ctx-handover</code> is reserved for two cases:</p> <ul> <li><code>--no-fold</code> mid-session checkpoint.</li> <li>Recovery, when a prior session aborted before wrap-up.</li> </ul> <p>See <code>/ctx-wrap-up</code> and <code>/ctx-handover</code>.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Session Lifecycle</li> <li>Recipe: Recover an Aborted Session</li> <li>Skill: <code>/ctx-wrap-up</code></li> <li>Skill: <code>/ctx-handover</code></li> <li>Skill: <code>/ctx-remember</code></li> </ul>","path":["ctx handover"],"tags":[]},{"location":"cli/hook/","level":1,"title":"Hook","text":"","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#ctx-hook","level":3,"title":"<code>ctx hook</code>","text":"<p>Manage hook-related settings: messages, notifications, pause/resume, and event log.</p> <pre><code>ctx hook <subcommand> [flags]\n</code></pre>","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#subcommands","level":2,"title":"Subcommands","text":"Subcommand Description <code>ctx hook message list</code> Show all hook messages with override status <code>ctx hook message show <h> <v></code> Print the effective message template <code>ctx hook message edit <h> <v></code> Copy default to <code>.context/</code> for editing <code>ctx hook message reset <h> <v></code> Delete user override, revert to default <code>ctx hook notify [message]</code> Send a webhook notification <code>ctx hook notify setup</code> Configure and encrypt webhook URL <code>ctx hook notify test</code> Send a test notification <code>ctx hook pause</code> Pause all context hooks for this session <code>ctx hook resume</code> Resume paused context hooks <code>ctx hook event</code> Query the local hook event log","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#examples","level":2,"title":"Examples","text":"<pre><code># View and manage hook messages\nctx hook message list\nctx hook message show qa-reminder gate\nctx hook message edit qa-reminder gate\n\n# Webhook notifications\nctx hook notify setup\nctx hook notify --event loop \"Loop completed\"\n\n# Pause/resume hooks\nctx hook pause\nctx hook resume\n\n# Browse event log\nctx hook event --last 20\nctx hook event --hook qa-reminder --json\n</code></pre> <p>See also: Customizing Hook Messages | Webhook Notifications | Pausing Context Hooks | System Hooks Audit</p>","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hub/","level":1,"title":"Hub","text":"","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub","level":2,"title":"<code>ctx hub</code>","text":"<p>Operator commands for a <code>ctx</code> Hub: the gRPC server that fans out decisions, learnings, conventions, and tasks across projects. Use <code>ctx hub</code> to start and stop the server, inspect cluster state, add or remove peers at runtime, and hand off leadership before maintenance.</p> <p>Who Needs This Page</p> <p>You only need <code>ctx hub</code> if you are running a hub server or cluster. For client-side operations (register, subscribe, sync, publish, listen), see <code>ctx connect</code>. For the mental model behind the hub as a whole, read the <code>ctx</code> Hub overview.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-start","level":3,"title":"<code>ctx hub start</code>","text":"<p>Start the hub gRPC server.</p> <p>Examples:</p> <pre><code>ctx hub start                           # Foreground, default port 9900\nctx hub start --port 8080               # Custom port\nctx hub start --data-dir /srv/ctx-hub   # Custom data directory\n</code></pre> <p>On first run, generates an admin token and prints it to stdout. Save this token; it's required for <code>ctx connection register</code> in client projects. Subsequent runs reuse the stored token from <code><data-dir>/admin.token</code>.</p> <p>Default data directory: <code>~/.ctx/hub-data/</code></p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#daemon-mode","level":4,"title":"Daemon Mode","text":"<p>Run the hub as a detached background process:</p> <pre><code>ctx hub start --daemon          # Fork to background\nctx hub stop                    # Graceful shutdown\n</code></pre> <p>The daemon writes a PID file to <code><data-dir>/hub.pid</code>. Stop the daemon with <code>ctx hub stop</code> (see below).</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#cluster-mode","level":4,"title":"Cluster Mode","text":"<p>For high availability, run multiple hubs with Raft-based leader election:</p> <pre><code>ctx hub start --port 9900 \\\n  --peers host2:9901,host3:9901\n</code></pre> <p>Raft is used only for leader election. Data replication uses sequence-based gRPC sync on the append-only JSONL log; there is no multi-node consensus on writes. See the HA cluster recipe for the full setup and the Raft-lite durability caveat.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#flags","level":4,"title":"Flags","text":"Flag Description Default <code>--port</code> Hub listen port <code>9900</code> <code>--data-dir</code> Hub data directory <code>~/.ctx/hub-data/</code> <code>--daemon</code> Run the hub server in the background <code>false</code> <code>--peers</code> Comma-separated peer addresses for cluster mode (none)","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#validation","level":4,"title":"Validation","text":"<p>The hub validates every published entry before accepting it:</p> <ul> <li>Type must be one of <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code></li> <li>ID and Origin are required and non-empty</li> <li>Content size capped at 1 MB (text-only)</li> <li>Duplicate project registration is rejected (one token per project)</li> </ul>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stop","level":3,"title":"<code>ctx hub stop</code>","text":"<p>Stop a running hub daemon.</p> <p>Examples:</p> <pre><code>ctx hub stop                            # Stop using default data dir\nctx hub stop --data-dir /srv/ctx-hub    # Custom data directory\n</code></pre> <p>Sends <code>SIGTERM</code> to the PID recorded in <code><data-dir>/hub.pid</code>, waits for in-flight RPCs to drain, and removes the PID file. Safe to rerun: if no daemon is running, returns a \"no running hub\" error without side effects.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-status","level":3,"title":"<code>ctx hub status</code>","text":"<p>Show cluster status: role, peers, sync state, entry count, and uptime.</p> <p>Examples:</p> <pre><code>ctx hub status\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-peer","level":3,"title":"<code>ctx hub peer</code>","text":"<p>Add or remove peers from the cluster at runtime. Useful for scaling up or replacing a decommissioned node without restarting the leader.</p> <p>Examples:</p> <pre><code>ctx hub peer add host2:9901\nctx hub peer remove host2:9901\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stepdown","level":3,"title":"<code>ctx hub stepdown</code>","text":"<p>Transfer leadership to another node gracefully. Triggers a new election among the remaining followers before the current leader steps down. Use before taking the leader offline for maintenance.</p> <p>Examples:</p> <pre><code>ctx hub stepdown\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx connect</code>: client-side commands   (register, subscribe, sync, publish, listen)</li> <li><code>ctx</code> Hub overview: mental   model and user stories</li> <li><code>ctx</code> Hub: Getting Started</li> <li>Hub operations: production   deployment, backup, monitoring</li> <li>Hub failure modes</li> <li>Hub security model</li> </ul>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/init-status/","level":1,"title":"Init and Status","text":"","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-init","level":3,"title":"<code>ctx init</code>","text":"<p>Initialize a new <code>.context/</code> directory with template files.</p> <pre><code>ctx init [flags]\n</code></pre> <p>Git is required</p> <p><code>ctx init</code> (and every non-administrative <code>ctx</code> subcommand) refuses to operate without a <code>.git/</code> working tree at the project root. <code>ctx</code> already needed git to work properly; that requirement is now enforced rather than assumed.</p> <p>Handovers and closeouts stamp the current commit into their frontmatter, and the editorial pipeline pins in-repo evidence to a short SHA (none of which works without a repo). </p> <p>Run <code>git init</code> first if the project does not already have one. </p> <p>There is no <code>--allow-no-git</code> escape hatch. </p> <p>Flags:</p> Flag Short Description <code>--force</code> <code>-f</code> Overwrite existing context files <code>--minimal</code> <code>-m</code> Only create essential files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>CONSTITUTION.md</code>) <code>--merge</code> Auto-merge <code>ctx</code> content into existing <code>CLAUDE.md</code> <p>Creates:</p> <ul> <li><code>.context/</code> directory with all template files</li> <li><code>.context/kb/</code> (with <code>index.md</code> and <code>topics/</code>) and   <code>.context/ingest/</code> (with <code>KB-RULES.md</code>, mode prompts,   <code>OPERATOR.md</code>, <code>PROMPT.md</code>, <code>closeouts/</code>, <code>schemas/</code>) and   <code>.context/handovers/</code>: the editorial-pipeline scaffolding   (Phase KB). Embedded   templates are copied; existing files are preserved.</li> <li><code>.claude/settings.local.json</code> with pre-approved <code>ctx</code> permissions</li> <li><code>CLAUDE.md</code> with bootstrap instructions (or merges into existing)</li> </ul> <p>Claude Code hooks and skills are provided by the <code>ctx</code> plugin (see Integrations).</p> <p>Example:</p> <pre><code># Standard init\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --reset\n\n# Merge into existing files\nctx init --merge\n</code></pre> <p>After <code>ctx init</code> succeeds, the final output includes a hint showing the exact <code>eval \"$(ctx activate)\"</code> line to bind the new directory for your shell. Every other <code>ctx</code> command requires that binding (or an equivalent direct <code>CTX_DIR=/abs/path/.context</code> export) before it will run.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-activate","level":3,"title":"<code>ctx activate</code>","text":"<p>Emit a shell-native <code>export CTX_DIR=...</code> line for the target <code>.context/</code> directory. <code>ctx</code> does not search the filesystem during day-to-day commands: each one needs <code>CTX_DIR</code> set before it runs. <code>activate</code> is the convenience that figures out the path for you so you can bind it with one line.</p> <pre><code># Walk up from CWD, emit if exactly one candidate visible.\neval \"$(ctx activate)\"\n</code></pre> <p>Flags:</p> Flag Description <code>--shell</code> Shell dialect override. POSIX-family (<code>bash</code>, <code>zsh</code>, <code>sh</code>) all share one syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from <code>$SHELL</code>. <p>Resolution:</p> Candidate count from CWD Behavior Zero Error. Use <code>ctx init</code> to create one, or <code>cd</code> closer to the project root. One Emit <code>export CTX_DIR=<path></code> for that candidate. Two or more Refuse. List every candidate. Re-run from a more specific cwd. <p><code>activate</code> is args-free under the single-source-anchor model; the explicit-path mode was removed because hub-client / hub-server scenarios store at <code>~/.ctx/hub-data/</code> and never read <code>.context/</code>, so they activate from the project root like everyone else. Direct binding without a project-local scan is still available via <code>export CTX_DIR=/abs/path/.context</code> or the inline form.</p> <p>If the parent shell already has <code>CTX_DIR</code> set to a different value, the output gains a leading <code># ctx: replacing stale CTX_DIR=...</code> comment so the user sees the change in <code>eval</code> output before the replacement takes effect.</p> <p>See also: Activating a Context Directory for the full recipe including direnv setup and CI patterns.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-deactivate","level":3,"title":"<code>ctx deactivate</code>","text":"<p>Emit a shell-native <code>unset CTX_DIR</code> line. Pairs with <code>activate</code>.</p> <pre><code>eval \"$(ctx deactivate)\"\n</code></pre> <p>Flags:</p> Flag Description <code>--shell</code> Shell dialect override. POSIX-family (<code>bash</code>, <code>zsh</code>, <code>sh</code>) all share one <code>unset</code> syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from <code>$SHELL</code>. <p><code>deactivate</code> does not touch the filesystem, doesn't require a declared context directory, and never fails under normal operation; unsetting an already-unset variable is a no-op across supported shells.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-status","level":3,"title":"<code>ctx status</code>","text":"<p>Show the current context summary.</p> <pre><code>ctx status [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--json</code> Output as JSON <code>--verbose</code> <code>-v</code> Include file contents summary <p>Output:</p> <ul> <li>Context directory path</li> <li>Total files and token estimate</li> <li>Status of each file (loaded, empty, missing)</li> <li>Recent activity (modification times)</li> <li>Drift warnings if any</li> </ul> <p>Example:</p> <pre><code>ctx status\nctx status --json\nctx status --verbose\n</code></pre>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-agent","level":3,"title":"<code>ctx agent</code>","text":"<p>Print an AI-ready context packet optimized for LLM consumption.</p> <pre><code>ctx agent [flags]\n</code></pre> <p>Flags:</p> Flag Default Description <code>--budget</code> 8000 Token budget: controls content selection and prioritization <code>--format</code> md Output format: <code>md</code> or <code>json</code> <code>--cooldown</code> 10m Suppress repeated output within this duration (requires <code>--session</code>) <code>--session</code> (none) Session ID for cooldown isolation (e.g., <code>$PPID</code>) <code>--include-hub</code> false Include hub entries from <code>.context/hub/</code> <p>How budget works:</p> <p>The budget controls how much context is included. Entries are selected in priority tiers:</p> <ol> <li>Constitution: always included in full (inviolable rules)</li> <li>Tasks: all active tasks, up to 40% of budget</li> <li>Conventions: all conventions, up to 20% of budget</li> <li>Decisions: scored by recency and relevance to active tasks</li> <li>Learnings: scored by recency and relevance to active tasks</li> <li>Steering: applicable steering file bodies,    scored by their <code>inclusion</code> mode and description match    against the active prompt</li> <li>Skill: named skill content (from <code>--skill</code>)</li> <li>Hub: entries from <code>.context/hub/</code> (with <code>--include-hub</code>,    see <code>ctx connect</code>)</li> </ol> <p>Decisions and learnings are ranked by a combined score (how recent + how relevant to your current tasks). High-scoring entries are included with their full body. Entries that don't fit get title-only summaries in an \"Also Noted\" section. Superseded entries are excluded.</p> <p>Output Sections:</p> Section Source Selection Read These Files all <code>.context/</code> Non-empty files in priority order Constitution <code>CONSTITUTION.md</code> All rules (never truncated) Current Tasks <code>TASKS.md</code> All unchecked tasks (budget-capped) Key Conventions <code>CONVENTIONS.md</code> All items (budget-capped) Recent Decisions <code>DECISIONS.md</code> Full body, scored by relevance Key Learnings <code>LEARNINGS.md</code> Full body, scored by relevance Also Noted overflow Title-only summaries <p>Example:</p> <pre><code># Default (8000 tokens, markdown)\nctx agent\n\n# Smaller packet for tight context windows\nctx agent --budget 4000\n\n# JSON format for programmatic use\nctx agent --format json\n\n# Pipe to file\nctx agent --budget 4000 > context.md\n\n# With cooldown (hooks/automation: requires --session)\nctx agent --session $PPID\n</code></pre> <p>Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-load","level":3,"title":"<code>ctx load</code>","text":"<p>Load and display assembled context as AI would see it.</p> <pre><code>ctx load [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--budget <tokens></code> Token budget for assembly (default: 8000) <code>--raw</code> Output raw file contents without assembly <p>Example:</p> <pre><code>ctx load\nctx load --budget 16000\nctx load --raw\n</code></pre>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/journal/","level":1,"title":"Journal","text":"","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal","level":3,"title":"<code>ctx journal</code>","text":"<p>Browse and search AI session history from Claude Code and other tools.</p> <pre><code>ctx journal <subcommand>\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source","level":4,"title":"<code>ctx journal source</code>","text":"<p>List all parsed sessions.</p> <pre><code>ctx journal source [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--limit</code> <code>-n</code> Maximum sessions to display (default: 20) <code>--project</code> <code>-p</code> Filter by project name <code>--tool</code> <code>-t</code> Filter by tool (e.g., <code>claude-code</code>) <code>--all-projects</code> Include sessions from all projects <p>Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.</p> <p>Example:</p> <pre><code>ctx journal source\nctx journal source --limit 5\nctx journal source --project ctx\nctx journal source --tool claude-code\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source-show","level":4,"title":"<code>ctx journal source --show</code>","text":"<p>Show details of a specific session.</p> <pre><code>ctx journal source --show [session-id] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--latest</code> Show the most recent session <code>--full</code> Show full message content <code>--all-projects</code> Search across all projects <p>The session ID can be a full UUID, partial match, or session slug name.</p> <p>Example:</p> <pre><code>ctx journal source --show abc123\nctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show --latest\nctx journal source --show --latest --full\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-import","level":4,"title":"<code>ctx journal import</code>","text":"<p>Import sessions to editable journal files in <code>.context/journal/</code>.</p> <pre><code>ctx journal import [session-id] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Import all sessions (only new files by default) <code>--all-projects</code> Import from all projects <code>--regenerate</code> Re-import existing files (preserves YAML frontmatter by default) <code>--keep-frontmatter</code> Preserve enriched YAML frontmatter during regeneration (default: true) <code>--yes</code>, <code>-y</code> Skip confirmation prompt <code>--dry-run</code> Show what would be imported without writing files <p>Safe by default: <code>--all</code> only imports new sessions. Existing files are skipped. Use <code>--regenerate</code> to re-import existing files (conversation content is regenerated, YAML frontmatter from enrichment is preserved by default). Use <code>--keep-frontmatter=false</code> to discard enriched frontmatter during regeneration.</p> <p>Locked entries (via <code>ctx journal lock</code>) are always skipped, regardless of flags.</p> <p>Single-session import (<code>ctx journal import <id></code>) always writes without prompting, since you are explicitly targeting one session.</p> <p>The <code>journal/</code> directory should be gitignored (like <code>sessions/</code>) since it contains raw conversation data.</p> <p>Example:</p> <pre><code>ctx journal import abc123                 # Import one session\nctx journal import --all                  # Import only new sessions\nctx journal import --all --dry-run        # Preview what would be imported\nctx journal import --all --regenerate     # Re-import existing (prompts)\nctx journal import --all --regenerate -y  # Re-import without prompting\nctx journal import --all --regenerate --keep-frontmatter=false -y  # Discard frontmatter\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-lock","level":4,"title":"<code>ctx journal lock</code>","text":"<p>Protect journal entries from being overwritten by <code>import --regenerate</code> or modified by enrichment skills (<code>/ctx-journal-enrich</code>, <code>/ctx-journal-enrich-all</code>).</p> <pre><code>ctx journal lock <pattern> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Lock all journal entries <p>The pattern matches filenames by slug, date, or short ID. Locking a multi-part entry locks all parts. The lock is recorded in <code>.context/journal/.state.json</code> and a <code>locked: true</code> line is added to the file's YAML frontmatter for visibility.</p> <p>Example:</p> <pre><code>ctx journal lock abc12345\nctx journal lock 2026-01-21-session-abc12345.md\nctx journal lock --all\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-unlock","level":4,"title":"<code>ctx journal unlock</code>","text":"<p>Remove lock protection from journal entries.</p> <pre><code>ctx journal unlock <pattern> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Unlock all journal entries <p>Example:</p> <pre><code>ctx journal unlock abc12345\nctx journal unlock --all\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-sync","level":4,"title":"<code>ctx journal sync</code>","text":"<p>Sync lock state from journal frontmatter to <code>.state.json</code>.</p> <pre><code>ctx journal sync\n</code></pre> <p>Scans all journal markdowns and updates <code>.state.json</code> to match each file's frontmatter. Files with <code>locked: true</code> in frontmatter are marked locked in state; files without a <code>locked:</code> line have their lock cleared.</p> <p>This is the inverse of <code>ctx journal lock</code>: instead of state driving frontmatter, frontmatter drives state. Useful after batch enrichment where you add <code>locked: true</code> to frontmatter manually.</p> <p>Example:</p> <pre><code># After enriching entries and adding locked: true to frontmatter\nctx journal sync\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal_1","level":3,"title":"<code>ctx journal</code>","text":"<p>Analyze and synthesize imported session files.</p> <pre><code>ctx journal <subcommand>\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-site","level":4,"title":"<code>ctx journal site</code>","text":"<p>Generate a static site from journal entries in <code>.context/journal/</code>.</p> <pre><code>ctx journal site [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--output</code> <code>-o</code> Output directory (default: .context/journal-site) <code>--build</code> Run zensical build after generating <code>--serve</code> Run zensical serve after generating <p>Creates a <code>zensical</code>-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.</p> <p>Requires <code>zensical</code> to be installed for <code>--build</code> or <code>--serve</code>:</p> <pre><code>pipx install zensical\n</code></pre> <p>Example:</p> <pre><code>ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-obsidian","level":4,"title":"<code>ctx journal obsidian</code>","text":"<p>Generate an Obsidian vault from journal entries in <code>.context/journal/</code>.</p> <pre><code>ctx journal obsidian [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--output</code> <code>-o</code> Output directory (default: .context/journal-obsidian) <p>Creates an Obsidian-compatible vault with:</p> <ul> <li>Wikilinks (<code>[[target|display]]</code>) for all internal navigation</li> <li>MOC pages (Map of Content) for topics, key files, and session types</li> <li>Related sessions footer linking entries that share topics</li> <li>Transformed frontmatter (<code>topics</code> → <code>tags</code> for Obsidian integration)</li> <li>Minimal <code>.obsidian/</code> config enforcing wikilink mode</li> </ul> <p>No external dependencies are required: Open the output directory as an Obsidian  vault directly.</p> <p>Example:</p> <pre><code>ctx journal obsidian                        # Generate in .context/journal-obsidian/\nctx journal obsidian --output ~/vaults/ctx  # Custom output directory\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-check","level":4,"title":"<code>ctx journal schema check</code>","text":"<p>Validate JSONL session files against the embedded schema and report drift.</p> <pre><code>ctx journal schema check [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--dir</code> Directory to scan for JSONL files <code>--all-projects</code> Scan all Claude Code project directories <code>--quiet</code> <code>-q</code> Exit code only (0 = clean, 1 = drift) <p>Scans JSONL files for unknown fields, missing required fields, unknown record types, and unknown content block types. When drift is found, writes a Markdown report to <code>.context/reports/schema-drift.md</code>. When drift resolves, the report is automatically deleted.</p> <p>Designed for interactive use, CI pipelines, and nightly cron jobs.</p> <p>Example:</p> <pre><code>ctx journal schema check                    # Current project\nctx journal schema check --all-projects     # All projects\nctx journal schema check --quiet            # Exit code only\nctx journal schema check --dir /path/to     # Custom directory\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-dump","level":4,"title":"<code>ctx journal schema dump</code>","text":"<p>Print the embedded JSONL schema definition.</p> <pre><code>ctx journal schema dump\n</code></pre> <p>Shows all known record types with their required and optional fields, and all recognized content block types with their parse status. Useful for inspecting what the schema validator expects.</p> <p>Example:</p> <pre><code>ctx journal schema dump\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-serve","level":3,"title":"<code>ctx serve</code>","text":"<p>Serve any zensical directory locally. This is a serve-only command: It does not generate or regenerate site content.</p> <pre><code>ctx serve [directory]\n</code></pre> <p>If no directory is specified, defaults to the journal site (<code>.context/journal-site</code>).</p> <p>Requires <code>zensical</code> to be installed:</p> <pre><code>pipx install zensical\n</code></pre> <p><code>ctx serve</code> vs. <code>ctx journal site --serve</code></p> <p><code>ctx journal site --serve</code> generates the journal site then serves it: an all-in-one command. <code>ctx serve</code> only serves an existing directory, and works with any zensical site (journal, docs, etc.).</p> <p>Example:</p> <pre><code>ctx serve                        # Serve journal site (no regeneration)\nctx serve .context/journal-site  # Same, explicit path\nctx serve ./site                 # Serve the docs site\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/kb/","level":1,"title":"ctx kb","text":"","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb","level":2,"title":"<code>ctx kb</code>","text":"<p>Knowledge-base editorial pipeline (Phase KB). Manages the <code>.context/kb/</code> knowledge base via mode-aware skills and a small set of supporting CLI commands. The editorial constitution lives at <code>.context/ingest/KB-RULES.md</code> (laid down by <code>ctx init</code>).</p> <pre><code>ctx kb [subcommand]\n</code></pre> Subcommand Type Purpose <code>ctx kb topic new \"<name>\"</code> CLI (real) Sole writer of topic-page scaffolds. Creates <code>.context/kb/topics/<slug>/index.md</code> from the embedded template. Refuses when the topic exists. <code>ctx kb note \"<text>\"</code> CLI (real) Appends a one-liner to <code>.context/ingest/findings.md</code>. Never touches a topic page. <code>ctx kb reindex</code> CLI (real) Refreshes the <code>CTX:KB:TOPICS</code> managed block in <code>.context/kb/index.md</code>. <code>ctx kb ingest <folder\\|paths></code> Skill-driven Mode-aware editorial pass. CLI form refuses on empty input and points at the <code>/ctx-kb-ingest</code> skill. <code>ctx kb ask \"<question>\"</code> Skill-driven Q&A grounded in the kb. CLI form refuses on empty input and points at the <code>/ctx-kb-ask</code> skill. <code>ctx kb site-review</code> Skill-driven Mechanical structural audit. Points at <code>/ctx-kb-site-review</code>. <code>ctx kb ground</code> Skill-driven External grounding via <code>grounding-sources.md</code>. Refuses when the file is empty. <p>Skill-driven vs real CLI</p> <p>The mode skills (<code>ingest</code>, <code>ask</code>, <code>site-review</code>, <code>ground</code>) do the editorial work themselves: the agent reads <code>.context/ingest/30-INGEST.md</code> (etc.) and executes the pass per the pass-mode contract. The CLI form for those subcommands validates input and prints the canonical skill invocation. The real CLI commands (<code>topic new</code>, <code>note</code>, <code>reindex</code>) own concrete state changes.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-topic-new-name","level":3,"title":"<code>ctx kb topic new \"<name>\"</code>","text":"<p>Scaffolds a folder-shaped topic at <code>.context/kb/topics/<slug>/index.md</code> from the embedded template.</p> <p>Slug: lowercase + kebab-case. Slashes are preserved for vendor-namespaced topology (e.g. <code>cursor/hooks</code>, <code>cursor/skills</code>, <code>cursor/rules</code> under a shared <code>cursor/</code> folder).</p> <p>Refuses when the topic folder already exists. Use the existing folder instead; the editorial pass extends pages, it doesn't reset them.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-note-text","level":3,"title":"<code>ctx kb note \"<text>\"</code>","text":"<p>Appends a timestamped one-liner to <code>.context/ingest/findings.md</code>. Use for parking findings the next ingest pass should absorb.</p> <pre><code>ctx kb note \"follow-up: chase the v1.2 release notes for the SIGTERM change\"\n</code></pre>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-reindex","level":3,"title":"<code>ctx kb reindex</code>","text":"<p>Refreshes the <code>CTX:KB:TOPICS</code> managed block inside <code>.context/kb/index.md</code> so the kb landing page enumerates current topic folders. Run after <code>ctx kb topic new</code> to update the landing.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#skill-driven-subcommands","level":3,"title":"Skill-Driven Subcommands","text":"<p><code>ingest</code>, <code>ask</code>, <code>site-review</code>, <code>ground</code> exist as CLI surfaces so the editorial workflow is drivable from outside Claude Code (via the fallback <code>PROMPT.md</code> auto-router). In Claude Code, prefer the skills:</p> <pre><code>/ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n/ctx-kb-ask \"does the kb say hooks fire async?\"\n/ctx-kb-site-review\n/ctx-kb-ground\n</code></pre> <p>See the Build a Knowledge Base recipe for the full workflow.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Typical KB Session</li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["ctx kb"],"tags":[]},{"location":"cli/loop/","level":1,"title":"Loop","text":"","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/loop/#ctx-loop","level":2,"title":"<code>ctx loop</code>","text":"<p>Generate a shell script for running an autonomous loop.</p> <p>An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.</p> <pre><code>ctx loop [flags]\n</code></pre> <p>Flags:</p> Flag Short Description Default <code>--tool <tool></code> <code>-t</code> AI tool: <code>claude</code>, <code>aider</code>, or <code>generic</code> <code>claude</code> <code>--prompt <file></code> <code>-p</code> Prompt file to use <code>.context/loop.md</code> <code>--max-iterations <n></code> <code>-n</code> Maximum iterations (0 = unlimited) <code>0</code> <code>--completion <signal></code> <code>-c</code> Completion signal to detect <code>SYSTEM_CONVERGED</code> <code>--output <file></code> <code>-o</code> Output script filename <code>loop.sh</code> <p>Examples:</p> <pre><code># Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n</code></pre> <p>Running the generated loop:</p> <pre><code>ctx loop\nchmod +x loop.sh\n./loop.sh\n</code></pre> <p>See also: Autonomous Loops for the full workflow.</p>","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/mcp/","level":1,"title":"MCP Server","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp","level":2,"title":"<code>ctx mcp</code>","text":"<p>Run <code>ctx</code> as a Model Context Protocol (MCP) server. MCP is a standard protocol that lets AI tools discover and consume context from external sources via JSON-RPC 2.0 over stdin/stdout.</p> <p>This makes <code>ctx</code> accessible to any MCP-compatible AI tool without custom hooks or integrations:</p> <ul> <li>Claude Desktop</li> <li>Cursor</li> <li>Windsurf</li> <li>VS Code Copilot</li> <li>Any tool supporting MCP</li> </ul>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp-serve","level":3,"title":"<code>ctx mcp serve</code>","text":"<p>Start the MCP server. This command reads JSON-RPC 2.0 requests from stdin and writes responses to stdout. It is intended to be launched by MCP clients (Claude Desktop, Cursor, VS Code Copilot), not run directly from a shell. See Configuration below for how each host launches it.</p> <p>Flags: None. The server uses the declared context directory from <code>CTX_DIR</code>. As with every other <code>ctx</code> command, that variable must be set: the server does not walk the filesystem.</p> <p>Examples:</p> <pre><code># Normal invocation (by an MCP client via stdio transport)\nctx mcp serve\n\n# Pin a context directory for a specific workspace\nCTX_DIR=/path/to/project/.context ctx mcp serve\n\n# Verify the binary starts without a client attached (Ctrl-C to exit)\nctx mcp serve < /dev/null\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#configuration","level":2,"title":"Configuration","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#claude-desktop","level":3,"title":"Claude Desktop","text":"<p>Add to <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>:</p> <pre><code>{\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#cursor","level":3,"title":"Cursor","text":"<p>Add to <code>.cursor/mcp.json</code> in your project:</p> <pre><code>{\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#vs-code-copilot","level":3,"title":"VS Code (Copilot)","text":"<p>Add to <code>.vscode/mcp.json</code>:</p> <pre><code>{\n  \"servers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resources","level":2,"title":"Resources","text":"<p>Resources expose context files as read-only content. Each resource has a URI, name, and returns Markdown text.</p> URI Name Description <code>ctx://context/constitution</code> constitution Hard rules that must never be violated <code>ctx://context/tasks</code> tasks Current work items and their status <code>ctx://context/conventions</code> conventions Code patterns and standards <code>ctx://context/architecture</code> architecture System architecture documentation <code>ctx://context/decisions</code> decisions Architectural decisions with rationale <code>ctx://context/learnings</code> learnings Gotchas, tips, and lessons learned <code>ctx://context/glossary</code> glossary Project-specific terminology <code>ctx://context/agent</code> agent All files assembled in priority read order <p>The <code>agent</code> resource assembles all non-empty context files into a single Markdown document, ordered by the configured read priority.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resource-subscriptions","level":3,"title":"Resource Subscriptions","text":"<p>Clients can subscribe to resource changes via <code>resources/subscribe</code>. The server polls for file mtime changes (default: 5 seconds) and emits <code>notifications/resources/updated</code> when a subscribed file changes on disk.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#tools","level":2,"title":"Tools","text":"<p>Tools expose <code>ctx</code> commands as callable operations. Each tool accepts JSON arguments and returns text results.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_status","level":3,"title":"<code>ctx_status</code>","text":"<p>Show context health: file count, token estimate, and per-file summary.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_add","level":3,"title":"<code>ctx_add</code>","text":"<p>Add a task, decision, learning, or convention to the context.</p> Argument Type Required Description <code>type</code> string Yes Entry type: task, decision, learning, convention <code>content</code> string Yes Title or main content <code>priority</code> string No Priority level (tasks only): high, medium, low <code>context</code> string Conditional Context field (decisions and learnings) <code>rationale</code> string Conditional Rationale (decisions only) <code>consequence</code> string Conditional Consequence (decisions only) <code>lesson</code> string Conditional Lesson learned (learnings only) <code>application</code> string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_complete","level":3,"title":"<code>ctx_complete</code>","text":"<p>Mark a task as done by number or text match.</p> Argument Type Required Description <code>query</code> string Yes Task number (e.g. \"1\") or search text","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_drift","level":3,"title":"<code>ctx_drift</code>","text":"<p>Detect stale or invalid context. Returns violations, warnings, and passed checks.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_journal_source","level":3,"title":"<code>ctx_journal_source</code>","text":"<p>Query recent AI session history (summaries, decisions, topics).</p> Argument Type Required Description <code>limit</code> number No Max sessions to return (default: 5) <code>since</code> string No ISO date filter: sessions after this date (YYYY-MM-DD) <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_watch_update","level":3,"title":"<code>ctx_watch_update</code>","text":"<p>Apply a structured context update to <code>.context/</code> files. Supports task, decision, learning, convention, and complete entry types. Human confirmation is required before calling.</p> Argument Type Required Description <code>type</code> string Yes Entry type: task, decision, learning, convention, complete <code>content</code> string Yes Main content <code>context</code> string Conditional Context background (decisions/learnings) <code>rationale</code> string Conditional Rationale (decisions only) <code>consequence</code> string Conditional Consequence (decisions only) <code>lesson</code> string Conditional Lesson learned (learnings only) <code>application</code> string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_compact","level":3,"title":"<code>ctx_compact</code>","text":"<p>Move completed tasks to the archive section and remove empty sections from context files. Human confirmation required.</p> Argument Type Required Description <code>archive</code> boolean No Also write tasks to <code>.context/archive/</code> (default: false)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_next","level":3,"title":"<code>ctx_next</code>","text":"<p>Suggest the next pending task based on priority and position.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_check_task_completion","level":3,"title":"<code>ctx_check_task_completion</code>","text":"<p>Advisory check: after a write operation, detect if any pending tasks were silently completed. Returns nudge text if a match is found.</p> Argument Type Required Description <code>recent_action</code> string No Brief description of what was just done <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_event","level":3,"title":"<code>ctx_session_event</code>","text":"<p>Signal a session lifecycle event. Type <code>end</code> triggers the session-end persistence ceremony - human confirmation required.</p> Argument Type Required Description <code>type</code> string Yes Event type: start, end <code>caller</code> string No Caller identifier (cursor, windsurf, vscode, claude-desktop)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_steering_get","level":3,"title":"<code>ctx_steering_get</code>","text":"<p>Retrieve applicable steering files for a prompt. Without a prompt, returns always-included files only.</p> Argument Type Required Description <code>prompt</code> string No Prompt text to match against steering file descriptions <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_search","level":3,"title":"<code>ctx_search</code>","text":"<p>Search across <code>.context/</code> files for a query string. Returns matching lines with file paths and line numbers.</p> Argument Type Required Description <code>query</code> string Yes Search string to match against <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_start","level":3,"title":"<code>ctx_session_start</code>","text":"<p>Execute session-start hooks and return aggregated context from hook outputs.</p> <p>Arguments: None.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_end","level":3,"title":"<code>ctx_session_end</code>","text":"<p>Execute session-end hooks with an optional summary. Returns aggregated context from hook outputs.</p> Argument Type Required Description <code>summary</code> string No Session summary passed to hook scripts","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_remind","level":3,"title":"<code>ctx_remind</code>","text":"<p>List pending session-scoped reminders.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#prompts","level":2,"title":"Prompts","text":"<p>Prompts provide pre-built templates for common workflows. Clients can list available prompts via <code>prompts/list</code> and retrieve a specific prompt via <code>prompts/get</code>.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-session-start","level":3,"title":"<code>ctx-session-start</code>","text":"<p>Load full context at the beginning of a session. Returns all context files assembled in priority read order with session orientation instructions.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-decision-add","level":3,"title":"<code>ctx-decision-add</code>","text":"<p>Format an architectural decision entry with all required fields.</p> Argument Type Required Description <code>content</code> string Yes Decision title <code>context</code> string Yes Background context <code>rationale</code> string Yes Why this decision was made <code>consequence</code> string Yes Expected consequence","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-learning-add","level":3,"title":"<code>ctx-learning-add</code>","text":"<p>Format a learning entry with all required fields.</p> Argument Type Required Description <code>content</code> string Yes Learning title <code>context</code> string Yes Background context <code>lesson</code> string Yes The lesson learned <code>application</code> string Yes How to apply this lesson","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-reflect","level":3,"title":"<code>ctx-reflect</code>","text":"<p>Guide end-of-session reflection. Returns a structured review prompt covering progress assessment and context update recommendations.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-checkpoint","level":3,"title":"<code>ctx-checkpoint</code>","text":"<p>Report session statistics: tool calls made, entries added, and pending updates queued during the current session.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/memory/","level":1,"title":"Memory","text":"","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory","level":2,"title":"<code>ctx memory</code>","text":"<p>Bridge Claude Code's auto memory (MEMORY.md) into <code>.context/</code>.</p> <p>Claude Code maintains per-project auto memory at <code>~/.claude/projects/<slug>/memory/MEMORY.md</code>. This command group discovers that file, mirrors it into <code>.context/memory/mirror.md</code> (git-tracked), and detects drift.</p> <pre><code>ctx memory <subcommand>\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-sync","level":3,"title":"<code>ctx memory sync</code>","text":"<p>Copy MEMORY.md to <code>.context/memory/mirror.md</code>. Archives the previous mirror before overwriting.</p> <pre><code>ctx memory sync [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Show what would happen without writing <p>Exit codes:</p> Code Meaning 0 Synced successfully 1 MEMORY.md not found (auto memory inactive) <p>Examples:</p> <pre><code>ctx memory sync\n# Archived previous mirror to mirror-2026-03-05-143022.md\n# Synced MEMORY.md -> .context/memory/mirror.md\n#   Source: ~/.claude/projects/-home-user-project/memory/MEMORY.md\n#   Lines: 47 (was 32)\n#   New content: 15 lines since last sync\n\nctx memory sync --dry-run\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-status","level":3,"title":"<code>ctx memory status</code>","text":"<p>Show drift, timestamps, line counts, and archive count.</p> <pre><code>ctx memory status\n</code></pre> <p>Exit codes:</p> Code Meaning 0 No drift 1 MEMORY.md not found 2 Drift detected (MEMORY.md changed since sync) <p>Examples:</p> <pre><code>ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines (modified since last sync)\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-diff","level":3,"title":"<code>ctx memory diff</code>","text":"<p>Show what changed in MEMORY.md since last sync.</p> <pre><code>ctx memory diff\n</code></pre> <p>Examples:</p> <pre><code>ctx memory diff\n# --- .context/memory/mirror.md (mirror)\n# +++ ~/.claude/projects/.../memory/MEMORY.md (source)\n# +- new learning: memory bridge works\n</code></pre> <p>No output when files are identical.</p>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-publish","level":3,"title":"<code>ctx memory publish</code>","text":"<p>Push curated <code>.context/</code> content into MEMORY.md so the agent sees it natively.</p> <pre><code>ctx memory publish [flags]\n</code></pre> <p>Content is selected in priority order: pending tasks, recent decisions (7 days), key conventions, recent learnings (7 days). Wrapped in <code><!-- ctx:published --></code> markers. Claude-owned content outside the markers is preserved.</p> <p>Flags:</p> Flag Description Default <code>--budget</code> Line budget for published content <code>80</code> <code>--dry-run</code> Show what would be published <p>Examples:</p> <pre><code>ctx memory publish --dry-run\n# Publishing .context/ -> MEMORY.md...\n#   Budget: 80 lines\n#   Published block:\n#     5 pending tasks (from TASKS.md)\n#     3 recent decisions (from DECISIONS.md)\n#     5 key conventions (from CONVENTIONS.md)\n#   Total: 42 lines (within 80-line budget)\n# Dry run - no files written.\n\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter budget\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-unpublish","level":3,"title":"<code>ctx memory unpublish</code>","text":"<p>Remove the ctx-managed marker block from MEMORY.md, preserving Claude-owned content.</p> <p>Examples:</p> <pre><code>ctx memory unpublish\n</code></pre> <p>Hook integration: The <code>check-memory-drift</code> hook runs on every prompt and nudges the agent when MEMORY.md has changed since last sync. The nudge fires once per session. See Memory Bridge.</p>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-import","level":3,"title":"<code>ctx memory import</code>","text":"<p>Classify and promote entries from MEMORY.md into structured <code>.context/</code> files.</p> <pre><code>ctx memory import [flags]\n</code></pre> <p>Each entry is classified by keyword heuristics:</p> Keywords Target <code>always use</code>, <code>prefer</code>, <code>never use</code>, <code>standard</code> CONVENTIONS.md <code>decided</code>, <code>chose</code>, <code>trade-off</code>, <code>approach</code> DECISIONS.md <code>gotcha</code>, <code>learned</code>, <code>watch out</code>, <code>bug</code>, <code>caveat</code> LEARNINGS.md <code>todo</code>, <code>need to</code>, <code>follow up</code> TASKS.md Everything else Skipped <p>Deduplication prevents re-importing the same entry across runs.</p> <p>Flags:</p> Flag Description <code>--dry-run</code> Show classification plan without writing <p>Examples:</p> <pre><code>ctx memory import --dry-run\n# Scanning MEMORY.md for new entries...\n#   Found 6 entries\n#\n#   -> \"always use ctx from PATH\"\n#      Classified: CONVENTIONS.md (keywords: always use)\n#\n#   -> \"decided to use heuristic classification over LLM-based\"\n#      Classified: DECISIONS.md (keywords: decided)\n#\n# Dry run - would import: 4 entries\n# Skipped: 2 entries (session notes/unclassified)\n\nctx memory import    # Actually write entries to .context/ files\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/message/","level":1,"title":"Message","text":"","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message","level":3,"title":"<code>ctx hook message</code>","text":"<p>Manage hook message templates.</p> <p>Hook messages control the text hooks emit. The hook logic (when to fire, counting, state tracking) is universal; the messages are opinions that can be customized per-project.</p> <pre><code>ctx hook message <subcommand>\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-list","level":3,"title":"<code>ctx hook message list</code>","text":"<p>Show all hook messages with category and override status.</p> <pre><code>ctx hook message list [--json]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output in JSON format <p>Example:</p> <pre><code>ctx hook message list\nctx hook message list --json | jq '.[] | select(.override)'\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-show","level":3,"title":"<code>ctx hook message show</code>","text":"<p>Print the effective message template for a hook/variant pair. Shows the user override if present, otherwise the embedded default.</p> <pre><code>ctx hook message show <hook> <variant>\n</code></pre> <p>Example:</p> <pre><code>ctx hook message show qa-reminder gate\nctx hook message show check-context-size checkpoint\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-edit","level":3,"title":"<code>ctx hook message edit</code>","text":"<p>Copy the embedded default template for <code><hook> <variant></code> to <code>.context/hooks/messages/<hook>/<variant>.txt</code> so you can edit it directly. The override takes effect the next time the hook fires.</p> <pre><code>ctx hook message edit <hook> <variant>\n</code></pre> <p>If an override already exists, the command fails and directs you to edit it in place or reset it first.</p> <p>Example:</p> <pre><code>ctx hook message edit qa-reminder gate\n# Edit .context/hooks/messages/qa-reminder/gate.txt in your editor\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-reset","level":3,"title":"<code>ctx hook message reset</code>","text":"<p>Delete a user override and revert to the embedded default. Silent no-op if no override exists.</p> <pre><code>ctx hook message reset <hook> <variant>\n</code></pre> <p>Example:</p> <pre><code>ctx hook message reset qa-reminder gate\n</code></pre> <p>See Customizing hook messages for the full workflow.</p>","path":["Message"],"tags":[]},{"location":"cli/notify/","level":1,"title":"Notify","text":"","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify","level":2,"title":"<code>ctx hook notify</code>","text":"<p>Send fire-and-forget webhook notifications from skills, loops, and hooks.</p> <pre><code>ctx hook notify --event <name> [--session-id <id>] \"message\"\n</code></pre> <p>Flags:</p> Flag Short Description <code>--event</code> <code>-e</code> Event name (required) <code>--session-id</code> <code>-s</code> Session ID (optional) <p>Behavior:</p> <ul> <li>No webhook configured: silent no-op (exit 0)</li> <li>Webhook set but event not in <code>events</code> list: silent no-op (exit 0)</li> <li>Webhook set and event matches: fire-and-forget HTTP POST</li> <li>HTTP errors silently ignored (no retry)</li> </ul> <p>Examples:</p> <pre><code>ctx hook notify --event loop \"Loop completed after 5 iterations\"\nctx hook notify -e nudge -s session-abc \"Context checkpoint at prompt #20\"\n</code></pre>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-setup","level":3,"title":"<code>ctx hook notify setup</code>","text":"<p>Configure the webhook URL interactively. The URL is encrypted with AES-256-GCM using the encryption key and stored in <code>.context/.notify.enc</code>.</p> <p>Examples:</p> <pre><code>ctx hook notify setup\n</code></pre> <p>The encrypted file is safe to commit. The key (<code>~/.ctx/.ctx.key</code>) lives outside the project and is never committed.</p>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-test","level":3,"title":"<code>ctx hook notify test</code>","text":"<p>Send a test notification and report the HTTP response status.</p> <p>Examples:</p> <pre><code>ctx hook notify test\n</code></pre> <p>Payload format (JSON POST):</p> <pre><code>{\n  \"event\": \"loop\",\n  \"message\": \"Loop completed after 5 iterations\",\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> Field Type Description <code>event</code> string Event name from <code>--event</code> flag <code>message</code> string Notification message <code>session_id</code> string Session ID (omitted if empty) <code>timestamp</code> string UTC RFC3339 timestamp <code>project</code> string Project directory name <p>See also: Webhook Notifications recipe.</p>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/pad/","level":1,"title":"Scratchpad","text":"","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad","level":2,"title":"<code>ctx pad</code>","text":"<p>Encrypted scratchpad for sensitive one-liners that travel with the project.</p> <p>When invoked without a subcommand, lists all entries.</p> <pre><code>ctx pad\nctx pad <subcommand>\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-add","level":3,"title":"<code>ctx pad add</code>","text":"<p>Append a new entry to the scratchpad.</p> <pre><code>ctx pad add <text>\nctx pad add <label> --file <path>\n</code></pre> <p>Flags:</p> Flag Short Description <code>--file</code> <code>-f</code> Ingest a file as a blob entry (max 64 KB) <p>Examples:</p> <pre><code>ctx pad add \"DATABASE_URL=postgres://user:pass@host/db\"\nctx pad add \"deploy config\" --file ./deploy.yaml\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-show","level":3,"title":"<code>ctx pad show</code>","text":"<p>Output the raw text of an entry by number. For blob entries, prints decoded file content (or writes to disk with <code>--out</code>).</p> <pre><code>ctx pad show <n>\nctx pad show <n> --out <path>\n</code></pre> <p>Arguments:</p> <ul> <li><code>n</code>: 1-based entry number</li> </ul> <p>Flags:</p> Flag Description <code>--out</code> Write decoded blob content to a file (blobs only) <p>Examples:</p> <pre><code>ctx pad show 3\nctx pad show 2 --out ./recovered.yaml\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-rm","level":3,"title":"<code>ctx pad rm</code>","text":"<p>Remove one or more entries by stable ID. Supports individual IDs and ranges.</p> <pre><code>ctx pad rm <id> [id...]\n</code></pre> <p>Arguments:</p> <ul> <li><code>id</code>: One or more entry IDs (e.g., <code>3</code>, <code>1 4</code>, <code>3-5</code>)</li> </ul> <p>Examples:</p> <pre><code>ctx pad rm 2\nctx pad rm 1 4\nctx pad rm 3-5\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-normalize","level":3,"title":"<code>ctx pad normalize</code>","text":"<p>Reassign entry IDs as a contiguous sequence 1..N, closing any gaps left by deletions.</p> <p>Examples:</p> <pre><code>ctx pad normalize\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-edit","level":3,"title":"<code>ctx pad edit</code>","text":"<p>Replace, append to, or prepend to an entry.</p> <pre><code>ctx pad edit <n> [text]\n</code></pre> <p>Arguments:</p> <ul> <li><code>n</code>: 1-based entry number</li> <li><code>text</code>: Replacement text (mutually exclusive with   <code>--append</code>/<code>--prepend</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--append</code> Append text to the end of the entry <code>--prepend</code> Prepend text to the beginning of entry <code>--file</code> Replace blob file content (preserves label) <code>--label</code> Replace blob label (preserves content) <p>Examples:</p> <pre><code>ctx pad edit 2 \"new text\"\nctx pad edit 2 --append \" suffix\"\nctx pad edit 2 --prepend \"prefix \"\nctx pad edit 1 --file ./v2.yaml\nctx pad edit 1 --label \"new name\"\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-mv","level":3,"title":"<code>ctx pad mv</code>","text":"<p>Move an entry from one position to another.</p> <pre><code>ctx pad mv <from> <to>\n</code></pre> <p>Arguments:</p> <ul> <li><code>from</code>: Source position (1-based)</li> <li><code>to</code>: Destination position (1-based)</li> </ul> <p>Examples:</p> <pre><code>ctx pad mv 3 1      # promote entry 3 to the top\nctx pad mv 1 5      # bury entry 1 to position 5\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-resolve","level":3,"title":"<code>ctx pad resolve</code>","text":"<p>Show both sides of a merge conflict in the encrypted scratchpad.</p> <p>Examples:</p> <pre><code>ctx pad resolve\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-import","level":3,"title":"<code>ctx pad import</code>","text":"<p>Bulk-import lines from a file into the scratchpad. Each non-empty line becomes a separate entry. All entries are written in a single encrypt/write cycle.</p> <p>With <code>--blob</code>, import all first-level files from a directory as blob entries. Each file becomes a blob with the filename as its label. Subdirectories and non-regular files are skipped.</p> <pre><code>ctx pad import <file>\nctx pad import -              # read from stdin\nctx pad import --blob <dir>   # import directory files as blobs\n</code></pre> <p>Arguments:</p> <ul> <li><code>file</code>: Path to a text file, <code>-</code> for stdin, or a directory   (with <code>--blob</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--blob</code> Import first-level files from a directory as blobs <p>Examples:</p> <pre><code>ctx pad import notes.txt\ngrep TODO *.go | ctx pad import -\nctx pad import --blob ./ideas/\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-export","level":3,"title":"<code>ctx pad export</code>","text":"<p>Export all blob entries from the scratchpad to a directory as files. Each blob's label becomes the filename. Non-blob entries are skipped.</p> <pre><code>ctx pad export [dir]\n</code></pre> <p>Arguments:</p> <ul> <li><code>dir</code>: Target directory (default: current directory)</li> </ul> <p>Flags:</p> Flag Short Description <code>--force</code> <code>-f</code> Overwrite existing files instead of timestamping <code>--dry-run</code> Print what would be exported without writing <p>When a file already exists, a unix timestamp is prepended to avoid collisions (e.g., <code>1739836200-label</code>). Use <code>--force</code> to overwrite instead.</p> <p>Examples:</p> <pre><code>ctx pad export ./ideas\nctx pad export --dry-run\nctx pad export --force ./backup\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-merge","level":3,"title":"<code>ctx pad merge</code>","text":"<p>Merge entries from one or more scratchpad files into the current pad. Each input file is auto-detected as encrypted or plaintext. Entries are deduplicated by exact content.</p> <pre><code>ctx pad merge FILE...\n</code></pre> <p>Arguments:</p> <ul> <li><code>FILE...</code>: One or more scratchpad files to merge (encrypted   or plaintext)</li> </ul> <p>Flags:</p> Flag Short Description <code>--key</code> <code>-k</code> Path to key file for decrypting input files <code>--dry-run</code> Print what would be merged without writing <p>Examples:</p> <pre><code>ctx pad merge worktree/.context/scratchpad.enc\nctx pad merge notes.md backup.enc\nctx pad merge --key /path/to/other.key foreign.enc\nctx pad merge --dry-run pad-a.enc pad-b.md\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pause/","level":1,"title":"Pause","text":"","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/pause/#ctx-hook-pause","level":2,"title":"<code>ctx hook pause</code>","text":"<p>Pause all context nudge and reminder hooks for the current session. Security hooks (dangerous command blocking) and housekeeping hooks still fire.</p> <pre><code>ctx hook pause [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--session-id</code> Session ID (overrides stdin) <p>Example:</p> <pre><code># Pause hooks for a quick investigation\nctx hook pause\n\n# Resume when ready\nctx hook resume\n</code></pre> <p>See also:</p> <ul> <li><code>ctx hook resume</code>: the matching resume command</li> <li>Pausing Context Hooks recipe</li> </ul>","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/prune/","level":1,"title":"Prune","text":"","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/prune/#ctx-prune","level":3,"title":"<code>ctx prune</code>","text":"<p>Remove per-session state files from <code>.context/state/</code> that are older than the specified age. Session state files are identified by UUID suffixes (<code>context-check-<session-id></code>, <code>heartbeat-<session-id></code>, and similar). Global files without session IDs (<code>events.jsonl</code>, <code>memory-import.json</code>, and other non-per-session markers) are always preserved.</p> <pre><code>ctx prune [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--days</code> Prune files older than this many days (default: 7) <code>--dry-run</code> Show what would be pruned without deleting <p>Examples:</p> <pre><code>ctx prune                 # Prune files older than 7 days\nctx prune --days 3        # Prune files older than 3 days\nctx prune --dry-run       # Preview without deleting\n</code></pre> <p>See State maintenance for the recommended cadence and automation recipe.</p>","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/remind/","level":1,"title":"Remind","text":"","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind","level":2,"title":"<code>ctx remind</code>","text":"<p>Session-scoped reminders that surface at session start. Reminders are stored verbatim and relayed verbatim: no summarization, no categories.</p> <p>When invoked with a text argument and no subcommand, adds a reminder.</p> <pre><code>ctx remind \"text\"\nctx remind <subcommand>\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-add","level":3,"title":"<code>ctx remind add</code>","text":"<p>Add a reminder. This is the default action: <code>ctx remind \"text\"</code> and <code>ctx remind add \"text\"</code> are equivalent.</p> <pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind add \"check CI after the deploy\" --after 2026-02-25\n</code></pre> <p>Arguments:</p> <ul> <li><code>text</code>: The reminder message (verbatim)</li> </ul> <p>Flags:</p> Flag Short Description <code>--after</code> <code>-a</code> Don't surface until this date (YYYY-MM-DD) <p>Examples:</p> <pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind \"check CI after the deploy\" --after 2026-02-25\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-list","level":3,"title":"<code>ctx remind list</code>","text":"<p>List all pending reminders. Date-gated reminders that aren't yet due are annotated with <code>(after DATE, not yet due)</code>.</p> <p>Examples:</p> <pre><code>ctx remind list\nctx remind ls            # alias\n</code></pre> <p>Aliases: <code>ls</code></p>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-dismiss","level":3,"title":"<code>ctx remind dismiss</code>","text":"<p>Remove one or more reminders by ID, or remove all with <code>--all</code>. Supports individual IDs and ranges.</p> <pre><code>ctx remind dismiss <id> [id...]\nctx remind dismiss --all\n</code></pre> <p>Arguments:</p> <ul> <li><code>id</code>: One or more reminder IDs (e.g., <code>3</code>, <code>3 5-7</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--all</code> Dismiss all reminders <p>Aliases: <code>rm</code></p> <p>Examples:</p> <pre><code>ctx remind dismiss 3\nctx remind dismiss 3 5-7\nctx remind dismiss --all\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-normalize","level":3,"title":"<code>ctx remind normalize</code>","text":"<p>Reassign reminder IDs as a contiguous sequence 1..N, closing any gaps left by dismissals.</p> <p>Examples:</p> <pre><code>ctx remind normalize\n</code></pre> <p>See also: Session Reminders recipe.</p>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/resume/","level":1,"title":"Resume","text":"","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/resume/#ctx-hook-resume","level":2,"title":"<code>ctx hook resume</code>","text":"<p>Resume context hooks after a pause. Silent no-op if not paused.</p> <pre><code>ctx hook resume [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--session-id</code> Session ID (overrides stdin) <p>Example:</p> <pre><code>ctx hook resume\n</code></pre> <p>See also:</p> <ul> <li><code>ctx hook pause</code>: the matching pause command</li> <li>Pausing Context Hooks recipe</li> </ul>","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/serve/","level":1,"title":"Serve","text":"","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#ctx-serve","level":2,"title":"<code>ctx serve</code>","text":"<p>Serve a static site locally via zensical.</p> <p>With no argument, serves the journal site at <code>.context/journal-site</code>. With a directory argument, serves that directory if it contains a <code>zensical.toml</code>.</p> <pre><code>ctx serve                             # Serve .context/journal-site\nctx serve ./my-site                   # Serve a specific directory\nctx serve ./docs                      # Serve any zensical site\n</code></pre> <p>This Command Does NOT Start a Hub</p> <p><code>ctx serve</code> is purely for static-site serving. To run a <code>ctx</code> Hub for cross-project knowledge sharing, use <code>ctx hub start</code>. That command lives in its own group because the hub is a gRPC server, not a static site.</p> <p>Requires zensical to be installed:</p> <pre><code>pipx install zensical\n</code></pre>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#arguments","level":3,"title":"Arguments","text":"Argument Description <code>[directory]</code> Directory containing a <code>zensical.toml</code> to serve <p>When omitted, serves <code>.context/journal-site</code> by default, the directory produced by <code>ctx journal site</code>.</p> <p>Examples:</p> <pre><code>ctx serve                         # Default: serve .context/journal-site\nctx serve ./my-site               # Serve a specific directory\nctx serve ./docs                  # Serve any zensical site\n</code></pre>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx journal</code>: generate the journal site   that <code>ctx serve</code> displays.</li> <li><code>ctx hub start</code>: for running a <code>ctx</code> Hub   server, not a static site.</li> <li>Browsing and enriching past sessions:   the recipe that combines <code>ctx journal</code> and <code>ctx serve</code>.</li> </ul>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/setup/","level":1,"title":"Setup","text":"","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/setup/#ctx-setup","level":2,"title":"<code>ctx setup</code>","text":"<p>Generate AI tool integration configuration.</p> <pre><code>ctx setup <tool> [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--write</code> <code>-w</code> Write the generated config to disk (e.g. <code>.github/copilot-instructions.md</code>) <p>Supported tools:</p> Tool Description <code>claude-code</code> Redirects to plugin install instructions <code>cursor</code> Cursor IDE <code>kiro</code> Kiro IDE <code>cline</code> Cline (VS Code extension) <code>aider</code> Aider CLI <code>copilot</code> GitHub Copilot <code>opencode</code> OpenCode (terminal-first AI coding agent) <code>windsurf</code> Windsurf IDE <p>Claude Code Uses the Plugin System</p> <p>Claude Code integration is now provided via the <code>ctx</code> plugin. Running <code>ctx setup claude-code</code> prints plugin install instructions.</p> <p>Examples:</p> <pre><code># Print hook instructions to stdout\nctx setup cursor\nctx setup aider\n\n# Generate and write .github/copilot-instructions.md\nctx setup copilot --write\n\n# Generate MCP config and sync steering files\nctx setup kiro --write\nctx setup cursor --write\nctx setup cline --write\n\n# Generate OpenCode plugin, skills, AGENTS.md, and global MCP config\nctx setup opencode --write\n</code></pre>","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/site/","level":1,"title":"Site","text":"","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site","level":2,"title":"<code>ctx site</code>","text":"<p>Site management commands for the ctx.ist static site.</p> <pre><code>ctx site <subcommand>\n</code></pre>","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site-feed","level":3,"title":"<code>ctx site feed</code>","text":"<p>Generate an Atom 1.0 feed from finalized blog posts in <code>docs/blog/</code>.</p> <pre><code>ctx site feed [flags]\n</code></pre> <p>Scans <code>docs/blog/</code> for files matching <code>YYYY-MM-DD-*.md</code>, parses YAML frontmatter, and generates a valid Atom feed. Only posts with <code>reviewed_and_finalized: true</code> are included. Summaries are extracted from the first paragraph after the heading.</p> <p>Flags:</p> Flag Short Type Default Description <code>--out</code> <code>-o</code> string <code>site/feed.xml</code> Output path <code>--base-url</code> string <code>https://ctx.ist</code> Base URL for entry links <p>Output:</p> <pre><code>Generated site/feed.xml (21 entries)\n\nSkipped:\n  2026-02-25-the-homework-problem.md: not finalized\n\nWarnings:\n  2026-02-09-defense-in-depth.md: no summary paragraph found\n</code></pre> <p>Three buckets: included (count), skipped (with reason), warnings (included but degraded). <code>exit 0</code> always: warnings inform but do not block.</p> <p>Frontmatter requirements:</p> Field Required Feed mapping <code>title</code> Yes <code><title></code> <code>date</code> Yes <code><updated></code> <code>reviewed_and_finalized</code> Yes Draft gate (must be <code>true</code>) <code>author</code> No <code><author><name></code> <code>topics</code> No <code><category term=\"\"></code> <p>Examples:</p> <pre><code>ctx site feed                                # Generate site/feed.xml\nctx site feed --out /tmp/feed.xml            # Custom output path\nctx site feed --base-url https://example.com # Custom base URL\nmake site-feed                               # Makefile shortcut\nmake site                                    # Builds site + feed\n</code></pre>","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/skill/","level":1,"title":"Skill","text":"","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill","level":2,"title":"<code>ctx skill</code>","text":"<p>Manage reusable instruction bundles that can be installed into <code>.context/skills/</code>.</p> <p>A skill is a directory containing a <code>SKILL.md</code> file with YAML frontmatter (<code>name</code>, <code>description</code>) and a Markdown instruction body. Skills are loaded by the agent context packet when <code>--skill <name></code> is passed to <code>ctx agent</code>.</p> <pre><code>ctx skill <subcommand>\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-install","level":3,"title":"<code>ctx skill install</code>","text":"<p>Install a skill from a source directory.</p> <pre><code>ctx skill install <source>\n</code></pre> <p>Arguments:</p> <ul> <li><code>source</code>: Path to a directory containing <code>SKILL.md</code></li> </ul> <p>Examples:</p> <pre><code>ctx skill install ./my-skills/code-review\n# Installed code-review → .context/skills/code-review\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-list","level":3,"title":"<code>ctx skill list</code>","text":"<p>List all installed skills.</p> <p>Examples:</p> <pre><code>ctx skill list\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-remove","level":3,"title":"<code>ctx skill remove</code>","text":"<p>Remove an installed skill.</p> <p>Arguments:</p> <ul> <li><code>name</code>: Skill name to remove</li> </ul> <p>Examples:</p> <pre><code>ctx skill remove code-review\n</code></pre> <p>See also: Building Project Skills recipe.</p>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/steering/","level":1,"title":"Steering","text":"","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering","level":2,"title":"<code>ctx steering</code>","text":"<p>Manage steering files: persistent behavioral rules for AI coding assistants.</p> <p>A steering file is a small Markdown document with YAML frontmatter that tells the AI how to behave in a specific context. <code>ctx steering</code> keeps those files in <code>.context/steering/</code>, decides which ones apply for a given prompt, and syncs them out to each AI tool's native format (Claude Code, Cursor, Kiro, Cline).</p> <pre><code>ctx steering <subcommand>\n</code></pre> <p>Steering vs Decisions vs Conventions</p> <p>The three look similar on disk but serve different purposes:</p> <ul> <li>Decisions record what was chosen and why.   Consumed mostly by humans (and by the agent via   <code>ctx agent</code>).</li> <li>Conventions describe how the codebase is written.   Consumed as reference material.</li> <li>Steering tells the AI how to behave when asked   about X. Consumed by the AI tool's prompt injection   layer, conditionally on prompt match.</li> </ul> <p>If you find yourself writing \"the AI should always do X\", that belongs in steering, not decisions.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#anatomy-of-a-steering-file","level":3,"title":"Anatomy of a Steering File","text":"<pre><code>---\nname: security\ndescription: Security rules for all code changes\ninclusion: always    # always | auto | manual\ntools: []            # empty = all tools\npriority: 10         # lower = injected first\n---\n\n# Security rules\n\n- Validate all user input at system boundaries.\n- Never log secrets, tokens, or credentials.\n- Prefer constant-time comparison for tokens.\n</code></pre> <p>Inclusion modes:</p> Mode When it's included <code>always</code> Every prompt, unconditionally <code>auto</code> When the prompt matches the <code>description</code> keywords <code>manual</code> Only when the user names it explicitly <p>Priority: lower numbers inject first, so high-priority rules appear at the top of the prompt. Default is <code>50</code>.</p> <p>Tools: an empty list means all configured tools receive the file; list specific tool names to scope it.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-init","level":3,"title":"<code>ctx steering init</code>","text":"<p>Create a starter set of steering files in <code>.context/steering/</code> to use as a scaffolding baseline.</p> <p>Examples:</p> <pre><code>ctx steering init\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-add","level":3,"title":"<code>ctx steering add</code>","text":"<p>Create a new steering file with default frontmatter.</p> <pre><code>ctx steering add <name>\n</code></pre> <p>Arguments:</p> <ul> <li><code>name</code>: Steering file name (without <code>.md</code> extension)</li> </ul> <p>Examples:</p> <pre><code>ctx steering add security\n# Created .context/steering/security.md\n</code></pre> <p>The generated file uses <code>inclusion: manual</code> and <code>priority: 50</code> by default. Edit the frontmatter to change behavior.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-list","level":3,"title":"<code>ctx steering list</code>","text":"<p>List all steering files with their inclusion mode, priority, and tool scoping.</p> <p>Examples:</p> <pre><code>ctx steering list\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-preview","level":3,"title":"<code>ctx steering preview</code>","text":"<p>Preview which steering files would be included for a given prompt. Useful for validating <code>auto</code>-inclusion descriptions against realistic prompts.</p> <pre><code>ctx steering preview [prompt]\n</code></pre> <p>Examples:</p> <pre><code>ctx steering preview \"create a REST API endpoint\"\n# Steering files matching prompt \"create a REST API endpoint\":\n#   api-standards        inclusion=auto     priority=20  tools=all\n#   security             inclusion=always   priority=10  tools=all\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-sync","level":3,"title":"<code>ctx steering sync</code>","text":"<p>Sync steering files to tool-native formats for tools that have a built-in rules primitive. Not every tool needs this; Claude Code and Codex use a different delivery mechanism (see below).</p> <p>Examples:</p> <pre><code>ctx steering sync\n</code></pre> <p>Which tools are sync targets?</p> Tool Sync target Mechanism Cursor <code>.cursor/rules/</code> Cursor reads the directory natively Cline <code>.clinerules/</code> Cline reads the directory natively Kiro <code>.kiro/steering/</code> Kiro reads the directory natively Claude Code (no-op) Delivered via hook + MCP (see next section) Codex (no-op) Same as Claude Code <p>For the three native-rules tools, <code>ctx steering sync</code> writes each matching steering file to the appropriate directory with tool-specific frontmatter transforms. Unchanged files are skipped (idempotent).</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#how-claude-code-and-codex-consume-steering","level":3,"title":"How Claude Code and Codex Consume Steering","text":"<p>Claude Code has no native \"steering files\" primitive, so <code>ctx steering sync</code> skips it entirely. Instead, steering reaches Claude through two non-sync channels, both activated by <code>ctx setup claude-code</code> (which installs the plugin):</p> <p>1. Automatic injection via the <code>PreToolUse</code> hook. The Claude Code plugin wires a <code>PreToolUse</code> hook that runs <code>ctx agent --budget 8000</code> before each tool call. <code>ctx agent</code> loads <code>.context/steering/</code> and calls <code>steering.Filter</code> with an empty prompt, so only files with <code>inclusion: always</code> match. Those files are included as Tier 6 of the context packet. The packet is printed on stdout, which Claude Code injects as additional context. This fires on every tool call; no user action.</p> <p>2. On-demand MCP tool call (<code>ctx_steering_get</code>). The <code>ctx</code> plugin ships a <code>.mcp.json</code> file that automatically registers the <code>ctx</code> MCP server (<code>ctx mcp serve</code>) with Claude Code on plugin install. Once registered, Claude can invoke the <code>ctx_steering_get</code> tool mid-task to fetch matching steering files for a specific prompt. This is the only path that resolves <code>inclusion: auto</code> and <code>inclusion: manual</code> matches for Claude Code; Claude passes the prompt to the MCP tool, which runs the keyword match against each file's description.</p> <p>Verify the MCP server is registered:</p> <pre><code>claude mcp list\n</code></pre> <p>Expected line: <code>ctx: ctx mcp serve - ✓ Connected</code>. If it's missing, reinstall the plugin from Claude Code (<code>/plugin</code> → find <code>ctx</code> → uninstall → install again); older plugin versions shipped without the <code>.mcp.json</code> file.</p> <p>Prefer <code>inclusion: always</code> for Claude Code</p> <p>Because the PreToolUse hook passes an empty prompt to <code>ctx agent</code>, only <code>always</code> files fire automatically. <code>auto</code> files require Claude to call the <code>ctx_steering_get</code> MCP tool on its own; <code>manual</code> files require an explicit user invocation. For rules that should reliably fire on every Claude Code session, use <code>inclusion: always</code>. Reserve <code>auto</code>/<code>manual</code> for situational libraries where the opt-in cost is acceptable and you understand Claude may not pull them in without prompting.</p> <p>The foundation files scaffolded by <code>ctx init</code> already default to <code>inclusion: always</code> for this reason.</p> <p>Practical implications:</p> <ul> <li>Running <code>ctx steering sync</code> before starting a Claude   session does nothing for Claude's benefit. Skip it.</li> <li><code>ctx steering preview</code> still works for validating your   descriptions; it doesn't depend on sync.</li> <li>If Claude Code is your only tool, the <code>ctx steering</code>   commands you care about are <code>add</code>, <code>list</code>, <code>preview</code>,   <code>init</code> (never <code>sync</code>).</li> <li>If you use both Claude Code and (say) Cursor,   <code>ctx steering sync</code> covers Cursor (where <code>auto</code> and   <code>manual</code> work natively) while the hook+MCP pipeline   covers Claude Code. For rules you need to fire   automatically on both, use <code>inclusion: always</code>.</li> </ul>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-agent-integration","level":3,"title":"<code>ctx agent</code> Integration","text":"<p>When <code>ctx agent</code> builds a context packet, steering files are loaded as Tier 6 of the budget-aware assembly (see <code>ctx agent</code>). Files with <code>inclusion: always</code> are always included; <code>auto</code> files are scored against the current prompt and included in priority order until the tier budget is exhausted.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx setup</code>: configure which tools receive   steering syncs</li> <li><code>ctx trigger</code>: lifecycle scripts (a different   hooking concept, see below)</li> <li>Building steering files recipe:   walkthrough from first file to synced output</li> </ul>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/sysinfo/","level":1,"title":"Sysinfo","text":"","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/sysinfo/#ctx-sysinfo","level":3,"title":"<code>ctx sysinfo</code>","text":"<p>Display a snapshot of system resources (memory, swap, disk, load) with threshold-based alert severities. Mirrors what the <code>check-resource</code> hook plumbing monitors in the background, but this command prints the full report at any severity level, not only at DANGER.</p> <pre><code>ctx sysinfo [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output in JSON format <p>Alert thresholds:</p> Resource WARNING DANGER Memory ≥ 75% ≥ 90% Swap ≥ 50% ≥ 75% Disk ≥ 85% ≥ 95% Load ≥ 1.0x CPUs ≥ 1.5x CPUs <p>Examples:</p> <pre><code>ctx sysinfo                  # Human-readable table\nctx sysinfo --json           # Structured output\n</code></pre>","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/system/","level":1,"title":"System","text":"","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system","level":3,"title":"<code>ctx system</code>","text":"<p>Hidden parent command that hosts Claude Code hook plumbing and a small set of session-lifecycle plumbing subcommands used by skills and editor integrations. The parent is registered without a visible group in <code>ctx --help</code>; run <code>ctx system --help</code> to see its subcommands.</p> <pre><code>ctx system <subcommand>\n</code></pre> <p>Commands Previously under <code>ctx system</code></p> <p>Several user-facing maintenance commands used to live under <code>ctx system</code> and were promoted to top-level:</p> <ul> <li><code>ctx system events</code> → <code>ctx hook event</code></li> <li><code>ctx system message</code> → <code>ctx hook message</code></li> <li><code>ctx system prune</code> → <code>ctx prune</code></li> <li><code>ctx system resources</code> → <code>ctx sysinfo</code></li> <li><code>ctx system stats</code> → <code>ctx usage</code></li> </ul> <p><code>ctx system bootstrap</code> remains under <code>ctx system</code> as a hidden, agent-only command. Update any scripts or personal docs that reference the old paths.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#plumbing-subcommands","level":2,"title":"Plumbing Subcommands","text":"<p>These are not hook handlers; they're called by skills and editor integrations during the session lifecycle. Safe to run manually.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-journal","level":4,"title":"<code>ctx system mark-journal</code>","text":"<p>Update processing state for a journal entry. Records the current date in <code>.context/journal/.state.json</code>. Used by journal skills to record pipeline progress.</p> <pre><code>ctx system mark-journal <filename> <stage>\n</code></pre> <p>Stages: <code>exported</code>, <code>enriched</code>, <code>normalized</code>, <code>fences_verified</code></p> Flag Description <code>--check</code> Check if stage is set (exit 1 if not) <p>Example:</p> <pre><code>ctx system mark-journal 2026-01-21-session-abc12345.md enriched\nctx system mark-journal 2026-01-21-session-abc12345.md normalized\nctx system mark-journal --check 2026-01-21-session-abc12345.md fences_verified\n</code></pre>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-wrapped-up","level":4,"title":"<code>ctx system mark-wrapped-up</code>","text":"<p>Suppress context checkpoint nudges after a wrap-up ceremony. Writes a marker file that <code>check-context-size</code> checks before emitting checkpoint boxes. The marker expires after 2 hours.</p> <p>Called automatically by <code>/ctx-wrap-up</code> after persisting context (not intended for direct use).</p> <pre><code>ctx system mark-wrapped-up\n</code></pre> <p>No flags, no arguments. Idempotent: running it again updates the marker timestamp.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-pause-ctx-system-resume","level":4,"title":"<code>ctx system pause</code> / <code>ctx system resume</code>","text":"<p>Session-scoped hook suppression. <code>ctx system pause</code> writes a marker file that causes hook plumbing to no-op for the current session; <code>ctx system resume</code> removes it. These are the hook-plumbing counterparts to the <code>ctx hook pause</code> / <code>ctx hook resume</code> commands (which call them internally).</p> <p>Read the session ID from stdin JSON (same as hooks) or pass <code>--session-id</code>.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-session-event","level":4,"title":"<code>ctx system session-event</code>","text":"<p>Records a session lifecycle event (start or end) to the event log. Called by editor integrations when a workspace is opened or closed.</p> <pre><code>ctx system session-event --type start --caller vscode\nctx system session-event --type end --caller vscode\n</code></pre>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#hook-subcommands","level":2,"title":"Hook Subcommands","text":"<p>Hidden Claude Code hook handlers implementing the hook contract: read JSON from stdin, perform logic, emit output on stdout, exit 0. Block commands output JSON with a <code>decision</code> field.</p> <p>UserPromptSubmit hooks: <code>context-load-gate</code>, <code>check-context-size</code>, <code>check-persistence</code>, <code>check-ceremony</code>, <code>check-journal</code>, <code>check-version</code>, <code>check-resource</code>, <code>check-knowledge</code>, <code>check-map-staleness</code>, <code>check-memory-drift</code>, <code>check-reminder</code>, <code>check-freshness</code>, <code>check-hub-sync</code>, <code>check-skill-discovery</code>, <code>heartbeat</code>.</p> <p>PreToolUse hooks: <code>block-non-path-ctx</code>, <code>block-dangerous-command</code>, <code>qa-reminder</code>, <code>specs-nudge</code>.</p> <p>PostToolUse hooks: <code>post-commit</code>, <code>check-task-completion</code>.</p> <p>See AI Tools for registration details and the Claude Code plugin integration.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/trace/","level":1,"title":"Commit Context Tracing","text":"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace","level":3,"title":"<code>ctx trace</code>","text":"<p>Show the context behind git commits. Links commits back to the decisions, tasks, learnings, and sessions that motivated them.</p> <p><code>git log</code> shows what changed, <code>git blame</code> shows who, and <code>ctx trace</code> shows why.</p> <pre><code>ctx trace [commit] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--last N</code> Show context for last N commits <code>--json</code> Output as JSON for scripting <p>Examples:</p> <pre><code># Show context for a specific commit\nctx trace abc123\n\n# Show context for last 10 commits\nctx trace --last 10\n\n# JSON output\nctx trace abc123 --json\n</code></pre> <p>Output:</p> <pre><code>Commit: abc123 \"Fix auth token expiry\"\nDate:   2026-03-14 10:00:00 -0700\nContext:\n  [Decision] #12: Use short-lived tokens with server-side refresh\n    Date: 2026-03-10\n\n  [Task] #8: Implement token rotation for compliance\n    Status: completed\n</code></pre> <p>When listing recent commits with <code>--last</code>:</p> <pre><code>abc123  Fix auth token expiry         decision:12, task:8\ndef456  Add rate limiting             decision:15, learning:7\n789abc  Update dependencies           (none)\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-file","level":3,"title":"<code>ctx trace file</code>","text":"<p>Show the context trail for a file. Combines <code>git log</code> with context resolution.</p> <pre><code>ctx trace file <path[:line-range]> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--last N</code> Maximum commits to show (default: 20) <p>Examples:</p> <pre><code># Show context trail for a file\nctx trace file src/auth.go\n\n# Show context for specific line range\nctx trace file src/auth.go:42-60\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-tag","level":3,"title":"<code>ctx trace tag</code>","text":"<p>Manually tag a commit with context. For commits made without the hook, or to add extra context after the fact.</p> <p>Tags are stored in <code>.context/trace/overrides.jsonl</code> since git trailers cannot be added to existing commits without rewriting history.</p> <pre><code>ctx trace tag <commit> --note \"<text>\"\n</code></pre> <p>Examples:</p> <pre><code>ctx trace tag HEAD --note \"Hotfix for production outage\"\nctx trace tag abc123 --note \"Part of Q1 compliance initiative\"\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-hook","level":3,"title":"<code>ctx trace hook</code>","text":"<p>Enable or disable the prepare-commit-msg hook for automatic context tracing. When enabled, commits automatically receive a <code>ctx-context</code> trailer with references to relevant decisions, tasks, learnings, and sessions.</p> <pre><code>ctx trace hook <enable|disable>\n</code></pre> <p>Prerequisites: <code>ctx</code> must be on your <code>$PATH</code>. If you installed via <code>go install</code>, ensure <code>$GOPATH/bin</code> (or <code>$HOME/go/bin</code>) is in your shell's <code>$PATH</code>.</p> <p>What the hook does:</p> <ol> <li>Before each commit, collects context from three sources:</li> <li>Pending context accumulated during work (<code>ctx add</code>, <code>ctx task complete</code>)</li> <li>Staged file changes to <code>.context/</code> files</li> <li>Working state (in-progress tasks, active AI session)</li> <li>Injects a <code>ctx-context</code> trailer into the commit message</li> <li>After commit, records the mapping in <code>.context/trace/history.jsonl</code></li> </ol> <p>Examples:</p> <pre><code># Install the hook\nctx trace hook enable\n\n# Remove the hook\nctx trace hook disable\n</code></pre> <p>Resulting commit message:</p> <pre><code>Fix auth token expiry handling\n\nRefactored token refresh logic to handle edge case\nwhere refresh token expires during request.\n\nctx-context: decision:12, task:8, session:abc123\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#reference-types","level":3,"title":"Reference Types","text":"<p>The <code>ctx-context</code> trailer supports these reference types:</p> Prefix Points to Example <code>decision:<n></code> Entry #n in DECISIONS.md <code>decision:12</code> <code>learning:<n></code> Entry #n in LEARNINGS.md <code>learning:5</code> <code>task:<n></code> Task #n in TASKS.md <code>task:8</code> <code>convention:<n></code> Entry #n in CONVENTIONS.md <code>convention:3</code> <code>session:<id></code> AI session by ID <code>session:abc123</code> <code>\"<text>\"</code> Free-form context note <code>\"Performance fix for P1 incident\"</code>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#storage","level":3,"title":"Storage","text":"<p>Context trace data is stored in the <code>.context/</code> directory:</p> File Purpose Lifecycle <code>state/pending-context.jsonl</code> Accumulates refs during work Truncated after each commit <code>trace/history.jsonl</code> Permanent commit-to-context map Append-only, never truncated <code>trace/overrides.jsonl</code> Manual tags for existing commits Append-only","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trigger/","level":1,"title":"Trigger","text":"","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger","level":2,"title":"<code>ctx trigger</code>","text":"<p>Manage lifecycle triggers: executable scripts that fire at specific events during an AI session. Triggers can block tool calls, inject context, and automate reactions: any side effect you want at session boundaries, tool boundaries, or file-save events.</p> <pre><code>ctx trigger <subcommand>\n</code></pre> <p>Triggers Execute Arbitrary Scripts</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks: only enable scripts you've read and understand. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#where-triggers-live","level":3,"title":"Where Triggers Live","text":"<p>Triggers live in <code>.context/hooks/<trigger-type>/</code> as executable scripts. The on-disk directory name is still <code>hooks/</code> for historical reasons even though the command is <code>ctx trigger</code>. Each script:</p> <ul> <li>Reads a JSON payload from stdin.</li> <li>Returns a JSON payload on stdout.</li> <li>Returns a non-zero exit code to block or error.</li> </ul> <pre><code>.context/\n└── hooks/\n    ├── session-start/\n    │   └── inject-context.sh\n    ├── pre-tool-use/\n    │   └── block-legacy.sh\n    └── post-tool-use/\n        └── record-edit.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#trigger-types","level":3,"title":"Trigger Types","text":"Type Fires when <code>session-start</code> An AI session begins <code>session-end</code> An AI session ends <code>pre-tool-use</code> Before an AI tool call is executed <code>post-tool-use</code> After an AI tool call returns <code>file-save</code> When a file is saved <code>context-add</code> When a context entry is added","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#input-and-output-contract","level":3,"title":"Input and Output Contract","text":"<p>Each trigger receives a JSON object on stdin with the event details. Minimal contract (fields vary by trigger type):</p> <pre><code>{\n  \"type\": \"pre-tool-use\",\n  \"tool\": \"write_file\",\n  \"path\": \"src/auth.go\",\n  \"session_id\": \"abc123-...\"\n}\n</code></pre> <p>The trigger may write a JSON object to stdout to influence behavior. Example for a blocking <code>pre-tool-use</code> trigger:</p> <pre><code>{\n  \"action\": \"block\",\n  \"message\": \"Editing src/auth.go requires approval from #security\"\n}\n</code></pre> <p>For non-blocking event loggers, simply read stdin and exit 0 without writing to stdout.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-add","level":3,"title":"<code>ctx trigger add</code>","text":"<p>Create a new trigger script with a template. The generated file has a bash shebang, a stdin reader using <code>jq</code>, and a basic JSON output structure.</p> <pre><code>ctx trigger add <trigger-type> <name>\n</code></pre> <p>Arguments:</p> <ul> <li><code>trigger-type</code>: One of <code>session-start</code>, <code>session-end</code>,   <code>pre-tool-use</code>, <code>post-tool-use</code>, <code>file-save</code>, <code>context-add</code></li> <li><code>name</code>: Script name (without <code>.sh</code> extension)</li> </ul> <p>Examples:</p> <pre><code>ctx trigger add session-start inject-context\n# Created .context/hooks/session-start/inject-context.sh\n\nctx trigger add pre-tool-use block-legacy\n# Created .context/hooks/pre-tool-use/block-legacy.sh\n</code></pre> <p>The generated script is not executable by default. Enable it with <code>ctx trigger enable</code> after reviewing the contents.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-list","level":3,"title":"<code>ctx trigger list</code>","text":"<p>List all discovered triggers, grouped by trigger type, with their enabled/disabled status.</p> <p>Examples:</p> <pre><code>ctx trigger list\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-test","level":3,"title":"<code>ctx trigger test</code>","text":"<p>Run all enabled triggers of a given type against a mock payload. Use <code>--tool</code> and <code>--path</code> to customize the mock input for tool-related events.</p> <pre><code>ctx trigger test <trigger-type> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--tool</code> Tool name to put in mock input <code>--path</code> File path to put in mock input <p>Examples:</p> <pre><code>ctx trigger test session-start\nctx trigger test pre-tool-use --tool write_file --path src/main.go\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-enable","level":3,"title":"<code>ctx trigger enable</code>","text":"<p>Enable a trigger by setting its executable permission bit. Searches every trigger-type directory for a script matching <code><name></code>.</p> <pre><code>ctx trigger enable <name>\n</code></pre> <p>Examples:</p> <pre><code>ctx trigger enable inject-context\n# Enabled .context/hooks/session-start/inject-context.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-disable","level":3,"title":"<code>ctx trigger disable</code>","text":"<p>Disable a trigger by clearing its executable permission bit. Searches every trigger-type directory for a script matching <code><name></code>.</p> <pre><code>ctx trigger disable <name>\n</code></pre> <p>Examples:</p> <pre><code>ctx trigger disable inject-context\n# Disabled .context/hooks/session-start/inject-context.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#three-hooking-concepts-in-ctx-dont-confuse-them","level":3,"title":"Three Hooking Concepts in <code>ctx</code> (Don't Confuse Them)","text":"<p>This is a common source of confusion. <code>ctx</code> has three distinct hook-like layers, and they serve different purposes:</p> Layer Owned by Where it runs Configured via <code>ctx trigger</code> You <code>.context/hooks/<type>/*.sh</code> <code>ctx trigger add/enable</code> <code>ctx system</code> hooks <code>ctx</code> itself built-in, called by <code>ctx</code>'s own lifecycle internal (see <code>ctx system --help</code>) Claude Code hooks Claude Code <code>.claude/settings.local.json</code> edit JSON, or <code>/ctx-sanitize-permissions</code> <p>Use <code>ctx trigger</code> when you want project-specific automation that your AI tool will run at lifecycle events. Use Claude Code hooks for tool-specific integrations that don't need to be portable across tools. <code>ctx system</code> hooks are not something you author; they're the internal nudge machinery that ships with ctx.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx steering</code>: persistent AI behavioral   rules (a different concept; rules vs scripts)</li> <li>Authoring triggers recipe: a   full walkthrough with security guidance</li> </ul>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/usage/","level":1,"title":"Usage","text":"","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/usage/#ctx-usage","level":3,"title":"<code>ctx usage</code>","text":"<p>Display per-session token usage statistics from the local stats JSONL files written by the <code>heartbeat</code> hook. By default, shows the last 20 entries across all sessions. Use <code>--follow</code> to stream new entries as they arrive (like <code>tail -f</code>).</p> <pre><code>ctx usage [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>-f</code>, <code>--follow</code> Stream new entries as they arrive <code>-s</code>, <code>--session</code> Filter by session ID (prefix match) <code>-n</code>, <code>--last</code> Show last N entries (default: 20) <code>-j</code>, <code>--json</code> Output raw JSONL <p>Examples:</p> <pre><code>ctx usage                     # Last 20 entries across all sessions\nctx usage --follow            # Live stream (like tail -f)\nctx usage --session abc123    # Filter to one session\nctx usage --last 100 --json   # Last 100 as raw JSONL\n</code></pre>","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/watch/","level":1,"title":"Watch","text":"","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/watch/#ctx-watch","level":2,"title":"<code>ctx watch</code>","text":"<p>Watch for AI output and auto-apply context updates.</p> <p>Parses <code><context-update></code> XML commands from AI output and applies them to context files.</p> <pre><code>ctx watch [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--log <file></code> Log file to watch (default: stdin) <code>--dry-run</code> Preview updates without applying <p>Examples:</p> <pre><code># Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n</code></pre>","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/why/","level":1,"title":"Why","text":"","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"cli/why/#ctx-why","level":2,"title":"<code>ctx why</code>","text":"<p>Read <code>ctx</code>'s philosophy documents directly in the terminal.</p> <pre><code>ctx why [DOCUMENT]\n</code></pre> <p>Documents:</p> Name Description <code>manifesto</code> The <code>ctx</code> Manifesto: creation, not code <code>about</code> About <code>ctx</code>: what it is and why it exists <code>invariants</code> Design invariants: properties that must hold <p>Examples:</p> <pre><code># Interactive numbered menu\nctx why\n\n# Show a specific document\nctx why manifesto\nctx why about\nctx why invariants\n\n# Pipe to a pager\nctx why manifesto | less\n</code></pre>","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"home/","level":1,"title":"Home","text":"<ul> <li><code>ctx</code> is not a prompt.</li> <li><code>ctx</code> is version-controlled cognitive state.</li> </ul> <p><code>ctx</code> is the persistence layer for human-AI reasoning.</p> <p>Deterministic. Git-native. Human-readable. Local-first.</p> <p>Start here.</p> <p>Learn what <code>ctx</code> does, set it up, and run your first session.</p> <p>Pre-1.0: Moving Fast</p> <p><code>ctx</code> is under active development. This website tracks the development branch, not the latest release:</p> <p>Some features described here may not exist in the binary you have installed.</p> <p>Expect rough edges.</p> <p>If something is missing or broken, open an issue.</p>","path":["Home"],"tags":[]},{"location":"home/#introduction","level":2,"title":"Introduction","text":"","path":["Home"],"tags":[]},{"location":"home/#about","level":3,"title":"About","text":"<p>What <code>ctx</code> is, how it works, and why persistent context changes how you work with AI.</p>","path":["Home"],"tags":[]},{"location":"home/#is-it-right-for-me","level":3,"title":"Is It Right for Me?","text":"<p>Good fit, not-so-good fit, and a 5-minute trial to find out for yourself.</p>","path":["Home"],"tags":[]},{"location":"home/#faq","level":3,"title":"FAQ","text":"<p>Quick answers to the questions newcomers ask most about <code>ctx</code>, files, tooling, and trade-offs.</p>","path":["Home"],"tags":[]},{"location":"home/#get-started","level":2,"title":"Get Started","text":"","path":["Home"],"tags":[]},{"location":"home/#getting-started","level":3,"title":"Getting Started","text":"<p>Install the binary, set up the plugin, and verify it works.</p>","path":["Home"],"tags":[]},{"location":"home/#your-first-session","level":3,"title":"Your First Session","text":"<p>Step-by-step walkthrough from <code>ctx init</code> to verified recall.</p>","path":["Home"],"tags":[]},{"location":"home/#common-workflows","level":3,"title":"Common Workflows","text":"<p>Day-to-day commands for tracking context, checking health, and browsing history.</p>","path":["Home"],"tags":[]},{"location":"home/#concepts","level":2,"title":"Concepts","text":"","path":["Home"],"tags":[]},{"location":"home/#context-files","level":3,"title":"Context Files","text":"<p>What each <code>.context/</code> file does. What's their purpose. How do we best leverage them.</p>","path":["Home"],"tags":[]},{"location":"home/#configuration","level":3,"title":"Configuration","text":"<p>Flexible configuration: <code>.ctxrc</code>, environment variables, and CLI flags.</p>","path":["Home"],"tags":[]},{"location":"home/#hub","level":3,"title":"Hub","text":"<p>A fan-out channel for decisions, learnings, conventions, and tasks that need to cross project boundaries, without replicating everything else.</p>","path":["Home"],"tags":[]},{"location":"home/#working-with-ai","level":2,"title":"Working with AI","text":"","path":["Home"],"tags":[]},{"location":"home/#prompting-guide","level":3,"title":"Prompting Guide","text":"<p>Effective prompts for AI sessions with <code>ctx</code>.</p>","path":["Home"],"tags":[]},{"location":"home/#keeping-ai-honest","level":3,"title":"Keeping AI Honest","text":"<p>AI agents confabulate: they invent history, claim familiarity with decisions never made, and sometimes declare tasks complete when they aren't. Tools and habits to push back.</p>","path":["Home"],"tags":[]},{"location":"home/#my-ai-keeps-making-the-same-mistakes","level":3,"title":"My AI Keeps Making the Same Mistakes","text":"<p>Stop rediscovering the same bugs and dead-ends across sessions.</p>","path":["Home"],"tags":[]},{"location":"home/#joining-a-project","level":3,"title":"Joining a Project","text":"<p>You inherited a <code>.context/</code> directory. Get oriented fast: priority order, what to read first, how to ramp up.</p>","path":["Home"],"tags":[]},{"location":"home/#customization","level":2,"title":"Customization","text":"","path":["Home"],"tags":[]},{"location":"home/#steering-files","level":3,"title":"Steering Files","text":"<p>Tell the assistant how to behave when a specific kind of prompt arrives.</p>","path":["Home"],"tags":[]},{"location":"home/#lifecycle-triggers","level":3,"title":"Lifecycle Triggers","text":"<p>Make things happen at session boundaries: block dangerous tool calls, inject standup notes, log file saves.</p>","path":["Home"],"tags":[]},{"location":"home/#community","level":2,"title":"Community","text":"","path":["Home"],"tags":[]},{"location":"home/#ctx","level":3,"title":"#<code>ctx</code>","text":"<p>We are the builders who care about durable context. Join the community. Hang out in IRC. Star <code>ctx</code> on GitHub.</p>","path":["Home"],"tags":[]},{"location":"home/#contributing","level":3,"title":"Contributing","text":"<p>Development setup, project layout, and pull request process.</p>","path":["Home"],"tags":[]},{"location":"home/about/","level":1,"title":"About","text":"<p>\"Creation, not code; Context, not prompts; Verification, not vibes.\"</p> <p>Read the <code>ctx</code> Manifesto →</p> <p>\"Without durable context, intelligence resets; with <code>ctx</code>, creation compounds.\"</p> <p>Without persistent memory, every session starts at zero; <code>ctx</code> makes sessions cumulative.</p> <p>Join the <code>ctx</code> Community →</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#what-is-ctx","level":2,"title":"What Is <code>ctx</code>?","text":"<p><code>ctx</code> (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. It lives in a <code>.context/</code> directory in your repo.</p> <ul> <li>A session is interactive.</li> <li><code>ctx</code> enables cognitive continuity.</li> <li>Cognitive continuity enables durable, symbiotic-like human-AI workflows.</li> </ul> <p>Context Files</p> <p>Context files let AI tools remember decisions, conventions, and learnings:</p> <p>Context files are explicit and versionable contracts  between you and your agents.</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#why-do-i-keep-re-explaining-my-codebase","level":2,"title":"Why Do I Keep Re-Explaining My Codebase?!?!","text":"<p>You open a new AI session. The first thing you do is re-explain your project.</p> <p>Again.</p> <p>The architecture, the database choice, the naming conventions, the thing you tried last week that didn't work. You have said all of this before (maybe yesterday, maybe an hour ago), but the AI doesn't know that.</p> <ul> <li>You explain the same architecture every session;</li> <li>The AI suggests an approach you already rejected, again;</li> <li>A decision you made three sessions ago gets relitigated from scratch;</li> <li>You spend more time setting context than building features.</li> </ul> <p>This isn't AI failure: It is staleness. </p> <p>Without persistent memory, every session starts at zero.</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#day-and-night","level":3,"title":"Day and Night","text":"<p><code>ctx</code> gives your AI a memory that persists across sessions:</p> Without <code>ctx</code>With <code>ctx</code> <pre><code>Session 12: Monday morning\n\nAI:  \"I'll scaffold a MySQL schema for this.\"\nYou: \"We use PostgreSQL.\"\n\nAI:  \"For auth, session-based would be simplest.\"\nYou: \"We decided on JWT.\"\n\nAI:  \"Endpoints in camelCase?\"\nYou: \"snake_case! Like the rest of the API!!!!!!\"\n\nYou: *scrolls up to copy an existing endpoint*\n\n20 minutes spent restoring decisions that already exist.\n</code></pre> <pre><code>Session 12: Monday morning\n\nYou:  \"Let's continue the auth implementation.\"\nAI:   \"Per DECISIONS.md (2026-02-12): JWT, not sessions: \n       The project uses PostgreSQL with JWT\n       authentication. The API follows snake_case conventions.\n       Last session you completed the login endpoint: the\n       registration endpoint is next on TASKS.md. Want me to\n       start there?\"\n\n0 minutes re-explaining. Straight to building.\n</code></pre>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#how-ctx-solves-this","level":3,"title":"How <code>ctx</code> Solves This","text":"<p><code>ctx</code> creates a <code>.context/</code> directory in your project that stores structured knowledge files:</p> File What It Remembers <code>TASKS.md</code> What you're working on and what's next <code>DECISIONS.md</code> Architectural choices and why you made them <code>LEARNINGS.md</code> Gotchas, bugs, things that didn't work <code>CONVENTIONS.md</code> Naming patterns, code style, project rules <code>CONSTITUTION.md</code> Hard rules the AI must never violate <p>These files can version with your code in <code>git</code>: </p> <ul> <li>They load automatically at the session start    (via hooks in Claude Code, or manually with <code>ctx agent</code> for   other tools). </li> <li>The AI reads them, cites them, and builds on them, instead   of asking you to start over. <ul> <li>And when it acts, it can point to the exact file and line that      justifies the choice.</li> </ul> </li> </ul> <p>Every decision you record, every lesson you capture, makes the next session smarter.</p> <p><code>ctx</code> accumulates.</p> <p>Connect with <code>ctx</code></p> <ul> <li>Join the Community →: ask questions, share workflows, and help shape what comes next</li> <li>Read the Blog →: real-world patterns, ponderings, and lessons learned from building <code>ctx</code> using <code>ctx</code></li> </ul> <p>Ready to Get Started?</p> <ul> <li>Getting Started →: full installation and setup</li> <li>Your First Session →: step-by-step walkthrough from <code>ctx init</code> to verified recall</li> </ul>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/common-workflows/","level":1,"title":"Common Workflows","text":"<p>The commands below cover what you'll use most often: </p> <ul> <li>recording context, </li> <li>checking health, </li> <li>browsing history, </li> <li>and running loops.</li> </ul> <p>Each section is a self-contained snippet you can copy into your terminal.</p> <p>For deeper, step-by-step guides, see Recipes.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#track-context","level":2,"title":"Track Context","text":"<p>Prefer Skills over Raw Commands</p> <p>When working with an AI agent, use <code>/ctx-task-add</code>, <code>/ctx-decision-add</code>, or <code>/ctx-learning-add</code> instead of raw <code>ctx add</code> commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <pre><code># Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (full ADR fields required)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning\nctx learning add \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Mark task complete\nctx task complete \"user auth\"\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#leave-a-reminder-for-next-session","level":2,"title":"Leave a Reminder for Next Session","text":"<p>Drop a note that surfaces automatically at the start of your next session:</p> <pre><code># Leave a reminder\nctx remind \"refactor the swagger definitions\"\n\n# Date-gated: don't surface until a specific date\nctx remind \"check CI after the deploy\" --after 2026-02-25\n\n# List pending reminders\nctx remind list\n\n# Dismiss reminders by ID (supports ranges)\nctx remind dismiss 1\nctx remind dismiss 3 5-7\n</code></pre> <p>Reminders are relayed verbatim at session start by the <code>check-reminders</code> hook and repeat every session until you dismiss them.</p> <p>See Session Reminders for the full recipe.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#check-context-health","level":2,"title":"Check Context Health","text":"<pre><code># Detect stale paths, missing files, potential secrets\nctx drift\n\n# See full context summary\nctx status\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#browse-session-history","level":2,"title":"Browse Session History","text":"<p>List and search past AI sessions from the terminal:</p> <pre><code>ctx journal source --limit 5\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#journal-site","level":3,"title":"Journal Site","text":"<p>Import session transcripts to a browsable static site with search, navigation, and topic indices.</p> <p>The <code>ctx journal</code> command requires zensical (Python >= 3.10).</p> <p><code>zensical</code> is a Python-based static site generator from the Material for MkDocs team.</p> <p>(why zensical?).</p> <p>If you don't have it on your system, install <code>zensical</code> once with pipx:</p> <pre><code># One-time setup\npipx install zensical\n</code></pre> <p>Avoid <code>pip install zensical</code></p> <p><code>pip install</code> often fails: For example, on macOS, system Python installs a non-functional stub (<code>zensical</code> requires <code>Python >= 3.10</code>), and Homebrew Python blocks system-wide installs (<code>PEP 668</code>).</p> <p><code>pipx</code> creates an isolated environment with the correct Python version automatically.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#import-and-serve","level":3,"title":"Import and Serve","text":"<p>Then, import and serve:</p> <pre><code># Import all sessions to .context/journal/ (only new files)\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n</code></pre> <p>Open http://localhost:8000 to browse.</p> <p>To update after new sessions, run the same two commands again.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#safe-by-default","level":3,"title":"Safe by Default","text":"<p><code>ctx journal import --all</code> is safe by default:</p> <ul> <li>It only imports new sessions and skips existing files.</li> <li>Locked entries (via <code>ctx journal lock</code>) are always skipped by   both import and enrichment skills.</li> <li>If you add <code>locked: true</code> to frontmatter during enrichment, run   <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>.</li> </ul>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#re-importing-existing-files","level":3,"title":"Re-Importing Existing Files","text":"<p>Here is how you regenerate existing files.</p> <p>Backup your <code>.context</code> folder before regeneration, as this is a potentially destructive action.</p> <p>To re-import journal files, you need to explicitly opt-in using the <code>--regenerate</code> flag:</p> Flag combination Frontmatter Body <code>--regenerate</code> Preserved Overwritten from source <code>--regenerate --keep-frontmatter=false</code> Overwritten Overwritten <p>Regeneration Overwrites Body Edits</p> <p><code>--regenerate</code> preserves your YAML frontmatter (tags, summary, enrichment metadata) but it replaces the Markdown body with a fresh import.</p> <p>Any manual edits you made to the transcript will be lost.</p> <p>Lock entries you want to protect first: <code>ctx journal lock <session-id></code>.</p> <p>See Session Journal for the full pipeline including normalization and enrichment.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#scratchpad","level":2,"title":"Scratchpad","text":"<p>Store short, sensitive one-liners in an encrypted scratchpad that travels with the project:</p> <pre><code># Write a note\nctx pad set db-password \"postgres://user:pass@localhost/mydb\"\n\n# Read it back\nctx pad get db-password\n\n# List all keys\nctx pad list\n</code></pre> <p>The scratchpad is encrypted with a key stored at <code>~/.ctx/.ctx.key</code> (outside the project, never committed).</p> <p>See Scratchpad for details.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#run-an-autonomous-loop","level":2,"title":"Run an Autonomous Loop","text":"<p>Generate a script that iterates an AI agent until a completion signal is detected:</p> <pre><code>ctx loop\nchmod +x loop.sh\n./loop.sh\n</code></pre> <p>See Autonomous Loops for configuration and advanced usage.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#trace-commit-context","level":2,"title":"Trace Commit Context","text":"<p>Link your git commits back to the decisions, tasks, and learnings that motivated them. Enable the hook once:</p> <pre><code># Install the git hook (one-time setup)\nctx trace hook enable\n</code></pre> <p>From now on, every <code>git commit</code> automatically gets a <code>ctx-context</code> trailer linking it to relevant context. No extra steps needed; just use <code>ctx add</code>, <code>ctx task complete</code>, and commit as usual.</p> <pre><code># Later: why was this commit made?\nctx trace abc123\n\n# Recent commits with their context\nctx trace --last 10\n\n# Context trail for a specific file\nctx trace file src/auth.go\n\n# Manually tag a commit after the fact\nctx trace tag HEAD --note \"Hotfix for production outage\"\n</code></pre> <p>To stop: <code>ctx trace hook disable</code>.</p> <p>See CLI Reference: trace for details.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#agent-session-start","level":2,"title":"Agent Session Start","text":"<p>The first thing an AI agent should do at session start is discover where context lives:</p> <pre><code>ctx system bootstrap\n</code></pre> <p>This prints the resolved context directory, the files in it, and the operating rules. The <code>CLAUDE.md</code> template instructs the agent to run this automatically. See CLI Reference: bootstrap.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#the-two-skills-you-should-always-use","level":2,"title":"The Two Skills You Should Always Use","text":"<p>Using <code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end are the highest-value skills in the entire catalog:</p> <pre><code># session begins:\n/ctx-remember\n... do work ...\n# before closing the session:\n/ctx-wrap-up\n</code></pre> <p>Let's provide some context, because this is important:</p> <p>Although the agent will eventually discover your context through <code>CLAUDE.md → AGENT_PLAYBOOK.md</code>, <code>/ctx-remember</code> hydrates the full context up front (tasks, decisions, recent sessions) so the agent starts informed rather than piecing things together over several turns.</p> <p><code>/ctx-wrap-up</code> is the other half: A structured review that captures learnings, decisions, and tasks before you close the window.</p> <p>Hooks like <code>check-persistence</code> remind you (the user) mid-session that context hasn't been saved in a while, but they don't trigger persistence automatically: You still have to act. Also, a <code>CTRL+C</code> can end things at any moment with no reliable \"before session end\" event. </p> <p>In short, <code>/ctx-wrap-up</code> is the deliberate checkpoint that makes  sure nothing slips through. And <code>/ctx-remember</code> it its mirror skill to be used at session start.</p> <p>See Session Ceremonies for the full workflow.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-commands-vs-ai-skills","level":2,"title":"CLI Commands vs. AI Skills","text":"<p>Most <code>ctx</code> operations come in two flavors: a CLI command you run in your terminal and an AI skill (slash command) you invoke inside your coding assistant.</p> <p>Commands and skills are not interchangeable: Each has a distinct role.</p> <code>ctx</code> CLI command <code>ctx</code> AI skill Runs where Your terminal Inside the AI assistant Speed Fast (milliseconds) Slower (LLM round-trip) Cost Free Consumes tokens and context Analysis Deterministic heuristics Semantic / judgment-based Best for Quick checks, scripting, CI Deep analysis, generation, workflow orchestration","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#paired-commands","level":3,"title":"Paired Commands","text":"<p>These have both a CLI and a skill counterpart. Use the CLI for quick, deterministic checks; use the skill when you need the agent's judgment.</p> CLI Skill When to prefer the skill <code>ctx drift</code> <code>/ctx-drift</code> Semantic analysis: catches meaning drift the CLI misses <code>ctx status</code> <code>/ctx-status</code> Interpreted summary with recommendations <code>ctx task add</code> <code>/ctx-task-add</code> Agent decomposes vague goals into concrete tasks <code>ctx decision add</code> <code>/ctx-decision-add</code> Agent drafts rationale and consequences from discussion <code>ctx learning add</code> <code>/ctx-learning-add</code> Agent extracts the lesson from a debugging session <code>ctx convention add</code> <code>/ctx-convention-add</code> Agent observes a repeated pattern and codifies it <code>ctx task archive</code> <code>/ctx-archive</code> Agent reviews which tasks are truly done <code>ctx pad</code> <code>/ctx-pad</code> Agent reads/writes scratchpad entries in conversation flow <code>ctx journal</code> <code>/ctx-history</code> Agent searches session history with semantic understanding <code>ctx agent</code> <code>/ctx-agent</code> Agent loads and acts on the context packet <code>ctx loop</code> <code>/ctx-loop</code> Agent tailors the loop script to your project <code>ctx doctor</code> <code>/ctx-doctor</code> Agent adds semantic analysis to structural checks <code>ctx hook pause</code> <code>/ctx-pause</code> Agent pauses hooks with session-aware reasoning <code>ctx hook resume</code> <code>/ctx-resume</code> Agent resumes hooks after a pause <code>ctx remind</code> <code>/ctx-remind</code> Agent manages reminders in conversation flow","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#ai-only-skills","level":3,"title":"AI-Only Skills","text":"<p>These have no CLI equivalent. They require the agent's reasoning.</p> Skill Purpose <code>/ctx-remember</code> Load context and present structured readback at session start <code>/ctx-wrap-up</code> End-of-session ceremony: persist learnings, decisions, tasks <code>/ctx-next</code> Suggest 1-3 concrete next actions from context <code>/ctx-commit</code> Commit with integrated context capture <code>/ctx-reflect</code> Pause and assess session progress <code>/ctx-consolidate</code> Merge overlapping learnings or decisions <code>/ctx-prompt-audit</code> Analyze prompting patterns for improvement <code>/ctx-plan</code> Stress-test an existing plan through adversarial interview <code>/ctx-plan-import</code> Import Claude Code plan files into project specs <code>/ctx-implement</code> Execute a plan step-by-step with verification <code>/ctx-worktree</code> Manage parallel agent worktrees <code>/ctx-journal-enrich</code> Add metadata, tags, and summaries to journal entries <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich <code>/ctx-blog</code> Generate a blog post (zensical-flavored Markdown) <code>/ctx-blog-changelog</code> Generate themed blog post from commits between releases <code>/ctx-architecture</code> Build and maintain architecture maps (ARCHITECTURE.md, DETAILED_DESIGN.md)","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-only-commands","level":3,"title":"CLI-Only Commands","text":"<p>These are infrastructure: used in scripts, CI, or one-time setup.</p> Command Purpose <code>ctx init</code> Initialize <code>.context/</code> directory <code>ctx load</code> Output assembled context for piping <code>ctx task complete</code> Mark a task done by substring match <code>ctx sync</code> Reconcile context with codebase state <code>ctx compact</code> Consolidate and clean up context files <code>ctx trace</code> Show context behind git commits <code>ctx trace hook</code> Enable/disable commit context tracing hook <code>ctx setup</code> Generate AI tool integration config <code>ctx watch</code> Watch AI output and auto-apply context updates <code>ctx serve</code> Serve any zensical directory (default: journal) <code>ctx permission snapshot</code> Save settings as a golden image <code>ctx permission restore</code> Restore settings from golden image <code>ctx journal site</code> Generate browsable journal from exports <code>ctx hook notify setup</code> Configure webhook notifications <code>ctx decision</code> List and filter decisions <code>ctx learning</code> List and filter learnings <code>ctx task</code> List tasks, manage archival and snapshots <code>ctx why</code> Read the philosophy behind <code>ctx</code> <code>ctx guide</code> Quick-reference cheat sheet <code>ctx site</code> Site management commands <code>ctx config</code> Manage runtime configuration profiles <code>ctx system</code> System diagnostics and hook commands <code>ctx completion</code> Generate shell autocompletion scripts <p>Rule of Thumb</p> <p>Quick check? Use the CLI. </p> <p>Need judgment? Use the skill.</p> <p>When in doubt, start with the CLI: It's free and instant.</p> <p>Escalate to the skill when heuristics aren't enough.</p> <p>Next Up: Context Files →: what each <code>.context/</code> file does and how to use it</p> <p>See Also:</p> <ul> <li>Recipes: targeted how-to guides for specific tasks</li> <li>Knowledge Capture: patterns for recording decisions, learnings, and conventions</li> <li>Context Health: keeping your <code>.context/</code> accurate and drift-free</li> <li>Session Archaeology: digging into past sessions</li> <li>Task Management: tracking and completing work items</li> </ul>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/community/","level":1,"title":"#ctx","text":"<p>Open source is better together.</p> <p>We are the builders who care about durable context, verifiable decisions,  and human-AI workflows that compound over time.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#help-ctx-change-how-ai-remembers","level":2,"title":"Help <code>ctx</code> Change How AI Remembers","text":"<p>If you like the idea, a star helps <code>ctx</code> reach engineers who run into context drift every day:</p> <p> Star <code>ctx</code> on GitHub ⭐</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#ctx-you","level":2,"title":"<code>ctx</code> ♥️ You","text":"<p>Join the community to ask questions, share feedback, and connect with other users:</p> <ul> <li> Discord join the <code>ctx</code> Discord:   Real-time discussion, field notes, and early ideas.</li> <li> Read the <code>ctx</code> Source on GitHub:   Issues, discussions, and contributions.</li> </ul>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#want-to-contribute","level":2,"title":"Want to Contribute?","text":"<p>Early adopters shape the conventions.</p> <p><code>ctx</code> is free and open source software.</p> <p>Contributions are always welcome and appreciated.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#code-of-conduct","level":2,"title":"Code of Conduct","text":"<p>Clear context requires respectful collaboration.  </p> <p><code>ctx</code> follows the Contributor Covenant.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/configuration/","level":1,"title":"Configuration","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#configuration","level":2,"title":"Configuration","text":"<p><code>ctx</code> uses three layers of configuration. Each layer overrides the one below it:</p> <ol> <li>CLI flags: Per-invocation overrides (highest priority)</li> <li>Environment variables: Shell or CI/CD overrides</li> <li>The <code>.ctxrc</code> file: Project-level defaults (YAML)</li> <li>Built-in defaults: Hardcoded fallbacks (lowest priority)</li> </ol> <p>All settings are optional: If nothing is configured, <code>ctx</code> works out of the box with sensible defaults.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#the-ctxrc-file","level":2,"title":"The <code>.ctxrc</code> File","text":"<p>The <code>.ctxrc</code> file is an optional YAML file placed in the project root (next to your <code>.context/</code> directory). It lets you set project-level defaults that apply to every <code>ctx</code> command.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#location","level":3,"title":"Location","text":"<pre><code>my-project/\n├── .ctxrc              ← configuration file\n├── .context/\n│   ├── TASKS.md\n│   ├── DECISIONS.md\n│   └── ...\n└── src/\n</code></pre> <p><code>ctx</code> reads <code>.ctxrc</code> from the project root (i.e. the parent of <code>CTX_DIR</code>, or <code>dirname(CTX_DIR)/.ctxrc</code>). It does not walk up from CWD. That means whichever project you've activated via <code>eval \"$(ctx activate)\"</code> (or by exporting <code>CTX_DIR</code> directly), its paired <code>.ctxrc</code> is what governs the invocation. There is no global or user-level config file: configuration is always per-project.</p> <p>Contributors: Dev Configuration Profile</p> <p>The <code>ctx</code> repo ships two <code>.ctxrc</code> source profiles (<code>.ctxrc.base</code> and <code>.ctxrc.dev</code>). The working copy is gitignored and swapped between them via <code>ctx config switch dev</code> / <code>ctx config switch base</code>. See Contributing: Configuration Profiles.</p> <p>Using a Different <code>.context</code> Directory</p> <p>You point <code>ctx</code> at a <code>.context/</code> directory by setting the <code>CTX_DIR</code> environment variable, not through <code>.ctxrc</code>. <code>ctx</code> does not search the filesystem. Use <code>eval \"$(ctx activate)\"</code> to bind <code>CTX_DIR</code> for your shell. <code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename.</p> <p>See Environment Variables below for details.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#full-reference","level":3,"title":"Full Reference","text":"<p>A commented <code>.ctxrc</code> showing all options and their defaults:</p> <pre><code># .ctxrc: ctx runtime configuration\n# https://ctx.ist/configuration/\n#\n# All settings are optional. Missing values use defaults.\n# Priority: CLI flags > environment variables > .ctxrc > defaults\n#\n# token_budget: 8000\n# auto_archive: true\n# archive_after_days: 7\n# scratchpad_encrypt: true\n# event_log: false\n# entry_count_learnings: 30\n# entry_count_decisions: 20\n# convention_line_count: 200\n# injection_token_warn: 15000\n# context_window: 200000      # auto-detected for Claude Code; override for other tools\n# billing_token_warn: 0       # one-shot warning at this token count (0 = disabled)\n#\n# stale_age_days: 30      # days before drift flags a context file as stale (0 = disabled)\n# key_rotation_days: 90\n# task_nudge_interval: 5   # Edit/Write calls between task completion nudges\n#\n# notify:               # requires: ctx hook notify setup\n#   events:             # required: no events sent unless listed\n#     - loop\n#     - nudge\n#     - relay\n#\n# tool: \"\"              # Active AI tool: claude, cursor, cline, kiro, codex\n#\n# steering:             # Steering layer configuration\n#   dir: .context/steering\n#   default_inclusion: manual\n#   default_tools: []\n#\n# hooks:                # Hook system configuration\n#   dir: .context/hooks\n#   timeout: 10\n#   enabled: true\n#\n# provenance_required:  # Relax provenance flags for ctx add\n#   session_id: true    # Require --session-id (default: true)\n#   branch: true        # Require --branch (default: true)\n#   commit: true        # Require --commit (default: true)\n#\n# priority_order:\n#   - CONSTITUTION.md\n#   - TASKS.md\n#   - CONVENTIONS.md\n#   - ARCHITECTURE.md\n#   - DECISIONS.md\n#   - LEARNINGS.md\n#   - GLOSSARY.md\n#   - AGENT_PLAYBOOK.md\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#option-reference","level":3,"title":"Option Reference","text":"Option Type Default Description <code>token_budget</code> <code>int</code> <code>8000</code> Default token budget for <code>ctx agent</code> and <code>ctx load</code> <code>auto_archive</code> <code>bool</code> <code>true</code> Auto-archive completed tasks during <code>ctx compact</code> <code>archive_after_days</code> <code>int</code> <code>7</code> Days before completed tasks are archived <code>scratchpad_encrypt</code> <code>bool</code> <code>true</code> Encrypt scratchpad with AES-256-GCM <code>event_log</code> <code>bool</code> <code>false</code> Enable local hook event logging to <code>.context/state/events.jsonl</code> <code>entry_count_learnings</code> <code>int</code> <code>30</code> Drift warning when <code>LEARNINGS.md</code> exceeds this entry count (0 = disable) <code>entry_count_decisions</code> <code>int</code> <code>20</code> Drift warning when <code>DECISIONS.md</code> exceeds this entry count (0 = disable) <code>convention_line_count</code> <code>int</code> <code>200</code> Drift warning when <code>CONVENTIONS.md</code> exceeds this line count (0 = disable) <code>injection_token_warn</code> <code>int</code> <code>15000</code> Warn when auto-injected context exceeds this token count (0 = disable) <code>context_window</code> <code>int</code> <code>200000</code> Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools <code>billing_token_warn</code> <code>int</code> <code>0</code> (off) One-shot warning when session tokens exceed this threshold (0 = disabled). For plans where tokens beyond an included allowance cost extra <code>stale_age_days</code> <code>int</code> <code>30</code> Days before <code>ctx drift</code> flags a context file as stale (0 = disable) <code>key_rotation_days</code> <code>int</code> <code>90</code> Days before encryption key rotation nudge <code>task_nudge_interval</code> <code>int</code> <code>5</code> Edit/Write calls between task completion nudges <code>notify.events</code> <code>[]string</code> (all) Event filter for webhook notifications (empty = all) <code>priority_order</code> <code>[]string</code> (see below) Custom file loading priority for context assembly <code>tool</code> <code>string</code> (empty) Active AI tool identifier (<code>claude</code>, <code>cursor</code>, <code>cline</code>, <code>kiro</code>, <code>codex</code>). Used by steering sync and hook dispatch <code>steering.dir</code> <code>string</code> <code>.context/steering</code> Steering files directory <code>steering.default_inclusion</code> <code>string</code> <code>manual</code> Default inclusion mode for new steering files (<code>always</code>, <code>auto</code>, <code>manual</code>) <code>steering.default_tools</code> <code>[]string</code> (all) Default tool filter for new steering files (empty = all tools) <code>hooks.dir</code> <code>string</code> <code>.context/hooks</code> Hook scripts directory <code>hooks.timeout</code> <code>int</code> <code>10</code> Per-hook execution timeout in seconds <code>hooks.enabled</code> <code>bool</code> <code>true</code> Whether hook execution is enabled <code>provenance_required.session_id</code> <code>bool</code> <code>true</code> Require <code>--session-id</code> on <code>ctx add</code> for tasks, decisions, learnings <code>provenance_required.branch</code> <code>bool</code> <code>true</code> Require <code>--branch</code> on <code>ctx add</code> for tasks, decisions, learnings <code>provenance_required.commit</code> <code>bool</code> <code>true</code> Require <code>--commit</code> on <code>ctx add</code> for tasks, decisions, learnings <p>Default priority order (used when <code>priority_order</code> is not set):</p> <ol> <li><code>CONSTITUTION.md</code></li> <li><code>TASKS.md</code></li> <li><code>CONVENTIONS.md</code></li> <li><code>ARCHITECTURE.md</code></li> <li><code>DECISIONS.md</code></li> <li><code>LEARNINGS.md</code></li> <li><code>GLOSSARY.md</code></li> <li><code>AGENT_PLAYBOOK.md</code></li> </ol> <p>See Context Files for the rationale behind this ordering.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#environment-variables","level":2,"title":"Environment Variables","text":"<p>Environment variables override <code>.ctxrc</code> values but are overridden by CLI flags.</p> Variable Description Equivalent <code>.ctxrc</code> key <code>CTX_DIR</code> Declare the context directory path (required, no fallback) (none) <code>CTX_TOKEN_BUDGET</code> Override the default token budget <code>token_budget</code>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples","level":3,"title":"Examples","text":"<pre><code># Use a shared context directory\nCTX_DIR=/shared/team-context ctx status\n\n# Increase token budget for a single run\nCTX_TOKEN_BUDGET=16000 ctx agent\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#cli-global-flags","level":2,"title":"CLI Global Flags","text":"<p>CLI flags have the highest priority and override both environment variables and <code>.ctxrc</code> settings. These flags are available on every <code>ctx</code> command.</p> Flag Description <code>--tool <name></code> Override active AI tool identifier (e.g. <code>kiro</code>, <code>cursor</code>) <code>--version</code> Show version and exit <code>--help</code> Show command help and exit","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_1","level":3,"title":"Examples","text":"<pre><code># Point to a different context directory inline:\nCTX_DIR=/path/to/project/.context ctx status\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#priority-order","level":2,"title":"Priority Order","text":"<p>When the same setting is configured in multiple layers, the highest-priority layer wins:</p> <pre><code>CLI flags  >  Environment variables  >  .ctxrc  >  Built-in defaults\n(highest)                                          (lowest)\n</code></pre> <p>The context directory itself is resolved differently: it lives outside this priority chain. <code>CTX_DIR</code> (env) must be declared; <code>.ctxrc</code> does not carry a fallback for it, and there is no built-in default. See Activating a Context Directory.</p> <p>Example resolution for <code>token_budget</code>:</p> Layer Value Wins? <code>CTX_TOKEN_BUDGET</code> <code>4000</code> Yes <code>.ctxrc</code> <code>8000</code> No Default <code>8000</code> No","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_2","level":2,"title":"Examples","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#external-context-directory","level":3,"title":"External <code>.context</code> Directory","text":"<p>Store a project's context outside the project tree (useful when a repo is read-only, or when you want to keep notes adjacent rather than checked in). Declare the path via <code>CTX_DIR</code>:</p> <pre><code>export CTX_DIR=/home/you/ctx-stores/my-project/.context\n</code></pre> <p>One <code>.context/</code> per project</p> <p>The parent of the context directory is the project root by contract: <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook all read the codebase from <code>filepath.Dir(ContextDir())</code>. Pointing two projects at the same <code>.context/</code> directory will collide their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use <code>ctx hub</code>, not a shared <code>.context/</code>.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-token-budget","level":3,"title":"Custom Token Budget","text":"<p>Increase the token budget for projects with large context:</p> <pre><code># .ctxrc\ntoken_budget: 16000\n</code></pre> <p>This affects the default budget for <code>ctx agent</code> and <code>ctx load</code>. You can still override per-invocation with <code>ctx agent --budget 4000</code>.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#disabled-scratchpad-encryption","level":3,"title":"Disabled Scratchpad Encryption","text":"<p>Turn off encryption for the scratchpad (useful in ephemeral environments where key management is unnecessary):</p> <pre><code># .ctxrc\nscratchpad_encrypt: false\n</code></pre> <p>Unencrypted Scratchpads Store Secrets in Plaintext</p> <p>Only disable encryption if you understand the security implications.</p> <p>The scratchpad may contain sensitive data such as API keys, database URLs, or deployment credentials.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-priority-order","level":3,"title":"Custom Priority Order","text":"<p>Reorder context files to prioritize architecture over conventions:</p> <pre><code># .ctxrc\npriority_order:\n  - CONSTITUTION.md\n  - TASKS.md\n  - ARCHITECTURE.md\n  - DECISIONS.md\n  - CONVENTIONS.md\n  - LEARNINGS.md\n  - GLOSSARY.md\n  - AGENT_PLAYBOOK.md\n</code></pre> <p>Files not listed in <code>priority_order</code> receive the lowest priority (100). The order affects <code>ctx agent</code>, <code>ctx load</code>, and drift's file-priority calculations.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#billing-token-threshold","level":3,"title":"Billing Token Threshold","text":"<p>Get a one-shot warning when your session crosses a token threshold where extra charges begin (e.g., Claude Pro includes 200k tokens; beyond that costs extra):</p> <pre><code># .ctxrc\nbilling_token_warn: 180000   # warn before hitting the 200k paid boundary\n</code></pre> <p>The warning fires once per session the first time token usage exceeds the threshold. Set to <code>0</code> (or omit) to disable.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#adjusted-drift-thresholds","level":3,"title":"Adjusted Drift Thresholds","text":"<p>Raise or lower the entry-count thresholds that trigger drift warnings:</p> <pre><code># .ctxrc\nentry_count_learnings: 50   # warn above 50 learnings (default: 30)\nentry_count_decisions: 10   # warn above 10 decisions (default: 20)\nconvention_line_count: 300  # warn above 300 lines (default: 200)\n</code></pre> <p>Set any threshold to <code>0</code> to disable that specific check.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"<p>Get notified when loops complete, hooks fire, or agents reach milestones:</p> <pre><code># Configure the webhook URL (encrypted, safe to commit)\nctx hook notify setup\n\n# Test delivery\nctx hook notify test\n</code></pre> <p>Filter which events reach your webhook:</p> <pre><code># .ctxrc\nnotify:\n  events:\n    - loop      # loop completion/max-iteration\n    - nudge     # VERBATIM relay hooks fired\n    # - relay   # all hook output (verbose, for debugging)\n    # - heartbeat  # every-prompt session-alive signal\n</code></pre> <p>Notifications are opt-in: No events are sent unless explicitly listed.</p> <p>See Webhook Notifications for a step-by-step recipe.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#hook-message-overrides","level":2,"title":"Hook Message Overrides","text":"<p>Hook messages control what text hooks emit when they fire. Each message can be overridden per-project by placing a text file at the matching path under <code>.context/</code>:</p> <pre><code>.context/hooks/messages/{hook}/{variant}.txt\n</code></pre> <p>The override takes priority over the embedded default compiled into the <code>ctx</code> binary. An empty file silences the message while preserving the hook's logic (counting, state tracking, cooldowns).</p> <p>Use <code>ctx hook message</code> to discover and manage overrides:</p> <pre><code>ctx hook message list                      # see all messages\nctx hook message show qa-reminder gate     # view the current template\nctx hook message edit qa-reminder gate     # copy default for editing\nctx hook message reset qa-reminder gate    # revert to default\n</code></pre> <p>See Customizing Hook Messages for detailed examples including Python, JavaScript, and silence configurations.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#agent-bootstrapping","level":2,"title":"Agent Bootstrapping","text":"<p>AI agents need to know the resolved context directory at session start. The <code>ctx system bootstrap</code> command prints the context path, file list, and operating rules in both text and JSON formats:</p> <pre><code>ctx system bootstrap          # text output for agents\nctx system bootstrap -q       # just the context directory path\nctx system bootstrap --json   # structured output for automation\n</code></pre> <p>The <code>CLAUDE.md</code> template instructs the agent to run this as its first action. Every nudge (context checkpoint, persistence reminder, etc.) also includes a <code>Context: <dir></code> footer that re-anchors the agent to the correct directory throughout the session.</p> <p>This replaces the previous approach of hardcoding <code>.context/</code> paths in agent instructions. </p> <p>See CLI Reference: bootstrap for full details.</p> <p>See also: CLI Reference | Context Files | Scratchpad</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/context-files/","level":1,"title":"Context Files","text":"","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#context","level":2,"title":"<code>.context/</code>","text":"<p>Each context file in <code>.context/</code> serves a specific purpose. </p> <p>Files are designed to be human-readable, AI-parseable, and token-efficient.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#file-overview","level":2,"title":"File Overview","text":"<p>The core context files live directly under <code>.context/</code>. They are the substrate <code>ctx</code> reads in priority order when assembling the agent context packet:</p> File Purpose Priority <code>CONSTITUTION.md</code> Hard rules that must NEVER be violated 1 (highest) <code>TASKS.md</code> Current and planned work 2 <code>CONVENTIONS.md</code> Project patterns and standards 3 <code>ARCHITECTURE.md</code> System overview and components 4 <code>DECISIONS.md</code> Architectural decisions with rationale 5 <code>LEARNINGS.md</code> Lessons learned, gotchas, tips 6 <code>GLOSSARY.md</code> Domain terms and abbreviations 7 <code>AGENT_PLAYBOOK.md</code> Instructions for AI tools 8 (lowest) <p>Two subdirectories under <code>.context/</code> are implementation details that are user-editable but not part of the priority read order:</p> <ul> <li><code>.context/templates/</code>: format templates for <code>ctx decision add</code>   and <code>ctx learning add</code>. See templates below.</li> <li><code>.context/steering/</code>: behavioral rules with YAML frontmatter   that get synced into each AI tool's native config. See   steering below, and the full   Steering files page for the design and workflow.</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#outside-context","level":3,"title":"Outside <code>.context/</code>","text":"<p>Two other moving parts are often confused with context files but are not under <code>.context/</code>:</p> <ul> <li>Skills live in <code>.claude/skills/</code> (project-local) or are provided   by the installed <code>ctx</code> plugin. A typical project doesn't see the   plugin's skills at all; they ride with the plugin and are owned by   its update cycle. See <code>ctx skill</code> and   Skills reference.</li> <li>Hooks: Claude Code <code>PreToolUse</code>/<code>PostToolUse</code>/   <code>UserPromptSubmit</code> entries configured in <code>.claude/settings.json</code> or   shipped by a plugin. The <code>ctx</code> plugin registers its own hooks   automatically; a typical project does not author hooks by hand,   and any local edits to plugin-owned hook files will be overridden   on the next plugin update. If you need to customize behavior, edit   your own project settings, not the plugin's files. See   Hook sequence diagrams.</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"<p>The priority order follows a logical progression for AI tools:</p> <ol> <li><code>CONSTITUTION.md</code>: Inviolable rules first. The AI tool must know what it    cannot do before attempting anything.</li> <li><code>TASKS.md</code>: Current work items. What the AI tool should focus on.</li> <li><code>CONVENTIONS.md</code>: How to write code. Patterns and standards to follow    when implementing tasks.</li> <li><code>ARCHITECTURE.md</code>: System structure. Understanding of components and    boundaries before making changes.</li> <li><code>DECISIONS.md</code>: Historical context. Why things are the way they are,    to avoid re-debating settled decisions.</li> <li><code>LEARNINGS.md</code>: Gotchas and tips. Lessons from past work that inform    the current implementation.</li> <li><code>GLOSSARY.md</code>: Reference material. Domain terms and abbreviations for    lookup as needed.</li> <li><code>AGENT_PLAYBOOK.md</code>: Meta instructions last. How to use this context    system itself. Loaded last because the agent should understand the    content (rules, tasks, patterns) before the operating manual.</li> </ol>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#constitutionmd","level":2,"title":"<code>CONSTITUTION.md</code>","text":"<p>Purpose: Define hard invariants: Rules that must NEVER be violated,  regardless of the task.</p> <p>AI tools read this first and should refuse tasks that violate these rules.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure","level":3,"title":"Structure","text":"<pre><code># Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never store customer/user data in context files\n* [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n* [ ] All code must pass tests before commit\n* [ ] No `any` types in TypeScript without documented reason\n* [ ] No TODO comments in main branch (*move to `TASKS.md`*)\n\n## Process Invariants\n\n* [ ] All architectural changes require a decision record\n* [ ] Breaking changes require version bump\n* [ ] Generated files are never committed\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines","level":3,"title":"Guidelines","text":"<ul> <li>Keep rules minimal and absolute</li> <li>Each rule should be enforceable (can verify compliance)</li> <li>Use checkbox format for clarity</li> <li>Never compromise on these rules</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tasksmd","level":2,"title":"<code>TASKS.md</code>","text":"<p>Purpose: Track current work, planned work, and blockers.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_1","level":3,"title":"Structure","text":"<p>Tasks are organized by Phase: logical groupings that preserve order and enable replay. </p> <p>Tasks stay in their Phase permanently; status is tracked via checkboxes and  inline tags.</p> <pre><code># Tasks\n\n## Phase 1: Initial Setup\n\n* [x] Set up project structure\n* [x] Configure linting and formatting\n* [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n* [ ] Implement user authentication `#priority:high`\n* [ ] Add API rate limiting `#priority:medium`\n  * Blocked by: Need to finalize auth first\n\n## Backlog\n\n* [ ] Performance optimization `#priority:low`\n* [ ] Add metrics dashboard `#priority:deferred`\n</code></pre> <p>Key principles:</p> <ul> <li>Tasks never move between sections: mark as <code>[x]</code> or <code>[-]</code> in place</li> <li>Use <code>#in-progress</code> inline tag to indicate current work</li> <li>Phase headers provide structure and replay order</li> <li>Backlog section for unscheduled work</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tags","level":3,"title":"Tags","text":"<p>Use inline backtick-wrapped tags for metadata:</p> Tag Values Purpose <code>#priority</code> <code>high</code>, <code>medium</code>, <code>low</code> Task urgency <code>#area</code> <code>core</code>, <code>cli</code>, <code>docs</code>, <code>tests</code> Codebase area <code>#estimate</code> <code>1h</code>, <code>4h</code>, <code>1d</code> Time estimate (optional) <code>#in-progress</code> (none) Currently being worked on <p>Lifecycle tags (for session correlation):</p> Tag Format When to add <code>#added</code> <code>YYYY-MM-DD-HHMMSS</code> Auto-added by <code>ctx task add</code> <code>#started</code> <code>YYYY-MM-DD-HHMMSS</code> When beginning work on the task <p>These timestamps help correlate tasks with session files and track which session started vs completed work.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning <code>[ ]</code> Pending <code>[x]</code> Completed <code>[-]</code> Skipped (include reason)","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_1","level":3,"title":"Guidelines","text":"<ul> <li>Never delete tasks; mark as <code>[x]</code> completed or <code>[-]</code> skipped</li> <li>Never move tasks between sections; use inline tags for status</li> <li>Use <code>ctx task archive</code> periodically to move completed tasks to archive</li> <li>Mark current work with <code>#in-progress</code> inline tag</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#decisionsmd","level":2,"title":"<code>DECISIONS.md</code>","text":"<p>Purpose: Record architectural decisions with rationale so they don't get re-debated.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_2","level":3,"title":"Structure","text":"<pre><code># Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequence**: What are the implications?\n\n**Alternatives Considered**:\n* Alternative A: Why rejected\n* Alternative B: Why rejected\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example","level":3,"title":"Example","text":"<pre><code>## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting a new project, need to choose the type-checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequence**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#learningsmd","level":2,"title":"<code>LEARNINGS.md</code>","text":"<p>Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_3","level":3,"title":"Structure","text":"<pre><code># Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example_1","level":3,"title":"Example","text":"<pre><code>## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#categories","level":3,"title":"Categories","text":"<p>Organize learnings by topic:</p> <ul> <li>Testing</li> <li>Build & Deploy</li> <li>Performance</li> <li>Security</li> <li>Third-Party Libraries</li> <li>Git and Workflow</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#conventionsmd","level":2,"title":"<code>CONVENTIONS.md</code>","text":"<p>Purpose: Document project patterns, naming conventions, and standards.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_4","level":3,"title":"Structure","text":"<pre><code># Conventions\n\n## Naming\n\n* **Files**: kebab-case for all source files\n* **Components**: PascalCase for React components\n* **Functions**: camelCase, verb-first (getUser, parseConfig)\n* **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_2","level":3,"title":"Guidelines","text":"<ul> <li>Include concrete examples</li> <li>Explain the \"why\" not just the \"what\"</li> <li>Keep patterns minimal: Only document what's non-obvious</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#architecturemd","level":2,"title":"<code>ARCHITECTURE.md</code>","text":"<p>Purpose: Provide system overview and component relationships.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_5","level":3,"title":"Structure","text":"<pre><code># Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n* path/to/file.ts: Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_3","level":3,"title":"Guidelines","text":"<ul> <li>Keep diagrams simple (Mermaid works well)</li> <li>Focus on boundaries and interfaces</li> <li>Update when major structural changes occur</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#glossarymd","level":2,"title":"<code>GLOSSARY.md</code>","text":"<p>Purpose: Define domain terms, abbreviations, and project vocabulary.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_6","level":3,"title":"Structure","text":"<pre><code># Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_4","level":3,"title":"Guidelines","text":"<ul> <li>Define project-specific meanings</li> <li>Clarify potentially ambiguous terms</li> <li>Include abbreviations used in code or docs</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#agent_playbookmd","level":2,"title":"<code>AGENT_PLAYBOOK.md</code>","text":"<p>Purpose: Explicit instructions for how AI tools should read, apply,  and update context.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#key-sections","level":3,"title":"Key Sections","text":"<p>Read Order: Priority order for loading context files</p> <p>When to Update: Events that trigger context updates</p> <p>How to Avoid Hallucinating Memory: Critical rules:</p> <ol> <li>Never assume: If not in files, you don't know it</li> <li>Never invent history: Don't claim \"we discussed\" without evidence</li> <li>Verify before referencing: Search files before citing</li> <li>When uncertain, say so</li> <li>Trust files over intuition</li> </ol> <p>Context Update Commands: Format for automated updates via <code>ctx watch</code>:</p> <pre><code><context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"complete\">user auth</context-update>\n<context-update type=\"learning\"\n  context=\"Debugging hooks\"\n  lesson=\"Hooks receive JSON via stdin\"\n  application=\"Parse JSON stdin with the host language\"\n>Hook Input Format</context-update>\n<context-update type=\"decision\"\n  context=\"Need a caching layer\"\n  rationale=\"Redis is fast and team has experience\"\n  consequence=\"Must provision Redis infrastructure\"\n>Use Redis for caching</context-update>\n</code></pre> <p>See Integrations  for full documentation.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#templates","level":2,"title":"<code>templates/</code>","text":"<p>Location: <code>.context/templates/</code>. Status: implementation detail, user-editable.</p> <p>Purpose: Format templates for <code>ctx decision add</code> and <code>ctx learning add</code>. These control the structure of new entries appended to DECISIONS.md and LEARNINGS.md.</p> <p><code>ctx init</code> deploys two starter templates:</p> <ul> <li><code>decision.md</code>: sections Context, Rationale, Consequence</li> <li><code>learning.md</code>: sections Context, Lesson, Application</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing","level":3,"title":"Customizing","text":"<p>Edit the templates directly. Changes take effect immediately on the next <code>ctx add</code> command. For example, to add a \"References\" section to all new decisions, edit <code>.context/templates/decision.md</code>.</p> <p>Templates are committed to git, so customizations are shared with the team.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#steering","level":2,"title":"<code>steering/</code>","text":"<p>Location: <code>.context/steering/</code>. Status: implementation detail, user-editable.</p> <p>Purpose: Behavioral rules with YAML frontmatter that tell an AI assistant how to behave when a specific kind of prompt arrives. Unlike the core context files (which describe what the project is), steering files describe what to do and ride alongside the prompt through the AI tool's native rule pipeline (Claude Code, Cursor, Kiro, Cline). <code>ctx</code> matches steering files to prompts and syncs them out to each tool's config.</p> <p><code>ctx init</code> scaffolds four foundation files:</p> <ul> <li><code>product.md</code>: who this project serves and why</li> <li><code>tech.md</code>: the technology stack and its constraints</li> <li><code>structure.md</code>: how the code is organized</li> <li><code>workflow.md</code>: how work moves through the system</li> </ul> <p>Each file carries YAML frontmatter describing when it applies (always, matching prompts, or manually referenced) and what tool scope it covers. The foundation files use <code>inclusion: always</code> by default so every session picks them up.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing_1","level":3,"title":"Customizing","text":"<p>Edit the files directly. Add your own steering files with <code>ctx steering add</code>, preview the match set with <code>ctx steering preview</code>, and run <code>ctx steering sync</code> to push them into each AI tool's config after changes. Steering files are committed to git, so they're shared with the team.</p> <p>For the design rationale, the full inclusion/priority model, and the end-to-end sync workflow, see the dedicated Steering files page.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"<p>All context files follow these conventions:</p> <ol> <li>Headers define structure: <code>#</code> for title, <code>##</code> for sections, <code>###</code> for     items</li> <li>Bold keys for fields: <code>**Key**:</code> followed by value</li> <li>Code blocks are literal: Never parse code block content as structure</li> <li>Lists are ordered: Items appear in priority/chronological order</li> <li>Tags are inline: Backtick-wrapped tags like <code>#priority:high</code></li> </ol>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Refactoring with Intent:   how persistent context prevents drift during refactoring sessions</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"<p>Keep context files concise:</p> <ul> <li>Use abbreviations in tags, not prose;</li> <li>Omit obvious words (\"The,\" \"This\");</li> <li>Prefer bullet points over paragraphs;</li> <li>Keep examples minimal but illustrative;</li> <li>Archive old completed items periodically.</li> </ul> <p>Next Up: Prompting Guide →:    effective prompts for AI sessions with <code>ctx</code></p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/contributing/","level":1,"title":"Contributing","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#development-setup","level":2,"title":"Development Setup","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#prerequisites","level":3,"title":"Prerequisites","text":"<ul> <li>Go (version defined in <code>go.mod</code>)</li> <li>Claude Code</li> <li>Git</li> <li>GNU Make</li> <li>Zensical</li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#1-fork-or-clone-the-repository","level":3,"title":"1. Fork (or Clone) the Repository","text":"<pre><code># Fork on GitHub, then:\ngit clone https://github.com/<you>/ctx.git\ncd ctx\n\n# Or, if you have push access:\ngit clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#2-build-and-install-the-binary","level":3,"title":"2. Build and Install the Binary","text":"<pre><code>make build\nsudo make install\n</code></pre> <p>This compiles the <code>ctx</code> binary and places it in <code>/usr/local/bin/</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#3-install-the-plugin-from-your-local-clone","level":3,"title":"3. Install the Plugin from Your Local Clone","text":"<p>The repository ships a Claude Code plugin under <code>internal/assets/claude/</code>. Point Claude Code at your local copy so that skills and hooks reflect your working tree: no reinstall needed after edits:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter the absolute path to the root of your clone,    e.g. <code>~/WORKSPACE/ctx</code>    (this is where <code>.claude-plugin/marketplace.json</code> lives: it points    Claude Code to the actual plugin in <code>internal/assets/claude</code>);</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code>.</li> </ol> <p>Claude Code Caches Plugin Files</p> <p>Even though the marketplace points at a directory on disk, Claude Code caches skills and hooks. After editing files under <code>internal/assets/claude/</code>, clear the cache and restart:</p> <pre><code>make plugin-reload   # then restart Claude Code\n</code></pre> <p>See Skill or Hook Changes for details.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#4-verify","level":3,"title":"4. Verify","text":"<pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n</code></pre> <p>You should see the <code>ctx</code> plugin listed, sourced from your local path.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#project-layout","level":2,"title":"Project Layout","text":"<pre><code>ctx/\n├── cmd/ctx/            # CLI entry point\n├── internal/\n│   ├── assets/claude/  # ← Claude Code plugin (skills, hooks)\n│   ├── bootstrap/      # Project initialization templates\n│   ├── claude/         # Claude Code integration helpers\n│   ├── cli/            # Command implementations\n│   ├── config/         # Configuration loading\n│   ├── context/        # Core context logic\n│   ├── crypto/         # Scratchpad encryption\n│   ├── drift/          # Drift detection\n│   ├── index/          # Context file indexing\n│   ├── journal/        # Journal site generation\n│   ├── memory/         # Memory bridge (discover, mirror, import, publish)\n│   ├── notify/         # Webhook notifications\n│   ├── rc/             # .ctxrc parsing\n│   ├── journal/        # Session history, parsers, and state\n│   ├── sysinfo/        # System resource monitoring\n│   ├── task/           # Task management\n│   └── validation/     # Input validation\n├── .claude/\n│   └── skills/         # Dev-only skills (not distributed)\n├── assets/             # Static assets (banners, logos)\n├── docs/               # Documentation site source\n├── editors/            # Editor extensions (VS Code)\n├── examples/           # Example configurations\n├── hack/               # Build scripts\n├── specs/              # Feature specifications\n└── .context/           # ctx's own context (dogfooding)\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skills-two-directories-one-rule","level":3,"title":"Skills: Two Directories, One Rule","text":"Directory What lives here Distributed to users? <code>internal/assets/claude/skills/</code> The 39 <code>ctx-*</code> skills that ship with the plugin Yes <code>.claude/skills/</code> Dev-only skills (release, QA, backup, etc.) No <p><code>internal/assets/claude/skills/</code> is the single source of truth for user-facing skills. If you are adding or modifying a <code>ctx-*</code> skill, edit it there.</p> <p><code>.claude/skills/</code> holds skills that only make sense inside this repository (release automation, QA checks, backup scripts). These are never distributed to users.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#dev-only-skills-reference","level":4,"title":"Dev-Only Skills Reference","text":"Skill When to use <code>/_ctx-absorb</code> Merge deltas from a parallel worktree or separate checkout <code>/_ctx-audit</code> Detect code-level drift after YOLO sprints or before releases <code>/_ctx-qa</code> Run QA checks before committing <code>/_ctx-release</code> Run the full release process <code>/_ctx-release-notes</code> Generate release notes for <code>dist/RELEASE_NOTES.md</code> <code>/_ctx-alignment-audit</code> Audit doc claims against agent instructions <code>/_ctx-update-docs</code> Check docs/code consistency after changes <code>/_ctx-command-audit</code> Audit CLI surface after renames, moves, or deletions <p>Six skills previously in this list have been promoted to bundled plugin skills and are now available to all <code>ctx</code> users: <code>/ctx-brainstorm</code>, <code>/ctx-link-check</code>, <code>/ctx-permission-sanitize</code>, <code>/ctx-skill-create</code>, <code>/ctx-spec</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#how-to-add-things","level":2,"title":"How to Add Things","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-cli-command","level":3,"title":"Adding a New CLI Command","text":"<ol> <li>Create a package under <code>internal/cli/<name>/</code> with <code>doc.go</code>, <code>cmd.go</code>,    and <code>run.go</code>;</li> <li>Implement <code>Cmd() *cobra.Command</code> as the entry point;</li> <li>Add <code>Use*</code> and <code>DescKey*</code> constants in <code>internal/config/embed/cmd/<name>.go</code>;</li> <li>Add command descriptions in <code>internal/assets/commands/commands.yaml</code>;</li> <li>Add examples in <code>internal/assets/commands/examples.yaml</code>;</li> <li>Add flag descriptions in <code>internal/assets/commands/flags.yaml</code>;</li> <li>Register the command in <code>internal/bootstrap/group.go</code> (add import +    entry in the appropriate group function);</li> <li>Create an output package at <code>internal/write/<name>/</code> for all    user-facing output (see Package Taxonomy);</li> <li>Create error constructors at <code>internal/err/<name>/</code> for    domain-specific errors;</li> <li>Add tests in the same package (<code><name>_test.go</code>);</li> <li>Add a doc page at <code>docs/cli/<name>.md</code> and update     <code>docs/cli/index.md</code>;</li> <li>Add the page to <code>zensical.toml</code> nav.</li> </ol> <p>Pattern to follow: <code>internal/cli/pad/pad.go</code> (parent with subcommands) or <code>internal/cli/drift/</code> (single command).</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#package-taxonomy","level":3,"title":"Package Taxonomy","text":"<p><code>ctx</code> separates concerns into a strict package taxonomy. Knowing where things go prevents code review friction and keeps the AST lint tests happy.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#output-internalwrite","level":4,"title":"Output: <code>internal/write/</code>","text":"<p>Every CLI command's user-facing output lives in its own sub-package under <code>internal/write/<domain>/</code>. Output functions accept <code>*cobra.Command</code> and call <code>cmd.Println(...)</code>, never <code>fmt.Print*</code> directly. All text strings are loaded from YAML via <code>desc.Text(text.DescKey*)</code>, never inline.</p> <pre><code>internal/write/add/add.go       # output for ctx add\ninternal/write/stat/stat.go     # output for ctx usage\ninternal/write/resource/        # output for ctx sysinfo\n</code></pre> <p>Exception: <code>write/rc/</code> writes to <code>os.Stderr</code> because rc loads before cobra is initialized.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#errors-internalerr","level":4,"title":"Errors: <code>internal/err/</code>","text":"<p>Domain-specific error constructors live under <code>internal/err/<domain>/</code>. Each package mirrors the write structure. Functions return <code>error</code> (never custom error types) and load messages from YAML via <code>desc.Text(text.DescKey*)</code>.</p> <pre><code>internal/err/add/add.go         # errors for ctx add\ninternal/err/config/config.go   # errors for configuration\ninternal/err/cli/cli.go         # errors for CLI argument validation\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#config-constants-internalconfig","level":4,"title":"Config Constants: <code>internal/config/</code>","text":"<p>Pure-constant leaf packages with zero internal dependencies (stdlib only). Over 60 sub-packages, organized by domain. See <code>internal/config/README.md</code> for the full decision tree.</p> What you're adding Where it goes File names, extensions, paths <code>config/file/</code>, <code>config/dir/</code> Regex patterns <code>config/regex/</code> CLI flag names (<code>--flag-name</code>) <code>config/flag/flag.go</code> Flag description YAML keys <code>config/embed/flag/<cmd>.go</code> Command Use/DescKey strings <code>config/embed/cmd/<cmd>.go</code> User-facing text YAML keys <code>config/embed/text/<domain>.go</code> Time durations, thresholds <code>config/<domain>/</code>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#the-assets-pipeline","level":4,"title":"The Assets Pipeline","text":"<p>User-facing text flows through a three-level chain:</p> <ol> <li>Go constant (<code>config/embed/text/</code>) defines a string key:    <code>DescKeyWriteAddedTo = \"write.added-to\"</code></li> <li>Call site resolves it: <code>desc.Text(text.DescKeyWriteAddedTo)</code></li> <li>YAML (<code>internal/assets/commands/text/write.yaml</code>) holds the    actual text: <code>write.added-to: { short: \"Added to %s\" }</code></li> </ol> <p>The same pattern applies to command descriptions (<code>commands.yaml</code>), flag descriptions (<code>flags.yaml</code>), and examples (<code>examples.yaml</code>). The <code>TestDescKeyYAMLLinkage</code> test verifies every constant resolves to a non-empty YAML value.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-session-parser","level":3,"title":"Adding a New Session Parser","text":"<p>The journal system uses a <code>SessionParser</code> interface. To add support for a new AI tool (e.g. Aider, Cursor):</p> <ol> <li>Create <code>internal/journal/parser/<tool>.go</code>;</li> <li>Implement parsing logic that returns <code>[]*Session</code>;</li> <li>Register the parser in <code>FindSessions()</code> / <code>FindSessionsForCWD()</code>;</li> <li>Use <code>config.Tool*</code> constants for the tool identifier;</li> <li>Add test fixtures and parser tests.</li> </ol> <p>Pattern to follow: the Claude Code JSONL parser in <code>internal/journal/parser/</code>.</p> <p>Multilingual Session Headers</p> <p>The Markdown parser recognizes session header prefixes configured via <code>session_prefixes</code> in <code>.ctxrc</code> (default: <code>Session:</code>). To support a new language, users add a prefix to their <code>.ctxrc</code> - no code change needed. New parser implementations can use <code>rc.SessionPrefixes()</code> if they also need prefix-based header detection.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-bundled-skill","level":3,"title":"Adding a Bundled Skill","text":"<ol> <li>Create <code>internal/assets/claude/skills/<skill-name>/SKILL.md</code>;</li> <li>Follow the skill format: trigger, negative triggers, steps, quality gate;</li> <li>Run <code>make plugin-reload</code> and restart Claude Code to test;</li> <li>Add a <code>Skill</code> entry to <code>.claude-plugin/plugin.json</code> if user-invocable;</li> <li>Document in <code>docs/reference/skills.md</code>.</li> </ol> <p>Pattern to follow: any skill in <code>internal/assets/claude/skills/ctx-status/</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#test-expectations","level":3,"title":"Test Expectations","text":"<ul> <li>Unit tests: colocated with source (<code>foo.go</code> → <code>foo_test.go</code>);</li> <li>Test helpers: use <code>t.Helper()</code> so failures point to callers;</li> <li>HOME isolation: use <code>t.TempDir()</code> + <code>t.Setenv(\"HOME\", ...)</code> for   tests that touch <code>~/.claude/</code> or <code>~/.ctx/</code>;</li> <li>rc.Reset(): call after <code>os.Chdir</code> in tests that change working   directory (rc caches on first access);</li> <li>No network: all tests run offline, use fixtures.</li> </ul> <p>Run <code>make test</code> before submitting. Target: no failures, no skips.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#day-to-day-workflow","level":2,"title":"Day-to-Day Workflow","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#go-code-changes","level":3,"title":"Go Code Changes","text":"<p>After modifying Go source files, rebuild and reinstall:</p> <pre><code>make build && sudo make install\n</code></pre> <p>The <code>ctx</code> binary is statically compiled. There is no hot reload. You must rebuild for Go changes to take effect.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skill-or-hook-changes","level":3,"title":"Skill or Hook Changes","text":"<p>Edit files under <code>internal/assets/claude/skills/</code> or <code>internal/assets/claude/hooks/</code>.</p> <p>Claude Code caches plugin files, so edits aren't picked up automatically.</p> <p>Clear the cache and restart:</p> <pre><code>make plugin-reload   # nukes ~/.claude/plugins/cache/activememory-ctx/\n# then restart Claude Code\n</code></pre> <p>The plugin will be re-installed from your local marketplace on startup. No version bump is needed during development.</p> <p>Version Bumps Are for Releases, Not Iteration</p> <p>Only bump <code>VERSION</code>, <code>plugin.json</code>, and <code>marketplace.json</code> when cutting a release. During development, <code>make plugin-reload</code> is all you need.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"<p>The repo ships two <code>.ctxrc</code> source profiles. The working copy (<code>.ctxrc</code>) is gitignored and swapped between them:</p> File Purpose <code>.ctxrc.base</code> Golden baseline: all defaults, no logging <code>.ctxrc.dev</code> Dev profile: notify events enabled, verbose logging <code>.ctxrc</code> Working copy (gitignored: copied from one of the above) <p>Use <code>ctx</code> commands to switch:</p> <pre><code>ctx config switch dev      # switch to dev profile\nctx config switch base     # switch to base profile\nctx config status          # show which profile is active\n</code></pre> <p>After cloning, run <code>ctx config switch dev</code> to get started with full logging.</p> <p>See Configuration for the full <code>.ctxrc</code> option reference.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#backups","level":3,"title":"Backups","text":"<p><code>ctx</code> does not ship a backup command. File-level backup is an OS / infrastructure concern; <code>ctx hub</code> handles the cross-machine knowledge persistence that matters most. For everything else, see Backup Strategy: rsync, Time Machine, Borg, or whichever tool already handles the rest of your files.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-tests","level":3,"title":"Running Tests","text":"<pre><code>make test   # fast: all tests\nmake audit  # full: fmt + vet + lint + drift + docs + test\nmake smoke  # build + run basic commands end-to-end\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-the-docs-site-locally","level":3,"title":"Running the Docs Site Locally","text":"<pre><code>make site-setup  # one-time: install zensical via pipx\nmake site-serve  # serve at localhost\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#submitting-changes","level":2,"title":"Submitting Changes","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#before-you-start","level":3,"title":"Before You Start","text":"<ol> <li>Check existing issues to avoid duplicating effort;</li> <li>For large changes, open an issue first to discuss the approach;</li> <li>Read the specs in <code>specs/</code> for design context.</li> </ol>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#pull-request-process","level":3,"title":"Pull Request Process","text":"<p>Respect the maintainers' time and energy: Keep your pull requests isolated and strive to minimze code changes.</p> <p>If you Pull Request solves more than one distinct issues, it's better to create separate pull requests instead of sending them in one large bundle.</p> <ol> <li>Create a feature branch: <code>git checkout -b feature/my-feature</code>;</li> <li>Make your changes;</li> <li>Run <code>make audit</code> to catch issues early;</li> <li>Commit with a clear message;</li> <li>Push and open a pull request.</li> </ol> <p>Audit Your Code Before Submitting</p> <p>Run <code>make audit</code> before submitting:</p> <p><code>make audit</code> covers formatting, vetting, linting, drift checks,  doc consistency, and tests in one pass.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#commit-messages","level":3,"title":"Commit Messages","text":"<p>Following conventional commits is recommended but not required:</p> <p>Types: <code>feat</code>, <code>fix</code>, <code>docs</code>, <code>test</code>, <code>refactor</code>, <code>chore</code></p> <p>Examples:</p> <ul> <li><code>feat(cli): add ctx export command</code></li> <li><code>fix(drift): handle missing files gracefully</code></li> <li><code>docs: update installation instructions</code></li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-style","level":3,"title":"Code Style","text":"<ul> <li>Follow Go conventions (<code>gofmt</code>, <code>go vet</code>);</li> <li>Keep functions focused and small;</li> <li>Add tests for new functionality;</li> <li>Handle errors explicitly; use descriptive names (<code>readErr</code>,   <code>writeErr</code>) not repeated <code>err</code>;</li> <li>No magic strings: all repeated literals go in <code>internal/config/</code>;</li> <li>Output goes through <code>internal/write/</code> packages, not <code>fmt.Print*</code>;</li> <li>Errors go through <code>internal/err/</code> constructors, not inline   <code>fmt.Errorf</code>;</li> <li>See Package Taxonomy and   <code>.context/CONVENTIONS.md</code> for the full reference.</li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-of-conduct","level":2,"title":"Code of Conduct","text":"<p>A clear context requires respectful collaboration.</p> <p><code>ctx</code> follows the Contributor Covenant.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#boring-legal-stuff","level":2,"title":"Boring Legal Stuff","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#developer-certificate-of-origin-dco","level":3,"title":"Developer Certificate of Origin (DCO)","text":"<p>By contributing, you agree to the Developer Certificate of Origin.</p> <p>All commits must be signed off:</p> <pre><code>git commit -s -m \"feat: add new feature\"\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#license","level":3,"title":"License","text":"<p>Contributions are licensed under the Apache 2.0 License.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/faq/","level":1,"title":"FAQ","text":"","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-markdown","level":2,"title":"Why Markdown?","text":"<p>Markdown is human-readable, version-controllable, and tool-agnostic. Every AI model can parse it natively. Every developer can read it in a terminal, a browser, or a code review. There's no schema to learn, no binary format to decode, no vendor lock-in. You can inspect your context with <code>cat</code>, diff it with <code>git diff</code>, and review it in a PR.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-ctx-work-offline","level":2,"title":"Does <code>ctx</code> Work Offline?","text":"<p>Yes. <code>ctx</code> is completely local. It reads and writes files on disk, generates context packets from local state, and requires no network access. The only feature that touches the network is the optional webhook notifications hook, which you have to explicitly configure.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-gets-committed-to-git","level":2,"title":"What Gets Committed to Git?","text":"<p>The <code>.context/</code> directory: yes, commit it. That's the whole point. Team members and AI agents read the same context files.</p> <p>What not to commit:</p> <ul> <li><code>.ctx.key</code>: your encryption key. Stored at <code>~/.ctx/.ctx.key</code>,   never in the repo. <code>ctx init</code> handles this automatically.</li> <li><code>journal/</code> and <code>logs/</code>: generated data, potentially large.   <code>ctx init</code> adds these to <code>.gitignore</code>.</li> <li><code>scratchpad.enc</code>: your choice. It's encrypted, so it's safe to   commit if you want shared scratchpad state. See   Scratchpad for details.</li> </ul>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#how-big-should-my-token-budget-be","level":2,"title":"How Big Should My Token Budget Be?","text":"<p>The default is 8000 tokens, which works well for most projects. Configure it via <code>.ctxrc</code> or the <code>CTX_TOKEN_BUDGET</code> environment variable:</p> <pre><code># In .ctxrc\ntoken_budget = 12000\n\n# Or as an environment variable\nexport CTX_TOKEN_BUDGET=12000\n\n# Or per-invocation\nctx agent --budget 4000\n</code></pre> <p>Higher budgets include more context but cost more tokens per request. Lower budgets force sharper prioritization: <code>ctx</code> drops lower-priority content first, so CONSTITUTION and TASKS always make the cut.</p> <p>See Configuration for all available settings.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-not-a-database","level":2,"title":"Why Not a Database?","text":"<p>Files are inspectable, diffable, and reviewable in pull requests. You can <code>grep</code> them, <code>cat</code> them, pipe them through <code>jq</code> or <code>awk</code>. They work with every version control system and every text editor.</p> <p>A database would add a dependency, require migrations, and make context opaque. The design bet is that context should be as visible and portable as the code it describes.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-it-work-with-tools-other-than-claude-code","level":2,"title":"Does It Work with Tools Other than Claude Code?","text":"<p>Yes. <code>ctx agent</code> outputs a context packet that any AI tool can consume: paste it into ChatGPT, Cursor, Copilot, Aider, or anything else that accepts text input.</p> <p>Claude Code gets first-class integration via the <code>ctx</code> plugin (hooks, skills, automatic context loading). VS Code Copilot Chat has a dedicated <code>ctx</code> extension. Other tools integrate via generated instruction files or manual pasting.</p> <p>See Integrations for tool-specific setup, including the multi-tool recipe.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#can-i-use-ctx-on-an-existing-project","level":2,"title":"Can I Use <code>ctx</code> on an Existing Project?","text":"<p>Yes. Run <code>ctx init</code> in any repo and it creates <code>.context/</code> with template files. Start recording decisions, tasks, and conventions as you work. Context grows naturally; you don't need to backfill everything on day one.</p> <p>See Getting Started for the full setup flow, or Joining a <code>ctx</code> Project if someone else already initialized it.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-happens-when-context-files-get-too-big","level":2,"title":"What Happens When Context Files Get Too Big?","text":"<p>Token budgeting handles this automatically. <code>ctx agent</code> prioritizes content by file priority (CONSTITUTION first, GLOSSARY last) and trims lower-priority entries when the budget is tight.</p> <p>For manual maintenance, <code>ctx compact</code> archives completed tasks and old entries, keeping active context lean. You can also run <code>ctx task archive</code> to move completed tasks out of TASKS.md.</p> <p>The goal is to keep context files focused on current state. Historical entries belong in git history or the archive.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#is-context-meant-to-be-shared","level":2,"title":"Is <code>.context/</code> Meant to Be Shared?","text":"<p>Yes. Commit it to your repo. Every team member and every AI agent reads the same files. That's the mechanism for shared memory: decisions made in one session are visible in the next, regardless of who (or what) starts it.</p> <p>The only per-user state is the encryption key (<code>~/.ctx/.ctx.key</code>) and the optional scratchpad. Everything else is team-shared by design.</p> <p>Related:</p> <ul> <li>Getting Started - installation and first setup</li> <li>Configuration - <code>.ctxrc</code>, environment variables, and defaults</li> <li>Context Files - what each file does and how to use it</li> </ul>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/first-session/","level":1,"title":"Your First Session","text":"<p>Here's what a complete first session looks like, from initialization to the moment your AI cites your project context back to you.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-1-initialize-your-project","level":2,"title":"Step 1: Initialize Your Project","text":"<p>Run <code>ctx init</code> in your project root:</p> <pre><code>cd your-project\nctx init\n</code></pre> <p>Sample output:</p> <pre><code>Context initialized in .context/\n\n  ✓ CONSTITUTION.md\n  ✓ TASKS.md\n  ✓ DECISIONS.md\n  ✓ LEARNINGS.md\n  ✓ CONVENTIONS.md\n  ✓ ARCHITECTURE.md\n  ✓ GLOSSARY.md\n  ✓ AGENT_PLAYBOOK.md\n\nSetting up encryption key...\n  ✓ ~/.ctx/.ctx.key\n\nClaude Code plugin (hooks + skills):\n  Install: claude /plugin marketplace add ActiveMemory/ctx\n  Then:    claude /plugin install ctx@activememory-ctx\n\nNext steps:\n  1. Edit .context/TASKS.md to add your current tasks\n  2. Run 'ctx status' to see context summary\n  3. Run 'ctx agent' to get AI-ready context packet\n</code></pre> <p>This created your <code>.context/</code> directory with template files. </p> <p>For Claude Code, install the <code>ctx</code> plugin to get automatic hooks and skills.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-2-activate-the-project","level":2,"title":"Step 2: Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory the rest of these commands should use:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the next steps fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. For more options (multiple <code>.context/</code> directories, scripts, CI), see Activating a Context Directory.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-3-populate-your-context","level":2,"title":"Step 3: Populate Your Context","text":"<p>Add a task and a decision: These are the entries your AI will remember:</p> <pre><code>ctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to TASKS.md\n\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to DECISIONS.md\n</code></pre> <p>These entries are what the AI will recall in future sessions. You don't need to populate everything now: Context grows naturally as you work.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-4-check-your-context","level":2,"title":"Step 4: Check Your Context","text":"<pre><code>ctx status\n</code></pre> <p>Sample output:</p> <pre><code>Context Status\n====================\n\nContext Directory: .context/\nTotal Files: 8\nToken Estimate: 1,247 tokens\n\nFiles:\n  ✓ CONSTITUTION.md (loaded)\n  ✓ TASKS.md (1 items)\n  ✓ DECISIONS.md (1 items)\n  ○ LEARNINGS.md (empty)\n  ✓ CONVENTIONS.md (loaded)\n  ✓ ARCHITECTURE.md (loaded)\n  ✓ GLOSSARY.md (loaded)\n  ✓ AGENT_PLAYBOOK.md (loaded)\n\nRecent Activity:\n  - TASKS.md modified 2 minutes ago\n  - DECISIONS.md modified 1 minute ago\n</code></pre> <p>Notice the token estimate: This is how much context your AI will load.</p> <p>The <code>○</code> next to <code>LEARNINGS.md</code> means it's still empty; it will fill in as you capture lessons during development.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-5-start-an-ai-session","level":2,"title":"Step 5: Start an AI Session","text":"<p>With Claude Code (and the <code>ctx</code> plugin), start every session with:</p> <pre><code>/ctx-remember\n</code></pre> <p>This loads your context and presents a structured  readback so you can confirm the agent knows what is going on. Context also loads automatically via hooks, but the explicit ceremony gives you a readback to verify.</p> <p>Steering Files Fire Automatically</p> <p>If you edited the four foundation files scaffolded by <code>ctx init</code> (<code>.context/steering/product.md</code>, <code>tech.md</code>, <code>structure.md</code>, <code>workflow.md</code>), their <code>inclusion: always</code> rules are prepended to every tool call via the plugin's <code>PreToolUse</code> hook, with no <code>/ctx-remember</code> needed, no MCP call. Edit a file, save, and the next tool call in Claude Code picks it up. See Steering files for details on the inclusion modes.</p> <p>Using VS Code?</p> <p>With VS Code Copilot Chat (and the <code>ctx</code> extension),  type <code>@ctx /agent</code> in chat to load your context packet, or <code>@ctx /status</code> to check your project context. Run <code>ctx setup copilot --write</code> once to generate <code>.github/copilot-instructions.md</code> for automatic context loading.</p> <p>If you are not using Claude Code, generate a  context packet for your AI tool:</p> <pre><code>ctx agent --budget 8000\n</code></pre> <p>Sample output:</p> <pre><code># Context Packet\nGenerated: 2026-02-14T15:30:45Z | Budget: 8000 tokens | Used: ~2450\n\n## Read These Files (in order)\n1. .context/CONSTITUTION.md\n2. .context/TASKS.md\n3. .context/CONVENTIONS.md\n...\n\n## Current Tasks\n- [ ] Implement user authentication\n- [ ] Add rate limiting to API endpoints\n\n## Key Conventions\n- Use gofmt for formatting\n- Path construction uses filepath.Join\n\n## Recent Decisions\n## [2026-02-14-120000] Use PostgreSQL for the primary database\n\n**Context**: Evaluated PostgreSQL, MySQL, and SQLite...\n**Rationale**: PostgreSQL offers better JSON support...\n\n## Key Learnings\n## [2026-02-14-100000] Connection pool sizing matters\n\n**Context**: Hit connection limits under load...\n**Lesson**: Default pool size of 10 is too low for concurrent requests...\n\n## Also Noted\n- Use JWT for session management\n- Always validate input at API boundary\n</code></pre> <p>Paste this output into your AI tool's system prompt or conversation start.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-6-verify-it-works","level":2,"title":"Step 6: Verify It Works","text":"<p>Ask your AI: \"What are our current tasks?\"</p> <p>A working setup produces a response like:</p> <pre><code>Based on the project context, you have one active task:\n\n- **Implement user authentication** (pending)\n\nThere's also a recent architectural decision to **use PostgreSQL for\nthe primary database**, chosen for its ACID compliance and JSON support.\n\nWant me to start on the authentication task?\n</code></pre> <p>That's the success moment:</p> <p>The AI is citing your exact context entries from Step 2, not hallucinating or asking you to re-explain.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-gets-created","level":2,"title":"What Gets Created","text":"<pre><code>.context/\n├── CONSTITUTION.md     # Hard rules: NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n└── AGENT_PLAYBOOK.md   # How AI tools should use this\n</code></pre> <p>Claude Code integration (hooks + skills) is provided by the <code>ctx</code> plugin: See Integrations/Claude Code.</p> <p>VS Code Copilot Chat integration is provided by the <code>ctx</code> extension: See Integrations/VS Code.</p> <p>See Context Files for detailed documentation of each file.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-to-gitignore","level":2,"title":"What to <code>.gitignore</code>","text":"<p>Rule of Thumb</p> <ul> <li>If it's knowledge (decisions, tasks, learnings,   conventions), commit it.</li> <li>If it's generated output, raw session data, or a secret, <code>.gitignore</code> it.</li> </ul> <p>Commit your <code>.context/</code> knowledge files: that's the whole point.</p> <p>You should <code>.gitignore</code> the generated and sensitive paths:</p> <pre><code># Journal data (large, potentially sensitive)\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Hook logs (machine-specific)\n.context/logs/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n</code></pre> <p><code>ctx init</code> Patches Your .Gitignore for You</p> <p><code>ctx init</code> automatically adds these entries to your <code>.gitignore</code>.</p> <p>Review the additions with <code>cat .gitignore</code> after init.</p> <p>See also:</p> <ul> <li>Security Considerations</li> <li>Scratchpad Encryption</li> <li>Session Journal</li> </ul> <p>Next Up: Common Workflows →:  day-to-day commands for tracking context, checking health, and browsing history.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/getting-started/","level":1,"title":"Getting Started","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"<p><code>ctx</code> does not require <code>git</code>, but using version control with your <code>.context/</code> directory is strongly recommended:</p> <p>AI sessions occasionally modify or overwrite context files inadvertently. With <code>git</code>, the AI can check history and restore lost content: Without it, the data is gone.</p> <p>Also, several <code>ctx</code> features (journal changelog, blog generation) also use <code>git</code> history directly.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#installation","level":2,"title":"Installation","text":"<p>Every setup starts with the <code>ctx</code> binary: the CLI tool itself.</p> <p>If you use Claude Code, you also install the <code>ctx</code> plugin, which adds hooks (context autoloading, persistence nudges) and 25+ <code>/ctx-*</code> skills. For other AI tools, <code>ctx</code> integrates via generated instruction files or manual context pasting: see Integrations for tool-specific setup.</p> <p>Pick one of the options below to install the binary. Claude Code users should also follow the plugin steps included in each option.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-1-build-from-source-recommended","level":3,"title":"Option 1: Build from Source (Recommended)","text":"<p>Requires Go (version defined in  <code>go.mod</code>) and Claude Code.</p> <pre><code>git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n</code></pre> <p>Install the Claude Code plugin from your local clone:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter the path to the root of your clone,    e.g. <code>~/WORKSPACE/ctx</code>    (this is where <code>.claude-plugin/marketplace.json</code> lives: It points    Claude Code to the actual plugin in <code>internal/assets/claude</code>)</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>This points Claude Code at the plugin source on disk. Changes you make to hooks or skills take effect immediately: No reinstall is needed.</p> <p>Local Installs Need Manual Enablement</p> <p>Unlike marketplace installs, local plugin installs are not auto-enabled globally. The plugin will only work in projects that explicitly enable it. Run <code>ctx init</code> in each project (it auto-enables the plugin), or add the entry to <code>~/.claude/settings.json</code> manually:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Verify:</p> <pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n</code></pre> <p>Use the Source, Luke</p> <p>Building from source gives you the latest features and bug fixes.</p> <p>Since <code>ctx</code> is predominantly a developer tool, this is the recommended approach: </p> <p>You get the freshest code, can inspect what you are installing, and the plugin stays in sync with the binary.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-2-binary-download-marketplace","level":3,"title":"Option 2: Binary Download + Marketplace","text":"<p>Pre-built binaries are available from the releases page.</p> Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64\nchmod +x ctx-0.8.1-linux-amd64\nsudo mv ctx-0.8.1-linux-amd64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-arm64\nchmod +x ctx-0.8.1-linux-arm64\nsudo mv ctx-0.8.1-linux-arm64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-arm64\nchmod +x ctx-0.8.1-darwin-arm64\nsudo mv ctx-0.8.1-darwin-arm64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-amd64\nchmod +x ctx-0.8.1-darwin-amd64\nsudo mv ctx-0.8.1-darwin-amd64 /usr/local/bin/ctx\n</code></pre> <p>Download <code>ctx-0.8.1-windows-amd64.exe</code> from the releases page and add it to your <code>PATH</code>.</p> <p>Claude Code users: install the plugin from the marketplace:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace;</li> <li>Enter <code>ActiveMemory/ctx</code>;</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code>.</li> </ol> <p>Other tool users: see Integrations for tool-specific setup (Cursor, Copilot, Aider, Windsurf, etc.).</p> <p>Verify the Plugin Is Enabled</p> <p>After installing, confirm the plugin is enabled globally. Check <code>~/.claude/settings.json</code> for an <code>enabledPlugins</code> entry. If missing, run <code>ctx init</code> in your project (it auto-enables the plugin), or add it manually:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Verify:</p> <pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed (Claude Code only)\n</code></pre>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#verifying-checksums","level":4,"title":"Verifying Checksums","text":"<p>Each binary has a corresponding <code>.sha256</code> checksum file. To verify your download:</p> <pre><code># Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.8.1-linux-amd64.sha256\n</code></pre> <p>On macOS, use <code>shasum -a 256 -c</code> instead of <code>sha256sum -c</code>.</p> Plugin Details <p>After installation (either option) you get:</p> <ul> <li>Context autoloading: <code>ctx agent</code> runs on every tool use (with cooldown)</li> <li>Persistence nudges: reminders to capture learnings and decisions</li> <li>Post-commit hooks: nudge context capture after <code>git commit</code></li> <li>Context size monitoring: alerts as sessions grow large</li> <li>Project skills: <code>/ctx-status</code>, <code>/ctx-task-add</code>, <code>/ctx-history</code>, and more</li> </ul> <p>See Integrations for the full hook and skill reference.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#quick-start","level":2,"title":"Quick Start","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#1-initialize-context","level":3,"title":"1. Initialize Context","text":"<pre><code>cd your-project\nctx init\n</code></pre> <p>This creates a <code>.context/</code> directory with template files and an encryption key at <code>~/.ctx/</code> for the encrypted scratchpad. For Claude Code, install the <code>ctx</code> plugin for automatic hooks and skills.</p> <p><code>ctx init</code> also scaffolds four foundation steering files in <code>.context/steering/</code>: <code>product.md</code>, <code>tech.md</code>, <code>structure.md</code>, <code>workflow.md</code>. They are placeholders until you customize them (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than buried here.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#2-customize-your-steering-files","level":3,"title":"2. Customize Your Steering Files","text":"<p>Steering files are behavioral rules prepended to every AI prompt: the layer that tells your AI how to act on this specific project. They are distinct from decisions (what was chosen) and conventions (how the codebase is written); see <code>ctx</code> for Steering Files for the full model.</p> <p><code>ctx init</code> scaffolded four foundation files; open each and fill it in:</p> File What to fill in <code>product.md</code> What the project is, who uses it, what's out of scope <code>tech.md</code> Languages, frameworks, runtime, hard constraints <code>structure.md</code> Directory layout, where new files go, naming rules <code>workflow.md</code> Branch strategy, commit conventions, pre-commit checks <p>Each scaffolded file ships with a tombstone marker line (<code><!-- remove this after you edit the steering file !--></code>). As long as the marker is present, the file is silently skipped on every load path: the agent context packet, MCP <code>ctx_steering_get</code>, and native-tool sync (Cursor / Cline / Kiro). The skip is deliberate: injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow \"Describe the product...\" as if it were a rule.</p> <p>Replace each file's body with real content, then delete the tombstone line. When the line is gone, the file becomes active on the next AI tool call.</p> <p>Don't want steering at all? Pass <code>--no-steering-init</code> to <code>ctx init</code> to skip the scaffold entirely. Existing edits are never clobbered by re-running <code>ctx init</code>.</p> <p>Inclusion modes (<code>always</code> / <code>auto</code> / <code>manual</code>), priority, and tool scoping are covered in Writing Steering Files and <code>ctx steering</code>.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#3-activate-the-project","level":3,"title":"3. Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory the rest of these commands should use:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the next steps fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. For more options (multiple <code>.context/</code> directories, scripts, CI), see Activating a Context Directory.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#4-check-status","level":3,"title":"4. Check Status","text":"<pre><code>ctx status\n</code></pre> <p>Shows context summary: files present, token estimate, and recent activity.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5-start-using-with-ai","level":3,"title":"5. Start Using with AI","text":"<p>With Claude Code (and the <code>ctx</code> plugin installed), context loads automatically via hooks.</p> <p>With VS Code Copilot Chat, install the <code>ctx</code> extension and use <code>@ctx /status</code>, <code>@ctx /agent</code>, and other slash commands directly in chat. Run <code>ctx setup copilot --write</code> to generate <code>.github/copilot-instructions.md</code> for automatic context loading.</p> <p>For other tools, paste the output of:</p> <pre><code>ctx agent --budget 8000\n</code></pre>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5b-set-up-for-your-ai-tool","level":3,"title":"5B. Set Up for Your AI Tool","text":"<p>If you use an MCP-compatible tool, generate the integration config with <code>ctx setup</code>:</p> KiroCursorCline <pre><code>ctx setup kiro --write\n# Creates .kiro/settings/mcp.json and syncs steering files\n</code></pre> <pre><code>ctx setup cursor --write\n# Creates .cursor/mcp.json and syncs steering files\n</code></pre> <pre><code>ctx setup cline --write\n# Creates .vscode/mcp.json and syncs steering files\n</code></pre> <p>This registers the <code>ctx</code> MCP server and syncs any steering files into the tool's native format. Re-run after adding or changing steering files.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#6-verify-it-works","level":3,"title":"6. Verify It Works","text":"<p>Ask your AI: \"Do you remember?\"</p> <p>It should cite specific context: current tasks, recent decisions, or previous session topics.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#7-set-up-companion-tools-highly-recommended","level":3,"title":"7. Set Up Companion Tools (Highly Recommended)","text":"<p><code>ctx</code> works on its own, but two companion MCP servers unlock significantly better agent behavior. The investment is small and the benefits compound over sessions:</p> <ul> <li> Gemini Search grounded web search with citations. Skills like <code>/ctx-code-review</code>   and <code>/ctx-explain</code> use it for up-to-date documentation lookups instead   of relying on training data. </li> <li> <p>GitNexus: code   knowledge graph with symbol resolution, blast radius analysis, and   domain clustering. Skills like <code>/ctx-refactor</code> and <code>/ctx-code-review</code>   use it for impact analysis and dependency awareness.</p> </li> </ul> <pre><code># Index your project for GitNexus (run once, then after major changes)\nnpx gitnexus analyze\n</code></pre> <p>Both are optional MCP servers: if they are not connected, skills degrade gracefully to built-in capabilities. See Companion Tools for setup details and verification.</p> <p>Next Up:</p> <ul> <li>Your First Session →: a step-by-step walkthrough   from <code>ctx init</code> to verified recall</li> <li>Common Workflows →: day-to-day commands for   tracking context, checking health, and browsing history</li> </ul>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/hub/","level":1,"title":"Hub","text":"","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#sharing-is-caring","level":2,"title":"Sharing Is Caring","text":"<p><code>ctx</code> projects are normally independent: each project has its own <code>.context/</code> directory, its own decisions, its own learnings, its own journal. That's the right default, since most work is project-local, and mixing context across projects tends to dilute more than it helps.</p> <p>But sometimes a decision or a learning should cross project boundaries. A convention you codified in one project deserves to be visible in another. A gotcha you discovered debugging service A is the same gotcha waiting for you in service B. The <code>ctx</code> Hub is the feature that makes those specific entries travel, without replicating everything else.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#what-the-hub-actually-is","level":2,"title":"What the Hub Actually Is","text":"<p>In one paragraph: the <code>ctx</code> Hub is a fan-out channel for four specific kinds of structured entries: <code>decision</code>, <code>learning</code>, <code>convention</code>, and <code>task</code>. You publish an entry with <code>ctx add --share</code> in one project, and it appears in <code>.context/hub/</code> for every other project subscribed to that type. When you run <code>ctx agent --include-hub</code>, those shared entries become part of your next agent context packet.</p> <p>That is the entire feature. The Hub does not:</p> <ul> <li>Share your session journal (<code>.context/journal/</code>). That stays   local to each project.</li> <li>Share your scratchpad (<code>.context/pad</code>). Encrypted notes never   leave the machine that created them.</li> <li>Share your <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, or   <code>CONVENTIONS.md</code> wholesale. Only entries you explicitly   <code>--share</code> cross the boundary.</li> <li>Provide user identity or attribution. The Hub identifies   projects, not people.</li> </ul> <p>If you want \"my agent in project B sees everything my agent did in project A,\" that's not the Hub. Local session density stays local.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#who-its-for","level":2,"title":"Who It's For","text":"<p>Two shapes, same mechanics, different trust models.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"<p>One developer, many projects. You want a learning from project A to show up when you open project B a week later. You want a convention you codified in your dotfiles project to be visible everywhere else on your workstation. Run a Hub on localhost, register each project, done.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#small-trusted-team","level":3,"title":"Small Trusted Team","text":"<p>A few teammates on a LAN or a hub.ctx-like self-hosted server. You want team conventions to propagate without a wiki. You want lessons from one on-call engineer's 3 AM incident to reach everyone else's agent on the next session. Same mechanics as the personal case, plus TLS in front and a short security runbook.</p> <p>The Hub is not a multi-tenant public service. It assumes everyone holding a client token is friendly. Don't stand up <code>hub.example.com</code> for untrusted participants.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#going-further","level":2,"title":"Going Further","text":"<ul> <li>First-time setup: Hub: Getting Started,   a five-minute walkthrough on localhost.</li> <li>Mental model and user stories: Hub Overview,   what flows, what doesn't, and when not to use it.</li> <li>Team / LAN deployment: Multi-machine setup.</li> <li>Redundancy: HA cluster.</li> <li>Operating a Hub: Hub Operations   and Hub Failure Modes.</li> <li>Security posture: Hub Security Model.</li> <li>Command reference: <code>ctx serve</code>,   <code>ctx connect</code>,   <code>ctx hub</code>.</li> </ul>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/is-ctx-right/","level":1,"title":"Is It Right for Me?","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#good-fit","level":2,"title":"Good Fit","text":"<p><code>ctx</code> shines when context matters more than code.</p> <p>If any of these sound like your project, it's worth trying:</p> <ul> <li>Multi-session AI work: You use AI across many sessions on the same   codebase, and re-explaining is slowing you down.</li> <li>Architectural decisions that matter: Your project has non-obvious   choices (database, auth strategy, API design) that the AI keeps   second-guessing.</li> <li>\"Why\" matters as much as \"what\": you need the AI to understand   rationale, not just current code</li> <li>Team handoffs: Multiple people (or multiple AI tools) work on the   same project and need shared context.</li> <li>AI-assisted development across tools: Uou switch between Claude Code,   Cursor, Copilot, or other tools and want context to follow the project,   not the tool.</li> <li>Long-lived projects: Anything you'll work on for weeks or months,   where accumulated knowledge has compounding value.</li> </ul>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#may-not-be-the-right-fit","level":2,"title":"May Not Be the Right Fit","text":"<p><code>ctx</code> adds overhead that isn't worth it for every project. Be honest about when to skip it:</p> <ul> <li>One-off scripts: If the project is a single file you'll finish today,   there's nothing to remember.</li> <li>RAG-only workflows: If retrieval from an external knowledge base already   gives the agent everything it needs for each session, adding <code>ctx</code> may be   unnecessary. RAG retrieves information; <code>ctx</code> defines the project's   working memory: They are complementary.</li> <li>No AI involvement: <code>ctx</code> is designed for human-AI workflows; without   an AI consumer, the files are just documentation.</li> <li>Enterprise-managed context platforms: If your organization provides   centralized context services, <code>ctx</code> may duplicate that layer.</li> </ul> <p>For a deeper technical comparison with RAG, prompt management tools, and agent frameworks, see <code>ctx</code> and Similar Tools.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#project-size-guide","level":2,"title":"Project Size Guide","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#solo-developer-single-repo","level":3,"title":"Solo Developer, Single Repo","text":"<p>This is <code>ctx</code>'s sweet spot. </p> <p>You get the most value here: one person, one project, decisions, and learnings  accumulating over time. Setup takes 5 minutes and the <code>.context/</code> directory directory stays small, and every session gets faster.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#small-team-one-or-two-repos","level":3,"title":"Small Team, One or Two Repos","text":"<p>Works well. </p> <p>Context files commit to git, so the whole team shares the same decisions and conventions. Each person's AI starts with the team's decisions already loaded. Merge conflicts on <code>.context/</code> files are rare and easy to resolve (they are just Markdown).</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#multiple-repos-or-larger-teams","level":3,"title":"Multiple Repos or Larger Teams","text":"<p><code>ctx</code> operates per repository.</p> <p>Each repo has its own <code>.context/</code> directory with its own decisions, tasks, and learnings. This matches the way code, ownership, and history already work in <code>git</code>.</p> <p>There is no built-in cross-repo context layer.</p> <p>For organizations that need centralized, organization-wide knowledge, <code>ctx</code> complements a platform solution by providing durable, project-local working memory for AI sessions.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#5-minute-trial","level":2,"title":"5-Minute Trial","text":"<p>Zero commitment. Try it, and delete <code>.context/</code> if it's not for you.</p> <p>Using Claude Code?</p> <p>Install the <code>ctx</code> plugin from the Marketplace for Claude-native hooks,  skills, and automatic context loading:</p> <ol> <li>Type <code>/plugin</code> and press Enter</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter <code>ActiveMemory/ctx</code></li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>You'll still need the <code>ctx</code> binary for the CLI: See Getting Started for install options.</p> <pre><code># 1. Initialize\ncd your-project\nctx init\n\n# 2. Activate the project (bind CTX_DIR for this shell).\n#    Required: ctx does not walk the filesystem to find .context/.\neval \"$(ctx activate)\"\n\n# 3. Add one real decision from your project\nctx decision add \"Your actual architectural choice\" \\\n  --context \"What prompted this decision\" \\\n  --rationale \"Why you chose this approach\" \\\n  --consequence \"What changes as a result\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# 4. Check what the AI will see\nctx status\n\n# 5. Start an AI session and ask: \"Do you remember?\"\n</code></pre> <p>If the AI cites your decision back to you, it's working.</p> <p>Want to remove it later? One command:</p> <pre><code>rm -rf .context/\n</code></pre> <p>No dependencies to uninstall. No configuration to revert. Just files.</p> <p>Ready to try it out?</p> <ul> <li>Join the Community→: Open Source is better together.</li> <li>Getting Started →: Full installation and setup.</li> <li><code>ctx</code> and Similar Tools →: Detailed comparison   with other approaches.</li> </ul>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/joining-a-project/","level":1,"title":"Joining a Project","text":"<p>You've joined a team or inherited a project, and there's a <code>.context/</code> directory in the repo. Good news: someone already set up persistent context. This page gets you oriented fast.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#what-to-read-first","level":2,"title":"What to Read First","text":"<p>The files in <code>.context/</code> have a deliberate priority order. Read them top-down:</p> <ol> <li>CONSTITUTION.md: Hard rules. Read this before you touch anything.    These are inviolable constraints the team has agreed on.</li> <li>TASKS.md: Current and planned work. Shows what's in progress,    what's pending, and what's blocked.</li> <li>CONVENTIONS.md: How the team writes code. Naming patterns,    file organization, preferred idioms.</li> <li>ARCHITECTURE.md: System overview. Components, boundaries, data flow.</li> <li>DECISIONS.md: Why things are the way they are. Saves you from    re-proposing something the team already evaluated and rejected.</li> <li>LEARNINGS.md: Gotchas, tips, and hard-won lessons. The stuff    that doesn't fit anywhere else but will save you hours.</li> </ol> <p>See Context Files for detailed documentation of each file's structure and purpose.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#activate-the-project","level":2,"title":"Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory to read from:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the commands in the rest of this guide fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. See Activating a Context Directory for more options (multiple <code>.context/</code> directories, scripts, CI).</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#checking-context-health","level":2,"title":"Checking Context Health","text":"<p>Before you start working, check whether the context is current:</p> <pre><code>ctx status\n</code></pre> <p>This shows file counts, token estimates, and recent activity. If files haven't been touched in weeks, the context may be stale.</p> <pre><code>ctx drift\n</code></pre> <p>This compares context files against recent code changes and flags potential drift: decisions that no longer match the codebase, conventions that have shifted, or tasks that look outdated.</p> <p>If things are stale, mention it to the team. Don't silently fix it yourself on day one.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#starting-your-first-session","level":2,"title":"Starting Your First Session","text":"<p>Generate a context packet to prime your AI:</p> <pre><code>ctx agent --budget 8000\n</code></pre> <p>This outputs a token-budgeted summary of the project context, ordered by priority. With Claude Code and the <code>ctx</code> plugin, context loads automatically via hooks. You can also use the <code>/ctx-remember</code> skill to get a structured readback of what the AI knows.</p> <p>The readback is your verification step: if the AI can cite specific tasks and decisions, the context is working.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#adding-context","level":2,"title":"Adding Context","text":"<p>As you work, you'll discover things worth recording. Use the CLI:</p> <pre><code># Record a decision you made or learned about\nctx decision add \"Use connection pooling for DB access\" \\\n  --rationale \"Reduces connection overhead under load\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Capture a gotcha you hit\nctx learning add \"Redis timeout defaults to 5s\" \\\n  --context \"Hit timeouts during bulk operations\" \\\n  --application \"Set explicit timeout for batch jobs\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add a convention you noticed the team follows\nctx convention add \"All API handlers return structured errors\"\n</code></pre> <p>You can also just tell the AI: \"Record this as a learning\" or \"Add this decision to context.\" With the <code>ctx</code> plugin, context-update commands handle the file writes.</p> <p>See the Knowledge Capture recipe for the full workflow.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#session-etiquette","level":2,"title":"Session Etiquette","text":"<p>A few norms for working in a ctx-managed project:</p> <ul> <li>Respect existing conventions. If <code>CONVENTIONS.md</code> says   \"use <code>filepath.Join</code>,\" use <code>filepath.Join</code>. If you disagree, propose   a change, don't silently diverge.</li> <li>Don't restructure context files without asking. The file layout   and section structure are shared state. Reorganizing them affects   every team member and every AI session.</li> <li>Mark tasks done when complete. Check the box (<code>[x]</code>) in place.   Don't move tasks between sections or delete them.</li> <li>Add context as you go. Decisions, learnings, and conventions   you discover are valuable to the next person (or the next session).</li> </ul>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"<p>Ignoring CONSTITUTION.md. The constitution exists for a reason. If a task conflicts with a constitution rule, the task is wrong. Raise it with the team instead of working around the constraint.</p> <p>Deleting tasks. Never delete a task from TASKS.md. Mark it <code>[x]</code> (done) or <code>[-]</code> (skipped with a reason). The history matters for session replay and audit.</p> <p>Bypassing hooks. If the project uses <code>ctx</code> hooks (pre-commit nudges, context autoloading), don't disable them. They exist to keep context fresh. If a hook is noisy or broken, fix it or file a task.</p> <p>Over-contributing on day one. Read first, then contribute. Adding a dozen learnings before you understand the project's norms creates noise, not signal.</p> <p>Related:</p> <ul> <li>Getting Started: installation and setup from scratch</li> <li>Context Files: detailed file reference</li> <li>Knowledge Capture: recording decisions,    learnings, and conventions</li> <li>Session Lifecycle: how a typical AI    session flows with <code>ctx</code></li> </ul>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/keeping-ai-honest/","level":1,"title":"Keeping AI Honest","text":"","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-problem","level":2,"title":"The Problem","text":"<p>AI agents confabulate. They invent history that never happened, claim familiarity with decisions that were never made, and sometimes declare a task complete when it is not. This is not malice - it is the default behavior of a system optimizing for plausible-sounding responses.</p> <p>When your AI says \"we decided to use Redis for caching last week,\" can you verify that? When it says \"the auth module is complete,\" can you confirm it? Without grounded, persistent context, the answer is no. You are trusting vibes.</p> <p><code>ctx</code> replaces vibes with verifiable artifacts.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#grounded-memory","level":2,"title":"Grounded Memory","text":"<p>Every entry in <code>ctx</code> context files has a timestamp and structured fields. When the AI cites a decision, you can check it.</p> <pre><code>## [2026-01-28-143022] Use Event Sourcing for Audit Trail\n\n**Status**: Accepted\n\n**Context**: Compliance requires full mutation history.\n\n**Decision**: Event sourcing for the audit subsystem only.\n\n**Rationale**: Append-only log meets compliance requirements\nwithout imposing event sourcing on the entire domain model.\n</code></pre> <p>The timestamp <code>2026-01-28-143022</code> is not decoration. It is a verifiable anchor. If the AI references this decision, you can open DECISIONS.md, find the entry, and confirm it says what the AI claims. If the entry does not exist, the AI is hallucinating - and you know immediately.</p> <p>This is grounded memory: claims that trace back to artifacts you control and can audit.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#constitutionmd-hard-guardrails","level":2,"title":"<code>CONSTITUTION.md</code>: Hard Guardrails","text":"<p>CONSTITUTION.md defines rules the AI must treat as inviolable. These are not suggestions or best practices - they are constraints that override task requirements.</p> <pre><code># Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these,\nthe task is wrong.\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] All public API changes require a decision record\n* [ ] Never delete context files without explicit user approval\n</code></pre> <p>The AI reads these at session start, before anything else. A well- integrated agent will refuse a task that conflicts with a constitutional rule, citing the specific rule it would violate.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-agent-playbooks-anti-hallucination-rules","level":2,"title":"The Agent Playbook's Anti-Hallucination Rules","text":"<p>The AGENT_PLAYBOOK.md file includes a section called \"How to Avoid Hallucinating Memory\" with five explicit rules:</p> <ol> <li>Never assume. If it is not in the context files, you do not    know it.</li> <li>Never invent history. Do not claim \"we discussed\" something    without a file reference.</li> <li>Verify before referencing. Search files before citing them.</li> <li>When uncertain, say so. \"I don't see a decision on this\" is    always better than a fabricated one.</li> <li>Trust files over intuition. If the files say PostgreSQL but    your training data suggests MySQL, the files win.</li> </ol> <p>These rules create a behavioral contract. The AI is not left to guess how confident it should be - it has explicit instructions to ground every claim in the context directory.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#drift-detection","level":2,"title":"Drift Detection","text":"<p>Context files can go stale. You rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist. Stale context is almost as dangerous as no context: the AI treats outdated information as current truth.</p> <p><code>ctx drift</code> detects this divergence:</p> <pre><code>ctx drift\n</code></pre> <p>It scans context files for references to files, paths, and symbols that no longer exist in the codebase. Stale references get flagged so you can update or remove them before they mislead the next session.</p> <p>Regular drift checks - weekly, or after major refactors - keep your context files honest the same way tests keep your code honest.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-verification-loop","level":2,"title":"The Verification Loop","text":"<p>The <code>/ctx-commit</code> skill includes a built-in verification step: before staging, it maps claims to evidence and runs self-audit questions to surface gaps. This catches inconsistencies at the point where they matter most: right before code is committed.</p> <p>This closes the loop. You write context. The AI reads context. The verification step confirms that context still matches reality. When it does not, you fix it - and the next session starts from truth, not from drift.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#trust-through-structure","level":2,"title":"Trust through Structure","text":"<p>The common thread across all of these mechanisms is structure over prose. Timestamps make claims verifiable. Constitutional rules make boundaries explicit. Drift detection makes staleness visible. The playbook makes behavioral expectations concrete.</p> <p>You do not need to trust the AI. You need to trust the system -- and verify when it matters.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Detecting and Fixing Drift: the   full workflow for keeping context files accurate</li> <li>Invariants: the properties   that must hold for any valid <code>ctx</code> implementation</li> <li>Agent Security: threat model and   mitigations for AI agents operating with persistent context</li> </ul>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/opencode/","level":1,"title":"ctx for OpenCode","text":"","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#the-problem","level":2,"title":"The Problem","text":"<p>Every OpenCode session starts from zero. You re-explain your architecture, the AI repeats mistakes it made yesterday, and decisions get rediscovered instead of remembered.</p> <p>Without <code>ctx</code>:</p> <pre><code>> \"Add the validation middleware we discussed\"\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n</code></pre> <p>With <code>ctx</code>:</p> <pre><code>> \"Add the validation middleware we discussed\"\n\nYes. From the Jan 15 session. You decided on Zod schemas at the\nroute level (DECISIONS.md #12), and the pattern is in\nCONVENTIONS.md. I'll follow the existing middleware in\nsrc/middleware/auth.ts as a reference.\n</code></pre> <p>That's the whole pitch: your AI remembers.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#setup-one-command","level":2,"title":"Setup (One Command)","text":"<p>Install the <code>ctx</code> binary first (installation docs), then run from your project root:</p> <pre><code>ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>This does three things:</p> <ol> <li><code>ctx setup opencode --write</code>: generates the project-local OpenCode plugin,    skills, and <code>AGENTS.md</code>, then merges the <code>ctx</code> MCP server into OpenCode's    global config (<code>~/.config/opencode/opencode.json</code> or    <code>$OPENCODE_HOME/opencode.json</code>). This writes outside the project root    because non-interactive shells (like MCP subprocesses) cannot discover    project-local config; the same reason the Copilot CLI integration    writes to <code>~/.copilot/mcp-config.json</code>.</li> <li><code>ctx init</code>: creates the <code>.context/</code> directory with template files.</li> <li><code>eval \"$(ctx activate)\"</code>: binds <code>CTX_DIR</code> for your shell.</li> </ol>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose <code>.opencode/plugins/ctx.ts</code> Lifecycle plugin (hooks into <code>ctx system</code> commands) <code>~/.config/opencode/opencode.json</code> Global MCP server registration (or <code>$OPENCODE_HOME/opencode.json</code>) <code>AGENTS.md</code> Agent instructions (OpenCode reads this natively) <code>.opencode/skills/ctx-*/SKILL.md</code> Slash command skills <p>The plugin is a single file with no runtime dependencies; no <code>bun install</code> or <code>npm install</code> needed. OpenCode loads it automatically on launch.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"<p>The plugin wires OpenCode lifecycle events to <code>ctx</code>. You don't need to do anything; it just works.</p> Event What fires What it does New session <code>session.created</code> Warms <code>ctx</code> state in the background (bootstrap + agent packet) so MCP queries are fast on first use Agent idle <code>session.idle</code> Runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI) After <code>git commit</code> <code>tool.execute.after</code> Runs <code>ctx system post-commit</code> to capture context state After file edit <code>tool.execute.after</code> Runs <code>ctx system check-task-completion</code> to detect silent task completions Every shell call <code>shell.env</code> Injects <code>CTX_DIR</code> so all <code>ctx</code> commands in the agent's shell resolve to the right project Context compaction <code>experimental.session.compacting</code> Pushes <code>ctx system bootstrap</code> output into the compaction context so the agent retains breadcrumbs to re-read context files post-compaction <p>The compaction hook matters most. When OpenCode compresses your context window to free up tokens, the plugin makes sure the compressed summary includes a pointer back to your <code>.context/</code> directory and its file inventory, so the agent can re-read tasks, decisions, and learnings on demand, even though the original messages are gone.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#how-compaction-works","level":3,"title":"How Compaction Works","text":"<p>When your conversation exceeds the context window, OpenCode runs a compaction pass (you can trigger one manually with <code>/compact</code>). The compaction agent summarizes older messages and drops the originals. Without <code>ctx</code>, all accumulated knowledge disappears. With <code>ctx</code>, the plugin intercepts the <code>experimental.session.compacting</code> event and appends <code>ctx system bootstrap</code> output (context directory path and file inventory) into the compaction context. The result: the compressed summary retains the breadcrumbs the agent needs to re-read tasks, decisions, learnings, and conventions on demand, even though the original messages that loaded them are gone.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-is-not-included","level":3,"title":"What Is Not Included","text":"<p>Note: dangerous-command blocking is Claude Code-specific and is not part of the OpenCode integration. OpenCode's execution model (explicit user approval for every shell command) makes a pre-execution blocklist unnecessary.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#slash-commands","level":2,"title":"Slash Commands","text":"<p>Four skills are available as slash commands:</p> Command When to use <code>/ctx-agent</code> Load full context packet. Use at session start or when context feels stale. <code>/ctx-remember</code> \"Do you remember?\"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. <code>/ctx-status</code> Context summary at a glance: file count, token estimate, recent activity. <code>/ctx-wrap-up</code> End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to <code>.context/</code> files. <p>You don't need to use these often. The plugin handles most context loading automatically. These are for when you want explicit control.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#mcp-tools","level":2,"title":"MCP Tools","text":"<p>The <code>ctx</code> MCP server exposes tools directly to the agent. These let the AI read and write your context files without shell commands:</p> Tool Purpose <code>ctx_add</code> Add a task, decision, learning, or convention <code>ctx_complete</code> Mark a task done by number or text match <code>ctx_search</code> Full-text search across all <code>.context/</code> files <code>ctx_next</code> Suggest the next pending task by priority <code>ctx_drift</code> Detect stale context: dead paths, missing files <code>ctx_compact</code> Archive completed tasks, clean empty sections <code>ctx_remind</code> List pending session-scoped reminders <code>ctx_status</code> Context health: file count, token estimate <code>ctx_steering_get</code> Retrieve steering files applicable to the current prompt <code>ctx_journal_source</code> Query recent AI session history <code>ctx_session_event</code> Signal session start/end lifecycle events <code>ctx_watch_update</code> Apply structured updates to <code>.context/</code> files <code>ctx_check_task_completion</code> After a write, detect silently completed tasks <p>You don't invoke these yourself. The agent uses them as needed.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"<p>If you re-run <code>ctx setup opencode --write</code> (e.g., after updating <code>ctx</code>), the plugin and skills are rewritten in place. Restart OpenCode to pick up the refreshed plugin. OpenCode only loads plugins at launch, not mid-session.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix <code>opencode mcp list</code> shows <code>ctx ✗ failed MCP error -32000: Connection closed</code> <code>CTX_DIR</code> not resolving in the MCP subprocess Re-run <code>ctx setup opencode --write</code> to regenerate the sh-wrapper that sets <code>CTX_DIR</code> Plugin installed but no hooks fire Flat-file vs. subdirectory discovery mismatch (OpenCode requires <code>.opencode/plugins/<name>.ts</code>, not a subfolder) Verify the plugin is at <code>.opencode/plugins/ctx.ts</code>. Check with <code>opencode --print-logs --log-level DEBUG</code> <code>ctx agent</code> Markdown leaking into the TUI BunShell command missing <code>.nothrow().quiet()</code> Update to the latest plugin: <code>ctx setup opencode --write</code> and restart","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#verify-it-works","level":2,"title":"Verify It Works","text":"<p>Start a new OpenCode session and ask:</p> <pre><code>Do you remember?\n</code></pre> <p>The AI should cite specific context: current tasks, recent decisions, or previous session topics. If it says \"I don't have memory\" or \"Let me check,\" something went wrong; check that the plugin installed correctly and <code>.context/</code> has files in it.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#whats-next","level":2,"title":"What's Next","text":"<ul> <li>Your First Session: step-by-step walkthrough from   <code>ctx init</code> to verified recall.</li> <li>Common Workflows: day-to-day commands for   tracking context, checking health, and browsing history.</li> <li>Context Files: what lives in <code>.context/</code> and how   each file is used.</li> </ul>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/prompting-guide/","level":1,"title":"Prompting Guide","text":"<p>New to <code>ctx</code>?</p> <p>This guide references context files like <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>LEARNINGS.md</code>:</p> <p>These are plain Markdown files that <code>ctx</code> maintains in your project's <code>.context/</code> directory.</p> <p>If terms like \"context packet\" or \"session ceremony\" are unfamiliar,</p> <ul> <li>start with the <code>ctx</code> Manifesto for the why,</li> <li>About for the big picture,</li> <li>then Getting Started to set up your first   project.</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#literature-matters","level":2,"title":"Literature Matters","text":"<p>This guide is about crafting effective prompts for working with  AI assistants in <code>ctx</code>-enabled projects, but the guidelines given here apply to other AI systems, too.</p> <p>The right prompt triggers the right behavior. </p> <p>This guide documents prompts that reliably produce good results.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#tldr","level":2,"title":"TL;DR","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" What's next <code>/ctx-next</code> Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect <code>/ctx-reflect</code> Wrap up <code>/ctx-wrap-up</code> Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\" One more thing \"What's the single smartest addition?\" Set tone \"Push back if my assumptions are wrong.\" Constrain scope \"Only change files in X. Nothing else.\" Course correct \"Stop. That's not what I meant.\" Check health \"Run <code>ctx drift</code>\" Commit <code>/ctx-commit</code>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#do-you-remember","level":3,"title":"\"do you remember?\"","text":"<p>Triggers the AI to silently read <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, and check recent history via <code>ctx journal</code> before responding with a  structured readback:</p> <ol> <li>Last session: most recent session topic and date</li> <li>Active work: pending or in-progress tasks</li> <li>Recent context: 1-2 recent decisions or learnings</li> <li>Next step: offer to continue or ask what to focus on</li> </ol> <p>Use this at the start of every important session.</p> <pre><code>Do you remember what we were working on?\n</code></pre> <p>This question implies prior context exists. The AI checks files rather than admitting ignorance. The expected response cites specific context (session names, task counts, decisions), not vague summaries.</p> <p>If the AI instead narrates its discovery process (\"Let me check if there are files...\"), it has not loaded <code>CLAUDE.md</code> or <code>AGENT_PLAYBOOK.md</code> properly.</p> <p>For a detailed case study on making agents actually follow this protocol (including the failure modes, the timing problem, and the hook design that solved it) see The Dog Ate My Homework.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the Current State?\"","text":"<p>Prompts reading of <code>TASKS.md</code>, recent sessions, and status overview.</p> <p>Use this when resuming work after a break.</p> <p>Variants:</p> <ul> <li>\"Where did we leave off?\"</li> <li>\"What's in progress?\"</li> <li>\"Show me the open tasks.\"</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why Doesn't X Work?\"","text":"<p>This triggers root cause analysis rather than surface-level fixes.</p> <p>Use this when something fails unexpectedly.</p> <p>Framing as \"why\" encourages investigation before action. The AI will trace  through code, check configurations, and identify the actual cause.</p> <p>Real Example</p> <p>\"Why can't I run /ctx-reflect?\" led to discovering missing permissions in <code>settings.local.json</code> bootstrapping.</p> <p>This was a fix that benefited all users of <code>ctx</code>.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is This Consistent with Our Decisions?\"","text":"<p>This prompts checking <code>DECISIONS.md</code> before implementing.</p> <p>Use this before making architectural choices.</p> <p>Variants:</p> <ul> <li>\"Check if we've decided on this before\"</li> <li>\"Does this align with our conventions?\"</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-would-break-if-we","level":3,"title":"\"What Would Break If We...\"","text":"<p>This triggers defensive thinking and impact analysis.</p> <p>Use this before making significant changes.</p> <pre><code>What would break if we change the Settings struct?\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before You Start, Read X\"","text":"<p>This ensures specific context is loaded before work begins.</p> <p>Use this when you know the relevant context exists in a specific file.</p> <pre><code>Before you start, check ctx journal source for the auth discussion session\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-control","level":3,"title":"Scope Control","text":"<p>Constrain the AI to prevent sprawl. These are some of the most useful prompts in day-to-day work.</p> <pre><code>Only change files in internal/cli/add/. Nothing else.\n</code></pre> <pre><code>No new files. Modify the existing implementation.\n</code></pre> <pre><code>Keep the public API unchanged. Internal refactor only.\n</code></pre> <p>Use these when the AI tends to \"helpfully\" modify adjacent code, add documentation you didn't ask for, or create new abstractions.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#course-correction","level":3,"title":"Course Correction","text":"<p>Steer the AI when it goes off-track: Don't wait for it to finish a wrong approach.</p> <pre><code>Stop! That's not what I meant. Let me clarify.\n</code></pre> <pre><code>Let's step back. Explain what you're about to do before changing anything.\n</code></pre> <pre><code>Undo that last change and try a different approach.\n</code></pre> <p>These work because they interrupt momentum.</p> <p>Without explicit course correction, the AI tends to commit harder to a wrong path rather than reconsidering.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#failure-modes","level":3,"title":"Failure Modes","text":"<p>When the AI misbehaves, match the symptom to the recovery prompt:</p> Symptom Recovery prompt Hand-waves (\"should work now\") \"Show evidence: file/line refs, command output, or test name.\" Creates unnecessary files \"No new files. Modify the existing implementation.\" Expands scope unprompted \"Stop after the smallest working change. Ask before expanding scope.\" Narrates instead of acting \"Skip the explanation. Make the change and show the diff.\" Repeats a failed approach \"That didn't work last time. Try a different approach.\" Claims completion without proof \"Run the test. Show me the output.\" <p>These are recovery handles, not rules to paste into <code>CLAUDE.md</code>.</p> <p>Use them in the moment when you see the behavior.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-did-we-learn","level":3,"title":"\"What Did We Learn?\"","text":"<p>This prompts reflection on the session and often triggers adding learnings to <code>LEARNINGS.md</code>.</p> <p>Use this after completing a task or debugging session.</p> <p>This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add This as a Learning/decision\"","text":"<p>This is an explicit persistence request.</p> <p>Use this when you have discovered something worth remembering.</p> <pre><code>Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save Context Before We End\"","text":"<p>This triggers context persistence before the session closes.</p> <p>Use it at the end of the session or before switching topics.</p> <p>Variants:</p> <ul> <li>\"Let's persist what we did\"</li> <li>\"Update the context files\"</li> <li><code>/ctx-wrap-up</code>:the recommended end-of-session ceremony   (see Session Ceremonies)</li> <li><code>/ctx-reflect</code>: mid-session reflection checkpoint</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the Codebase for X\"","text":"<p>This triggers thorough codebase search rather than guessing.</p> <p>Use this when you need to understand how something works.</p> <p>This works because \"Explore\" signals that investigation is needed,  not immediate action.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How Does X Work in This Codebase?\"","text":"<p>This prompts reading actual code rather than explaining general concepts.</p> <p>Use this to understand the existing implementation.</p> <pre><code>How does session saving work in this codebase?\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find All Places Where X\"","text":"<p>This triggers a comprehensive search across the codebase.</p> <p>Use this before refactoring or understanding the impact.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What Should We Document from This?\"","text":"<p>This prompts identifying learnings, decisions, and conventions worth persisting.</p> <p>Use this after complex discussions or implementations.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is This the Right Approach?\"","text":"<p>This invites the AI to challenge the current direction.</p> <p>Use this when you want a sanity check.</p> <p>This works because it allows AI to disagree.</p> <p>AIs often default to agreeing; this prompt signals you want an honest assessment.</p> <p>Stronger variant: \"Push back if my assumptions are wrong.\" This sets the tone for the entire session: The AI will flag questionable choices proactively instead of waiting to be asked.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-am-i-missing","level":3,"title":"\"What Am I Missing?\"","text":"<p>This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.</p> <p>Use this before finalizing a design or implementation.</p> <p>Forward-looking variant: \"What's the single smartest addition you could make to this at this point?\" Use this after you think you're done: It surfaces improvements you wouldn't have thought to ask for. The constraint to one thing prevents feature sprawl.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#cli-commands-as-prompts","level":2,"title":"CLI Commands as Prompts","text":"<p>Asking the AI to run <code>ctx</code> commands is itself a prompt. These load context or trigger specific behaviors:</p> Command What it does \"Run <code>ctx status</code>\" Shows context summary, file presence, staleness \"Run <code>ctx agent</code>\" Loads token-budgeted context packet \"Run <code>ctx drift</code>\" Detects dead paths, stale files, missing context","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ctx-skills","level":3,"title":"<code>ctx</code> Skills","text":"<p>The <code>SKILS.md</code> Standard</p> <p>Skills are formalized prompts stored as <code>SKILL.md</code> files.</p> <p>The <code>/slash-command</code> syntax below is Claude Code specific. </p> <p>Other agents can use the same skill files, but invocation may differ. </p> <p>Use <code>ctx</code> skills  by name:</p> Skill When to use <code>/ctx-status</code> Quick context summary <code>/ctx-agent</code> Load full context packet <code>/ctx-remember</code> Recall project context and structured readback <code>/ctx-wrap-up</code> End-of-session context persistence <code>/ctx-history</code> Browse session history for past discussions <code>/ctx-reflect</code> Structured reflection checkpoint <code>/ctx-next</code> Suggest what to work on next <code>/ctx-commit</code> Commit with context persistence <code>/ctx-drift</code> Detect and fix context drift <code>/ctx-implement</code> Execute a plan step-by-step with verification <code>/ctx-loop</code> Generate autonomous loop script <code>/ctx-pad</code> Manage encrypted scratchpad <code>/ctx-archive</code> Archive completed tasks <code>/check-links</code> Audit docs for dead links <p>Ceremony vs. Workflow Skills</p> <p>Most skills work conversationally: \"what should we work on?\" triggers <code>/ctx-next</code>, \"save that as a learning\" triggers <code>/ctx-learning-add</code>. Natural language is the recommended approach.</p> <p>Two skills are the exception: <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> are ceremony skills for session boundaries: Invoke them as explicit slash commands: conversational triggers risk partial execution. See Session Ceremonies.</p> <p>Skills combine a prompt, tool permissions, and domain knowledge into a single invocation.</p> <p>Skills beyond Claude Code</p> <p>The <code>/slash-command</code> syntax above is Claude Code native, but the underlying <code>SKILL.md</code> files are a standard Markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates.</p> <p>See Integrations for setup details.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"<p>Based on our <code>ctx</code> development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:</p> Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\" \"Implement everything\" No phasing, sprawl risk Break into tasks, implement one at a time \"You should know this\" Assumes context is loaded \"Before you start, read X\"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reliability-checklist","level":2,"title":"Reliability Checklist","text":"<p>Before sending a non-trivial prompt, check these four elements. This is the guide's DNA in one screenful.</p> <ol> <li>Goal in one sentence: What does \"done\" look like?</li> <li>Files to read: What existing code or context should the AI    review before acting?</li> <li>Verification command: How will you prove it worked?    (test name, CLI command, expected output)</li> <li>Scope boundary: What should the AI not touch?</li> </ol> <p>A prompt that covers all four is almost always good enough.</p> <p>A prompt missing <code>#3</code> is how you get \"should work now\" without evidence.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#safety-invariants","level":2,"title":"Safety Invariants","text":"<p>These Are Invariants: Not Suggestions</p> <p>A prompting guide earns its trust by being honest about risk.</p> <p>These four rules mentioned below don't change with model versions, agent frameworks, or project size.</p> <p>Build them into your workflow once and stop thinking about them.</p> <p>Tool-using agents can read files, run commands, and modify your codebase. That power makes them useful. It also creates a trust boundary you should be aware of.</p> <p>These invariants apply regardless of which agent or model you use.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#treat-the-repository-text-as-untrusted-input","level":3,"title":"Treat the Repository Text as \"Untrusted Input\"","text":"<p>Issue descriptions, PR comments, commit messages, documentation, and even code comments can contain text that looks like instructions. An agent that reads a GitHub issue and then runs a command found inside it is executing untrusted input.</p> <p>The rule: Before running any command the agent found in repo text (issues, docs, comments), restate the command explicitly and confirm it does what you expect. Don't let the agent copy-paste from untrusted sources into a shell.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ask-before-destructive-operations","level":3,"title":"Ask Before Destructive Operations","text":"<p><code>git push --force</code>, <code>rm -rf</code>, <code>DROP TABLE</code>, <code>docker system prune</code>: these are irreversible or hard to reverse. A good agent should pause before running them, but don't rely on that.</p> <p>The rule: For any operation that deletes data, overwrites history, or affects shared infrastructure, require explicit confirmation. If the agent runs something destructive without asking, that's a course-correction moment: \"Stop. Never run destructive commands without asking first.\"</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-the-blast-radius","level":3,"title":"Scope the Blast Radius","text":"<p>An agent told to \"fix the tests\" might modify test fixtures, change assertions, or delete tests that inconveniently fail. An agent told to \"deploy\" might push to production. Broad mandates create broad risk.</p> <p>The rule: Constrain scope before starting work. The Reliability Checklist's scope boundary (<code>#4</code>) is your primary safety lever. When in doubt, err on the side of a tighter boundary.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#secrets-never-belong-in-context","level":3,"title":"Secrets Never Belong in Context","text":"<p><code>LEARNINGS.md</code>, <code>DECISIONS.md</code>, and session transcripts are plain-text files that may be committed to version control.</p> <p>Don't persist API keys, passwords, tokens, or credentials in context files.</p> <p>The rule: If the agent encounters a secret during work, it should use it transiently (environment variable, an alias to the secret instead of the actual secret, etc.) and never write it to a context file. </p> <p>Any Secret Seen IS Exposed</p> <p>If you see a secret in a context file, remove it immediately and  rotate the credential.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-plan-implement","level":2,"title":"Explore → Plan → Implement","text":"<p>For non-trivial work, name the phase you want:</p> <pre><code>Explore src/auth and summarize the current flow.\nThen propose a plan. After I approve, implement with tests.\n</code></pre> <p>This prevents the AI from jumping straight to code. </p> <p>The three phases map to different modes of thinking:</p> <ul> <li>Explore: read, search, understand: no changes</li> <li>Plan: propose approach, trade-offs, scope: no changes</li> <li>Implement: write code, run tests, verify: changes</li> </ul> <p>Small fixes skip straight to implement. Complex or uncertain work benefits from all three.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#prompts-by-task-type","level":2,"title":"Prompts by Task Type","text":"<p>Different tasks need different prompt structures. The pattern: symptom + location + verification.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#bugfix","level":3,"title":"Bugfix","text":"<pre><code>Users report search returns empty results for queries with hyphens.\nReproduce in src/search/. Write a failing test for \"foo-bar\",\nfix the root cause, run: go test ./internal/search/...\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#refactor","level":3,"title":"Refactor","text":"<pre><code>Inspect src/auth/ and list duplication hotspots.\nPropose a refactor plan scoped to one module.\nAfter approval, remove duplication without changing behavior.\nAdd a test if coverage is missing. Run: make audit\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#research","level":3,"title":"Research","text":"<pre><code>Explore the request flow around src/api/.\nSummarize likely bottlenecks with evidence.\nPropose 2-3 hypotheses. Do not implement yet.\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#docs","level":3,"title":"Docs","text":"<pre><code>Update docs/cli-reference.md to reflect the new --format flag.\nConfirm the flag exists in the code and the example works.\n</code></pre> <p>Notice each prompt includes what to verify and how. Without that, you get a \"should work now\" instead of evidence.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"<p>Tasks in <code>TASKS.md</code> are indirect prompts to the AI. How you write them shapes how the AI approaches the work.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-motivation-not-just-the-goal","level":3,"title":"State the Motivation, Not Just the Goal","text":"<p>Tell the AI why you are building something, not just what.</p> <p>Bad: \"Build a calendar view.\"</p> <p>Good: \"Build a calendar view. The motivation is that all notes and tasks we build later should be viewable here.\"</p> <p>The second version lets the AI anticipate downstream requirements:</p> <p>It will design the calendar's data model to be compatible with future features: Without you having to spell out every integration point. Motivation turns a one-off task into a directional task.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"<p>Bad task (implementation-focused): <pre><code>- [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n</code></pre></p> <p>The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?</p> <p>Good task (deliverable-focused): <pre><code>- [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx journal source` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n</code></pre></p> <p>Now the AI knows the subtasks serve a specific user-facing deliverable.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"<p>For complex tasks, add explicit \"done when\" criteria:</p> <pre><code>- [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n</code></pre> <p>This prevents premature \"task complete\" when only the implementation details are done, but the feature doesn't actually work.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"<p>Completing all subtasks does not mean the parent task is complete.</p> <p>The parent task describes what the user gets.</p> <p>Subtasks describe how to build it.</p> <p>Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-do-these-approaches-work","level":2,"title":"Why Do These Approaches Work?","text":"<p>The patterns in this guide aren't invented here: They are practitioner translations of well-established, peer-reviewed research, most of which predate the current AI (hype) wave.</p> <p>The underlying ideas come from decades of work in machine learning, cognitive science, and numerical optimization. For a concrete case study showing how these principles play out when an agent decides whether to follow instructions (attention competition, optimization toward least-resistance paths, and observable compliance as a design goal) see The Dog Ate My Homework.</p> <p>Phased work (\"Explore → Plan → Implement\") applies chain-of-thought reasoning: Decomposing a problem into sequential steps before acting. Forcing intermediate reasoning steps measurably improves output quality in language models, just as it does in human problem-solving. Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (2022).</p> <p>Root-cause prompts (\"Why doesn't X work?\") use step-back abstraction: Retreating to a higher-level question before diving into specifics. This mirrors how experienced engineers debug: they ask \"what should happen?\" before asking \"what went wrong?\" Zheng et al., Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models (2023).</p> <p>Exploring alternatives (\"Propose 2-3 approaches\") leverages self-consistency: Generating multiple independent reasoning paths and selecting the most coherent result. The idea traces back to ensemble methods in ML: A committee of diverse solutions outperforms any single one. Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models (2022).</p> <p>Impact analysis (\"What would break if we...\") is a form of tree-structured exploration: Branching into multiple consequence paths before committing. This is the same principle behind game-tree search (minimax, MCTS) that has powered decision-making systems since the 1950s. Yao et al., Tree of Thoughts: Deliberate Problem Solving with Large Language Models (2023).</p> <p>Motivation prompting (\"Build X because Y\") works through goal conditioning: Providing the objective function alongside the task. In optimization terms, you are giving the gradient direction, not just the loss. The model can make locally coherent decisions that serve the global objective because it knows what \"better\" means.</p> <p>Scope constraints (\"Only change files in X\") apply constrained optimization: Bounding the search space to prevent divergence. This is the same principle behind regularization in ML: Without boundaries, powerful optimizers find solutions that technically satisfy the objective but are practically useless.</p> <p>CLI commands as prompts (\"Run <code>ctx status</code>\") interleave reasoning with acting: The model thinks, acts on external tools, observes results, then thinks again. Grounding reasoning in real tool output reduces hallucination because the model can't ignore evidence it just retrieved. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022).</p> <p>Task decomposition (\"Prompts by Task Type\") applies least-to-most prompting: Breaking a complex problem into subproblems and solving them sequentially, each building on the last. This is the research version of \"plan, then implement one slice.\" Zhou et al., Least-to-Most Prompting Enables Complex Reasoning in Large Language Models (2022).</p> <p>Explicit planning (\"Explore → Plan → Implement\") is directly supported by plan-and-solve prompting, which addresses missing-step failures in zero-shot reasoning by extracting a plan before executing. The phased structure prevents the model from jumping to code before understanding the problem. Wang et al., Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (2023).</p> <p>Session reflection (\"What did we learn?\", <code>/ctx-reflect</code>) is a form of verbal reinforcement learning: Improving future performance by persisting linguistic feedback as memory rather than updating weights. This is exactly what <code>LEARNINGS.md</code> and <code>DECISIONS.md</code> provide: a durable feedback signal across sessions. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning (2023).</p> <p>These aren't prompting \"hacks\" that you will find in the \"1000 AI Prompts for the Curious\" listicles: They are applications of foundational principles:</p> <ul> <li>Decomposition,</li> <li>Abstraction,</li> <li>Ensemble Reasoning,</li> <li>Search,</li> <li>and Constrained Optimization.</li> </ul> <p>They work because language models are, at their core, optimization systems navigating probabilistic landscapes.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>The Attention Budget:   Why your AI forgets what you just told it, and how token budgets shape   context strategy</li> <li>The Dog Ate My Homework:   A case study in making agents follow instructions: attention timing,   delegation decay, and observable compliance as a design goal</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#contributing","level":2,"title":"Contributing","text":"<p>Found a prompt that works well? Open an issue or PR with:</p> <ol> <li>The prompt text;</li> <li>What behavior it triggers;</li> <li>When to use it;</li> <li>Why it works (optional but helpful).</li> </ol> <p>Dive Deeper:</p> <ul> <li>Recipes: targeted how-to guides for specific tasks</li> <li>CLI Reference: all commands and flags</li> <li>Integrations: setup for Claude Code, Cursor, Aider</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/repeated-mistakes/","level":1,"title":"My AI Keeps Making the Same Mistakes","text":"","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-problem","level":2,"title":"The Problem","text":"<p>You found a bug last Tuesday. You debugged it, understood the root cause, and moved on. Today, a new session hits the exact same bug. The AI rediscovers it from scratch, burning twenty minutes on something you already solved.</p> <p>Worse: you spent an hour last week evaluating two database migration strategies, picked one, documented why in a comment somewhere, and now the AI is cheerfully suggesting the approach you rejected. Again.</p> <p>This is not a model problem. It is a memory problem. Without persistent context, every session starts with amnesia.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#how-ctx-stops-the-loop","level":2,"title":"How <code>ctx</code> Stops the Loop","text":"<p><code>ctx</code> gives your AI three files that directly prevent repeated mistakes, each targeting a different failure mode.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#decisionsmd-stop-relitigating-settled-choices","level":3,"title":"<code>DECISIONS.md</code>: Stop Relitigating Settled Choices","text":"<p>When you make an architectural decision, record it with rationale and rejected alternatives. The AI reads this at session start and treats it as settled.</p> <pre><code>## [2026-02-12] Use JWT for Authentication\n\n**Status**: Accepted\n\n**Context**: Need stateless auth for the API layer.\n\n**Decision**: JWT with short-lived access tokens and refresh rotation.\n\n**Rationale**: Stateless, scales horizontally, team has prior experience.\n\n**Alternatives Considered**:\n- Session-based auth: Rejected. Requires sticky sessions or shared store.\n- API keys only: Rejected. No user identity, no expiry rotation.\n</code></pre> <p>Next session, when the AI considers auth, it reads this entry and builds on the decision instead of re-debating it. If someone asks \"why not sessions?\", the rationale is already there.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#learningsmd-capture-gotchas-once","level":3,"title":"<code>LEARNINGS.md</code>: Capture Gotchas Once","text":"<p>Learnings are the bugs, quirks, and non-obvious behaviors that cost you time the first time around. Write them down so they cost you zero time the second time.</p> <pre><code>## Build\n\n### CGO Required for SQLite on Alpine\n\n**Discovered**: 2026-01-20\n\n**Context**: Docker build failed silently with \"no such table\" at runtime.\n\n**Lesson**: The go-sqlite3 driver requires CGO_ENABLED=1 and gcc\ninstalled in the build stage. Alpine needs apk add build-base.\n\n**Application**: Always use the golang:alpine image with build-base\nfor SQLite builds. Never set CGO_ENABLED=0.\n</code></pre> <p>Without this entry, the next session that touches the Dockerfile will hit the same wall. With it, the AI knows before it starts.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#constitutionmd-draw-hard-lines","level":3,"title":"<code>CONSTITUTION.md</code>: Draw Hard Lines","text":"<p>Some mistakes are not about forgetting - they are about boundaries the AI should never cross. CONSTITUTION.md sets inviolable rules.</p> <pre><code>* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never disable security linters without a documented exception\n* [ ] All database migrations must be reversible\n</code></pre> <p>The AI reads these as absolute constraints. It does not weigh them against convenience. It refuses tasks that would violate them.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-accumulation-effect","level":2,"title":"The Accumulation Effect","text":"<p>Each of these files grows over time. Session one captures two decisions. Session five adds a tricky learning about timezone handling. Session twelve records a convention about error message formatting.</p> <p>By session twenty, your AI has a knowledge base that no single person carries in their head. New team members - human or AI - inherit it instantly.</p> <p>The key insight: you are not just coding. You are building a knowledge layer that makes every future session faster.</p> <p><code>ctx</code> files version with your code in git. They survive branch switches, team changes, and model upgrades. The context outlives any single session.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#getting-started","level":2,"title":"Getting Started","text":"<p>Capture your first decision or learning right now:</p> <pre><code>ctx decision add \"Use PostgreSQL\" \\\n  --context \"Need a relational database for the project\" \\\n  --rationale \"Team expertise, JSONB support, mature ecosystem\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\nctx learning add \"Vitest mock hoisting\" \\\n  --context \"Tests failing intermittently\" \\\n  --lesson \"vi.mock() must be at file top level\" \\\n  --application \"Use vi.doMock() for dynamic mocks\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Knowledge Capture: the full workflow   for persisting decisions, learnings, and conventions</li> <li>Context Files Reference: structure and format for   every file in <code>.context/</code></li> <li>About <code>ctx</code>: the bigger picture - why persistent context   changes how you work with AI</li> </ul>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/steering/","level":1,"title":"Steering Files","text":"","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#steering-files","level":2,"title":"Steering Files","text":"<p><code>ctx</code> projects talk to AI assistants through several layers (context files, decisions, conventions, the agent context packet) but none of those can tell the assistant how to behave when a specific kind of prompt arrives. That's what steering files are for.</p> <p>A steering file is a small Markdown document with YAML frontmatter that says: \"when the user asks about X, prepend these rules to the prompt.\" <code>ctx</code> manages those files in <code>.context/steering/</code>, decides which ones match each prompt, and syncs them out to each AI tool's native config (Claude Code, Cursor, Kiro, Cline) so the rules actually land in the prompt pipeline.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#not-the-same-as-decisions-or-conventions","level":2,"title":"Not the Same as Decisions or Conventions","text":"<p>The three look similar on disk but serve different purposes:</p> Kind Purpose Decisions (<code>DECISIONS.md</code>) What was chosen and why Conventions (<code>CONVENTIONS.md</code>) How the codebase is written Steering (<code>.context/steering/*.md</code>) How the AI should behave on matching prompts <p>If you find yourself writing \"the AI should always do X when asked about Y,\" that belongs in steering, not decisions.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#your-first-steering-files","level":2,"title":"Your First Steering Files","text":"<p><code>ctx init</code> scaffolds four foundation steering files in <code>.context/steering/</code> so you start with something to edit rather than an empty directory:</p> File What to fill in <code>product.md</code> What the project is, who it's for, what's out of scope <code>tech.md</code> Languages, frameworks, runtime, hard constraints <code>structure.md</code> Directory layout, where new files go, naming rules <code>workflow.md</code> Branch strategy, commit conventions, pre-commit checks <p>Each file starts with an inline HTML comment explaining the three inclusion modes, priority semantics, and tool scoping. The comment is invisible in rendered Markdown but visible when you open the file to edit it; it's self-documenting scaffolding, not forever guidance. Delete the comment once you've customized the file.</p> <p>Default settings for foundation files:</p> <ul> <li><code>inclusion: always</code>: fires on every AI tool call</li> <li><code>priority: 10</code>: injected near the top of the prompt</li> <li><code>tools: []</code>: applies to every configured AI tool</li> </ul> <p>You should open each of these files and replace the placeholder content with your project's actual rules. Re-running <code>ctx init</code> is safe: existing files are left alone, so your edits survive. Use <code>ctx init --no-steering-init</code> to opt out of the scaffold entirely.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#inclusion-modes","level":2,"title":"Inclusion Modes","text":"<p>Each steering file declares an inclusion mode in its frontmatter:</p> Mode When the file is included <code>always</code> Every prompt, unconditionally <code>auto</code> When the prompt keywords match the file's description <code>manual</code> Only when the user explicitly names the file <p>Which mode to pick depends on the AI tool you use, because the two tool families consume steering very differently.</p> <p>Claude Code and Codex: prefer <code>inclusion: always</code> for rules that must fire reliably. These tools have two delivery channels:</p> <ol> <li>The plugin's <code>PreToolUse</code> hook runs <code>ctx agent</code>    with an empty prompt, so only <code>always</code> files match    and get injected automatically on every tool call.</li> <li>The <code>ctx_steering_get</code> MCP tool, registered    automatically when the <code>ctx</code> plugin is installed. Claude    can call this tool mid-task to fetch <code>auto</code> or    <code>manual</code> files matching a specific prompt. Verify    with <code>claude mcp list</code>; look for <code>ctx: ✓ Connected</code>.</li> </ol> <p>Use <code>always</code> for invariants and anything that must fire every session. Use <code>auto</code> for situational rules where \"Claude fetches this when the prompt is relevant\" is the right behavior; those still land, just on Claude's judgment. Use <code>manual</code> for reference libraries you'll name explicitly.</p> <p>Cursor, Cline, Kiro: <code>auto</code> is the natural default. These tools read <code>.cursor/rules/</code>, <code>.clinerules/</code>, or <code>.kiro/steering/</code> natively and resolve the description match on their own, so <code>auto</code> files fire when the prompt matches. <code>manual</code> files load on explicit invocation. <code>always</code> still works but consumes context budget on every turn.</p> <p>Mixed setups: if a rule must fire on Claude Code, pick <code>always</code>, even if it's overkill for your Cursor setup. The context budget cost is small; the alternative (silently not firing) is worse.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-families-of-ai-tools-two-delivery-paths","level":2,"title":"Two Families of AI Tools, Two Delivery Paths","text":"<p>Not every AI tool consumes steering the same way. <code>ctx</code> handles two tool families differently, and it's worth knowing which family your editor is in before you wonder why a rule isn't firing.</p> <p>Native-rules tools (Cursor, Cline, Kiro) have a built-in rules primitive. They read a specific directory (<code>.cursor/rules/</code>, <code>.clinerules/</code>, <code>.kiro/steering/</code>) and apply the rules they find there. <code>ctx</code> handles these via <code>ctx steering sync</code>, which exports your files into the tool-native format. Run <code>sync</code> whenever you edit a steering file.</p> <p>Hook + MCP tools (Claude Code, Codex) have no native rules primitive, so <code>ctx steering sync</code> is a no-op for them. Instead, <code>ctx</code> delivers steering through two non-sync channels:</p> <ol> <li>Automatic injection via a <code>PreToolUse</code> hook. The    <code>ctx setup claude-code</code> plugin wires a hook that runs    <code>ctx agent --budget 8000</code> before each tool call.    <code>ctx agent</code> loads your steering files, filters them by    the active prompt, and includes matching bodies in the    context packet it prints. Claude Code feeds that output    back into its context. Every tool call, automatically.</li> <li>On-demand via the <code>ctx_steering_get</code> MCP tool. The    <code>ctx</code> MCP server exposes a tool Claude can call mid-task    to fetch matching steering files for a specific prompt.    Claude decides when to call it; it's not automatic.</li> </ol> <p>Both channels activate when you run <code>ctx setup claude-code --write</code>. After that, steering just works for Claude Code.</p> <p>Practical takeaway:</p> <ul> <li>Using Cursor/Cline/Kiro only? Run <code>ctx steering sync</code>   after edits.</li> <li>Using Claude Code or Codex only? Never run <code>sync</code>; the   hook+MCP pipeline handles it.</li> <li>Using both? Run <code>sync</code> for the native-rules tools; the   hook+MCP pipeline covers Claude Code automatically.</li> </ul>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-shapes-of-automation-rules-and-scripts","level":2,"title":"Two Shapes of Automation: Rules and Scripts","text":"<p>Steering is one of two hook-like layers <code>ctx</code> provides for customizing AI behavior. They're complementary:</p> <ul> <li>Steering: persistent rules that get prepended to   prompts. Declarative, text-only, scored by match.</li> <li>Triggers: executable shell scripts   that fire at lifecycle events. Imperative, runs arbitrary   code, gated by exit codes.</li> </ul> <p>Pick steering when you want \"always remind the AI of X.\" Pick triggers when you want \"do Y when event Z happens.\" They can coexist; many projects use both.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Writing Steering Files:   a six-step walkthrough: scaffold, write the rule, preview   matches, list, get-rules-in-front-of-the-AI (two paths   depending on tool family), verify.</li> <li><code>ctx steering</code> reference: full   command, flag, and frontmatter reference; includes the   per-tool delivery-mechanism table and a dedicated section   on how Claude Code and Codex consume steering.</li> <li><code>ctx setup</code>: configure which AI   tools receive steering. For Cursor/Cline/Kiro this is   about sync targets; for Claude Code/Codex it installs   the plugin that wires the <code>PreToolUse</code> hook and MCP   server.</li> <li>Lifecycle Triggers: the imperative   companion to steering files.</li> </ul>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/triggers/","level":1,"title":"Lifecycle Triggers","text":"","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#lifecycle-triggers","level":2,"title":"Lifecycle Triggers","text":"<p>Some things can't be expressed as a rule you want the AI to follow. Sometimes you want something to happen: block a dangerous tool call, inject today's standup notes into the next session, log every file save to a journal. That's what triggers are for.</p> <p>A trigger is an executable shell script that <code>ctx</code> runs at a specific lifecycle event: the start of a session, before a tool call, when a file is saved, and so on. Triggers read a JSON payload from stdin, do whatever they need, and write a JSON response on stdout. They can allow, block, or inject context into the pipeline depending on the event type.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#trigger-types","level":2,"title":"Trigger Types","text":"Type Fires when Use case <code>session-start</code> A new AI session begins Inject rotating context, standup notes <code>session-end</code> An AI session ends Persist summaries, send notifications <code>pre-tool-use</code> Before a tool call executes Block, gate, or audit <code>post-tool-use</code> After a tool call completes Log, react, post-process <code>file-save</code> A file is saved Lint on save, update indices <code>context-add</code> A new entry is added to <code>.context/</code> Cross-link, notify, enrich","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-are-arbitrary-code-treat-them-like-pre-commit-hooks","level":2,"title":"Triggers Are Arbitrary Code: Treat Them like Pre-Commit Hooks","text":"<p>Only Enable Scripts You've Read and Understand</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.</p> <p><code>ctx trigger add</code> intentionally creates new scripts disabled (no executable bit). You must <code>ctx trigger enable <name></code> after reviewing the contents. That's not a suggestion; it's the security model.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#three-hook-like-layers-in-ctx","level":2,"title":"Three Hook-like Layers in <code>ctx</code>","text":"<p>Triggers are one of three distinct hook-like concepts in ctx. The names are similar but the owners and use cases are not:</p> Layer Owned by Where they live When to use <code>ctx trigger</code> You <code>.context/hooks/<type>/*.sh</code> Project-specific automation, any AI tool <code>ctx system</code> hooks <code>ctx</code> itself built-in, wired into tool configs Built-in nudges (you don't author these) Claude Code hooks Claude Code <code>.claude/settings.local.json</code> Claude-Code-only tool-specific integration <p>This page is about the first category. The other two run automatically and are invisible to you.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-vs-steering-same-problem-different-shape","level":2,"title":"Triggers vs Steering: Same Problem, Different Shape","text":"<p>Triggers are the imperative counterpart to steering files. Steering expresses persistent rules the AI reads before each prompt; triggers express side effects that run on lifecycle events. They're complementary, not competing:</p> <ul> <li>Want the AI to remember something? → Steering.</li> <li>Want a script to run when something happens? → Trigger.</li> </ul> <p>Most projects use both.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Authoring Lifecycle Triggers:   walkthrough with security guidance: scaffold, test,   enable, iterate.</li> <li><code>ctx trigger</code> reference: command   reference, trigger type table, input/output contract.</li> <li>Steering files: the declarative   counterpart to triggers.</li> </ul>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/vscode/","level":1,"title":"ctx for VS Code","text":"","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#the-problem","level":2,"title":"The Problem","text":"<p>Every Copilot Chat session in VS Code starts from zero. You re-explain what you were doing, the AI repeats yesterday's mistakes, and decisions you spent an hour reasoning through last week get rediscovered instead of remembered.</p> <p>Without <code>ctx</code>:</p> <pre><code>@workspace add the validation middleware we discussed\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n</code></pre> <p>With <code>ctx</code>:</p> <pre><code>@ctx Do you remember?\n\nLast session (2026-05-09): you decided on Zod schemas at the route level\n(DECISIONS.md #12). Pattern lives in CONVENTIONS.md. Open task: wire\nthe auth middleware into the new /admin routes (TASKS.md, in-progress).\nThe reference implementation is src/middleware/auth.ts.\n</code></pre> <p>That's the whole pitch: your AI remembers, right inside the IDE you already work in.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#setup","level":2,"title":"Setup","text":"<p>Install the extension and the <code>ctx</code> binary, then <code>ctx init</code> your project:</p> <ol> <li>Install the extension from the    VS Code Marketplace    (publisher: <code>activememory</code>, display name: <code>ctx</code>: Persistent Context    for AI). Or build from source (see    editors/vscode/README.md).</li> <li>Install the <code>ctx</code> CLI if you haven't already    (installation docs). If you skip    this step, the extension will auto-download the right binary for    your platform on first use (see Auto-Bootstrap    below).</li> <li>From your project root, run:</li> </ol> <pre><code>ctx init && eval \"$(ctx activate)\"\n</code></pre> <ol> <li>Open Copilot Chat in VS Code and type <code>@ctx /init</code> to verify    the extension can reach the CLI.</li> </ol>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose <code>.context/</code> Project-local context directory (created by <code>ctx init</code>) <code>.github/copilot-instructions.md</code> Repository instructions Copilot reads natively; regenerated automatically whenever <code>.context/</code> files change <p>The extension itself lives in VS Code's extension storage. No project files are added beyond <code>.context/</code> and the Copilot instructions.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#how-you-use-it","level":2,"title":"How You Use It","text":"<p>Type <code>@ctx</code> in the Copilot Chat view to invoke the chat participant. Then either:</p> <ul> <li>Use a slash command: <code>@ctx /status</code>, <code>@ctx /wrapup</code>, etc. There   are 45 commands; the most common ones live in the Slash Commands   table below.</li> <li>Use natural language: <code>@ctx what should I work on?</code> routes to   <code>/next</code>; <code>@ctx time to wrap up</code> routes to <code>/wrapup</code>. See   Natural Language.</li> </ul> <p>The extension shows context-aware follow-up suggestions after each command. For example, after <code>/init</code> you'll see buttons for \"Show status\" or \"Generate copilot integration.\"</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"<p>The extension registers several VS Code event handlers that mirror Claude Code's hook system. These run in the background; no user action needed.</p> Trigger What fires File save Task-completion check on non-<code>.context/</code> files Git commit Notification prompting to add a Decision, Learning, run <code>/verify</code>, or Skip <code>.context/</code> file change Refreshes pending reminders and regenerates <code>.github/copilot-instructions.md</code> Dependency file change When <code>go.mod</code>, <code>package.json</code>, etc. change, prompts to refresh the dependency map (<code>/map</code>) Every 5 minutes Updates the reminder status-bar item and writes a heartbeat timestamp Extension activate Fires <code>ctx system session-event --type start</code> Extension deactivate Fires <code>ctx system session-event --type end</code>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#status-bar","level":3,"title":"Status Bar","text":"<p>A <code>$(bell) ctx</code> indicator appears in the status bar when you have pending reminders. It refreshes every 5 minutes and hides itself when nothing is due.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#slash-commands","level":2,"title":"Slash Commands","text":"<p>The extension surfaces 45 commands across six categories. The most commonly used:</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#core-context","level":3,"title":"Core Context","text":"Command When to use <code>/init</code> Initialize a <code>.context/</code> directory with template files <code>/status</code> Token estimate, file count, what's recent <code>/agent</code> Print AI-ready context packet <code>/drift</code> Detect stale paths, missing files, dead references <code>/recall</code> Browse and search prior AI session history <code>/add</code> Add a task, decision, learning, or convention","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#session-lifecycle","level":3,"title":"Session Lifecycle","text":"Command When to use <code>/wrapup</code> End-of-session ceremony: status, drift, journal audit <code>/remember</code> Structured readback (trigger: \"Do you remember?\") from tasks, decisions, learnings, recent journal <code>/reflect</code> Surface items worth persisting as decisions or learnings <code>/pause</code> / <code>/resume</code> Save and restore session state for later","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#discovery-planning","level":3,"title":"Discovery & Planning","text":"Command When to use <code>/brainstorm</code> Browse and develop ideas from <code>ideas/</code> <code>/spec</code> List or scaffold feature specs from templates <code>/verify</code> Run verification (doctor + drift) <code>/map</code> Show dependency map (go.mod, package.json) <p>Full list (with maintenance, audit, metadata, and system commands) is in editors/vscode/README.md.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#natural-language","level":2,"title":"Natural Language","text":"<p>Plain English after <code>@ctx</code> is routed to the right command:</p> <ul> <li>\"What should I work on next?\" → <code>/next</code></li> <li>\"Time to wrap up\" → <code>/wrapup</code></li> <li>\"Show me the status\" → <code>/status</code></li> <li>\"Add a decision\" → <code>/add</code></li> <li>\"Check for drift\" → <code>/drift</code></li> </ul> <p>If the phrase doesn't match a known pattern, the extension surfaces a short menu of likely matches.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#auto-bootstrap","level":2,"title":"Auto-Bootstrap","text":"<p>If the <code>ctx</code> CLI isn't on PATH (or at a path configured via <code>ctx.executablePath</code>), the extension auto-downloads the right binary:</p> <ol> <li>Detects OS and architecture (darwin / linux / windows, amd64 / arm64).</li> <li>Fetches the latest release from    GitHub Releases.</li> <li>Downloads and verifies the matching binary.</li> <li>Caches it in VS Code's global storage directory.</li> </ol> <p>Subsequent sessions reuse the cached binary. To pin a specific version, set <code>ctx.executablePath</code> in your VS Code settings.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li>VS Code 1.93+</li> <li>GitHub Copilot Chat extension</li> <li><code>ctx</code> CLI on PATH, or let the extension auto-download it</li> </ul>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#configuration","level":2,"title":"Configuration","text":"Setting Default Description <code>ctx.executablePath</code> <code>ctx</code> Path to the <code>ctx</code> CLI binary. Set this if <code>ctx</code> isn't on PATH and you don't want auto-download.","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"<p>The extension updates through the VS Code Marketplace like any other extension; install new versions via the Extensions view. Updates to the <code>ctx</code> CLI are independent: bump it via your package manager, or let the auto-bootstrap fetch the latest release.</p> <p>Unlike the OpenCode integration, there is no <code>ctx setup</code> step for VS Code. The extension carries its own runtime; <code>ctx</code>'s role is only to provide the CLI it shells out to.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix <code>@ctx</code> participant doesn't appear in Copilot Chat Copilot Chat not installed or not signed in Install GitHub Copilot Chat and ensure you're signed in to a Copilot-eligible account <code>@ctx /status</code> says <code>ctx</code> not found CLI not on PATH and auto-download disabled Either add <code>ctx</code> to PATH (<code>brew install activememory/tap/ctx</code> or download from Releases), or unset <code>ctx.executablePath</code> to let the extension auto-download Status-bar reminder never updates Heartbeat suppressed or <code>.context/</code> doesn't exist Run <code>ctx init</code> from your project root; reload VS Code if the indicator still doesn't appear within 5 minutes Commands run but nothing is captured to <code>.context/</code> Workspace folder missing or <code>.context/</code> outside the open folder Make sure your project root (the one with <code>.context/</code>) is the workspace root, not a subdirectory of it","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#verify-it-works","level":2,"title":"Verify It Works","text":"<p>Open Copilot Chat and ask:</p> <pre><code>@ctx Do you remember?\n</code></pre> <p>You should see a structured readback citing specific tasks, decisions, and recent session topics. If you instead see \"I don't have memory\" or \"Let me check,\" something went wrong: confirm the CLI is reachable (<code>@ctx /system doctor</code>) and <code>.context/</code> has files in it.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#whats-next","level":2,"title":"What's Next","text":"<ul> <li>Your First Session: step-by-step walkthrough from   <code>ctx init</code> to verified recall.</li> <li>Common Workflows: day-to-day commands for   tracking context, checking health, and browsing history.</li> <li>Context Files: what lives in <code>.context/</code> and how   each file is used.</li> <li>Setup across AI Tools: wiring <code>ctx</code>   for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf   alongside VS Code.</li> </ul>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"operations/","level":1,"title":"Operations","text":"<p>Guides for installing, upgrading, integrating, and running <code>ctx</code>. Split into three groups by audience.</p>","path":["Operations"],"tags":[]},{"location":"operations/#day-to-day","level":2,"title":"Day-to-Day","text":"<p>Everyday operation guides for anyone running <code>ctx</code> in a project or adopting it in a team.</p>","path":["Operations"],"tags":[]},{"location":"operations/#integration","level":3,"title":"Integration","text":"<p>Adopt <code>ctx</code> in an existing project: initialize context files, migrate from other tools, and onboard team members.</p>","path":["Operations"],"tags":[]},{"location":"operations/#upgrade","level":3,"title":"Upgrade","text":"<p>Upgrade between versions with step-by-step migration notes and breaking-change guidance.</p>","path":["Operations"],"tags":[]},{"location":"operations/#ai-tools","level":3,"title":"AI Tools","text":"<p>Configure <code>ctx</code> with Claude Code, Cursor, Aider, Copilot, Windsurf, and other AI coding tools.</p>","path":["Operations"],"tags":[]},{"location":"operations/#autonomous-loops","level":3,"title":"Autonomous Loops","text":"<p>Run an unattended AI agent that works through tasks overnight, with <code>ctx</code> providing persistent memory between iterations.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub","level":2,"title":"Hub","text":"<p>Operator guides for running a <code>ctx</code> Hub, the gRPC server that fans out structured entries across projects. If you're a client connecting to a Hub someone else runs, see <code>ctx connect</code> and the Hub recipes instead.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub-operations","level":3,"title":"Hub Operations","text":"<p>Data directory layout, daemon management, systemd unit, backup and restore, log rotation, monitoring, and upgrades.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub-failure-modes","level":3,"title":"Hub Failure Modes","text":"<p>What can go wrong in network, storage, cluster, auth, and clock layers, and what you should do about each one. Includes the short-list table oncall engineers will want bookmarked.</p>","path":["Operations"],"tags":[]},{"location":"operations/#maintainers","level":2,"title":"Maintainers","text":"<p>Runbooks for people shipping <code>ctx</code> itself.</p>","path":["Operations"],"tags":[]},{"location":"operations/#cutting-a-release","level":3,"title":"Cutting a Release","text":"<p>Step-by-step runbook for maintainers: bump version, generate release notes, run the release script, and verify the result.</p>","path":["Operations"],"tags":[]},{"location":"operations/#runbooks","level":2,"title":"Runbooks","text":"<p>Step-by-step procedures you run with your agent. Each runbook includes a prompt to paste into a Claude Code session and guidance on triaging the results.</p> Runbook Purpose When to run Release checklist Full pre-release sequence Before every release Plugin release Plugin-specific release steps Plugin changes ship Breaking migration Guide users across breaking changes Releases with renames Hub deployment Set up a <code>ctx</code> Hub end-to-end First-time hub setup New contributor Onboarding: clone to first session New contributors Codebase audit AST audits, magic strings, dead code, doc alignment Before release, quarterly Docs semantic audit Narrative gaps, weak pages, structural problems Before release, after adding pages Sanitize permissions Clean <code>.claude/settings.local.json</code> of over-broad grants After heavy permission granting Architecture exploration Systematic architecture docs across repos New codebase onboarding, reviews <p>Recommended cadence:</p> <ul> <li>Before every release: release checklist (which includes   codebase audit + docs semantic audit)</li> <li>Monthly: sanitize permissions</li> <li>Quarterly: full sweep of all audit runbooks</li> </ul>","path":["Operations"],"tags":[]},{"location":"operations/autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"<p>Iterate until done.</p> <p>An autonomous loop is an iterative AI development workflow where an agent  works on tasks until completion, without constant human intervention. </p> <p><code>ctx</code> provides the memory that makes this possible:</p> <ul> <li><code>ctx</code> provides the memory: persistent context that survives across iterations</li> <li>The loop provides the automation: continuous execution until done</li> </ul> <p>Together, they enable fully autonomous AI development where the agent remembers everything across iterations.</p> <p>Origin</p> <p>This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique.</p> <p>We use generic terminology here so the concepts remain clear regardless of trends.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"<pre><code>graph TD\n    A[Start Loop] --> B[Load .context/loop.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B</code></pre> <ol> <li>Loop reads <code>.context/loop.md</code> and invokes AI</li> <li>AI loads context from <code>.context/</code></li> <li>AI picks one task and completes it</li> <li>AI updates context files (mark task done, add learnings)</li> <li>AI commits changes</li> <li>Loop checks for completion signals</li> <li>Repeat until converged or blocked</li> </ol>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#quick-start-shell-while-loop-recommended","level":2,"title":"Quick Start: Shell While Loop (Recommended)","text":"<p>The best way to run an autonomous loop is a plain shell script that invokes your AI tool in a fresh process on each iteration. This is \"pure ralph\":</p> <p>The only state that carries between iterations is what lives in <code>.context/</code> and the git history. No context window bleed, no accumulated tokens, no hidden state.</p> <p>Create a <code>loop.sh</code>:</p> <pre><code>#!/bin/bash\n# loop.sh: an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-.context/loop.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n</code></pre> <p>Make it executable and run:</p> <pre><code>chmod +x loop.sh\n./loop.sh\n</code></pre> <p>You can also generate this script with <code>ctx loop</code> (see CLI Reference).</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-do-we-use-a-shell-loop","level":3,"title":"Why Do We Use a Shell Loop?","text":"<p>Each iteration starts a fresh AI process with zero context window history. The agent knows only what it reads from <code>.context/</code> files: Exactly the information you chose to persist. </p> <p>This is the core loop principle: memory is explicit, not accidental.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#alternative-claude-codes-built-in-loop","level":2,"title":"Alternative: Claude Code's Built-in Loop","text":"<p>Claude Code has built-in loop support:</p> <pre><code># Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n</code></pre> <p>This is convenient for quick iterations, but be aware of important caveats:</p> <p>This Loop Is Not Pure</p> <p>Claude Code's <code>/loop</code> runs all iterations within the same session. This means:</p> <ul> <li>State leaks between iterations: The context window accumulates   output from every previous iteration. The agent \"remembers\" things   it saw earlier (even if they were never persisted to <code>.context/</code>).</li> <li>Token budget degrades: Each iteration adds to the context window,   leaving less room for actual work in later iterations.</li> <li>Not ergonomic for long runs: Users report that the built-in loop   is less predictable for 10+ iteration runs compared to a shell loop.</li> </ul> <p>For short explorations (2-5 iterations) or interactive use, <code>/loop</code> works fine. For overnight unattended runs or anything where iteration independence matters, use the shell while loop instead.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#the-contextloopmd-file","level":2,"title":"The <code>.context/loop.md</code> File","text":"<p>The prompt file instructs the AI on how to work autonomously. Here's a template:</p> <pre><code># Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n\n1. `.context/CONSTITUTION.md`: NEVER violate these rules\n2. `.context/TASKS.md`: Find work to do\n3. `.context/CONVENTIONS.md`: Follow these patterns\n4. `.context/DECISIONS.md`: Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n\n- Mark task complete in `TASKS.md`\n- Add any learnings to `LEARNINGS.md`\n- Add any decisions to `DECISIONS.md`\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED`: All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n- (no signal): More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"<p>The loop watches for these signals in AI output:</p> Signal Meaning When to Use <code>SYSTEM_CONVERGED</code> All tasks complete No pending tasks in TASKS.md <code>SYSTEM_BLOCKED</code> Cannot proceed Needs clarification, access, or decision <code>BOOTSTRAP_COMPLETE</code> Initial setup done Project scaffolding finished","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"<p>converged state</p> <pre><code>I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n</code></pre> <p>blocked state</p> <pre><code>I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-ctx-and-loops-work-well-together","level":2,"title":"Why <code>ctx</code> and  Loops Work Well Together","text":"Without <code>ctx</code> With <code>ctx</code> Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in <code>DECISIONS.md</code> Learnings are lost Learnings accumulate in <code>LEARNINGS.md</code> Tasks can be forgotten Tasks tracked in <code>TASKS.md</code>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"<p>During the loop, the AI should update context files:</p> <p>Mark task complete: <pre><code>ctx task complete \"implement user auth\"\n</code></pre></p> <p>Or emit an update command (parsed by <code>ctx watch</code>): <pre><code><context-update type=\"complete\">user auth</context-update>\n</code></pre></p> <p>Add learning: <pre><code>ctx learning add \"Rate limiting requires Redis connection\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p> <p>Or via update command: <pre><code><context-update type=\"learning\"\n  context=\"Implementing rate limiter\"\n  lesson=\"Rate limiting requires Redis connection\"\n  application=\"Ensure Redis is provisioned before enabling rate limits\"\n>Rate Limiting Redis Dependency</context-update>\n</code></pre></p> <p>Record decision: <pre><code>ctx decision add \"Use JWT tokens for API authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"<p>Run <code>ctx watch</code> alongside the loop to automatically process context updates:</p> <pre><code># Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n</code></pre> <p>The watch command processes context updates from the loop output in real time.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#project-setup","level":2,"title":"Project Setup","text":"<p>Initialize a project for autonomous loop operation, then activate it so the loop's <code>ctx</code> commands know which <code>.context/</code> to use:</p> <pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p>For unattended overnight runs, put the binding directly at the top of your loop script (<code>export CTX_DIR=/abs/path/.context</code>) so it survives without depending on a live shell. See Activating a Context Directory.</p> <p>The loop prompt template is deployed to <code>.context/loop.md</code> during initialization. It instructs the agent to:</p> <ul> <li>Work autonomously without asking clarifying questions;</li> <li>Follow one-task-per-iteration discipline;</li> <li>Use <code>SYSTEM_CONVERGED</code> / <code>SYSTEM_BLOCKED</code> signals;</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-project-structure","level":2,"title":"Example Project Structure","text":"<pre><code>my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample <code>TASKS.md</code> for Autonomous Loops","text":"<pre><code># Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n</code></pre> <p>The loop will work through these systematically, marking each complete.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"<p>Cause: AI not emitting completion signals</p> <p>Fix: Ensure .context/loop.md explicitly instructs signaling: <pre><code>End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"<p>Cause: AI not updating context files</p> <p>Fix: Add explicit instructions to .context/loop.md: <pre><code>After completing a task, you MUST:\n1. Run: ctx task complete \"<task>\"\n2. Add learnings: ctx learning add \"...\" --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"<p>Cause: Task not marked complete before next iteration</p> <p>Fix: Ensure commit happens after context update:</p> <pre><code>Order of operations:\n1. Complete coding work\n2. Update context files (*`ctx task complete`, `ctx add`*)\n3. Commit **ALL** changes including `.context/`\n4. Then signal status\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"<p>Cause: Constitution not read first</p> <p>Fix: Make constitution check explicit in <code>.context/loop.md</code>:</p> <pre><code>BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Building <code>ctx</code> Using <code>ctx</code>:    The dogfooding story: how autonomous loops built the tool that powers them</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#resources","level":2,"title":"Resources","text":"<ul> <li>Geoffrey Huntley's Ralph Wiggum Technique:    The original inspiration</li> <li>Context CLI: Command reference</li> <li>Integrations: Tool-specific setup</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/hub-failure-modes/","level":1,"title":"Hub Failure Modes","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#ctx-hub-failure-modes","level":1,"title":"<code>ctx</code> Hub: Failure Modes","text":"<p>What can go wrong, what the system does about it, and what you should do. Complementary to <code>ctx</code> Hub Operations.</p> <p>Design Posture</p> <p>The hub is best-effort knowledge sharing, not a durable ledger. Local <code>.context/</code> files are the source of truth for each project; the hub is a fan-out channel. This framing informs every failure-mode decision below.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#network","level":2,"title":"Network","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#client-loses-connection-mid-stream","level":3,"title":"Client Loses Connection Mid-Stream","text":"<p>What happens: <code>ctx connection listen</code> detects the EOF, waits with exponential backoff, and reconnects. On reconnect it passes its last-seen sequence; the hub replays everything newer.</p> <p>What you should do: nothing. If reconnects are looping, check firewall state on the hub and <code>ctx hub status</code> output.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-majority-side-reachable","level":3,"title":"Partition: Majority Side Reachable","text":"<p>What happens: clients routed to the majority side continue to publish and listen. The minority nodes step down to followers that cannot accept writes (Raft quorum lost).</p> <p>What you should do: let it heal. When the partition closes, followers catch up via sequence-based sync automatically.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-split-brain-no-quorum","level":3,"title":"Partition: Split Brain (No Quorum)","text":"<p>What happens: no node holds a majority, so no leader is elected. All nodes become read-only. <code>ctx connection publish</code> and <code>ctx add --share</code> fail with a \"no leader\" error; local writes still succeed.</p> <p>What you should do: fix the network. If the partition is permanent (e.g., a data center is gone), bootstrap a new cluster from the survivors with <code>ctx hub peer remove</code> for the dead nodes.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#hub-unreachable-during-ctx-add-share","level":3,"title":"Hub Unreachable during <code>ctx add --share</code>","text":"<p>What happens: the local write succeeds; the share step prints a warning and exits non-zero on the share leg only. <code>--share</code> is best-effort; it never blocks local context updates.</p> <p>What you should do: run <code>ctx connection publish</code> later to backfill, or rely on another <code>--share</code> for the same entry ID. The hub deduplicates by entry ID.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#storage","level":2,"title":"Storage","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#disk-full-on-the-leader","level":3,"title":"Disk Full on the Leader","text":"<p>What happens: <code>entries.jsonl</code> append fails. The hub rejects writes with an error and stays up for read traffic. Clients retry; followers keep their in-sync status using whatever the leader already wrote.</p> <p>What you should do: free disk or grow the volume, then nothing else; the hub resumes accepting writes on the next append attempt.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#corrupt-entriesjsonl","level":3,"title":"Corrupt <code>entries.jsonl</code>","text":"<p>What happens: if the last line is a partial JSON write from a crash, the hub truncates it on startup and logs a warning. If any earlier line is malformed, the hub refuses to start.</p> <p>What you should do: inspect with <code>jq -c . <data-dir>/entries.jsonl > /dev/null</code> to find the bad line. Move the bad region to a <code>.quarantine</code> file, then start. Nothing is ever silently dropped.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#metajson-entriesjsonl-sequence-mismatch","level":3,"title":"<code>meta.json</code> / <code>entries.jsonl</code> Sequence Mismatch","text":"<p>What happens: the hub refuses to start. This usually means someone copied one file without the other.</p> <p>What you should do: restore both files from the same backup, or accept the higher sequence by regenerating <code>meta.json</code> from <code>entries.jsonl</code> (manual for now; file a bug).</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#cluster","level":2,"title":"Cluster","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-clean-shutdown","level":3,"title":"Leader Crash, Clean Shutdown","text":"<p>What happens: <code>ctx hub stop</code> triggers <code>stepdown</code> first, so a new leader is elected before the old one exits. In-flight writes drain. Clients reconnect to the new leader transparently.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-hard-fail-kill-9-power-loss","level":3,"title":"Leader Crash, Hard Fail (Kill -9, Power Loss)","text":"<p>What happens: Raft detects the missing heartbeat and elects a new leader within a few seconds. Writes the old leader accepted but had not yet replicated can be lost. See the Raft-lite warning in the cluster recipe.</p> <p>What you should do: if you need stronger durability, run <code>ctx connection listen</code> on a dedicated \"collector\" project that persists entries locally as a write-ahead backup.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#split-brain-after-rejoin","level":3,"title":"Split-Brain After Rejoin","text":"<p>What happens: Raft reconciles: the minority side's uncommitted writes are discarded, and the majority's log is authoritative.</p> <p>What you should do: nothing automatic. If you know the minority had important writes, grep for them in <code><data-dir>/entries.jsonl.rejected</code> (written by the reconciliation pass) and replay them with <code>ctx connection publish</code>.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#auth-and-tokens","level":2,"title":"Auth and Tokens","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#lost-admin-token","level":3,"title":"Lost Admin Token","text":"<p>What happens: you cannot register new projects.</p> <p>What you should do: retrieve it from <code><data-dir>/admin.token</code>. If that file is also gone, stop the hub and regenerate. Note that all existing client tokens keep working; only new registrations need the admin token.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-admin-token","level":3,"title":"Compromised Admin Token","text":"<p>What happens: anyone with the token can register new projects and publish. They cannot read existing entries without a client token for a project that subscribes.</p> <p>What you should do: rotate the admin token (regenerate <code><data-dir>/admin.token</code> and restart), revoke suspicious client registrations via <code>clients.json</code>, and audit <code>entries.jsonl</code> for unexpected origins.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-client-token","level":3,"title":"Compromised Client Token","text":"<p>What happens: the attacker can publish as that project and read anything that project is subscribed to. Because <code>Origin</code> is self-asserted on publish, the attacker can also publish entries tagged with any other project's name, so attribution in <code>entries.jsonl</code> cannot be trusted after a token compromise.</p> <p>What you should do: remove the client's entry from <code>clients.json</code>, restart the hub, and re-register the legitimate project with a fresh token. Audit <code>entries.jsonl</code> for entries published after the compromise timestamp and quarantine any that look suspicious; remember that <code>Origin</code> on those entries proves nothing.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-hub-host","level":3,"title":"Compromised Hub Host","text":"<p>What happens: <code><data-dir>/clients.json</code> stores client tokens verbatim (not hashed). Anyone with read access to that file has every client token in hand and can impersonate any registered project until each one is rotated.</p> <p>What you should do: treat it as a total hub compromise. Stop the hub, wipe <code><data-dir></code> (keep a forensic copy first), regenerate the admin token, and have every client re-register. See Security model for the mitigations that reduce the blast radius while the hashing follow-up is pending.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#clock-skew","level":2,"title":"Clock Skew","text":"<p>Hub entries carry a timestamp assigned by the publishing client. The hub does not rewrite timestamps. Clients with significant clock skew will publish entries that look out of order in the shared feed.</p> <p>What you should do: run NTP on all client machines. If you see entries dated in the future or far past, the publisher's clock is the culprit.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#the-short-list","level":2,"title":"The Short List","text":"Symptom First thing to check Client can't reach hub Firewall, then <code>ctx hub status</code> \"No leader\" errors Cluster quorum; run <code>ctx hub status</code> on each peer Hub won't start after crash Last line of <code>entries.jsonl</code> Entries missing after restore Check <code>clients.json</code> sequence vs local <code>.sync-state.json</code> Duplicate entries in shared feed Client replayed after restore, safe (dedup by ID) Followers lagging Disk or network on the follower, not the leader","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub Operations</li> <li><code>ctx</code> Hub security model</li> <li>HA cluster recipe</li> </ul>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub/","level":1,"title":"Hub Operations","text":"","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#ctx-hub-operations","level":1,"title":"<code>ctx</code> Hub: Operations","text":"<p>Running the <code>ctx</code> <code>ctx</code> Hub in production. This page is for operators: people running a hub for themselves or a team, not people writing to a hub someone else is running.</p> <p>If you have not read it yet, start with the <code>ctx</code> Hub overview. It explains what the hub is, the two user stories it supports (personal cross-project brain vs small trusted team), and what it does not do. A client-side tour is in Getting Started.</p> <p>Operator Cheat Sheet</p> <ul> <li>The hub fans out four entry types only: <code>decision</code>,   <code>learning</code>, <code>convention</code>, <code>task</code>. Journals, scratchpad,   and other local state are out of scope.</li> <li>Identity is per-project, not per-user. Attribution is   limited to <code>Origin</code>, which is self-asserted by the   publishing client.</li> <li>The data model is an append-only JSONL log plus two   small JSON sidecar files. Nothing is rewritten in place.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#data-directory-layout","level":2,"title":"Data Directory Layout","text":"<p>The hub stores everything under a single data directory (default <code>~/.ctx/hub-data/</code>, override with <code>--data-dir</code>).</p> <pre><code><data-dir>/\n  admin.token        # Initial admin token (chmod 600)\n  clients.json       # Registered client tokens and project names\n  meta.json          # Sequence counter, version, cluster metadata\n  entries.jsonl      # Append-only log (single source of truth)\n  hub.pid            # Daemon PID file (daemon mode only)\n  raft/              # Raft state (cluster mode only)\n    log.db\n    stable.db\n    snapshots/\n</code></pre> <p>Invariants:</p> <ul> <li><code>entries.jsonl</code> is append-only. Every line is a valid JSON   object. Corrupt lines are fatal at startup: fix or truncate   before restart.</li> <li><code>meta.json</code> is authoritative for the next sequence number. On   restart, the hub reads the last valid line of <code>entries.jsonl</code> and   refuses to start if the sequences disagree.</li> <li><code>clients.json</code> holds hashed client tokens; losing it invalidates   all client registrations.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#starting-and-stopping","level":2,"title":"Starting and Stopping","text":"ForegroundDaemon <pre><code>ctx hub start                    # Ctrl-C to stop\nctx hub start --port 8080        # Custom port\nctx hub start --data-dir /srv/ctx-hub\n</code></pre> <pre><code>ctx hub start --daemon           # Fork to background\nctx hub stop                      # Graceful shutdown\n</code></pre> <p><code>--stop</code> sends SIGTERM to the PID in <code>hub.pid</code>, waits for in-flight RPCs to drain, then exits. If the daemon is wedged, remove <code>hub.pid</code> and send <code>SIGKILL</code> manually. <code>entries.jsonl</code> is crash-safe, so you will not lose accepted writes.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#systemd-unit","level":2,"title":"Systemd Unit","text":"<p>For production single-node deployments, run the hub as a systemd service instead of <code>--daemon</code>:</p> <pre><code># /etc/systemd/system/ctx-hub.service\n[Unit]\nDescription=ctx `ctx` Hub\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=ctx\nGroup=ctx\nExecStart=/usr/local/bin/ctx hub start --port 9900 \\\n    --data-dir /var/lib/ctx-hub\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/ctx-hub\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\n</code></pre> <pre><code>sudo systemctl enable --now ctx-hub\nsudo journalctl -u ctx-hub -f\n</code></pre>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#backup-and-restore","level":2,"title":"Backup and Restore","text":"<p>Because <code>entries.jsonl</code> is append-only, backups are trivial:</p> <pre><code># Hot backup, safe while the hub is running.\ncp <data-dir>/entries.jsonl backups/entries-$(date +%F).jsonl\ncp <data-dir>/meta.json      backups/meta-$(date +%F).json\ncp <data-dir>/clients.json   backups/clients-$(date +%F).json\n</code></pre> <p>For a consistent snapshot across all three files, stop the hub, copy, then start again, or use a filesystem-level snapshot (LVM, ZFS, Btrfs).</p> <p>Restore:</p> <pre><code>ctx hub stop                           # Stop the hub\ncp backups/entries-2026-04-10.jsonl <data-dir>/entries.jsonl\ncp backups/meta-2026-04-10.json      <data-dir>/meta.json\ncp backups/clients-2026-04-10.json   <data-dir>/clients.json\nctx hub start --daemon\n</code></pre> <p>Clients that pushed sequences above the restored watermark will re-publish on the next <code>listen</code> reconnect, because the hub now reports a lower sequence than what clients have on disk. This is safe; the store deduplicates by entry ID.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#log-rotation","level":2,"title":"Log Rotation","text":"<p><code>entries.jsonl</code> grows unbounded. For long-lived hubs, rotate it offline:</p> <pre><code>ctx hub stop\nmv <data-dir>/entries.jsonl <data-dir>/entries-$(date +%F).jsonl.old\n# Replay the last N days into a fresh entries.jsonl if you want a\n# trimmed active log, or leave the old file in place as history.\nctx hub start --daemon\n</code></pre> <p>Do not truncate <code>entries.jsonl</code> while the hub is running. The hub holds an open file handle; an in-place truncation confuses the sequence counter and loses writes.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#monitoring","level":2,"title":"Monitoring","text":"<p>Liveness probe:</p> <pre><code>ctx hub status --exit-code\n</code></pre> <p>Exit code <code>0</code> means the node is healthy (leader or in-sync follower); non-zero means degraded. Wire this into your monitoring of choice.</p> <p>For cluster deployments, watch for:</p> <ul> <li>Role flaps: the leader changing more than once per hour   suggests network instability or disk contention.</li> <li>Replication lag: <code>ctx hub status</code> shows per-peer sequence   offsets. Sustained lag > 100 sequences on a follower is worth   investigating.</li> <li><code>entries.jsonl</code> growth rate: sudden spikes often indicate a   misbehaving <code>ctx connection listen</code> reconnect loop.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#upgrading","level":2,"title":"Upgrading","text":"<p>The JSONL format is versioned in <code>meta.json</code>. <code>ctx</code> refuses to start against a newer store version than it understands; older store versions are upgraded in place at first start after an upgrade.</p> <p>Always back up <code><data-dir>/</code> before upgrading.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub failure modes</li> <li><code>ctx</code> Hub security model</li> <li><code>ctx serve</code> reference</li> <li><code>ctx hub</code> reference</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/integrations/","level":1,"title":"AI Tools","text":"","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#ai-tools","level":2,"title":"AI Tools","text":"<p>Context works with any AI tool that can read files. This guide covers setup  for popular AI coding assistants.</p> <p>Activate the Project Before Running <code>ctx</code> Commands</p> <p>After <code>ctx init</code>, run:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>This tells <code>ctx</code> which <code>.context/</code> directory the rest of the commands on this page should use. If you skip it, you'll see <code>Error: no context directory specified</code>. The <code>ctx setup <tool></code> commands work without activation, but most others (<code>ctx agent</code>, <code>ctx add</code>, <code>ctx status</code>, <code>ctx watch</code>) need it. See Activating a Context Directory.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"<p>Claude Code has the deepest integration via the <code>ctx</code> plugin.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup","level":3,"title":"Setup","text":"<p>First, install <code>ctx</code> and initialize your project, then activate it for the current shell:</p> <pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p>Then, install the <code>ctx</code> plugin in Claude Code:</p> <pre><code># From the ctx repository\nclaude /plugin install ./internal/assets/claude\n\n# Or from the marketplace\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n</code></pre> <p>Ensure the Plugin Is Enabled</p> <p>Installing a plugin registers it, but local installs may not auto-enable it globally. Verify <code>~/.claude/settings.json</code> contains:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Without this, the plugin's hooks and skills won't appear in other projects. Running <code>ctx init</code> auto-enables the plugin; use <code>--no-plugin-enable</code> to skip this step.</p> <p>This gives you:</p> Component Purpose <code>.context/</code> All context files <code>CLAUDE.md</code> Bootstrap instructions Plugin hooks Lifecycle automation Plugin skills Agent Skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works","level":3,"title":"How It Works","text":"<pre><code>graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]</code></pre> <ol> <li>Session start: Claude reads <code>CLAUDE.md</code>, which tells it to check <code>.context/</code></li> <li>First tool use: <code>PreToolUse</code> hook runs <code>ctx agent</code> and emits the context    packet (subsequent invocations within the cooldown window are silent)</li> <li>Next session: Claude reads context files and continues with context</li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#plugin-hooks","level":3,"title":"Plugin Hooks","text":"<p>The <code>ctx</code> plugin provides lifecycle hooks implemented as Go subcommands (<code>ctx system *</code>):</p> Hook Event Purpose <code>ctx system context-load-gate</code> PreToolUse (<code>.*</code>) Auto-inject context on first tool use <code>ctx system block-non-path-ctx</code> PreToolUse (<code>Bash</code>) Block <code>./ctx</code> or <code>go run</code>: force <code>$PATH</code> install <code>ctx system qa-reminder</code> PreToolUse (<code>Bash</code>) Remind agent to lint/test before committing <code>ctx system specs-nudge</code> PreToolUse (<code>EnterPlanMode</code>) Nudge agent to use project specs when planning <code>ctx system check-context-size</code> UserPromptSubmit Nudge context assessment as sessions grow <code>ctx system check-ceremonies</code> UserPromptSubmit Nudge /ctx-remember and /ctx-wrap-up adoption <code>ctx system check-persistence</code> UserPromptSubmit Remind to persist learnings/decisions <code>ctx system check-journal</code> UserPromptSubmit Remind to export/enrich journal entries <code>ctx system check-reminders</code> UserPromptSubmit Relay pending reminders at session start <code>ctx system check-version</code> UserPromptSubmit Warn when binary/plugin versions diverge <code>ctx system check-resources</code> UserPromptSubmit Warn when memory/swap/disk/load hit DANGER level <code>ctx system check-knowledge</code> UserPromptSubmit Nudge when knowledge files grow large <code>ctx system check-map-staleness</code> UserPromptSubmit Nudge when ARCHITECTURE.md is stale <code>ctx system heartbeat</code> UserPromptSubmit Session-alive signal with prompt count metadata <code>ctx system post-commit</code> PostToolUse (<code>Bash</code>) Nudge context capture and QA after git commits <p>A catch-all <code>PreToolUse</code> hook also runs <code>ctx agent</code> on every tool use (with cooldown) to autoload context.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#hook-configuration","level":3,"title":"Hook Configuration","text":"<p>The plugin's <code>hooks.json</code> wires everything automatically: no manual configuration in <code>settings.local.json</code> needed:</p> <pre><code>{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system context-load-gate\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system block-non-path-ctx\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system qa-reminder\" }\n        ]\n      },\n      {\n        \"matcher\": \"EnterPlanMode\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system specs-nudge\" }\n        ]\n      },\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx agent --budget 4000 2>/dev/null || true\" }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system post-commit\" }\n        ]\n      }\n    ],\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system check-context-size\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-ceremonies\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-persistence\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-journal\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-reminders\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-version\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-resources\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-knowledge\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-map-staleness\" },\n          { \"type\": \"command\", \"command\": \"ctx system heartbeat\" }\n        ]\n      }\n    ]\n  }\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#customizing-token-budget-and-cooldown","level":3,"title":"Customizing Token Budget and Cooldown","text":"<p>Edit the <code>PreToolUse</code> command to change the token budget or cooldown:</p> <pre><code>\"command\": \"ctx agent --budget 8000 --session $PPID >/dev/null || true\"\n\"command\": \"ctx agent --budget 4000 --cooldown 5m --session $PPID >/dev/null || true\"\n</code></pre> <p>The <code>--session $PPID</code> flag isolates the cooldown per session: <code>$PPID</code> resolves to the Claude Code process PID, so concurrent sessions don't interfere. The default cooldown is 10 minutes; use <code>--cooldown 0</code> to disable it.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"<ol> <li>Start a new Claude Code session;</li> <li>Ask: \"Do you remember?\"</li> <li>Claude should cite specific context:<ul> <li>Current tasks from <code>.context/TASKS.md</code>;</li> <li>Recent decisions or learnings;</li> <li>Recent session history from <code>ctx journal</code>.</li> </ul> </li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#local-plugin-development","level":3,"title":"Local Plugin Development","text":"<p>When developing <code>ctx</code> locally (adding skills, hooks, or changing plugin behavior), Claude Code caches the plugin by version. You must bump the version in both files and update the marketplace for changes to take effect:</p> <ol> <li>Bump version in both:</li> <li> <p><code>internal/assets/claude/.claude-plugin/plugin.json</code> (plugin manifest),    <code>.claude-plugin/marketplace.json</code> (marketplace listing*);</p> </li> <li> <p>Update the marketplace in Claude Code:</p> </li> <li>Open the Plugins UI (<code>/plugins</code> or Esc menu),</li> <li>Go to Marketplaces tab,</li> <li>Select the <code>activememory-ctx</code> Marketplace,</li> <li> <p>Choose Update marketplace;</p> </li> <li> <p>Start a new Claude Code session: skill changes aren't    reflected in existing sessions.</p> </li> </ol> <p>Both Version Files Must Match</p> <p>If you only bump <code>plugin.json</code> but not <code>marketplace.json</code> (or vice versa), Claude Code may not detect the update. Always bump both together.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check <code>ctx</code> is in PATH: <code>which ctx</code> Hook errors Verify plugin is installed: <code>claude /plugin list</code> New skill not visible Bump version in both <code>plugin.json</code> files, update marketplace","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"<p>If hooks aren't working, manually load context:</p> <pre><code># Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#agent-skills","level":3,"title":"Agent Skills","text":"<p>The <code>ctx</code> plugin ships Agent Skills following the agentskills.io specification.</p> <p>These are invoked in Claude Code with <code>/skill-name</code>.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-lifecycle-skills","level":4,"title":"Session Lifecycle Skills","text":"Skill Description <code>/ctx-remember</code> Recall project context at session start (ceremony) <code>/ctx-wrap-up</code> End-of-session context persistence (ceremony) <code>/ctx-status</code> Show context summary (tasks, decisions, learnings) <code>/ctx-agent</code> Get AI-optimized context packet <code>/ctx-next</code> Suggest 1-3 concrete next actions from context <code>/ctx-commit</code> Commit with integrated context capture <code>/ctx-reflect</code> Review session and suggest what to persist <code>/ctx-remind</code> Manage session-scoped reminders <code>/ctx-pause</code> Pause context hooks for this session <code>/ctx-resume</code> Resume context hooks after a pause","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-persistence-skills","level":4,"title":"Context Persistence Skills","text":"Skill Description <code>/ctx-task-add</code> Add a task to TASKS.md <code>/ctx-learning-add</code> Add a learning to LEARNINGS.md <code>/ctx-decision-add</code> Add a decision with context/rationale/consequence <code>/ctx-convention-add</code> Add a coding convention to CONVENTIONS.md <code>/ctx-archive</code> Archive completed tasks","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#scratchpad-skills","level":4,"title":"Scratchpad Skills","text":"Skill Description <code>/ctx-pad</code> Manage encrypted scratchpad entries","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-history-skills","level":4,"title":"Session History Skills","text":"Skill Description <code>/ctx-history</code> Browse AI session history <code>/ctx-journal-enrich</code> Enrich a journal entry with frontmatter/tags <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#blogging-skills","level":4,"title":"Blogging Skills","text":"<p>Blogging Is a Better Way of Creating Release Notes</p> <p>The blogging workflow can also double as generating release notes:</p> <p>AI reads your git commit history and creates a \"narrative\", which is essentially what a release note is for.</p> Skill Description <code>/ctx-blog</code> Generate blog post from recent activity <code>/ctx-blog-changelog</code> Generate blog post from commit range with theme","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#auditing-health-skills","level":4,"title":"Auditing & Health Skills","text":"Skill Description <code>/ctx-doctor</code> Troubleshoot <code>ctx</code> behavior with structural health checks <code>/ctx-drift</code> Detect and fix context drift (structural + semantic) <code>/ctx-consolidate</code> Merge redundant learnings or decisions into denser entries <code>/ctx-alignment-audit</code> Audit doc claims against playbook instructions <code>/ctx-prompt-audit</code> Analyze session logs for vague prompts <code>/check-links</code> Audit docs for dead internal and external links","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#planning-execution-skills","level":4,"title":"Planning & Execution Skills","text":"Skill Description <code>/ctx-loop</code> Generate a Ralph Loop iteration script <code>/ctx-implement</code> Execute a plan step-by-step with checks <code>/ctx-plan-import</code> Import Claude Code plan files into project specs <code>/ctx-worktree</code> Manage git worktrees for parallel agents <code>/ctx-architecture</code> Build and maintain architecture maps","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage-examples","level":4,"title":"Usage Examples","text":"<pre><code>/ctx-status\n/ctx-learning-add \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n</code></pre> <p>Skills support partial matching where applicable (e.g., session slugs).</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"<p>Cursor can use context files through its system prompt or by reading  files directly.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_1","level":3,"title":"Setup","text":"<pre><code># Generate Cursor configuration\nctx setup cursor\n\n# Initialize context\nctx init --minimal\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration","level":3,"title":"Configuration","text":"<p>Add to Cursor settings (<code>.cursor/settings.json</code>):</p> <pre><code>// split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage","level":3,"title":"Usage","text":"<ol> <li>Open your project in Cursor</li> <li>Context files are available in the file tree</li> <li>Reference them in prompts:     \"Check .context/DECISIONS.md for our approach to...\"</li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"<p>For more control, paste context directly:</p> <pre><code># Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n</code></pre> <p>Paste into Cursor's chat.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#aider","level":2,"title":"Aider","text":"<p>Aider works well with context files through its <code>--read</code> flag.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_2","level":3,"title":"Setup","text":"<pre><code># Generate Aider configuration\nctx setup aider\n\n# Initialize context\nctx init\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_1","level":3,"title":"Configuration","text":"<p>Create <code>.aider.conf.yml</code>:</p> <pre><code>read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_1","level":3,"title":"Usage","text":"<pre><code># Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"<p>Run <code>ctx watch</code> alongside Aider to capture context updates:</p> <pre><code># Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"<p>GitHub Copilot integrates with <code>ctx</code> at three levels: an automated instructions file, a VS Code Chat extension, and manual patterns.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_3","level":3,"title":"Setup","text":"<pre><code># Initialize context\nctx init\n\n# Generate .github/copilot-instructions.md\nctx setup copilot --write\n</code></pre> <p>The <code>--write</code> flag creates <code>.github/copilot-instructions.md</code>, which Copilot reads automatically at the start of every session. This file contains your project's constitution rules, current tasks, conventions, and architecture: giving Copilot persistent context without manual copy-paste.</p> <p>Re-run <code>ctx setup copilot --write</code> after updating your <code>.context/</code> files to regenerate the instructions.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#vs-code-chat-extension-ctx","level":3,"title":"VS Code Chat Extension (<code>@ctx</code>)","text":"<p>The <code>ctx</code> VS Code extension adds a <code>@ctx</code> chat participant to GitHub Copilot Chat, giving you direct access to 45 context commands from within the editor, plus automatic hooks on file save / git commit / <code>.context/</code> changes / dependency-file edits, and a reminder status-bar indicator.</p> <p>Full guide: <code>ctx</code> for VS Code</p> <p>The home-page guide covers daily workflows, the full command list, natural-language routing, auto-bootstrap of the <code>ctx</code> CLI, troubleshooting, and \"Verify It Works.\" This subsection is the install-and-pointers overview; the dedicated page is the authoritative reference.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#installation","level":4,"title":"Installation","text":"<p>The extension ships to the VS Code Marketplace under publisher <code>activememory</code> (display name: <code>ctx</code>: Persistent Context for AI). Install via the Extensions view or <code>code --install-extension</code>.</p> <p>To build from source instead (requires Node.js 20+):</p> <pre><code>cd editors/vscode\nnpm ci\nnpm run build\nnpx @vscode/vsce package\ncode --install-extension ctx-context-<version>.vsix\n</code></pre> <p>Reload VS Code. Type <code>@ctx</code> in Copilot Chat to verify.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created","level":4,"title":"What Gets Created","text":"File Purpose <code>.context/</code> Project-local context directory (created by <code>ctx init</code>, not by the extension) <code>.github/copilot-instructions.md</code> Repository instructions Copilot reads natively; regenerated automatically when <code>.context/</code> files change <p>The extension itself lives in VS Code's extension storage; no project files beyond <code>.context/</code> and the Copilot instructions are added.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_1","level":4,"title":"How It Works","text":"<ul> <li>Chat participant: <code>@ctx</code> is registered with VS Code's Chat API; 45 slash commands route to dedicated handlers that shell out to the <code>ctx</code> CLI.</li> <li>Automatic hooks: file save → task-completion check; git commit → decision/learning prompt; <code>.context/</code> change → regenerate Copilot instructions; dependency-file change → <code>/map</code> prompt.</li> <li>Status-bar reminder: a <code>$(bell) ctx</code> indicator surfaces pending session reminders, refreshing every 5 minutes.</li> <li>Natural language: plain English after <code>@ctx</code> is routed to the nearest matching command.</li> <li>Auto-bootstrap: if the <code>ctx</code> CLI isn't on PATH, the extension downloads the correct platform binary from GitHub Releases and caches it.</li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_2","level":4,"title":"Configuration","text":"Setting Default Description <code>ctx.executablePath</code> <code>ctx</code> Path to the <code>ctx</code> binary. Set this if <code>ctx</code> is not in your <code>PATH</code>.","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-persistence","level":3,"title":"Session Persistence","text":"<p><code>ctx init</code> creates a <code>.context/sessions/</code> directory for storing session data from non-Claude tools. The Markdown session parser scans this directory during <code>ctx journal</code>, enabling session history for Copilot and other tools.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-patterns","level":3,"title":"Manual Patterns","text":"<p>These patterns work without the extension, using Copilot's built-in file awareness:</p> <p>Pattern 1: Keep context files open</p> <p>Open <code>.context/CONVENTIONS.md</code> in a split pane. Copilot will reference it.</p> <p>Pattern 2: Reference in comments</p> <pre><code>// See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n</code></pre> <p>Pattern 3: Paste context into Copilot Chat</p> <pre><code>ctx agent --budget 2000\n</code></pre> <p>Paste output into Copilot Chat for context-aware responses.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#opencode","level":2,"title":"OpenCode","text":"<p>OpenCode is a terminal-first AI coding agent. <code>ctx</code> integrates via a thin lifecycle plugin, MCP server, and <code>AGENTS.md</code> instructions.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_4","level":3,"title":"Setup","text":"<pre><code># Generate OpenCode plugin, global MCP config, skills, and AGENTS.md\nctx setup opencode --write\n\n# Initialize context\nctx init\neval \"$(ctx activate)\"\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created_1","level":3,"title":"What Gets Created","text":"File Purpose <code>.opencode/plugins/ctx.ts</code> Lifecycle plugin (hooks to <code>ctx system</code>) <code>~/.config/opencode/opencode.json</code> Global MCP server registration (or <code>$OPENCODE_HOME/opencode.json</code>) <code>AGENTS.md</code> Agent instructions (read natively) <code>.opencode/skills/ctx-*/SKILL.md</code> <code>ctx</code> skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_2","level":3,"title":"How It Works","text":"<p>The plugin wires OpenCode lifecycle events to <code>ctx system</code>:</p> <ul> <li><code>session.created</code>: warms <code>ctx</code> state in the background (bootstrap + agent packet) so MCP queries are fast on first use.</li> <li><code>tool.execute.after</code> (shell, on <code>git commit</code>): runs <code>ctx system post-commit</code>.</li> <li><code>tool.execute.after</code> (edit/write): runs <code>ctx system check-task-completion</code>.</li> <li><code>session.idle</code>: runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI).</li> <li><code>shell.env</code>: injects <code>CTX_DIR</code> into the agent's shell so <code>ctx</code> commands resolve to the right project.</li> <li><code>experimental.session.compacting</code>: pushes <code>ctx system bootstrap</code> output into the compaction context so the agent keeps breadcrumbs back to <code>.context/</code>.</li> </ul> <p>The plugin is a single file with no runtime dependencies; no <code>bun install</code> needed. OpenCode loads it automatically on launch.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-updates","level":3,"title":"Context Updates","text":"<pre><code># Get AI-optimized context packet\nctx agent\n\n# Check context health\nctx status\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"<p>Windsurf supports custom instructions and file-based context.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_5","level":3,"title":"Setup","text":"<pre><code># Generate Windsurf configuration\nctx setup windsurf\n\n# Initialize context\nctx init\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_3","level":3,"title":"Configuration","text":"<p>Add to Windsurf settings:</p> <pre><code>// Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_2","level":3,"title":"Usage","text":"<p>Context files appear in the file tree. Reference them when chatting:</p> <ul> <li>\"What's in our task list?\" → AI reads <code>.context/TASKS.md</code></li> <li>\"What convention do we use for naming?\" → AI reads <code>.context/CONVENTIONS.md</code></li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#generic-integration","level":2,"title":"Generic Integration","text":"<p>For any AI tool that can read files, use these patterns:</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"<pre><code># Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"<pre><code>You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#automated-updates","level":3,"title":"Automated Updates","text":"<p>If your AI tool outputs to a log, use <code>ctx watch</code>:</p> <pre><code># Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n</code></pre> <p>The AI can emit updates like:</p> <pre><code><context-update type=\"complete\">implement caching</context-update>\n<context-update type=\"learning\"\n  context=\"Implementing caching layer\"\n  lesson=\"Important thing learned today\"\n  application=\"Apply this insight going forward\"\n>Caching Insight</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"<p>The <code>ctx watch</code> command parses update commands from AI output. Use this format:</p> <pre><code><context-update type=\"TYPE\" [attributes]>Content</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes <code>task</code> TASKS.md None <code>decision</code> DECISIONS.md <code>context</code>, <code>rationale</code>, <code>consequence</code> <code>learning</code> LEARNINGS.md <code>context</code>, <code>lesson</code>, <code>application</code> <code>convention</code> CONVENTIONS.md None <code>complete</code> TASKS.md None","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (Tasks, Conventions, Complete)","text":"<pre><code><context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (Learnings, Decisions)","text":"<p>Learnings and decisions support structured attributes for better documentation:</p> <p>Learning with full structure:</p> <pre><code><context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Parse JSON stdin with the host language (Go, Python, etc.): no jq needed\"\n>Hook Input Format</context-update>\n</code></pre> <p>Decision with full structure:</p> <pre><code><context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequence=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n</code></pre> <p>Learnings require: <code>context</code>, <code>lesson</code>, <code>application</code> attributes. Decisions require: <code>context</code>, <code>rationale</code>, <code>consequence</code> attributes. Updates missing required attributes are rejected with an error.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Skills That Fight the Platform:    Common pitfalls in skill design that work against the host tool</li> <li>The Anatomy of a Skill That Works:   What makes a skill reliable: the E/A/R framework and quality gates</li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/migration/","level":1,"title":"Integration","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#adopting-ctx-in-existing-projects","level":2,"title":"Adopting <code>ctx</code> in Existing Projects","text":"<p>Claude Code User?</p> <p>You probably want the plugin instead of this page.</p> <p>Install <code>ctx</code> from the marketplace: (<code>/plugin</code> → search \"<code>ctx</code>\" → Install) and you're done: hooks, skills, and updates are handled for you.</p> <p>See Getting Started for the full walkthrough.</p> <p>This guide covers adopting <code>ctx</code> in existing projects regardless of which tools your team uses.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#quick-paths","level":2,"title":"Quick Paths","text":"You have... Command What happens Nothing (greenfield) <code>ctx init</code> Creates <code>.context/</code>, <code>CLAUDE.md</code>, permissions Existing <code>CLAUDE.md</code> <code>ctx init --merge</code> Backs up your file, inserts <code>ctx</code> block after the H1 Existing <code>CLAUDE.md</code> + <code>ctx</code> markers <code>ctx init --reset</code> Replaces the <code>ctx</code> block, leaves your content intact <code>.cursorrules</code> / <code>.aider.conf.yml</code> <code>ctx init</code> <code>ctx</code> ignores those files: they coexist cleanly Team repo, first adopter <code>ctx init --merge && git add .context/ CLAUDE.md</code> Initialize and commit for the team","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-claudemd","level":2,"title":"Existing <code>CLAUDE.md</code>","text":"<p>This is the most common scenario:</p> <p>You have a <code>CLAUDE.md</code> with project-specific instructions and don't want to  lose them.</p> <p>You Own <code>CLAUDE.md</code></p> <p>After initialization, <code>CLAUDE.md</code> is yours: edit it freely.</p> <p>Add project instructions, remove sections you don't need, reorganize as  you see fit.</p> <p>The only part <code>ctx</code> manages is the block between the <code><!-- ctx:context --></code> and <code><!-- ctx:end --></code> markers; everything outside those markers is yours to change at any time.</p> <p>If you remove the markers, nothing breaks: <code>ctx</code> simply treats the file  as having no <code>ctx</code> content and will offer to merge again on the next  <code>ctx init</code>.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-ctx-init-does","level":3,"title":"What <code>ctx init</code> Does","text":"<p>When <code>ctx init</code> detects an existing <code>CLAUDE.md</code>, it checks for <code>ctx</code> markers (<code><!-- ctx:context --></code> ... <code><!-- ctx:end --></code>):</p> State Default behavior With <code>--merge</code> With <code>--force</code> No <code>CLAUDE.md</code> Creates from template Creates from template Creates from template Exists, no <code>ctx</code> markers Prompts to merge Auto-merges (no prompt) Auto-merges (no prompt) Exists, has <code>ctx</code> markers Skips (already set up) Skips Replaces the <code>ctx</code> block only","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-merge-flag","level":3,"title":"The <code>--merge</code> Flag","text":"<p><code>--merge</code> auto-merges without prompting. The merge process:</p> <ol> <li>Backs up your existing <code>CLAUDE.md</code> to <code>CLAUDE.md.<timestamp>.bak</code>;</li> <li>Finds the H1 heading (e.g., <code># My Project</code>) in your file;</li> <li>Inserts the <code>ctx</code> block immediately after it;</li> <li>Preserves everything else untouched.</li> </ol> <p>Your content before and after the <code>ctx</code> block remains exactly as it was.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#before-after-example","level":3,"title":"Before / After Example","text":"<p>Before: your existing <code>CLAUDE.md</code>:</p> <pre><code># My Project\n\n## Build Commands\n\n-`npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n</code></pre> <p>After <code>ctx init --merge</code>:</p> <pre><code># My Project\n\n<!-- ctx:context -->\n<!-- DO NOT REMOVE: This marker indicates ctx-managed content -->\n\n## IMPORTANT: You Have Persistent Memory\n\nThis project uses Context (`ctx`) for context persistence across sessions.\n...\n\n<!-- ctx:end -->\n\n## Build Commands\n\n- `npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n</code></pre> <p>Your build commands and code style sections are untouched. The <code>ctx</code> block sits between markers and can be updated independently.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-force-flag","level":3,"title":"The <code>--force</code> Flag","text":"<p>If your <code>CLAUDE.md</code> already has <code>ctx</code> markers (from a previous <code>ctx init</code>), the default behavior is to skip it. Use <code>--force</code> to replace the <code>ctx</code> block with the latest template: This is useful after upgrading <code>ctx</code>:</p> <pre><code>ctx init --reset\n</code></pre> <p>This only replaces content between <code><!-- ctx:context --></code> and <code><!-- ctx:end --></code>. Your own content outside the markers is preserved. A timestamped backup is created before any changes.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#undoing-a-merge","level":3,"title":"Undoing a Merge","text":"<p>Every merge creates a backup:</p> <pre><code>$ ls CLAUDE.md*.bak\nCLAUDE.md.1738000000.bak\n</code></pre> <p>To restore:</p> <pre><code>cp CLAUDE.md.1738000000.bak CLAUDE.md\n</code></pre> <p>Or if you are using <code>git</code>, simply:</p> <pre><code>git checkout CLAUDE.md\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-cursorrules-aider-copilot","level":2,"title":"Existing <code>.cursorrules</code> / Aider / Copilot","text":"<p><code>ctx</code> doesn't touch tool-specific config files. It creates its own files (<code>.context/</code>, <code>CLAUDE.md</code>) and coexists with whatever you already have.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-does-ctx-create","level":3,"title":"What Does <code>ctx</code> Create?","text":"<code>ctx</code> creates <code>ctx</code> does NOT touch <code>.context/</code> directory <code>.cursorrules</code> <code>CLAUDE.md</code> (or merges into) <code>.aider.conf.yml</code> <code>.claude/settings.local.json</code> (seeded by <code>ctx init</code>; the plugin manages hooks and skills) <code>.github/copilot-instructions.md</code> <code>.windsurfrules</code> Any other tool-specific config <p>Claude Code hooks and skills are provided by the <code>ctx</code> plugin, installed from the Claude Code marketplace (<code>/plugin</code> → search \"<code>ctx</code>\" → Install).</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#running-ctx-alongside-other-tools","level":3,"title":"Running <code>ctx</code> Alongside Other Tools","text":"<p>The <code>.context/</code> directory is the source of truth. Tool-specific configs point to it:</p> <ul> <li>Cursor: Reference <code>.context/</code> files in your system prompt   (see Cursor setup)</li> <li>Aider: Add <code>.context/</code> files to the <code>read:</code> list in <code>.aider.conf.yml</code>   (see Aider setup)</li> <li>Copilot: Keep <code>.context/</code> files open or reference them in comments   (see Copilot setup)</li> </ul> <p>You can generate a tool-specific configuration with:</p> <pre><code>ctx setup cursor    # Generate Cursor config snippet\nctx setup aider     # Generate .aider.conf.yml\nctx setup copilot   # Generate Copilot tips\nctx setup windsurf  # Generate Windsurf config\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#migrating-content-into-context","level":3,"title":"Migrating Content into <code>.context/</code>","text":"<p>If you have project knowledge scattered across <code>.cursorrules</code> or custom prompt files, consider migrating it:</p> <ol> <li>Rules / invariants → <code>.context/CONSTITUTION.md</code></li> <li>Code patterns → <code>.context/CONVENTIONS.md</code></li> <li>Architecture notes → <code>.context/ARCHITECTURE.md</code></li> <li>Known issues / tips → <code>.context/LEARNINGS.md</code></li> </ol> <p>You don't need to delete the originals: <code>ctx</code> and tool-specific files can coexist. But centralizing in <code>.context/</code> means every tool gets the same context.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#team-adoption","level":2,"title":"Team Adoption","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#context-is-designed-to-be-committed","level":3,"title":"<code>.context/</code> Is Designed to Be Committed","text":"<p>The context files (tasks, decisions, learnings, conventions, architecture) are meant to live in version control. However, some subdirectories are personal or sensitive and should not be committed.</p> <p><code>ctx init</code> automatically adds these <code>.gitignore</code> entries:</p> <pre><code># Journals contain full session transcripts: personal, potentially large\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Runtime state and logs (ephemeral, machine-specific):\n.context/state/\n.context/logs/\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n</code></pre> <p>With those in place, committing is straightforward:</p> <pre><code># One person initializes\nctx init --merge\n\n# Commit context files (journals and keys are already gitignored)\ngit add .context/ CLAUDE.md\ngit commit -m \"Add ctx context management\"\ngit push\n</code></pre> <p>Teammates pull and immediately have context. No per-developer setup needed.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-about-claude","level":3,"title":"What about <code>.claude/</code>?","text":"<p>The <code>.claude/</code> directory contains permissions that <code>ctx init</code> seeds. Hooks and skills are provided by the <code>ctx</code> plugin (not per-project files).</p> File Commit? Why <code>.claude/settings.local.json</code> No Machine-specific, accumulates session permissions <code>.claude/settings.golden.json</code> Yes Curated permission snapshot (via <code>ctx permission snapshot</code>)","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#merge-conflicts-in-context-files","level":3,"title":"Merge Conflicts in Context Files","text":"<p>Context files are plain Markdown. Resolve conflicts the same way you would for any other documentation file:</p> <pre><code># After a conflicting pull\ngit diff .context/TASKS.md    # See both sides\n# Edit to keep both sets of tasks, then:\ngit add .context/TASKS.md\ngit commit\n</code></pre> <p>Common conflict scenarios:</p> <ul> <li>TASKS.md: Two people added tasks: Keep both.</li> <li>DECISIONS.md: Same decision recorded differently: Unify the entry.</li> <li>LEARNINGS.md: Parallel discoveries: Keep both, remove duplicates.</li> </ul>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#gradual-adoption","level":3,"title":"Gradual Adoption","text":"<p>You don't need the whole team to switch at once:</p> <ol> <li>One person runs <code>ctx init --merge</code> and commits;</li> <li><code>CLAUDE.md</code> instructions work immediately for Claude Code users;</li> <li>Other tool users can adopt at their own pace using <code>ctx setup <tool></code>;</li> <li>Context files benefit everyone who reads them, even without tool integration.</li> </ol>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verifying-it-worked","level":2,"title":"Verifying It Worked","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#activate-the-project","level":3,"title":"Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory to use for the rest of the verification steps:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the status check below fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#check-status","level":3,"title":"Check Status","text":"<pre><code>ctx status\n</code></pre> <p>You should see your context files listed with token counts and no warnings.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#test-memory","level":3,"title":"Test Memory","text":"<p>Start a new AI session and ask: \"Do you remember?\"</p> <p>The AI should cite specific context:</p> <ul> <li>Current tasks from <code>.context/TASKS.md</code>;</li> <li>Recent decisions or learnings;</li> <li>Session history (if you've had prior sessions);</li> </ul> <p>If it responds with generic \"I don't have memory\", check that <code>ctx</code> is in your PATH (<code>which ctx</code>) and that hooks are configured (see Troubleshooting).</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verify-the-merge","level":3,"title":"Verify the Merge","text":"<p>If you used <code>--merge</code>, check that your original content is intact:</p> <pre><code># Your original content should still be there\ncat CLAUDE.md\n\n# The ctx block should be between markers\ngrep -c \"ctx:context\" CLAUDE.md  # Should print 1\ngrep -c \"ctx:end\" CLAUDE.md      # Should print 1\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Getting Started: Full setup walkthrough</li> <li>Context Files: What each <code>.context/</code> file does</li> <li>Integrations: Per-tool setup (Claude Code, Cursor, Aider, Copilot)</li> <li>CLI Reference: All <code>ctx</code> commands and flags</li> </ul>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/release/","level":1,"title":"Cutting a Release","text":"<p>Full Release Checklist</p> <p>This page covers the mechanics of cutting a release (bump, tag, push). For the complete pre-release ceremony (audits, tests, verification, and post-release steps), see the Release Checklist runbook.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#prerequisites","level":2,"title":"Prerequisites","text":"<p>Before you can cut a release you need:</p> <ul> <li>Push access to <code>origin</code> (GitHub)</li> <li>GPG signing configured (<code>make gpg-test</code>)</li> <li>Go installed (version in <code>go.mod</code>)</li> <li>Zensical installed (<code>make site-setup</code>)</li> <li>A clean working tree (<code>git status</code> shows nothing to commit)</li> </ul>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#step-by-step","level":2,"title":"Step-by-Step","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#1-update-the-version-file","level":3,"title":"1. Update the VERSION File","text":"<pre><code>echo \"0.9.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.9.0\"\n</code></pre> <p>The VERSION file uses bare semver (<code>0.9.0</code>), no <code>v</code> prefix. The release script adds the <code>v</code> prefix for git tags.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#2-generate-release-notes","level":3,"title":"2. Generate Release Notes","text":"<p>In Claude Code:</p> <pre><code>/_ctx-release-notes\n</code></pre> <p>This analyzes commits since the last tag and writes <code>dist/RELEASE_NOTES.md</code>. The release script refuses to proceed without this file.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#3-verify-docs-and-commit-any-remaining-changes","level":3,"title":"3. Verify Docs and Commit Any Remaining Changes","text":"<pre><code>/ctx-link-check    # audit docs for dead links\nmake audit          # full check: fmt, vet, lint, style, test\ngit status          # must be clean\n</code></pre>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#4-run-the-release","level":3,"title":"4. Run the Release","text":"<pre><code>make release\n</code></pre> <p>Or, if you are in a Claude Code session:</p> <pre><code>/_ctx-release\n</code></pre> <p>The release script does everything in order:</p> Step What happens 1 Reads <code>VERSION</code>, verifies release notes exist 2 Verifies working tree is clean 3 Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) 4 Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) 5 Adds new row to versions.md 6 Rebuilds the documentation site (<code>make site</code>) 7 Commits all version and docs updates 8 Runs <code>make test</code> and <code>make smoke</code> 9 Builds binaries for all 6 platforms via <code>hack/build-all.sh</code> 10 Creates a signed git tag (<code>v0.9.0</code>) 11 Pushes the tag to origin 12 Updates and pushes the <code>latest</code> tag","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#5-github-ci-takes-over","level":3,"title":"5. GitHub CI Takes Over","text":"<p>Pushing a <code>v*</code> tag triggers <code>.github/workflows/release.yml</code>:</p> <ol> <li>Checks out the tagged commit</li> <li>Runs the full test suite</li> <li>Builds binaries for all platforms</li> <li>Creates a GitHub Release with auto-generated notes</li> <li>Uploads binaries and SHA256 checksums</li> </ol>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#6-verify","level":3,"title":"6. Verify","text":"<ul> <li> GitHub Releases shows the new version</li> <li> All 6 binaries are attached (linux/darwin x amd64/arm64, windows x amd64)</li> <li> SHA256 files are attached</li> <li> Release notes look correct</li> </ul>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#what-gets-updated-automatically","level":2,"title":"What Gets Updated Automatically","text":"<p>The release script updates 8 files so you do not have to:</p> File What changes <code>internal/assets/claude/.claude-plugin/plugin.json</code> Plugin version <code>.claude-plugin/marketplace.json</code> Marketplace version (2 fields) <code>editors/vscode/package.json</code> VS Code extension version <code>editors/vscode/package-lock.json</code> VS Code lock version (2 fields) <code>docs/index.md</code> Download URLs <code>docs/home/getting-started.md</code> Download URLs <code>docs/operations/integrations.md</code> VSIX filename version <code>docs/reference/versions.md</code> New version row + latest pointer <p>The Go binary version is injected at build time via <code>-ldflags</code> from the VERSION file. No source file needs editing.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#build-targets-reference","level":2,"title":"Build Targets Reference","text":"Target What it does <code>make release</code> Full release (script + tag + push) <code>make build</code> Build binary for current platform <code>make build-all</code> Build all 6 platform binaries <code>make test</code> Unit tests <code>make smoke</code> Integration smoke tests <code>make audit</code> Full check (fmt + vet + lint + drift + docs + test) <code>make site</code> Rebuild documentation site","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#release-notes-not-found","level":3,"title":"\"Release Notes Not Found\"","text":"<pre><code>ERROR: dist/RELEASE_NOTES.md not found.\n</code></pre> <p>Run <code>/_ctx-release-notes</code> in Claude Code first, or write <code>dist/RELEASE_NOTES.md</code> manually.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#working-tree-is-not-clean","level":3,"title":"\"Working Tree Is Not Clean\"","text":"<pre><code>ERROR: Working tree is not clean.\n</code></pre> <p>Commit or stash all changes before running <code>make release</code>.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#tag-already-exists","level":3,"title":"\"Tag Already Exists\"","text":"<pre><code>ERROR: Tag v0.9.0 already exists.\n</code></pre> <p>You cannot release the same version twice. Either bump VERSION to a new version, or delete the old tag if the release was incomplete:</p> <pre><code>git tag -d v0.9.0\ngit push origin :refs/tags/v0.9.0\n</code></pre>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#ci-build-fails-after-tag-push","level":3,"title":"CI Build Fails After Tag Push","text":"<p>The tag is already published. Fix the issue, bump to a patch version (e.g. <code>0.9.1</code>), and release again. Do not force-push tags that others may have already fetched.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/upgrading/","level":1,"title":"Upgrade","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade","level":2,"title":"Upgrade","text":"<p>New versions of <code>ctx</code> may ship updated permissions, <code>CLAUDE.md</code> directives, or plugin hooks and skills.</p> <p>Claude Code User?</p> <p>The marketplace can update skills, hooks, and prompts independently: <code>/plugin</code> → select <code>ctx</code> → Update now (or enable auto-update).</p> <p>The <code>ctx</code> binary is separate: rebuild from source or download a new release when one is available, then run <code>ctx init --reset --merge</code>. Knowledge files are preserved automatically.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#tldr","level":2,"title":"TL:DR","text":"<pre><code># Plugin users (Claude Code)\n# /plugin → select ctx → Update now\n# Then update the binary and reinitialize:\nctx init --reset --merge\n\n# From-source / manual users\n# install new ctx binary, then:\nctx init --reset --merge\n# /plugin → select ctx → Update now   (if using Claude Code)\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-changes-between-versions","level":2,"title":"What Changes between Versions","text":"<p><code>ctx init</code> generates two categories of files:</p> Category Examples Changes between versions? Infrastructure <code>.claude/settings.local.json</code> (permissions), ctx-managed sections in <code>CLAUDE.md</code>, <code>ctx</code> plugin (hooks + skills) Yes Knowledge <code>.context/TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>, <code>ARCHITECTURE.md</code>, <code>GLOSSARY.md</code>, <code>CONSTITUTION.md</code>, <code>AGENT_PLAYBOOK.md</code> No: this is your data <p>Infrastructure is regenerated by <code>ctx init</code> and plugin updates. Knowledge files are yours and should never be overwritten.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade-steps","level":2,"title":"Upgrade Steps","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#1-install-the-new-version","level":3,"title":"1. Install the New Version","text":"<p>Build from source or download the binary:</p> <pre><code>cd /path/to/ctx-source\ngit pull\nmake build\nsudo make install\nctx --version   # verify\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#2-reinitialize","level":3,"title":"2. Reinitialize","text":"<pre><code>ctx init --reset --merge\n</code></pre> <ul> <li><code>--force</code> regenerates infrastructure files (permissions, ctx-managed   sections in <code>CLAUDE.md</code>).</li> <li><code>--merge</code> preserves your content outside <code>ctx</code> markers.</li> </ul> <p>Knowledge files (<code>.context/TASKS.md</code>, <code>DECISIONS.md</code>, etc.) are preserved automatically: <code>ctx init</code> only overwrites infrastructure, never your data.</p> <p>Encryption key: The encryption key lives at <code>~/.ctx/.ctx.key</code> (outside the project). Reinit does not affect it. If you have a legacy key at <code>.context/.ctx.key</code> or <code>~/.local/ctx/keys/</code>, copy it manually (see Syncing Scratchpad Notes).</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#3-update-the-ctx-plugin","level":3,"title":"3. Update the <code>ctx</code> Plugin","text":"<p>If you use Claude Code, update the plugin to get new hooks and skills:</p> <ol> <li>Open <code>/plugin</code> in Claude Code.</li> <li>Select <code>ctx</code>.</li> <li>Click Update now.</li> </ol> <p>Or enable auto-update so the plugin stays current without manual steps.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#4-review-custom-settings","level":3,"title":"4. Review Custom Settings","text":"<p>If you added custom permissions to <code>.claude/settings.local.json</code> beyond what <code>ctx init</code> provides, diff and merge:</p> <pre><code>diff .claude.bak/settings.local.json .claude/settings.local.json\n</code></pre> <p>Manually add back any custom entries that the new init dropped.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#5-verify","level":3,"title":"5. Verify","text":"<p>Activate the project first, otherwise <code>ctx status</code> and <code>ctx drift</code> will fail with <code>Error: no context directory specified</code>:</p> <pre><code>eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#6-clean-up","level":3,"title":"6. Clean Up","text":"<p>If you made manual backups, remove them once satisfied:</p> <pre><code>rm -rf .context.bak .claude.bak CLAUDE.md.bak\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-if-i-skip-the-upgrade","level":2,"title":"What If I Skip the Upgrade?","text":"<p>The old binary still works with your existing <code>.context/</code> files. But you may miss:</p> <ul> <li>New plugin hooks that enforce better practices or catch mistakes;</li> <li>Updated skill prompts that produce better results;</li> <li>New <code>.gitignore</code> entries for directories added in newer versions;</li> <li>Bug fixes in the CLI itself.</li> </ul> <p>The plugin and the binary can be updated independently. You can update the plugin (for new hooks/skills) even if you stay on an older binary, and vice versa.</p> <p>Context files are plain Markdown: They never break between versions.</p> <p>The surrounding infrastructure is what evolves.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/","level":1,"title":"Architecture Exploration","text":"","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#architecture-exploration","level":1,"title":"Architecture Exploration","text":"<p>Systematically build architecture documentation across one or more repositories using <code>ctx</code> skills. Each invocation does one unit of work; a simple loop drives the agent through all phases.</p> <p>When to use: When onboarding to a new codebase, performing architecture reviews, or building up <code>.context/</code> documentation across a workspace of repos.</p> <p>Prerequisites: <code>ctx</code> installed, repos cloned under a shared workspace directory (e.g., <code>~/WORKSPACE/</code>).</p> <p>Companion skills:</p> <ul> <li><code>/ctx-architecture</code>: structural baseline and principal analysis</li> <li><code>/ctx-architecture-enrich</code>: code intelligence enrichment via GitNexus</li> <li><code>/ctx-architecture-failure-analysis</code>: adversarial failure analysis</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#overview","level":2,"title":"Overview","text":"<p>The agent progresses through phases per repo, depth-first:</p> Phase Skill What it does <code>bootstrap</code> <code>ctx init</code> + <code>/ctx-architecture</code> Initialize context and build structural baseline <code>principal</code> <code>/ctx-architecture principal</code> Deep analysis: vision, bottlenecks, alternatives <code>enriched</code> <code>/ctx-architecture-enrich</code> Quantify with code intelligence (blast radius, flows) <code>frontier-N</code> <code>/ctx-architecture</code> (re-run) Explore unexplored areas found in convergence report <code>lens-*</code> <code>/ctx-architecture</code> with lens Focused exploration through conceptual lenses <p>Exploration stops when convergence >= 0.85, frontier runs plateau, or all lenses are exhausted.</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#setup","level":2,"title":"Setup","text":"<p>Create a tracking directory in your workspace root:</p> <pre><code>cd ~/WORKSPACE\nmkdir -p .arch-explorer\n</code></pre> <p>Create <code>.arch-explorer/manifest.json</code> listing your repos:</p> <pre><code>{\n  \"repos\": [\"ctx\", \"portal\", \"infra\"],\n  \"current_repo_index\": 0,\n  \"progress\": {}\n}\n</code></pre> <p>Create <code>.arch-explorer/run-log.md</code> (empty, the agent appends to it).</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#prompt","level":2,"title":"Prompt","text":"<p>Save this as <code>.arch-explorer/PROMPT.md</code> and invoke with your agent. The prompt is self-contained: the agent reads the manifest, picks the next unit of work, executes it, updates tracking, and stops.</p> <pre><code>You are an autonomous architecture exploration agent. Your job is to\nsystematically build and evolve architecture documentation across all\nrepositories in this workspace using `ctx` skills.\n\n## Execution Protocol\n\n### Step 1: Read State\n\nRead `.arch-explorer/manifest.json`. This tells you:\n- Which repos exist and their order\n- What has been done per repo (`progress` object)\n- Which repo to work on next (`current_repo_index`)\n\n### Step 2: Pick the Next Unit of Work\n\n**Strategy: depth-first, sequential.**\n\nFind the current repo (by `current_repo_index`). Determine its next\nphase from the progression below. If all phases are exhausted for this\nrepo (convergence score >= 0.85 or 3+ frontier runs with no new\nfindings), advance `current_repo_index` and pick the next repo.\n\n### Phase Progression (per repo)\n\nEach repo progresses through these phases in order:\n\n| Phase | Skill | Prerequisite |\n|-------|-------|-------------|\n| `bootstrap` | `ctx init` + `/ctx-architecture` | None |\n| `principal` | `/ctx-architecture principal` | bootstrap done |\n| `enriched` | `/ctx-architecture-enrich` | principal done, GitNexus indexed |\n| `frontier-N` | `/ctx-architecture` (re-run) | enriched done |\n\n**`bootstrap` is a single composite unit:** `ctx init` followed by\nstructural analysis. This is the ONLY phase that combines two actions.\nNo other phase may chain actions.\n\n**Frontier runs** are numbered: `frontier-1`, `frontier-2`, etc.\nEach frontier run reads CONVERGENCE-REPORT.md and picks unexplored\nareas. The skill handles this automatically.\n\nAfter the third frontier run OR when convergence >= 0.85, apply\n**conceptual lenses** (one per run):\n\n| Lens | Focus Areas |\n|------|-------------|\n| `security` | Auth flows, input validation, secrets, attack surfaces, trust boundaries |\n| `performance` | Hot paths, caching, concurrency, resource lifecycle, allocation patterns |\n| `stability` | Error handling, retries, graceful degradation, circuit breakers, timeouts |\n| `observability` | Logging, metrics, tracing, alerting, debugging affordances |\n| `data-integrity` | Storage, serialization, migrations, consistency, backup, recovery |\n\nFor lens runs, prepend the lens context as an explicit instruction to\nthe skill invocation:\n\n> \"Focus exploration on security: auth flows, input validation, secrets,\n> attack surfaces, trust boundaries.\"\n\nDo NOT wait for the skill to ask what to explore. Provide the lens\nfocus as input upfront.\n\n### Step 3: Do the Work\n\n1. `cd` into the sub-repo directory (`~/WORKSPACE/<repo-name>`, NOT\n   `~/WORKSPACE` itself).\n2. Verify `CTX_DIR` already points at THIS sub-repo's `.context/`:\n\n    ```bash\n    test \"$CTX_DIR\" = \"$PWD/.context\" || {\n      echo \"STOP: CTX_DIR=$CTX_DIR but this sub-repo needs $PWD/.context.\"\n      echo \"Re-launch the agent with CTX_DIR set to the sub-repo:\"\n      echo \"  cd $PWD && CTX_DIR=\\\"\\$PWD/.context\\\" claude --print 'Follow .arch-explorer/PROMPT.md' --allowedTools '*'\"\n      exit 1\n    }\n    ```\n\n    If it fails, STOP. The agent cannot change `CTX_DIR` for itself:\n    child shells and skill invocations inherit the parent Claude\n    process environment, which only the caller can control. Do not\n    proceed, do not run `ctx` commands, do not skip the check.\n3. If phase is `bootstrap`:\n    - Run `ctx init`, confirm `.context/` exists.\n    - Then run `/ctx-architecture` (structural baseline).\n4. If phase is `principal` or `frontier-*`:\n    - Run `/ctx-architecture` (add `principal` argument for principal phase).\n    - The skill will read existing artifacts and build on them.\n5. If phase is `enriched`:\n    - Verify GitNexus is connected: call `mcp__gitnexus__list_repos`.\n    - Success = non-empty list returned with no error.\n    - If GitNexus unavailable, log as `enriched-skipped` and advance\n      to `frontier-1`.\n    - Run `/ctx-architecture-enrich`.\n6. If phase is a lens run (`lens-security`, etc.):\n    - Run `/ctx-architecture` with lens focus prepended as instruction\n      (see lens table above for exact wording).\n\n### Step 4: Extract Results\n\nAfter the skill completes, gather:\n\n- **Convergence score**: from `map-tracking.json`, computed as:\n  average of all module `confidence` values (0.0-1.0). If\n  `map-tracking.json` is missing or has no confidence values,\n  record `null` and log a warning.\n- **Frontier count**: from CONVERGENCE-REPORT.md, count the number\n  of listed unexplored areas. If CONVERGENCE-REPORT.md is missing,\n  record `frontier_count: null` and log a warning. Treat missing\n  as \"exploration should continue\" (do not stall).\n- **Key findings**: 2-3 bullet points of what was discovered or\n  changed in this run (new modules mapped, danger zones found, etc.)\n- **New artifacts**: list any new files created in `.context/`\n\n### Step 5: Update Tracking\n\nUpdate `.arch-explorer/manifest.json`:\n\n```json\n{\n  \"progress\": {\n    \"ctx\": {\n      \"phases_completed\": [\"bootstrap\", \"principal\"],\n      \"current_phase\": \"enriched\",\n      \"lenses_explored\": [],\n      \"last_run\": \"2026-04-07T14:00:00Z\",\n      \"convergence_score\": 0.72,\n      \"frontier_count\": 3,\n      \"total_runs\": 2,\n      \"findings_summary\": \"14 modules mapped, 3 danger zones, 2 extension points\"\n    }\n  }\n}\n```\n\nAppend to `.arch-explorer/run-log.md`:\n\n```markdown\n## 2026-04-07T14:00:00Z / ctx / principal\n\n**Phase:** principal\n**Convergence:** 0.45 -> 0.72\n**Frontiers remaining:** 3\n**Key findings:**\n- Identified CLI dispatch as primary bottleneck (fan-out to 12 subsystems)\n- Security: context files readable by any process (no access control)\n- Strategic recommendation: extract context engine into library package\n\n**Artifacts updated:** ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md, map-tracking.json\n```\n\n### Step 6: Report and Stop\n\nPrint this exact format as the FINAL output of the invocation:\n\n```\n[arch-explorer] DONE\n  repo: ctx\n  phase: principal\n  convergence: 0.72\n  frontiers: 3\n  runs_on_repo: 3\n  next: ctx / enriched\n```\n\nThe `[arch-explorer] DONE` line is the terminal marker. After printing\nit, produce no further output. Execution is complete.\n\n## Rules\n\n1. **One unit per invocation.** The only composite unit is `bootstrap`\n   (init + structural). All other phases are exactly one skill run.\n2. **Additive only.** Never delete or overwrite existing artifacts.\n   The skills already handle incremental updates.\n3. **No duplicated work.** Read manifest before acting. If a phase is\n   already recorded as completed, skip it.\n4. **Log everything.** Every run gets a run-log entry, even failures\n   and skips.\n5. **Fail gracefully.** If a skill fails (missing GitNexus, broken repo,\n   etc.), log the failure with reason and advance to the next phase or\n   repo. Don't retry in the same invocation.\n6. **Respect `ctx` conventions.** Each repo gets its own `.context/`\n   directory. Never write architecture artifacts outside `.context/`.\n\n## Stopping Logic\n\nA repo is considered \"explored\" when ANY of these is true:\n- Convergence score >= 0.85 (from map-tracking.json)\n- 3+ frontier runs produced no new findings (frontier_count unchanged\n  across consecutive runs)\n- All 5 lenses have been applied\n- Convergence score is `null` after 3 attempts (artifacts aren't being\n  generated properly; log warning and move on)\n\nWhen a repo is explored, advance `current_repo_index` in the manifest.\n\n## When All Repos Are Done\n\nWhen every repo has reached its stopping condition, print:\n\n```\n[arch-explorer] ALL DONE\n  - ctx: 0.92 convergence, 8 runs, 5 lenses\n  - portal: 0.87 convergence, 6 runs, 3 lenses\n  ...\n```\n</code></pre>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#invocation","level":2,"title":"Invocation","text":"<p>The caller MUST set <code>CTX_DIR</code> to the sub-repo the agent will work on. The agent verifies this at Step 3.2 and stops if it does not match. The wrapper reads the manifest to pick the current sub-repo, then launches <code>claude</code> with <code>CTX_DIR</code> pinned to that sub-repo's <code>.context/</code>.</p> <p>Single run (safest for quota):</p> <pre><code>cd ~/WORKSPACE\nREPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\nCTX_DIR=\"$PWD/$REPO/.context\" \\\n  claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n</code></pre> <p>Batch of N runs:</p> <pre><code>cd ~/WORKSPACE\nfor i in $(seq 1 5); do\n  REPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\n  CTX_DIR=\"$PWD/$REPO/.context\" \\\n    claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n  echo \"--- Run $i complete (repo: $REPO) ---\"\ndone\n</code></pre> <p>Resume after interruption:</p> <p>Just run the wrapper again. The manifest tracks state; the agent picks up where it left off. <code>CTX_DIR</code> is recomputed from the manifest on each invocation, so the right sub-repo is always bound.</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#tips","level":2,"title":"Tips","text":"<ul> <li>Start small: list 1-2 repos in the manifest first. Add more   once you're confident in the output quality.</li> <li>GitNexus is optional: the enrichment phase is skipped   gracefully if GitNexus isn't connected. You still get structural   and principal analysis.</li> <li>Review between batches: check the run-log and generated   artifacts between batch runs. The agent is additive-only, but   early course correction saves wasted runs.</li> <li>Lens runs are the payoff: the first three phases build the   map; lens runs find the interesting things (security gaps,   performance cliffs, stability risks).</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#history","level":2,"title":"History","text":"<ul> <li>2026-04-07: Original prompt created as <code>hack/agents/architecture-explorer.md</code>.</li> <li>2026-04-16: Moved to docs as a runbook for discoverability.</li> <li>2026-04-20: Added <code>CTX_DIR</code> verification at Step 3.2 and per-invocation   <code>CTX_DIR</code> binding in the wrapper, so the agent writes artifacts to the   sub-repo's <code>.context/</code> instead of the inherited workspace one.</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/backup-strategy/","level":1,"title":"Backup Strategy","text":"<p><code>ctx backup</code> was removed. File-level backup is not <code>ctx</code>'s responsibility; your OS or a dedicated backup tool handles it better and without locking you into a specific mount strategy.</p> <p>This runbook explains what to back up, how <code>ctx hub</code> reduces the surface, and what options exist for the rest.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-to-back-up","level":2,"title":"What To Back Up","text":"<p>Per project:</p> <ul> <li><code>.context/</code>: all context files, journal, state, scratchpad.</li> <li><code>.claude/</code>: Claude Code settings, hooks, skills specific to the   project. Skip this entry when it lives in git; the repo is the   backup.</li> </ul> <p>Per user:</p> <ul> <li><code>~/.ctx/</code>: global config, the encryption key (<code>~/.ctx/.ctx.key</code>),   hub data directory (if running a local hub).</li> </ul>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#how-hub-reduces-backup-needs","level":2,"title":"How Hub Reduces Backup Needs","text":"<p><code>ctx hub</code> replicates the knowledge surface across machines:</p> <ul> <li><code>DECISIONS.md</code></li> <li><code>LEARNINGS.md</code></li> <li><code>CONVENTIONS.md</code></li> <li><code>CONSTITUTION.md</code></li> <li><code>ARCHITECTURE.md</code></li> <li>Task items promoted to hub</li> </ul> <p>If you run <code>ctx hub</code> (as a server or by subscribing to someone else's), the data that matters most survives losing any single machine.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-hub-does-not-replicate","level":2,"title":"What Hub Does Not Replicate","text":"<p>Hub is not a file-level backup. The following still live only on the machine that produced them:</p> <ul> <li>Journal entries (<code>.context/journal/*.md</code>)</li> <li>Runtime state (<code>.context/state/*</code>)</li> <li>Session event log (<code>.context/events.jsonl</code>)</li> <li>Scratchpad (<code>.context/.pad</code>)</li> <li>Encrypted notify/webhook config (<code>.context/.notify.enc</code>)</li> <li>The encryption key itself (<code>~/.ctx/.ctx.key</code>)</li> </ul> <p>If you need those to survive a disk failure, use a file-level backup.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#example-strategies","level":2,"title":"Example Strategies","text":"","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#1-cron-rsync-to-nas-or-external-drive","level":3,"title":"1. cron + rsync to NAS or External Drive","text":"<pre><code># Daily at 03:00, mirror ~/WORKSPACE and ~/.ctx to NAS\n0 3 * * * rsync -a --delete \\\n    --exclude='node_modules' \\\n    --exclude='dist' \\\n    --exclude='.context/state' \\\n    ~/WORKSPACE/ /mnt/nas/backup/workspace/\n0 3 * * * rsync -a --delete ~/.ctx/ /mnt/nas/backup/ctx-global/\n</code></pre> <p>Adjust excludes for the trash you don't want to back up. The <code>.context/state/</code> dir is ephemeral per-session; skip it.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#2-cron-cp-to-a-cloud-synced-directory","level":3,"title":"2. cron + cp to a Cloud-Synced Directory","text":"<p>iCloud Drive, Dropbox, or any directory watched by a sync client:</p> <pre><code>0 3 * * * cp -a ~/WORKSPACE/some-project/.context \\\n    ~/CloudDrive/ctx-backups/some-project/$(date +\\%Y-\\%m-\\%d)\n</code></pre> <p>Daily snapshots, cloud provider handles the replication.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#3-time-machine-macos","level":3,"title":"3. Time Machine (macOS)","text":"<p>If you already run Time Machine, ensure <code>~/WORKSPACE</code> and <code>~/.ctx</code> are not in its exclusion list. Time Machine handles versioning; you get point-in-time recovery for free.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#4-borg-or-restic-for-versioned-backups","level":3,"title":"4. Borg or restic for Versioned Backups","text":"<p>For deduplicated, versioned, encrypted backups:</p> <pre><code># Borg init (once)\nborg init --encryption=repokey /mnt/nas/borg-ctx\n\n# Daily backup\nborg create /mnt/nas/borg-ctx::'ctx-{now}' \\\n    ~/WORKSPACE ~/.ctx \\\n    --exclude '*/node_modules' \\\n    --exclude '*/.context/state'\n</code></pre> <p>Use <code>restic</code> if you prefer S3-compatible targets.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#when-you-still-need-file-level-backup-even-with-hub","level":2,"title":"When You Still Need File-Level Backup Even With Hub","text":"<ul> <li>Journal: session histories are local-only until exported.</li> <li>Scratchpad: private notes, encrypted locally.</li> <li>Encryption key: losing <code>~/.ctx/.ctx.key</code> means losing access   to every encrypted file in every project.</li> <li>Non-hub projects: projects that never called <code>ctx hub   register</code> have zero cross-machine persistence.</li> </ul> <p>For these, pick one strategy above and forget about it.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#why-ctx-no-longer-ships-a-backup-command","level":2,"title":"Why <code>ctx</code> No Longer Ships a Backup Command","text":"<p>Backup is inherently environment-specific: SMB, NFS, S3, rsync, Time Machine, Borg, restic. Every user has a different story. The previous <code>ctx backup</code> picked SMB via GVFS, which was Linux-only and narrow. Chasing mount strategies would never generalize.</p> <p>Hub is the right answer for the data <code>ctx</code> owns (knowledge). For everything else, your OS or a dedicated backup tool is the right layer.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/breaking-migration/","level":1,"title":"Breaking Migration","text":"","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#breaking-migration-guide","level":1,"title":"Breaking Migration Guide","text":"<p>Template for upgrading across breaking CLI renames or behavior changes. Use this as a starting point when writing migration notes for a specific release, or hand it to your agent as context for generating release-specific guidance.</p> <p>When to use: When a release includes breaking changes (command renames, removed flags, changed defaults) that require user action.</p> <p>Companion: Upgrade guide covers the general upgrade flow. This runbook covers the breaking-change specifics.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-1-identify-what-changed","level":2,"title":"Step 1: Identify What Changed","text":"<p>Ask your agent to diff the CLI surface between the old and new version:</p> <pre><code>Compare the CLI command surface between the previous release tag\nand HEAD. For each change, categorize as: renamed, removed,\nnew, or changed-behavior. Include old and new command signatures.\n</code></pre> <p>Or use the <code>/_ctx-command-audit</code> skill after the rename.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-2-regenerate-infrastructure","level":2,"title":"Step 2: Regenerate Infrastructure","text":"<pre><code># Install the new binary\nmake build && sudo make install\n\n# Regenerate CLAUDE.md and permissions\nctx init --reset --merge\n</code></pre> <p><code>--merge</code> preserves your knowledge files (TASKS.md, DECISIONS.md, etc.) while regenerating infrastructure (permissions, CLAUDE.md managed sections).</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-3-update-the-plugin","level":2,"title":"Step 3: Update the Plugin","text":"<pre><code>/plugin -> select ctx -> Update now\n</code></pre> <p>Or, if using a local clone:</p> <pre><code>make plugin-reload\n# restart Claude Code\n</code></pre>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-4-update-personal-scripts","level":2,"title":"Step 4: Update Personal Scripts","text":"<p>Search your scripts and aliases for old command names:</p> <pre><code># Example: find references to old command names\ngrep -r \"ctx old-command\" ~/scripts/ ~/.zshrc ~/.bashrc\n</code></pre> <p>Replace with the new names per the changelog.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-5-update-hook-configs","level":2,"title":"Step 5: Update Hook Configs","text":"<p>If you have custom hooks in <code>.claude/settings.local.json</code> that reference <code>ctx</code> commands, update them:</p> <pre><code>jq '.hooks' .claude/settings.local.json | grep \"ctx \"\n</code></pre>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-6-verify","level":2,"title":"Step 6: Verify","text":"<p>Activate the project first, otherwise <code>ctx status</code> and <code>ctx drift</code> will fail with <code>Error: no context directory specified</code>:</p> <pre><code>eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\nmake test           # if you're a contributor\n</code></pre> <p>See Activating a Context Directory.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#writing-release-specific-migration-notes","level":2,"title":"Writing Release-Specific Migration Notes","text":"<p>When preparing a release with breaking changes, create a section in the release notes using this template:</p> <pre><code>## Breaking Changes\n\n### `old-command` renamed to `new-command`\n\n**What changed**: `ctx old-command` is now `ctx new-command`.\nThe old name is removed (no deprecation alias).\n\n**Action required**:\n1. Run `ctx init --reset --merge` to update CLAUDE.md\n2. Update any scripts referencing `ctx old-command`\n3. Update hook configs if applicable\n\n**Why**: [brief rationale for the rename]\n</code></pre> <p>Repeat for each breaking change. Users should be able to follow the notes mechanically without needing to understand the codebase.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/codebase-audit/","level":1,"title":"Codebase Audit","text":"","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#codebase-audit","level":1,"title":"Codebase Audit","text":"<p>A structured audit of the codebase: dead code, magic strings, documentation drift, security surface, and roadmap opportunities.</p> <p>When to run: Before a release, after a long YOLO sprint, quarterly, or when planning the next phase of work.</p> <p>Time: ~15-30 minutes with a team of agents.</p>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#how-to-use-this-runbook","level":2,"title":"How to Use This Runbook","text":"<p>Start a Claude Code session with a clean git state (<code>git stash</code> or commit first). Paste or adapt the prompt below. The agent does the analysis; you triage the findings.</p>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#prompt","level":2,"title":"Prompt","text":"<pre><code>I want you to create an agent team to audit this codebase. Save each report as\na separate markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable: every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (session mining)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (godoc + inline)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check package-level comments match\npackage names. Output: drift items ranked by severity with exact file:line refs.\n\n### 3. Maintainability\nLook for: functions >80 lines that have logical split points; switch blocks\nwith >5 cases that could be table-driven or extracted; inline comments that\nsay \"step 1\", \"step 2\" or similar (sign the block wants to be a function);\nfiles with >400 lines; packages with flat structure that could benefit from\nsub-packages; functions that seem misplaced in their file. Do NOT flag\nthings that are fine as-is just because they could theoretically be different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app: focus on CLI-relevant attack surface, not web OWASP:\nfile path traversal (does user input flow into file paths unsanitized?),\ncommand injection (does user input flow into exec calls?), symlink following\n(does the tool follow symlinks when writing to .context/?), permission\nhandling (are file permissions set correctly?), sensitive data in outputs\n(do any commands leak secrets or session content?). Output: findings with\nseverity ratings and exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git log,\nrecent session discussions, and DECISIONS.md for story arcs worth writing\nabout. Suggest 3-5 blog post themes with: title, angle, target audience,\nkey commits/sessions to reference, and a 2-sentence pitch. Prioritize\nthemes that build a coherent narrative across posts.\n\n### 6. Roadmap & Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses:\nwhat are the highest-value improvements? Consider: user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with effort/impact estimates (not time estimates).\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and any user docs. Suggest improvements\nstructured as use-case pages: the problem, how ctx solves it, typical\nworkflow, gotchas. Identify gaps where a user would get stuck without\nreading source code. Output: list of documentation gaps and suggested\npage outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each: team composition (roles, agent types),\ntask distribution strategy, coordination approach, and which types of work\nit suits. Ground suggestions in actual project patterns, not generic advice.\n</code></pre>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#tips","level":2,"title":"Tips","text":"<ul> <li> <p>Clean state matters: the prompt says \"no code changes\" but accidents   happen. Start from a clean git state so you can <code>git checkout .</code> if needed.</p> </li> <li> <p>Adjust scope: drop analyses you don't need. Analyses 1-4 are the most   actionable. Analyses 5-8 are planning/creative and can be skipped if you   just want a technical audit.</p> </li> <li> <p>Reports feed TASKS.md: after the audit, read each report and create   tasks in the appropriate Phase section. The reports are input, not output.</p> </li> <li> <p>ideas/ is gitignored: reports saved there won't be committed. Move   specific findings to TASKS.md, DECISIONS.md, or LEARNINGS.md to persist them.</p> </li> </ul>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#history","level":2,"title":"History","text":"<ul> <li>2026-02-08: Original prompt created after a codebase audit sprint.</li> <li>2026-02-17: Improved with read-only agents, report structure template,   CLI-scoped security review, and maintainability thresholds.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/","level":1,"title":"Docs Semantic Audit","text":"","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#documentation-semantic-audit","level":1,"title":"Documentation Semantic Audit","text":"<p>Find structural problems that linters and link checkers cannot: weak pages that should be merged, heavy pages that should be split, missing cross-links, and narrative arcs that don't land.</p> <p>When to run: Before a release, after adding several new pages, when the site feels sprawling, or when you suspect narrative gaps.</p> <p>Time: ~20-40 minutes with an agent session.</p>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#why-this-is-a-runbook","level":2,"title":"Why This Is a Runbook","text":"<p>These judgments are inherently subjective and context-dependent. A page is \"weak\" relative to its neighbors; a narrative arc only matters if the docs intend to tell a story. Deterministic tools (broken-link checkers, word counters) can't do this. An LLM reading the full doc set can.</p>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#prompt","level":2,"title":"Prompt","text":"<p>Paste or adapt the following into a Claude Code session. The agent needs read access to <code>docs/</code> and the site nav structure.</p> <pre><code>Read every file under docs/ (including docs/blog/ and docs/recipes/).\nFor each file, note: title, word count, outbound links, inbound links\n(how many other pages link to it), and a one-line summary of its purpose.\n\nThen produce a report with these sections:\n\n## 1. Weak Dangling Pages\n\nPages that are thin, isolated, or redundant. Signs:\n- Under ~300 words with no unique content (just restates what another page says)\n- Zero or one inbound links (orphaned in the nav)\n- Content that would be stronger merged into an adjacent page\n- \"Try it in 5 minutes\" sections that assume installation already happened\n- Pages whose title doesn't work as a nav entry (too long, too vague)\n\nFor each: identify the page, explain why it's weak, and recommend\nmerge target or deletion.\n\n## 2. Overly Heavy Pages\n\nPages doing too much. Signs:\n- Over ~1500 words with multiple distinct topics\n- More than 4 H2 sections that could stand alone\n- Reader has to scroll past irrelevant content to find what they need\n- Mixed audience (beginner setup + advanced config on same page)\n\nFor each: identify the page, list the distinct topics, and suggest\nsplit points.\n\n## 3. Missing Cross-Links\n\nPlaces where a reader would naturally want to jump to related content\nbut no link exists. Look for:\n- Concepts mentioned but not linked (e.g., \"scratchpad\" without linking\n  to the scratchpad page)\n- Blog posts that describe features without linking to the reference docs\n- Recipes that reference workflows without linking to the relevant\n  getting-started section\n- Pages that end without a \"Next Up\" or \"See Also\" pointer\n\nFor each: source page, anchor text, suggested link target.\n\n## 4. Narrative Gaps\n\nThe docs should tell a coherent story: problem -> install -> first session\n-> daily workflow -> advanced patterns -> contributing. Look for:\n- Gaps in the progression (e.g., no bridge from \"first session\" to\n  \"daily habits\")\n- Blog posts that introduce concepts the reference docs don't cover\n- Recipes that assume knowledge no other page teaches\n- Features documented in CLI reference but missing from workflows/recipes\n\nFor each: describe the gap and suggest what page or section would fill it.\n\n## 5. Blog Cross-Linking Opportunities\n\nBlog posts are often written in isolation. Look for:\n- Posts that cover the same theme but don't reference each other\n- Posts that describe the evolution of a feature (natural \"part 1 / part 2\")\n- Posts that would benefit from a \"Related posts\" footer\n- Thematic clusters that could be linked from a recipe or reference page\n\nFor each: list the posts, the shared theme, and the suggested links.\n\n## Output Format\n\nFor every finding, include:\n- File path (docs/whatever.md)\n- Severity: high (actively confusing), medium (missed opportunity),\n  low (nice to have)\n- Concrete recommendation (merge into X, split at H2 Y, add link to Z)\n\nEnd with a prioritized action list: what to fix first.\n</code></pre>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#after-the-audit","level":2,"title":"After the Audit","text":"<ol> <li>Triage findings: not everything needs fixing. Focus on high severity.</li> <li>Merge weak pages first: fewer pages is almost always better.</li> <li>Add cross-links: cheapest improvement, highest reader impact.</li> <li>File split decisions in DECISIONS.md: page splits are architectural.</li> <li>Regenerate the site and spot-check nav after structural changes.</li> </ol>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#history","level":2,"title":"History","text":"<ul> <li>2026-02-17: Created after merging <code>docs/re-explaining.md</code> into   <code>docs/about.md</code>, which surfaced the pattern of weak standalone   pages that dilute rather than add.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/hub-deployment/","level":1,"title":"Hub Deployment","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#hub-deployment","level":1,"title":"Hub Deployment","text":"<p>Linear runbook for setting up a <code>ctx</code> Hub for yourself or a team. Consolidates pieces currently scattered across hub recipes and operations docs.</p> <p>When to use: First-time hub setup, or when onboarding a new team onto an existing hub.</p> <p>Prerequisites: <code>ctx</code> binary installed, network connectivity between hub and clients.</p> <p>Companion docs:</p> <ul> <li>Hub overview: what the hub   is and is not</li> <li>Hub operations: data directory, systemd,   backup, monitoring</li> <li>Hub failure modes: what can go wrong</li> </ul>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"Quick Start (foreground)Production (systemd) <pre><code>ctx hub start\n</code></pre> <p>See Hub Operations: Systemd Unit for the full unit file.</p> <pre><code>sudo systemctl enable --now ctx-hub\n</code></pre> <p>The hub creates <code>admin.token</code> on first start. Save this token; it is the only way to register clients.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-2-generate-the-admin-token","level":2,"title":"Step 2: Generate the Admin Token","text":"<p>On first start, the hub writes <code>admin.token</code> to the data directory (default <code>~/.ctx/hub-data/</code>):</p> <pre><code>cat ~/.ctx/hub-data/admin.token\n</code></pre> <p>This token has full admin privileges. Keep it secret.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-3-register-clients","level":2,"title":"Step 3: Register Clients","text":"<p>For each client (person or machine) that will connect:</p> <pre><code># On the hub machine\nctx hub register --name \"volkan-laptop\" --admin-token <admin-token>\n</code></pre> <p>This returns a client token. Distribute it securely to the client.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-4-connect-clients","level":2,"title":"Step 4: Connect Clients","text":"<p>On each client machine, register the project with the hub. The <code>ctx hub *</code> commands above run on the hub server itself and don't need a project. The <code>ctx connection *</code> commands below are different: they live inside a project (the encrypted hub config is stored at <code>.context/.connect.enc</code>), so you have to tell <code>ctx</code> which project first.</p> <pre><code># In the project directory on the client machine:\neval \"$(ctx activate)\"\nctx connection register <hub-address> --token <client-token>\n</code></pre> <p>Verify the connection:</p> <pre><code>ctx connection status\n</code></pre> <p>If the client doesn't have a project yet, run <code>ctx init</code> first, then <code>eval \"$(ctx activate)\"</code>. See Activating a Context Directory.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-5-verify-sync","level":2,"title":"Step 5: Verify Sync","text":"<p>Push a test entry from one client and verify it arrives. Make sure each client already ran <code>eval \"$(ctx activate)\"</code> from Step 4: otherwise <code>ctx add</code> and <code>ctx status</code> fail with <code>Error: no context directory specified</code>.</p> <pre><code># Client A (in its project directory, after activating):\nctx learning add \"Hub sync test\" --context \"Verifying hub setup\"\n\n# Client B (in its project directory, after activating):\nctx status   # should show the new learning\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-6-configure-backup","level":2,"title":"Step 6: Configure Backup","text":"<p>Set up regular backups of the hub data directory. See Hub Operations: Backup and Restore.</p> <p>Minimum:</p> <pre><code># Add to cron\n0 */6 * * * cp ~/.ctx/hub-data/entries.jsonl ~/backups/entries-$(date +\\%F).jsonl\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-7-configure-tls-when-available","level":2,"title":"Step 7: Configure TLS (When Available)","text":"<p>Coming Soon</p> <p>TLS support is planned (H-01/H-02). Until then, run the hub on a trusted network or behind a reverse proxy with TLS termination.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#team-onboarding-checklist","level":2,"title":"Team Onboarding Checklist","text":"<p>When adding a new team member to an existing hub:</p> <ul> <li> Generate a client token (<code>ctx hub register --name \"<name>\"</code>)</li> <li> Share the token and hub address securely</li> <li> Have them run <code>ctx connect <hub-address> --token <token></code></li> <li> Verify with <code>ctx connection status</code></li> <li> Point them to the Hub Getting Started recipe</li> </ul>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#connection-refused","level":3,"title":"\"Connection Refused\"","text":"<p>The hub isn't running or the port is wrong. Check:</p> <pre><code>ctx hub status          # on the hub machine\nss -tlnp | grep 9900   # default port\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#authentication-failed","level":3,"title":"\"Authentication Failed\"","text":"<p>The client token is wrong or was never registered. Re-register:</p> <pre><code>ctx hub register --name \"<name>\" --admin-token <admin-token>\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#entries-not-syncing","level":3,"title":"Entries Not Syncing","text":"<p>Check that the client is listening:</p> <pre><code>ctx connection status\n</code></pre> <p>If connected but not syncing, check the hub logs for sequence mismatch errors. See Hub Failure Modes for details.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/new-contributor/","level":1,"title":"New Contributor","text":"","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#new-contributor-onboarding","level":1,"title":"New Contributor Onboarding","text":"<p>Step-by-step onboarding sequence for new contributors. Consolidates setup instructions currently scattered across the README, contributing guide, and setup docs.</p> <p>When to use: First-time contributor setup, or when verifying your development environment after a major upgrade.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-1-clone-the-repository","level":2,"title":"Step 1: Clone the Repository","text":"<pre><code>git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n</code></pre> <p>Or fork first on GitHub, then clone your fork.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-2-initialize-context","level":2,"title":"Step 2: Initialize Context","text":"<pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p><code>ctx init</code> creates the <code>.context/</code> directory with knowledge files and the <code>.claude/</code> directory with agent configuration. <code>eval \"$(ctx activate)\"</code> tells <code>ctx</code> to use that directory for the rest of this runbook. If you skip the second line, the later steps fail with <code>Error: no context directory specified</code>.</p> <p>If <code>ctx</code> is not yet installed, proceed to Step 3 first, then come back.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-3-build-and-install","level":2,"title":"Step 3: Build and Install","text":"<pre><code>make build\nsudo make install\n</code></pre> <p>Verify:</p> <pre><code>ctx --version\n</code></pre>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-4-install-the-plugin-claude-code-users","level":2,"title":"Step 4: Install the Plugin (Claude Code Users)","text":"<p>If you use Claude Code, install the plugin from your local clone so skills and hooks reflect your working tree:</p> <ol> <li>Launch <code>claude</code></li> <li>Type <code>/plugin</code> and press Enter</li> <li>Select Marketplaces -> Add Marketplace</li> <li>Enter the absolute path to your clone (e.g., <code>~/WORKSPACE/ctx</code>)</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>Verify:</p> <pre><code>claude /plugin list   # should show ctx\n</code></pre> <p>See Contributing: Install the Plugin for details on cache clearing.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-5-switch-to-dev-profile","level":2,"title":"Step 5: Switch to Dev Profile","text":"<pre><code>ctx config switch dev\n</code></pre> <p>This enables verbose logging and notify events (useful during development).</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-6-verify-hooks","level":2,"title":"Step 6: Verify Hooks","text":"<p>Start a Claude Code session and check that hooks fire:</p> <pre><code>claude\n</code></pre> <p>You should see <code>ctx</code> session hooks (ceremonies reminder, context loading) on session start. If not, check that the plugin is installed correctly (Step 4).</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-7-run-your-first-session","level":2,"title":"Step 7: Run Your First Session","text":"<p>In Claude Code:</p> <pre><code>/ctx-status\n</code></pre> <p>This should show context file health, active tasks, and recent decisions. If it works, your setup is complete.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-8-verify-context-persistence","level":2,"title":"Step 8: Verify Context Persistence","text":"<p>End the session and start a new one:</p> <pre><code>/ctx-remember\n</code></pre> <p>The agent should recall what happened in the previous session. This confirms that context persistence is working end-to-end.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-9-run-tests","level":2,"title":"Step 9: Run Tests","text":"<pre><code>make test     # unit tests\nmake audit    # full check: fmt + vet + lint + drift + docs + test\n</code></pre> <p>All tests should pass with a clean clone.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#quick-reference","level":2,"title":"Quick Reference","text":"Task Command Build <code>make build</code> Install <code>sudo make install</code> Test <code>make test</code> Full audit <code>make audit</code> Rebuild docs site <code>make site</code> Serve docs locally <code>make site-serve</code> Clear plugin cache <code>make plugin-reload</code> Switch config profile <code>ctx config switch dev</code>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#next-steps","level":2,"title":"Next Steps","text":"<ul> <li>Read the contributing guide   for project layout, code style, and PR process</li> <li>Check TASKS.md   for open work items</li> <li>Ask <code>/ctx-next</code> for suggested work</li> </ul>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/plugin-release/","level":1,"title":"Plugin Release","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#plugin-release","level":1,"title":"Plugin Release","text":"<p>Plugin-specific release procedure. The general release checklist covers the full <code>ctx</code> release; this runbook covers the plugin-specific steps that are not part of that flow.</p> <p>When to use: When releasing plugin changes (new skills, hook updates, permission changes) independently of a <code>ctx</code> binary release, or as a sub-procedure within the full release.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#what-ships-in-the-plugin","level":2,"title":"What Ships in the Plugin","text":"<p>The plugin lives at <code>internal/assets/claude/</code> and includes:</p> Component Path What it does Skills <code>internal/assets/claude/skills/</code> User-facing <code>/ctx-*</code> slash commands Hooks <code>internal/assets/claude/hooks/</code> Pre/post tool-use hooks Plugin manifest <code>internal/assets/claude/.claude-plugin/plugin.json</code> Declares skills, hooks, version Marketplace <code>.claude-plugin/marketplace.json</code> Points Claude Code to the plugin","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-1-update-hooksjson-if-hooks-changed","level":2,"title":"Step 1: Update hooks.json (If Hooks Changed)","text":"<p>If you added, removed, or modified hooks:</p> <pre><code># Verify hook definitions match implementations\nmake audit\n</code></pre> <p>Check that <code>plugin.json</code> lists all hooks correctly. Missing hooks silently fail to fire.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-2-bump-version","level":2,"title":"Step 2: Bump Version","text":"<p>Update the version in three places:</p> <ul> <li><code>internal/assets/claude/.claude-plugin/plugin.json</code></li> <li><code>.claude-plugin/marketplace.json</code> (two fields)</li> <li><code>editors/vscode/package.json</code> + <code>package-lock.json</code>   (if VS Code extension is affected)</li> </ul> <p>The Release Script Does This</p> <p>If you're running <code>make release</code>, the script bumps these automatically from <code>VERSION</code>. Only bump manually if you're releasing the plugin independently.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-3-test-against-a-fresh-install","level":2,"title":"Step 3: Test Against a Fresh Install","text":"<pre><code># Clear cached plugin\nmake plugin-reload\n\n# Restart Claude Code, then:\nclaude /plugin list    # verify version\n</code></pre> <p>Test the critical paths:</p> <ul> <li> <code>/ctx-status</code> works</li> <li> Session hooks fire (ceremonies, context loading)</li> <li> At least one user-facing skill works end-to-end</li> <li> Pre-tool-use hooks block when they should</li> </ul>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-4-test-against-a-clean-project","level":2,"title":"Step 4: Test Against a Clean Project","text":"<p>Create a temporary project to verify the plugin works outside the <code>ctx</code> repo:</p> <pre><code>mkdir /tmp/test-ctx-plugin && cd /tmp/test-ctx-plugin\ngit init\nctx init\nclaude   # start a session, verify hooks fire\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-5-verify-skill-count","level":2,"title":"Step 5: Verify Skill Count","text":"<p>The plugin manifest declares all user-invocable skills. Verify the count matches:</p> <pre><code># Count skills in plugin.json\njq '.skills | length' internal/assets/claude/.claude-plugin/plugin.json\n\n# Count skill directories\nls -d internal/assets/claude/skills/ctx-*/ | wc -l\n</code></pre> <p>These numbers should match (some skills are not user-invocable and won't appear in both counts).</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-6-commit-and-tag","level":2,"title":"Step 6: Commit and Tag","text":"<p>If releasing independently of a binary release:</p> <pre><code>git add internal/assets/claude/ .claude-plugin/\ngit commit -m \"chore: release plugin v0.X.Y\"\ngit tag plugin-v0.X.Y\ngit push origin main --tags\n</code></pre> <p>If part of a full release, the release checklist handles this.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#skills-dont-appear-after-update","level":3,"title":"Skills Don't Appear After Update","text":"<p>Claude Code caches plugin files aggressively:</p> <pre><code>make plugin-reload    # clears cache\n# restart Claude Code\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#hooks-dont-fire","level":3,"title":"Hooks Don't Fire","text":"<p>Check that the hook is registered in <code>plugin.json</code> and that the command it calls exists:</p> <pre><code>jq '.hooks' internal/assets/claude/.claude-plugin/plugin.json\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#version-mismatch","level":3,"title":"Version Mismatch","text":"<p>If <code>claude /plugin list</code> shows an old version after updating:</p> <pre><code>make plugin-reload\n# restart Claude Code\nclaude /plugin list   # should show new version\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/release-checklist/","level":1,"title":"Release Checklist","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release-checklist","level":1,"title":"Release Checklist","text":"<p>The canonical pre-release sequence. This runbook ties together the audits, tests, and release steps that are otherwise scattered across docs and the operator's head.</p> <p>When to run: Before every release. No exceptions.</p> <p>Companion: The <code>/_ctx-release</code> skill automates the tag-and-push portion; this checklist covers everything before and after that automation.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#pre-release","level":2,"title":"Pre-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#1-run-the-codebase-audit","level":3,"title":"1. Run the Codebase Audit","text":"<p>Use the codebase audit runbook prompt with your agent. Focus on analyses 1-4 (extractable patterns, documentation drift, maintainability, security). Triage findings into TASKS.md; anything blocking ships before the release.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#2-run-the-docs-semantic-audit","level":3,"title":"2. Run the Docs Semantic Audit","text":"<p>Use the docs semantic audit runbook prompt. Fix high-severity findings (weak pages, broken narrative arcs). Medium-severity items can be deferred.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#3-sanitize-permissions","level":3,"title":"3. Sanitize Permissions","text":"<p>Follow the sanitize permissions runbook. Clean up <code>.claude/settings.local.json</code> before it gets committed as part of the release.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#4-run-the-full-test-suite","level":3,"title":"4. Run the Full Test Suite","text":"<pre><code>make audit    # fmt + vet + lint + drift + docs + test\nmake smoke    # integration smoke tests\n</code></pre> <p>All tests must pass. No exceptions.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#5-check-context-health","level":3,"title":"5. Check Context Health","text":"<p>Activate the project so the next commands know which <code>.context/</code> to read:</p> <pre><code>eval \"$(ctx activate)\"\nctx drift          # broken references, stale patterns\nctx status         # context file health\n/ctx-link-check    # dead links in docs\n</code></pre> <p>Fix anything flagged. If you see <code>Error: no context directory specified</code>, you skipped the <code>eval</code> line above. See Activating a Context Directory.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#6-review-tasksmd","level":3,"title":"6. Review TASKS.md","text":"<p>Scan for incomplete tasks tagged as release-blocking. Either finish them or explicitly defer with a reason in the task note.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release","level":2,"title":"Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#7-bump-version","level":3,"title":"7. Bump Version","text":"<pre><code>echo \"0.X.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.X.0\"\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#8-generate-release-notes","level":3,"title":"8. Generate Release Notes","text":"<p>In Claude Code:</p> <pre><code>/_ctx-release-notes\n</code></pre> <p>Review <code>dist/RELEASE_NOTES.md</code>. Ensure it captures all user-visible changes.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#9-cut-the-release","level":3,"title":"9. Cut the Release","text":"<pre><code>make release\n</code></pre> <p>Or in Claude Code: <code>/_ctx-release</code>. See Cutting a Release for the full step-by-step.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#post-release","level":2,"title":"Post-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#10-verify-the-github-release","level":3,"title":"10. Verify the GitHub Release","text":"<ul> <li> GitHub Releases shows the new version</li> <li> All 6 binaries are attached</li> <li> SHA256 checksums are attached</li> <li> Release notes render correctly</li> </ul>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#11-update-the-plugin-marketplace","level":3,"title":"11. Update the Plugin Marketplace","text":"<p>If the plugin version changed, verify the marketplace entry:</p> <pre><code>claude /plugin list   # shows updated version\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#12-announce","level":3,"title":"12. Announce","text":"<p>Post in the project's communication channels. Reference the release notes.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#13-clean-up","level":3,"title":"13. Clean Up","text":"<pre><code>rm dist/RELEASE_NOTES.md   # consumed by the release script\ngit stash pop              # if you stashed earlier\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/","level":1,"title":"Sanitize Permissions","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#sanitize-permissions","level":1,"title":"Sanitize Permissions","text":"<p>Manual procedure for cleaning up <code>.claude/settings.local.json</code>. The agent may analyze and recommend, but you make every edit.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#why-manual-not-automated","level":2,"title":"Why Manual, Not Automated","text":"<p><code>settings.local.json</code> controls what the agent can do without asking. An agent that can edit its own permission file is a self-escalation vector, especially if the skill is auto-accepted. Keep this manual.</p> <p>When to run: After busy sessions where you clicked \"Allow\" many times, weekly hygiene (pair with <code>ctx drift</code>), or before committing <code>.claude/settings.local.json</code>.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-1-snapshot","level":2,"title":"Step 1: Snapshot","text":"<pre><code>cp .claude/settings.local.json /tmp/settings-backup-$(date +%Y%m%d).json\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-2-extract-the-allow-list","level":2,"title":"Step 2: Extract the Allow List","text":"<pre><code>jq '.permissions.allow[]' .claude/settings.local.json | sort\n</code></pre> <p>Eyeball it. You're looking for four categories:</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-3-identify-problems","level":2,"title":"Step 3: Identify Problems","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#a-garbage-nonsense","level":3,"title":"A. Garbage / Nonsense","text":"<p>Entries that are clearly broken or meaningless:</p> <pre><code>Bash(done)\nBash(__NEW_LINE_aa838494a90279c4__ echo \"\")\n</code></pre> <p>Action: Delete.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#b-one-off-commands-session-debris","level":3,"title":"B. One-Off Commands (Session Debris)","text":"<p>Entries with hardcoded paths, literal arguments, or exact commands that were accepted during a specific debugging session:</p> <pre><code>Bash(git -C /home/jose/WORKSPACE/ctx log --oneline --all -20)\nBash(/home/jose/WORKSPACE/ctx/ctx decision add \"Use PostgreSQL\" --context ...)\n</code></pre> <p>Signs of a one-off:</p> <ul> <li>Full absolute paths to specific files</li> <li>Literal string arguments (not wildcards)</li> <li>Very specific flag combinations</li> <li>Commands that look like they came from a single task</li> </ul> <p>Action: Delete unless you want to promote to a wildcard pattern.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#c-subsumed-entries-redundant","level":3,"title":"C. Subsumed Entries (Redundant)","text":"<p>A narrow entry that's already covered by a broader one:</p> <pre><code># Narrow (redundant):\nBash(ctx journal source)\nBash(git -C /home/jose/WORKSPACE/ctx log --oneline -5)\n\n# Broad (already covers the above):\nBash(ctx journal source:*)\nBash(git -C:*)\n</code></pre> <p>To find these, look for entries where removing the specific args would match an existing wildcard entry.</p> <p>Action: Delete the narrow entry.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#d-duplicate-intent-different-spelling","level":3,"title":"D. Duplicate Intent, Different Spelling","text":"<p>Same command with env vars in different order, or slight variations:</p> <pre><code>Bash(CGO_ENABLED=0 CTX_SKIP_PATH_CHECK=1 go test:*)\nBash(CTX_SKIP_PATH_CHECK=1 CGO_ENABLED=0 go test:*)\n</code></pre> <p>Action: Keep one, delete the other.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-4-check-for-security-concerns","level":2,"title":"Step 4: Check for Security Concerns","text":"<p>While you're in here, also flag:</p> Pattern Risk <code>Bash(git push:*)</code> Bypasses block-git-push.sh hook <code>Bash(rm -rf:*)</code> Recursive delete, no confirmation <code>Bash(sudo:*)</code> Privilege escalation <code>Bash(echo:*)</code>, <code>Bash(cat:*)</code> Can compose into writes to sensitive files <code>Bash(curl:*)</code>, <code>Bash(wget:*)</code> Arbitrary network access Any write to <code>.claude/</code> paths Agent self-modification <p>See the <code>/ctx-permission-sanitize</code> skill for the full threat matrix.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-5-edit","level":2,"title":"Step 5: Edit","text":"<p>Edit <code>.claude/settings.local.json</code> directly in your editor. Remove flagged entries. Keep the JSON valid.</p> <pre><code># Validate JSON after editing\njq . .claude/settings.local.json > /dev/null && echo \"valid\" || echo \"BROKEN\"\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-6-verify","level":2,"title":"Step 6: Verify","text":"<pre><code># Compare before/after\ndiff /tmp/settings-backup-$(date +%Y%m%d).json .claude/settings.local.json\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-7-optionally-commit","level":2,"title":"Step 7: Optionally Commit","text":"<pre><code>git add .claude/settings.local.json\ngit commit -m \"chore: sanitize agent permissions\"\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#asking-the-agent-for-help","level":2,"title":"Asking the Agent for Help","text":"<p>You can safely ask the agent to analyze the file:</p> <p>\"Look at my settings.local.json and tell me which permissions look like one-offs or are redundant.\"</p> <p>The agent can read and report. You do the edits.</p> <p>Do not add these to your allow list:</p> <ul> <li><code>Skill(ctx-permission-sanitize)</code></li> <li><code>Edit(.claude/settings.local.json)</code></li> <li>Any <code>Bash(...)</code> pattern that writes to <code>.claude/</code></li> </ul>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#history","level":2,"title":"History","text":"<ul> <li>2026-02-15: Created as manual-only procedure after deciding   against a self-modifying skill.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"recipes/","level":1,"title":"Recipes","text":"<p>Workflow recipes combining <code>ctx</code> commands and skills to solve specific problems.</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#getting-started","level":2,"title":"Getting Started","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#guide-your-agent","level":3,"title":"Guide Your Agent","text":"<p>How commands, skills, and conversational patterns work together. Train your agent to be proactive through ask, guide, reinforce.</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#setup-across-ai-tools","level":3,"title":"Setup across AI Tools","text":"<p>Initialize <code>ctx</code> and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf. Includes shell completion, watch mode for non-native tools, and verification.</p> <p>Uses: <code>ctx init</code>, <code>ctx setup</code>, <code>ctx agent</code>, <code>ctx completion</code>, <code>ctx watch</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#multilingual-session-parsing","level":3,"title":"Multilingual Session Parsing","text":"<p>Parse session journal entries written in other languages. Configure recognized session-header prefixes so the journal pipeline works for Turkish, Japanese, and any other locale.</p> <p>Uses: <code>ctx journal source</code>, <code>ctx journal import</code>, <code>session_prefixes</code> in <code>.ctxrc</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#keeping-context-in-a-separate-repo","level":3,"title":"Keeping Context in a Separate Repo","text":"<p>Store context files outside the project tree: in a private repo, shared directory, or anywhere else. Useful for open source projects with private context or multi-repo setups.</p> <p>Uses: <code>ctx init</code>, <code>CTX_DIR</code>, <code>.ctxrc</code>, <code>/ctx-status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#build-a-knowledge-base","level":3,"title":"Build a Knowledge Base","text":"<p>Stand up the editorial pipeline for knowledge-shaped work (research projects, vendor-spec analysis, post-incident reviews). Covers the pass-mode contract, source-coverage state-machine ledger, topic-adjacency pre-flight, cold-reader rubric, closeout/fold mechanism, and folder-shaped topic pages.</p> <p>Uses: <code>ctx init</code>, <code>ctx kb topic new</code>, <code>ctx kb note</code>, <code>ctx kb reindex</code>, <code>ctx handover write</code>, <code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, <code>/ctx-kb-site-review</code>, <code>/ctx-kb-ground</code>, <code>/ctx-kb-note</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#typical-kb-session","level":3,"title":"Typical KB Session","text":"<p>The everyday flow once the pipeline is set up: session start recall, ingest a transcript, ask grounded questions, park findings, wrap up via the mandatory handover.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, <code>/ctx-kb-note</code>, <code>/ctx-wrap-up</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#recover-an-aborted-kb-session","level":3,"title":"Recover an Aborted KB Session","text":"<p>What to do when the session ends after one or more editorial passes but before <code>/ctx-handover</code>. Closeouts survive the abort; the next session's <code>/ctx-remember</code> reads them as unfolded postdated artifacts; the next <code>/ctx-handover</code> folds them retroactively.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#sessions","level":2,"title":"Sessions","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#the-complete-session","level":3,"title":"The Complete Session","text":"<p>Walk through a full <code>ctx</code> session from start to finish:</p> <ul> <li>Loading context,</li> <li>Picking what to work on,</li> <li>Committing with context,</li> <li>Capturing, reflecting, and saving a snapshot.</li> </ul> <p>Uses: <code>ctx status</code>, <code>ctx agent</code>, <code>/ctx-remember</code>, <code>/ctx-next</code>, <code>/ctx-commit</code>, <code>/ctx-reflect</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#session-ceremonies","level":3,"title":"Session Ceremonies","text":"<p>The two bookend rituals for every session: <code>/ctx-remember</code> at the start to load and confirm context, <code>/ctx-wrap-up</code> at the end to review the session and persist learnings, decisions, and tasks.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-wrap-up</code>, <code>/ctx-commit</code>, <code>ctx agent</code>, <code>ctx add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#browsing-and-enriching-past-sessions","level":3,"title":"Browsing and Enriching Past Sessions","text":"<p>Export your AI session history to a browsable journal site. Enrich entries with metadata and search across months of work.</p> <p>Uses: <code>ctx journal source/import</code>, <code>ctx journal site</code>, <code>ctx journal obsidian</code>, <code>ctx serve</code>, <code>/ctx-history</code>, <code>/ctx-journal-enrich</code>, <code>/ctx-journal-enrich-all</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#session-reminders","level":3,"title":"Session Reminders","text":"<p>Leave a message for your next session. Reminders surface automatically at session start and repeat until dismissed. Date-gate reminders to surface only after a specific date.</p> <p>Uses: <code>ctx remind</code>, <code>ctx remind list</code>, <code>ctx remind dismiss</code>, <code>ctx system check-reminders</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#reviewing-session-changes","level":3,"title":"Reviewing Session Changes","text":"<p>See what moved since your last session: context file edits, code commits, directories touched. Auto-detects session boundaries from state markers.</p> <p>Uses: <code>ctx change</code>, <code>ctx agent</code>, <code>ctx status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#pausing-context-hooks","level":3,"title":"Pausing Context Hooks","text":"<p>Silence all nudge hooks for a quick task that doesn't need ceremony overhead. Session-scoped: Other sessions are unaffected. Security hooks still fire.</p> <p>Uses: <code>ctx hook pause</code>, <code>ctx hook resume</code>, <code>/ctx-pause</code>, <code>/ctx-resume</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-and-tasks","level":2,"title":"Knowledge and Tasks","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#persisting-decisions-learnings-and-conventions","level":3,"title":"Persisting Decisions, Learnings, and Conventions","text":"<p>Record architectural decisions with rationale, capture gotchas and lessons learned, and codify conventions so they survive across sessions and team members.</p> <p>Uses: <code>ctx decision add</code>, <code>ctx learning add</code>, <code>ctx convention add</code>, <code>ctx decision reindex</code>, <code>ctx learning reindex</code>, <code>/ctx-decision-add</code>, <code>/ctx-learning-add</code>, <code>/ctx-convention-add</code>, <code>/ctx-reflect</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#tracking-work-across-sessions","level":3,"title":"Tracking Work across Sessions","text":"<p>Add, prioritize, complete, snapshot, and archive tasks. Keep <code>TASKS.md</code> focused as your project evolves across dozens of sessions.</p> <p>Uses: <code>ctx task add</code>, <code>ctx task complete</code>, <code>ctx task archive</code>, <code>ctx task snapshot</code>, <code>/ctx-task-add</code>, <code>/ctx-archive</code>, <code>/ctx-next</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#using-the-scratchpad","level":3,"title":"Using the Scratchpad","text":"<p>Use the encrypted scratchpad for quick notes, working memory, and sensitive values during AI sessions. Natural language in, encrypted storage out.</p> <p>Uses: <code>ctx pad</code>, <code>/ctx-pad</code>, <code>ctx pad show</code>, <code>ctx pad edit</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#syncing-scratchpad-notes-across-machines","level":3,"title":"Syncing Scratchpad Notes across Machines","text":"<p>Distribute your scratchpad encryption key, push and pull encrypted notes via git, and resolve merge conflicts when two machines edit simultaneously.</p> <p>Uses: <code>ctx init</code>, <code>ctx pad</code>, <code>ctx pad resolve</code>, <code>scp</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#bridging-claude-code-auto-memory","level":3,"title":"Bridging Claude Code Auto Memory","text":"<p>Mirror Claude Code's auto memory (MEMORY.md) into <code>.context/</code> for version control, portability, and drift detection. Import entries into structured context files with heuristic classification.</p> <p>Uses: <code>ctx memory sync</code>, <code>ctx memory status</code>, <code>ctx memory diff</code>, <code>ctx memory import</code>, <code>ctx memory publish</code>, <code>ctx system check-memory-drift</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hooks-and-notifications","level":2,"title":"Hooks and Notifications","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-output-patterns","level":3,"title":"Hook Output Patterns","text":"<p>Choose the right output pattern for your Claude Code hooks: <code>VERBATIM</code> relay for user-facing reminders, hard gates for invariants, agent directives for nudges, and five more patterns across the spectrum.</p> <p>Uses: <code>ctx</code> plugin hooks, <code>settings.local.json</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#customizing-hook-messages","level":3,"title":"Customizing Hook Messages","text":"<p>Customize what hooks say without changing what they do. Override the QA gate for Python (<code>pytest</code> instead of <code>make lint</code>), silence noisy ceremony nudges, or tailor post-commit instructions for your stack.</p> <p>Uses: <code>ctx hook message list</code>, <code>ctx hook message show</code>, <code>ctx hook message edit</code>, <code>ctx hook message reset</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-sequence-diagrams","level":3,"title":"Hook Sequence Diagrams","text":"<p>Mermaid sequence diagrams for every system hook: entry conditions, state reads, output, throttling, and exit points. Includes throttling summary table and state file reference.</p> <p>Uses: All <code>ctx system</code> hooks</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#auditing-system-hooks","level":3,"title":"Auditing System Hooks","text":"<p>The 12 system hooks that run invisibly during every session: what each one does, why it exists, and how to verify they're actually firing. Covers webhook-based audit trails, log inspection, and detecting silent hook failures.</p> <p>Uses: <code>ctx system</code>, <code>ctx hook notify</code>, <code>.context/logs/</code>, <code>.ctxrc</code> <code>notify.events</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"<p>Get push notifications when loops complete, hooks fire, or agents hit milestones. Webhook URL is encrypted: never stored in plaintext. Works with IFTTT, Slack, Discord, ntfy.sh, or any HTTP endpoint.</p> <p>Uses: <code>ctx hook notify setup</code>, <code>ctx hook notify test</code>, <code>ctx hook notify --event</code>, <code>.ctxrc</code> <code>notify.events</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"<p>Switch between dev and base runtime configurations without editing <code>.ctxrc</code> by hand. Verbose logging and webhooks for debugging, clean defaults for normal sessions.</p> <p>Uses: <code>ctx config switch</code>, <code>ctx config status</code>, <code>/ctx-config</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#maintenance","level":2,"title":"Maintenance","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#detecting-and-fixing-drift","level":3,"title":"Detecting and Fixing Drift","text":"<p>Keep context files accurate by detecting structural drift (stale paths, missing files, stale file ages) and task staleness.</p> <p>Uses: <code>ctx drift</code>, <code>ctx sync</code>, <code>ctx compact</code>, <code>ctx status</code>, <code>/ctx-drift</code>, <code>/ctx-status</code>, <code>/ctx-prompt-audit</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#state-directory-maintenance","level":3,"title":"State Directory Maintenance","text":"<p>Clean up session tombstones from <code>.context/state/</code>. Prune old per-session files, identify stale global markers, and keep the state directory lean.</p> <p>Uses: <code>ctx prune</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#troubleshooting","level":3,"title":"Troubleshooting","text":"<p>Diagnose hook failures, noisy nudges, stale context, and configuration issues. Start with <code>ctx doctor</code> for a structural health check, then use <code>/ctx-doctor</code> for agent-driven analysis of event patterns.</p> <p>Uses: <code>ctx doctor</code>, <code>ctx hook event</code>, <code>/ctx-doctor</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#claude-code-permission-hygiene","level":3,"title":"Claude Code Permission Hygiene","text":"<p>Keep <code>.claude/settings.local.json</code> clean: recommended safe defaults, what to never pre-approve, and a maintenance workflow for cleaning up session debris.</p> <p>Uses: <code>ctx init</code>, <code>/ctx-drift</code>, <code>/ctx-permission-sanitize</code>, <code>ctx permission snapshot</code>, <code>ctx permission restore</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#permission-snapshots","level":3,"title":"Permission Snapshots","text":"<p>Capture a known-good permission baseline as a golden image, then restore at session start to automatically drop session-accumulated permissions.</p> <p>Uses: <code>ctx permission snapshot</code>, <code>ctx permission restore</code>, <code>/ctx-permission-sanitize</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#turning-activity-into-content","level":3,"title":"Turning Activity into Content","text":"<p>Generate blog posts from project activity, write changelog posts from commit ranges, and publish a browsable journal site from your session history.</p> <p>The output is generic Markdown, but the skills are tuned for the <code>ctx</code>-style blog artifacts you see on this website.</p> <p>Uses: <code>ctx journal site</code>, <code>ctx journal obsidian</code>, <code>ctx serve</code>, <code>ctx journal import</code>, <code>/ctx-blog</code>, <code>/ctx-blog-changelog</code>, <code>/ctx-journal-enrich</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#importing-claude-code-plans","level":3,"title":"Importing Claude Code Plans","text":"<p>Import Claude Code plan files (<code>~/.claude/plans/*.md</code>) into <code>specs/</code> as permanent project specs. Filter by date, select interactively, and optionally create tasks referencing each imported spec.</p> <p>Uses: <code>/ctx-plan-import</code>, <code>/ctx-task-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#design-before-coding","level":3,"title":"Design Before Coding","text":"<p>Front-load design with a four-skill chain: brainstorm the approach, spec the design, task the work, implement step-by-step. Each step produces an artifact that feeds the next.</p> <p>Uses: <code>/ctx-brainstorm</code>, <code>/ctx-spec</code>, <code>/ctx-task-add</code>, <code>/ctx-implement</code>, <code>/ctx-decision-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#scrutinizing-a-plan","level":3,"title":"Scrutinizing a Plan","text":"<p>Once a plan exists, run an adversarial interview to surface what's weak, missing, or unexamined before you commit. Walks the plan depth-first: assumptions, failure modes, alternatives, sequencing, reversibility. The complement to brainstorm: brainstorm produces plans, this attacks them.</p> <p>Uses: <code>/ctx-plan</code>, <code>/ctx-spec</code>, <code>/ctx-decision-add</code>, <code>/ctx-learning-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#agents-and-automation","level":2,"title":"Agents and Automation","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#building-project-skills","level":3,"title":"Building Project Skills","text":"<p>Encode repeating workflows into reusable skills the agent loads automatically. Covers the full cycle: identify a pattern, create the skill, test with realistic prompts, and iterate until it triggers correctly.</p> <p>Uses: <code>/ctx-skill-create</code>, <code>ctx init</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#running-an-unattended-ai-agent","level":3,"title":"Running an Unattended AI Agent","text":"<p>Set up a loop where an AI agent works through tasks overnight without you at the keyboard, using <code>ctx</code> for persistent memory between iterations.</p> <p>This recipe shows how <code>ctx</code> supports long-running agent loops without losing context or intent.</p> <p>Uses: <code>ctx init</code>, <code>ctx loop</code>, <code>ctx watch</code>, <code>ctx load</code>, <code>/ctx-loop</code>, <code>/ctx-implement</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#when-to-use-a-team-of-agents","level":3,"title":"When to Use a Team of Agents","text":"<p>Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.</p> <p>This recipe covers the file overlap test, when teams make things worse, and what <code>ctx</code> provides at each level.</p> <p>Uses: <code>/ctx-worktree</code>, <code>/ctx-next</code>, <code>ctx status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#parallel-agent-development-with-git-worktrees","level":3,"title":"Parallel Agent Development with Git Worktrees","text":"<p>Split a large backlog across 3-4 agents using git worktrees, each on its own branch and working directory. Group tasks by file overlap, work in parallel, merge back.</p> <p>Uses: <code>/ctx-worktree</code>, <code>/ctx-next</code>, <code>git worktree</code>, <code>git merge</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#architecture-deep-dive","level":3,"title":"Architecture Deep Dive","text":"<p>Three-pass pipeline for understanding a codebase: map what exists, enrich with code intelligence, then hunt for where it will silently fail. Produces architecture docs, quantified dependency data, and ranked failure hypotheses.</p> <p>Uses: <code>/ctx-architecture</code>, <code>/ctx-architecture-enrich</code>, <code>/ctx-architecture-failure-analysis</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#writing-steering-files","level":3,"title":"Writing Steering Files","text":"<p>Tell your AI assistant how to behave with rule-based prompt injection that fires automatically when prompts match a description. Walks through scaffolding a steering file, previewing matches, and syncing to each AI tool's native format.</p> <p>Uses: <code>ctx steering add</code>, <code>ctx steering preview</code>, <code>ctx steering list</code>, <code>ctx steering sync</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#authoring-lifecycle-triggers","level":3,"title":"Authoring Lifecycle Triggers","text":"<p>Run executable shell scripts at session-start, pre-tool-use, file-save, and other lifecycle events. Script-based automation (complementary to steering's rule-based prompts), with a security-first workflow: scaffold disabled, test with mock input, enable only after review.</p> <p>Uses: <code>ctx trigger add</code>, <code>ctx trigger test</code>, <code>ctx trigger enable</code>, <code>ctx trigger disable</code>, <code>ctx trigger list</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hub","level":2,"title":"Hub","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hub-overview","level":3,"title":"Hub Overview","text":"<p>Mental model and three user stories for the <code>ctx</code> Hub. What flows, what doesn't, and when not to use it. Read this before any of the other Hub recipes.</p> <p>Uses: <code>ctx hub</code>, <code>ctx connection</code>, <code>ctx add --share</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-getting-started","level":3,"title":"<code>ctx</code> Hub: Getting Started","text":"<p>Stand up a single-node hub on localhost, register two projects, publish a decision from one, and watch it appear in the other. End-to-end in under five minutes.</p> <p>Uses: <code>ctx hub start</code>, <code>ctx connection register</code>, <code>ctx connection subscribe</code>, <code>ctx connection sync</code>, <code>ctx connection listen</code>, <code>ctx add --share</code>, <code>ctx agent --include-hub</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"<p>Story 1 day-to-day workflow: one developer, many projects, one hub on localhost. Records a learning in project A, watches it show up automatically in project B. Walks through a realistic day of using the hub as passive infrastructure (no manual <code>sync</code>, no <code>git push</code>, no ceremony).</p> <p>Uses: <code>ctx add --share</code>, <code>ctx connection subscribe</code>, <code>ctx agent --include-hub</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#team-knowledge-bus","level":3,"title":"Team Knowledge Bus","text":"<p>Story 2 day-to-day workflow: a small trusted team sharing decisions, learnings, and conventions via a hub on an internal server. Covers the team publishing culture, what belongs on the hub vs. local, token management, and the social rules that make a shared knowledge stream stay signal-rich.</p> <p>Uses: <code>ctx add --share</code>, <code>ctx connection status</code>, <code>ctx connection subscribe</code>, <code>ctx hub status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-multi-machine","level":3,"title":"<code>ctx</code> Hub: Multi-Machine","text":"<p>Run the hub on a LAN host as a daemon and connect from project directories on other workstations. Firewall guidance, TLS via a reverse proxy, and safe daemon restart semantics.</p> <p>Uses: <code>ctx hub start --daemon</code>, <code>ctx hub stop</code>, <code>ctx connection register</code>, <code>ctx connection status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-ha-cluster","level":3,"title":"<code>ctx</code> Hub: HA Cluster","text":"<p>Raft-based leader election across three or more nodes for redundancy. Covers bootstrap, runtime peer management, graceful stepdown, and the Raft-lite durability caveat.</p> <p>Uses: <code>ctx hub start --peers</code>, <code>ctx hub status</code>, <code>ctx hub peer add/remove</code>, <code>ctx hub stepdown</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/activating-context/","level":1,"title":"Activating a Context Directory","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#the-problem","level":2,"title":"The Problem","text":"<p>You ran a <code>ctx</code> command and got:</p> <pre><code>Error: no context directory specified for this project\n</code></pre> <p>This means <code>ctx</code> doesn't know which <code>.context/</code> directory to operate on. It will not guess, and it will not walk up from your current working directory looking for one; that behavior was removed deliberately, because silent inference was the source of several bugs (stray agent-created directories, cross-project bleed-through, webhook-route misrouting, sub-agent fragmentation). Every <code>ctx</code> command requires you to declare the target directory explicitly.</p> <p>This page shows you the three ways to do that and when to use each.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#tldr","level":2,"title":"TL;DR","text":"<p>If the project has already been initialized and you just need to bind it for your shell:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>That's 95% of the time. Add it to <code>.zshrc</code> / <code>.bashrc</code> per project with direnv, or run it once per terminal.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#when-you-see-the-error","level":2,"title":"When You See the Error","text":"<p>The exact error message depends on how many <code>.context/</code> directories are visible from the current directory:</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#zero-candidates","level":3,"title":"Zero Candidates","text":"<pre><code>Error: no context directory specified for this project\n</code></pre> <p>Either you haven't initialized this project yet (run <code>ctx init</code>) or you're in a directory that doesn't belong to a ctx-tracked project. If you know the project lives elsewhere, use one of the declaration methods below with its absolute path.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-candidate","level":3,"title":"One Candidate","text":"<pre><code>Error: no context directory specified; a likely candidate is at\n    /Users/you/repos/myproject/.context\n</code></pre> <p><code>ctx</code> found a single <code>.context/</code> on the way up from here but won't bind to it automatically. Run <code>eval \"$(ctx activate)\"</code> and <code>ctx</code> will emit the <code>export</code> for the candidate. Or set <code>CTX_DIR</code> by hand.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#multiple-candidates","level":3,"title":"Multiple Candidates","text":"<pre><code>Error: no context directory specified; multiple candidates visible:\n  /Users/you/repos/myproject/.context\n  /Users/you/repos/myproject/packages/web/.context\n</code></pre> <p>You're inside nested projects. Pick the one you mean:</p> <pre><code>ctx activate /Users/you/repos/myproject/.context\n# …copy and paste the `export` line it prints, or wrap in eval:\neval \"$(ctx activate /Users/you/repos/myproject/.context)\"\n</code></pre>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#three-ways-to-declare","level":2,"title":"Three Ways to Declare","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#1-ctx-activate-recommended-for-shells","level":3,"title":"1. <code>ctx activate</code> (Recommended for Shells)","text":"<p><code>ctx activate</code> emits a shell-native <code>export CTX_DIR=...</code> line to stdout. Wrap it in <code>eval</code> and the binding takes effect for the current shell:</p> <pre><code># Walk up from current dir and bind the single visible candidate:\neval \"$(ctx activate)\"\n\n# Bind a specific path explicitly:\neval \"$(ctx activate /abs/path/to/.context)\"\n\n# Clear the binding:\neval \"$(ctx deactivate)\"\n</code></pre> <p><code>ctx activate</code> validates paths strictly: the target must exist, be a directory, and contain at least one canonical context file (<code>CONSTITUTION.md</code> or <code>TASKS.md</code>). It refuses to emit for multiple upward candidates; pick one explicitly in that case.</p> <p>Under the hood, the emitted line is just:</p> <pre><code>export CTX_DIR='/abs/path/to/.context'\n</code></pre> <p>So you can copy it into your <code>.zshrc</code> / <code>.bashrc</code> if you want the binding permanent for a given shell setup. Better: use direnv with a per-project <code>.envrc</code>.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#2-ctx_dir-env-var","level":3,"title":"2. <code>CTX_DIR</code> Env Var","text":"<p>If you already know the path, export it directly:</p> <pre><code>export CTX_DIR=/abs/path/to/.context\nctx status\n</code></pre> <p><code>CTX_DIR</code> is the same variable <code>ctx activate</code> writes; <code>activate</code> is just a convenience that figures out the path for you.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#3-inline-one-shot","level":3,"title":"3. Inline One-Shot","text":"<p>For one-shot commands (CI jobs, scripts, debugging a specific project without changing your shell state), prefix the binding inline:</p> <pre><code>CTX_DIR=/abs/path/to/.context ctx status\n</code></pre> <p>This binds <code>CTX_DIR</code> for that invocation only.</p> <p><code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (<code>export CTX_DIR=$(pwd)</code>) before stray writes can leak to the project root.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-ci-and-scripts","level":2,"title":"For CI and Scripts","text":"<p>Do not rely on shell activation in automated flows. Set <code>CTX_DIR</code> explicitly at the top of the script:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\nexport CTX_DIR=\"$GITHUB_WORKSPACE/.context\"\nctx status\nctx drift\n</code></pre>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-claude-code-users","level":2,"title":"For Claude Code Users","text":"<p>The <code>ctx</code> plugin's hooks are generated with <code>CTX_DIR=\"$CLAUDE_PROJECT_DIR/.context\"</code> prefixed to each command, so hook-driven <code>ctx</code> invocations resolve correctly without any per-session setup. You only need to activate manually when running <code>ctx</code> yourself in a terminal.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-project-one-context","level":2,"title":"One Project, One <code>.context/</code>","text":"<p>The context directory is not a free-floating bag of files. It is pinned to a project by contract: <code>filepath.Dir(ContextDir())</code> is the project root. That parent directory is what <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook scan for code, secret files, and <code>MEMORY.md</code> respectively.</p> <p>The practical consequences:</p> <ul> <li>Don't share one <code>.context/</code> across multiple projects. It holds   per-project journals, per-session state, and per-project secrets.   Pointing two codebases at the same directory corrupts all three.</li> <li>If you want to share knowledge (CONSTITUTION, CONVENTIONS,   ARCHITECTURE) across projects, use <code>ctx hub</code>. It cherry-picks   entries at the right granularity and keeps the per-project bits   where they belong.</li> <li>The <code>CTX_DIR</code> you activate is implicitly a project-root   declaration. Setting <code>CTX_DIR=/weird/place/.context</code> means   you're telling <code>ctx</code> the project root is <code>/weird/place/</code>. That's   your call to make; <code>ctx</code> does not police it.</li> </ul>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#recommended-layout","level":3,"title":"Recommended Layout","text":"<pre><code>~/WORKSPACE/my-to-do-list\n  ├── .git\n  ├── .context          ← owned by this project; do not share\n  ├── ideas\n  │   └── ...\n  ├── Makefile\n  ├── Makefile.ctx\n  └── specs\n      └── ...\n</code></pre> <p><code>.context/</code> sits at the project root, next to <code>.git</code>. <code>ctx activate</code> binds to it; every <code>ctx</code> subsystem reads the project from its parent.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#why-not-walk-up-automatically","level":2,"title":"Why Not Walk Up Automatically?","text":"<p>Nested projects, submodules, rogue agent-created <code>.context/</code> directories, and sub-agent sessions all produced silent misrouting under the old walk-up model. <code>ctx</code> decided to stop guessing and require the caller to declare. Every other decision flows from there.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/architecture-deep-dive/","level":1,"title":"Architecture Deep Dive","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-problem","level":2,"title":"The Problem","text":"<p>Understanding a codebase at the surface level is easy. Understanding where it will break under real-world conditions takes three passes: mapping what exists, quantifying how it connects, and hunting for where it silently fails. Most teams stop at the first pass.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tldr","level":2,"title":"TL;DR","text":"<pre><code># Pass 1: Map the system\n/ctx-architecture\n\n# Pass 2: Enrich with code intelligence\n/ctx-architecture-enrich\n\n# Pass 3: Hunt for failure modes\n/ctx-architecture-failure-analysis\n</code></pre> <p>Each pass builds on the previous one. Run them in order. The output accumulates in <code>.context/</code>; each pass reads the prior artifacts and extends them.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-architecture</code> Skill Map modules, dependencies, data flow, patterns <code>/ctx-architecture-enrich</code> Skill Verify blast radius and flows with code intel <code>/ctx-architecture-failure-analysis</code> Skill Generate falsifiable incident hypotheses <code>ctx drift</code> CLI Detect stale paths and broken references <code>ctx status</code> CLI Quick structural overview","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-1-map-what-exists","level":3,"title":"Pass 1: Map What Exists","text":"<pre><code>/ctx-architecture\n</code></pre> <p>Produces:</p> <ul> <li>ARCHITECTURE.md: succinct project map (< 4000 tokens),   loaded at every session start</li> <li>DETAILED_DESIGN*.md: deep per-module reference with   exported API, data flow, danger zones, extension points</li> <li>CHEAT-SHEETS.md: lifecycle flow diagrams</li> <li>map-tracking.json: coverage state with confidence scores</li> </ul> <p>This pass forces deep code reading. No shortcuts, no code intelligence tools; the agent reads every module it analyzes. That forced reading is what makes the subsequent passes useful.</p> <p>When to run: First time on a codebase, or after significant structural changes (new packages, moved files, changed dependencies).</p> <p>Principal mode: Add <code>principal</code> to get strategic analysis (ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md from P4):</p> <pre><code>/ctx-architecture principal\n</code></pre>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-2-enrich-with-code-intelligence","level":3,"title":"Pass 2: Enrich with Code Intelligence","text":"<pre><code>/ctx-architecture-enrich\n</code></pre> <p>Takes the Pass 1 artifacts as baseline and layers on verified, graph-backed data from GitNexus:</p> <ul> <li>Blast radius numbers for key functions</li> <li>Execution flow traces through hot paths</li> <li>Domain clustering validation</li> <li>Registration site discovery</li> </ul> <p>This pass does not replace reading; it quantifies what reading found. If Pass 1 says \"module X depends on module Y,\" Pass 2 says \"module X has 47 callers in module Y, and changing function Z would affect 12 downstream consumers.\"</p> <p>When to run: After Pass 1, when you need quantified confidence for refactoring decisions or risk assessment.</p> <p>Requires: GitNexus MCP server connected.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-3-hunt-for-failure-modes","level":3,"title":"Pass 3: Hunt for Failure Modes","text":"<pre><code>/ctx-architecture-failure-analysis\n</code></pre> <p>The adversarial pass. Reads all prior artifacts, then systematically hunts for correctness bugs across 9 failure categories:</p> <ol> <li>Concurrency (races, deadlocks, goroutine leaks)</li> <li>Ordering assumptions (init, registration, shutdown)</li> <li>Cache staleness (TTL-less, read-your-writes, cross-process)</li> <li>Fan-out amplification (N+1, retry storms)</li> <li>Ownership and lifecycle (orphans, double-close)</li> <li>Error handling (silent swallowing, partial failure)</li> <li>Scaling cliffs (quadratic, unbounded, global locks)</li> <li>Idempotency failures (duplicate processing, retry mutations)</li> <li>State machine drift (illegal states, unvalidated transitions)</li> </ol> <p>Every finding must meet an evidence standard: code path, trigger, failure path, silence reason, and code evidence. A mandatory challenge phase attempts to disprove each finding before it is accepted. Findings carry a confidence level (High/Medium/Low) and explicit risk score.</p> <p>Produces DANGER-ZONES.md, a ranked inventory of findings split into Critical and Elevated tiers.</p> <p>When to run: Before releases, after major refactors, when investigating incident categories, or when onboarding.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#what-you-get","level":2,"title":"What You Get","text":"<p>After all three passes, <code>.context/</code> contains:</p> File From Purpose <code>ARCHITECTURE.md</code> Pass 1 System map (session-start context) <code>DETAILED_DESIGN*.md</code> Pass 1 Module-level deep reference <code>CHEAT-SHEETS.md</code> Pass 1 Lifecycle flow diagrams <code>map-tracking.json</code> Pass 1 Coverage and confidence data <code>CONVERGENCE-REPORT.md</code> Pass 1 What's covered, what's not <code>DANGER-ZONES.md</code> Pass 3 Ranked failure hypotheses <p>Pass 2 enriches Pass 1 artifacts in-place rather than creating new files.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tips","level":2,"title":"Tips","text":"<ul> <li>Run Pass 1 with focus areas if the codebase is large.   The skill asks what to go deep on, so name the modules you're   about to change.</li> <li>You don't need all three passes every time. Pass 1 is   the foundation. Pass 2 and 3 are for when you need   quantified confidence or adversarial rigor.</li> <li>Re-run Pass 1 incrementally. It tracks coverage in   <code>map-tracking.json</code> and only re-analyzes stale modules.</li> <li>Pass 3 is most valuable before releases. The ranked   DANGER-ZONES.md is a pre-release checklist.</li> <li>The trilogy maps to a question progression: How does it   work? How well does it connect? Where will it break?</li> </ul>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#see-also","level":2,"title":"See Also","text":"<p>See also: Detecting and Fixing Context Drift to keep architecture artifacts fresh between deep-dive sessions.</p> <p>See also: Detecting and Fixing Context Drift for structural checks that complement architecture analysis.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/autonomous-loops/","level":1,"title":"Running an Unattended AI Agent","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-problem","level":2,"title":"The Problem","text":"<p>You have a project with a clear list of tasks, and you want an AI agent to work through them autonomously: overnight, unattended, without you sitting at the keyboard.</p> <p>Each iteration needs to remember what the previous one did, mark tasks as completed, and know when to stop.</p> <p>Without persistent memory, every iteration starts fresh and the loop collapses. With <code>ctx</code>, each iteration can pick up where the last one left off, but only if the agent persists its context as part of the work.</p> <p>Unattended operation works because the agent treats context persistence as a first-class deliverable, not an afterthought.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                                    # 1. init context\neval \"$(ctx activate)\"                      # 2. bind CTX_DIR for this shell\n# Edit TASKS.md with phased work items\nctx loop --tool claude --max-iterations 10  # 3. generate loop.sh\n./loop.sh 2>&1 | tee /tmp/loop.log &        # 4. run the loop\nctx watch --log /tmp/loop.log               # 5. process context updates\n# Next morning:\nctx status && ctx load                      # 6. review the results\n</code></pre> <p>Activate, or Set CTX_DIR Inline for Unattended Runs</p> <p><code>eval \"$(ctx activate)\"</code> is fine for an interactive terminal. For an overnight unattended loop, put the binding at the top of <code>loop.sh</code> instead (<code>export CTX_DIR=/abs/path/.context</code>) so the loop doesn't depend on a live shell. If you skip both, <code>ctx loop</code>, <code>ctx watch</code>, <code>ctx status</code>, and <code>ctx load</code> fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Read on for permissions, isolation, and completion signals.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> Command Initialize project context and prompt templates <code>ctx loop</code> Command Generate the loop shell script <code>ctx watch</code> Command Monitor AI output and persist context updates <code>ctx load</code> Command Display assembled context (for debugging) <code>/ctx-loop</code> Skill Generate loop script from inside Claude Code <code>/ctx-implement</code> Skill Execute a plan step-by-step with verification","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-1-initialize-for-unattended-operation","level":3,"title":"Step 1: Initialize for Unattended Operation","text":"<p>Start by creating a <code>.context/</code> directory configured so the agent can work without human input.</p> <pre><code>ctx init\n</code></pre> <p>This creates <code>.context/</code> with the template files (including a loop prompt at <code>.context/loop.md</code>), and seeds Claude Code permissions in <code>.claude/settings.local.json</code>. Install the <code>ctx</code> plugin for hooks and skills.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-2-populate-tasksmd-with-phased-work","level":3,"title":"Step 2: Populate <code>TASKS.md</code> with Phased Work","text":"<p>Open <code>.context/TASKS.md</code> and organize your work into phases. The agent works through these systematically, top to bottom, using priority tags to break ties.</p> <pre><code># Tasks\n\n## Phase 1: Foundation\n\n- [ ] Set up project structure and build system `#priority:high`\n- [ ] Configure testing framework `#priority:high`\n- [ ] Create CI pipeline `#priority:medium`\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Hardening\n\n- [ ] Add rate limiting to API endpoints `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n- [ ] Write integration tests `#priority:medium`\n</code></pre> <p>Phased organization matters because it gives the agent natural boundaries. Phase 1 tasks should be completable without Phase 2 code existing yet.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-3-configure-the-loop-prompt","level":3,"title":"Step 3: Configure the Loop Prompt","text":"<p>The loop prompt at <code>.context/loop.md</code> instructs the agent to operate autonomously:</p> <ol> <li>Read <code>.context/CONSTITUTION.md</code> first (hard rules, never violated)</li> <li>Load context from <code>.context/</code> files</li> <li>Pick one task per iteration</li> <li>Complete the task and update context files</li> <li>Commit changes (including <code>.context/</code>)</li> <li>Signal status with a completion signal</li> </ol> <p>You can customize <code>.context/loop.md</code> for your project. The critical parts are the one-task-per-iteration discipline, proactive context persistence, and completion signals at the end:</p> <pre><code>## Signal Status\n\nEnd your response with exactly ONE of:\n\n* `SYSTEM_CONVERGED`: All tasks in `TASKS.md` are complete (*this is the\n  signal the loop script detects by default*)\n* `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n* (*no signal*): More work remains, continue to the next iteration\n\nNote: the loop script only checks for `SYSTEM_CONVERGED` by default.\n`SYSTEM_BLOCKED` is a convention for the human reviewing the log.\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-4-configure-permissions","level":3,"title":"Step 4: Configure Permissions","text":"<p>An unattended agent needs permission to use tools without prompting. By default, Claude Code asks for confirmation on file writes, bash commands, and other operations, which stops the loop and waits for a human who is not there.</p> <p>There are two approaches.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-a-explicit-allowlist-recommended","level":4,"title":"Option A: Explicit Allowlist (Recommended)","text":"<p>Grant only the permissions the agent needs. In <code>.claude/settings.local.json</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Bash(ctx:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n</code></pre> <p>Adjust the <code>Bash</code> patterns for your project's toolchain. The agent can run <code>make</code>, <code>go</code>, <code>git</code>, and <code>ctx</code> commands but cannot run arbitrary shell commands.</p> <p>This is recommended even in sandboxed environments because it limits blast radius.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-b-skip-all-permission-checks","level":4,"title":"Option B: Skip All Permission Checks","text":"<p>Claude Code supports a <code>--dangerously-skip-permissions</code> flag that disables all permission prompts:</p> <pre><code>claude --dangerously-skip-permissions -p \"$(cat .context/loop.md)\"\n</code></pre> <p>This Flag Means What It Says</p> <p>With <code>--dangerously-skip-permissions</code>, the agent can execute any shell command, write to any file, and make network requests without confirmation.</p> <p>Only use this on a sandboxed machine: ideally a virtual machine with no access to host credentials, no SSH keys, and no access to production systems.</p> <p>If you would not give an untrusted intern <code>sudo</code> on this machine, do not use this flag.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#enforce-isolation-at-the-os-level","level":4,"title":"Enforce Isolation at the OS Level","text":"<p>The only controls an agent cannot override are the ones enforced by the operating system, the container runtime, or the hypervisor.</p> <p>Do Not Skip This Section</p> <p>This is not optional hardening:</p> <p>An unattended agent with unrestricted OS access is an unattended shell with unrestricted OS access. </p> <p>The allowlist above is a strong first layer, but do not rely on a single runtime boundary.</p> <p>For unattended runs, enforce isolation at the infrastructure level:</p> Layer What to enforce User account Run the agent as a dedicated unprivileged user with no <code>sudo</code> access and no membership in privileged groups (<code>docker</code>, <code>wheel</code>, <code>adm</code>). Filesystem Restrict the project directory via POSIX permissions or ACLs. The agent should have no access to other users' files or system directories. Container Run inside a Docker/Podman sandbox. Mount only the project directory. Drop capabilities (<code>--cap-drop=ALL</code>). Disable network if not needed (<code>--network=none</code>). Never mount the Docker socket and do not run privileged containers. Prefer rootless containers. Virtual machine Prefer a dedicated VM with no shared folders, no host passthrough, and no keys to other machines. Network If the agent does not need the internet, disable outbound access entirely. If it does, restrict to specific domains via firewall rules. Resource limits Apply CPU, memory, and disk limits (cgroups/container limits). A runaway loop should not fill disk or consume all RAM. Self-modification Make instruction files read-only. <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, and <code>.context/CONSTITUTION.md</code> should not be writable by the agent user. If using project-local hooks, protect those too. <p>A minimal Docker setup for overnight runs:</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh 2>&1 | tee /tmp/loop.log\n</code></pre> <p>Defense in Depth</p> <p>Use multiple layers together: OS-level isolation (the boundary the agent cannot cross), a permission allowlist (what Claude Code will do within that boundary), and <code>CONSTITUTION.md</code> (a soft nudge for the common case).</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-5-generate-the-loop-script","level":3,"title":"Step 5: Generate the Loop Script","text":"<p>Use <code>ctx loop</code> to generate a <code>loop.sh</code> tailored to your AI tool:</p> <pre><code># Generate for Claude Code with a 10-iteration cap\nctx loop --tool claude --max-iterations 10\n\n# Generate for Aider\nctx loop --tool aider --max-iterations 10\n\n# Custom prompt file and output filename\nctx loop --tool claude --prompt my-prompt.md --output my-loop.sh\n</code></pre> <p>The generated script reads <code>.context/loop.md</code>, runs the tool, checks for completion signals, and loops until done or the cap is reached.</p> <p>You can also use the <code>/ctx-loop</code> skill from inside Claude Code.</p> <p>A Shell Loop Is the Best Practice</p> <p>The shell loop approach spawns a fresh AI process each iteration, so the only state that carries between iterations is what lives in <code>.context/</code> and git.</p> <p>Claude Code's built-in <code>/loop</code> runs iterations within the same session, which can allow context window state to leak between iterations. This can be convenient for short runs, but it is less reliable for unattended loops. </p> <p>See Shell Loop vs Built-in Loop for details.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-6-run-with-watch-mode","level":3,"title":"Step 6: Run with Watch Mode","text":"<p>Open two terminals. In the first, run the loop. In the second, run <code>ctx watch</code> to process context updates from the AI output.</p> <pre><code># Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n</code></pre> <p>The watch command parses XML context-update commands from the AI output and applies them:</p> <pre><code><context-update type=\"complete\">user registration</context-update>\n<context-update type=\"learning\"\n  context=\"Setting up user registration\"\n  lesson=\"Email verification needs SMTP configured\"\n  application=\"Add SMTP setup to deployment checklist\"\n>SMTP Requirement</context-update>\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-7-completion-signals-end-the-loop","level":3,"title":"Step 7: Completion Signals End the Loop","text":"<p>The generated script checks for one completion signal per run. By default this is <code>SYSTEM_CONVERGED</code>. You can change it with the <code>--completion</code> flag:</p> <pre><code>ctx loop --tool claude --completion BOOTSTRAP_COMPLETE --max-iterations 5\n</code></pre> <p>The following signals are conventions used in <code>.context/loop.md</code>:</p> Signal Convention How the script handles it <code>SYSTEM_CONVERGED</code> All tasks in <code>TASKS.md</code> are done Detected by default (<code>--completion</code> default value) <code>SYSTEM_BLOCKED</code> Agent cannot proceed Only detected if you set <code>--completion</code> to this <code>BOOTSTRAP_COMPLETE</code> Initial scaffolding done Only detected if you set <code>--completion</code> to this <p>The script uses <code>grep -q</code> on the agent's output, so any string works as a signal. If you need to detect multiple signals in one run, edit the generated <code>loop.sh</code> to add additional <code>grep</code> checks.</p> <p>When you return in the morning, check the log and the context files:</p> <pre><code>tail -100 /tmp/loop.log\nctx status\nctx load\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-8-use-ctx-implement-for-plan-execution","level":3,"title":"Step 8: Use <code>/ctx-implement</code> for Plan Execution","text":"<p>Within each iteration, the agent can use <code>/ctx-implement</code> to execute multi-step plans with verification between steps. This is useful for complex tasks that touch multiple files.</p> <p>The skill breaks a plan into atomic, verifiable steps:</p> <pre><code>Step 1/6: Create user model .................. OK\nStep 2/6: Add database migration ............. OK\nStep 3/6: Implement registration handler ..... OK\nStep 4/6: Write unit tests ................... OK\nStep 5/6: Run test suite ..................... FAIL\n  -> Fixed: missing test dependency\n  -> Re-verify ............................... OK\nStep 6/6: Update TASKS.md .................... OK\n</code></pre> <p>Each step is verified (build, test, syntax check) before moving to the next.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A typical overnight run:</p> <pre><code>ctx init\n# Edit TASKS.md and .context/loop.md\n\nctx loop --tool claude --max-iterations 20\n\n./loop.sh 2>&1 | tee /tmp/loop.log &\nctx watch --log /tmp/loop.log\n\n# Next morning:\nctx status\nctx load\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#why-autonomous-loops-work-proactive-context-persistence","level":2,"title":"Why Autonomous Loops Work: Proactive Context Persistence","text":"<p>The autonomous loop pattern works because the agent persists context as part of the job.</p> <p>Without proactive persistence, the loop degrades into disconnected iterations that repeat work, contradict decisions, and lose track of progress. The agent cannot rely on a human to prompt it. It must treat context updates as part of every task, not as a separate step.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-agent-playbook-self-check-drives-autonomy","level":3,"title":"The Agent Playbook Self-Check Drives Autonomy","text":"<p>The Agent Playbook defines a self-check prompt the agent applies continuously:</p> <pre><code>\"If this session ended right now, would the next session know what happened?\"\n</code></pre> <p>In interactive sessions, a human might remind the agent to save context. When the agent runs unattended, nobody is there to remind it.</p> <p>The self-check becomes the trigger: after meaningful work, the agent evaluates whether the context files reflect reality and updates them immediately if they do not.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#what-the-agent-does-proactively-between-iterations","level":3,"title":"What the Agent Does Proactively between Iterations","text":"<p>At milestones within an iteration, the agent persists without waiting for instructions:</p> Milestone What the agent does proactively Completes a task Marks it <code>[x]</code> in <code>TASKS.md</code> Discovers a gotcha Adds it to <code>LEARNINGS.md</code> Makes a design choice Records it in <code>DECISIONS.md</code> with rationale Identifies follow-up work Creates new tasks in <code>TASKS.md</code> with <code>#added</code> timestamp Hits an unexpected failure Documents the root cause before moving on","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#example-what-proactive-persistence-looks-like","level":3,"title":"Example: What Proactive Persistence Looks Like","text":"<p>Here is what the agent does during a single iteration beyond writing code:</p> <pre><code>Iteration 4:\n  1. Read TASKS.md -> pick \"Add email verification\" (#priority:high)\n  2. Add #started:2026-01-25-030012 to the task\n  3. Implement the feature (code, tests, docs if needed)\n  4. Tests pass -> mark task [x]\n  5. Add learning: \"SMTP config must be set before verification handler registers. Order matters in init().\"\n  6. Add decision: \"Use token-based verification links (not codes) because links work better in automated tests.\"\n  7. Create follow-up task: \"Add rate limiting to verification endpoint\" #added:...\n  8. Commit all changes including `.context/`\n  9. No signal emitted -> loop continues to iteration 5\n</code></pre> <p>Steps 2, 4, 5, 6, and 7 are proactive context persistence: </p> <p>The agent was not asked to do any of them.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#context-persistence-at-milestones","level":3,"title":"Context Persistence at Milestones","text":"<p>For long autonomous runs, the agent persists context at natural boundaries, often at phase transitions or after completing a cluster of related tasks. It updates <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>LEARNINGS.md</code> as it goes.</p> <p>If the loop crashes at 4 AM, the context files tell you exactly where to resume. You can also use <code>ctx journal source</code> to review the session transcripts.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-persistence-contract","level":3,"title":"The Persistence Contract","text":"<p>The autonomous loop has an implicit contract:</p> <ol> <li>Every iteration reads context: <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code></li> <li>Every iteration writes context: task updates, new learnings, decisions</li> <li>Every commit includes <code>.context/</code> so the next iteration sees changes</li> <li>Context stays current: if the loop stopped right now, nothing important is lost</li> </ol> <p>Break any part of this contract and the loop degrades.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tips","level":2,"title":"Tips","text":"<p>Markdown Is Not Enforcement</p> <p>Your real guardrails are permissions and isolation, not Markdown. <code>CONSTITUTION.md</code> can nudge the agent, but it is probabilistic. </p> <p>The permission allowlist and OS isolation are deterministic:</p> <p>For unattended runs, trust the sandbox and the allowlist, not the prose.</p> <ul> <li>Start with a small iteration cap. Use <code>--max-iterations 5</code> on your first run.</li> <li>Keep tasks atomic. Each task should be completable in a single iteration.</li> <li>Check signal discipline. If the loop runs forever, the agent is not emitting   <code>SYSTEM_CONVERGED</code> or <code>SYSTEM_BLOCKED</code>. Make the signal requirement explicit   in <code>.context/loop.md</code>.</li> <li>Commit after context updates. Finish code, update <code>.context/</code>, commit including   <code>.context/</code>, then signal.</li> <li>Set up webhook notifications to get notified   when the loop completes, hits max iterations, or when hooks fire nudges.   The generated loop script includes <code>ctx hook notify</code> calls automatically.</li> </ul>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#next-up","level":2,"title":"Next Up","text":"<p>When to Use a Team of Agents →: Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#see-also","level":2,"title":"See Also","text":"<ul> <li>Autonomous Loops: loop pattern, prompt templates, troubleshooting</li> <li>CLI Reference: <code>ctx</code> loop: flags and options</li> <li>CLI Reference: <code>ctx</code> watch: watch mode details</li> <li>CLI Reference: <code>ctx</code> init: init flags</li> <li>The Complete Session: interactive workflow</li> <li>Tracking Work Across Sessions: structuring TASKS.md</li> </ul>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/build-a-knowledge-base/","level":1,"title":"Build a Knowledge Base","text":"","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#the-problem","level":2,"title":"The Problem","text":"<p>You are doing knowledge-shaped work (vendor-spec analysis, a research project, a post-incident review, domain modeling) and the standard five context files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>, <code>CONSTITUTION.md</code>) don't fit. Because those files are tuned for code-development context, not for evidence-tracked knowledge with confidence bands, contradictions, and external citations.</p> <p>You need a place where:</p> <ul> <li>Every claim is pinned to a source you can re-verify.</li> <li>Topics grow into folders as they earn their depth.</li> <li>Two passes against the same source don't silently disagree.</li> <li>The next session knows what's incomplete, not just what's done.</li> </ul> <p>That's what the editorial pipeline is for.</p> <p>Prefer Skills to Raw Commands</p> <p>The pipeline is driven by skills (<code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, etc.). The CLI form (<code>ctx kb ingest</code>, etc.) exists for scripting and for non-Claude environments; the skill is the natural surface.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#tldr","level":2,"title":"TL;DR","text":"<pre><code>git init && ctx init                    # lays down the kb + ingest tree\nctx kb topic new \"Cursor Hooks\"         # scaffold a topic folder\n/ctx-kb-ingest ./docs/cursor-hooks.md \"cursor hooks\" # editorial pass\n/ctx-kb-ask \"does the kb say hooks fire async?\"      # grounded Q&A\n/ctx-wrap-up                            # ceremony; delegates to /ctx-handover\n                                        # for the per-session handover\n</code></pre>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> Command Scaffold <code>.context/kb/</code>, <code>.context/ingest/</code>, etc. <code>ctx kb topic new <name></code> Command Sole writer of topic-page scaffolds (folder shape) <code>ctx kb note \"<text>\"</code> Command Lightweight capture into <code>.context/ingest/findings.md</code> <code>ctx kb reindex</code> Command Refresh the <code>CTX:KB:TOPICS</code> managed block <code>ctx handover write</code> Command Per-session handover with closeout fold <code>/ctx-kb-ingest</code> Skill Mode-aware editorial pass (topic-page/triage/evidence) <code>/ctx-kb-ask</code> Skill Q&A grounded in the kb <code>/ctx-kb-site-review</code> Skill Mechanical structural audit <code>/ctx-kb-ground</code> Skill Re-grounding against external sources <code>/ctx-kb-note</code> Skill Capture a finding for the next ingest pass <code>/ctx-wrap-up</code> Skill End-of-session ceremony; delegates to the handover step","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-0-initialize-and-declare-scope","level":2,"title":"Step 0: Initialize and Declare Scope","text":"<pre><code>git init && ctx init\n</code></pre> <p><code>ctx init</code> lays down the editorial scaffolding alongside the standard context files:</p> <pre><code>.context/\n├── kb/\n│   ├── index.md\n│   └── topics/.gitkeep\n├── ingest/\n│   ├── KB-RULES.md             # editorial constitution\n│   ├── 00-GROUND.md\n│   ├── 30-INGEST.md\n│   ├── 40-ASK.md\n│   ├── 50-SITE_REVIEW.md\n│   ├── OPERATOR.md\n│   ├── PROMPT.md               # hand-fallback router\n│   ├── closeouts/.gitkeep\n│   └── schemas/\n│       └── *.md                # 10 schema templates\n└── handovers/.gitkeep\n</code></pre> <p>Open <code>.context/kb/index.md</code> and replace the placeholder <code>## Scope</code> paragraph with a one-paragraph statement of what this kb covers and what it does not. <code>/ctx-kb-ingest</code> refuses to run against an undeclared kb; scope is the precondition.</p> <p>Git is required</p> <p><code>ctx init</code> now refuses to run without <code>.git/</code>. The editorial pipeline's provenance (closeout <code>sha</code>/<code>branch</code>, evidence-index in-repo SHA pins) depends on it. Run <code>git init</code> first if the project does not already have one.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-1-scaffold-a-topic","level":2,"title":"Step 1: Scaffold a Topic","text":"<p>Topic pages live in folders, not flat files:</p> <pre><code>ctx kb topic new \"Cursor Hooks\"\n</code></pre> <p>This creates <code>.context/kb/topics/cursor-hooks/index.md</code> from the embedded template. The slug is computed by lowercasing + kebab- casing; vendor-namespaced shapes like <code>cursor/hooks</code> are preserved so you can grow into nested topology (<code>topics/cursor/hooks/</code>, <code>topics/cursor/skills/</code>, <code>topics/cursor/rules/</code>) without breaking citations.</p> <p><code>ctx kb topic new</code> is the sole writer of topic-page scaffolds. Skills invoke this command rather than synthesize a scaffold by hand; the embedded template is the single source of truth.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-2-run-an-editorial-pass","level":2,"title":"Step 2: Run an Editorial Pass","text":"<pre><code>/ctx-kb-ingest ./inputs/2026-04-12-call.md \"cursor hooks\"\n</code></pre> <p>The skill begins with a pass-mode declaration:</p> <p>Pass-mode: <code>topic-page</code> Reason: the user supplied one primary source and the intended topic is clear. Definition of done: create or extend <code>kb/topics/cursor-hooks/index.md</code>,  cite EV rows, run <code>ctx kb site build</code>, record cold-reader orientation.</p> <p>Then it:</p> <ol> <li>Resolves sources (paths / URLs / MCP resources) and updates    the source-coverage ledger at    <code>.context/kb/source-coverage.md</code> (a state machine across all    sources the kb has touched).</li> <li>Scans for adjacent incomplete topics in the ledger and    surfaces them so the new page acknowledges sibling gaps.</li> <li>Synthesizes prose section by section into the topic page,    minting <code>EV-###</code> rows in <code>evidence-index.md</code> for every cited    claim.</li> <li>Sets the Confidence floor (the page never claims more    certainty than its weakest cited band).</li> <li>Writes a closeout under    <code>.context/ingest/closeouts/<TS>-ingest-closeout.md</code> with    frontmatter, the cold-reader orientation rubric, and a    ledger-state advance per source.</li> </ol> <p>Three pass modes:</p> <ul> <li><code>topic-page</code> (default): write or extend a topic page.</li> <li><code>triage</code>: admit / skip sources against scope; no <code>EV-###</code> minted.</li> <li><code>evidence-only</code>: mint <code>EV-###</code> rows tagged <code>evidence-only</code>;   do not touch a topic page (explicit-request-only escape hatch).</li> </ul> <p>Mid-pass mode-switching is forbidden: the skill commits to one mode and aborts cleanly if the work no longer fits.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-3-qa-grounded-in-the-kb","level":2,"title":"Step 3: Q&A Grounded in the KB","text":"<pre><code>/ctx-kb-ask \"does the kb say hooks fire async?\"\n</code></pre> <p><code>/ctx-kb-ask</code> reads the kb's prose, cites <code>EV-###</code> rows, and refuses to web-jump. If the kb cannot answer, it opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> and reports the gap, which a future <code>/ctx-kb-ingest</code> pass can close.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-4-audit-re-ground","level":2,"title":"Step 4: Audit + Re-Ground","text":"<pre><code>/ctx-kb-site-review        # mechanical structural audit\n/ctx-kb-ground             # refresh sources listed in grounding-sources.md\n</code></pre> <p><code>site-review</code> coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence (those go through ingest).</p> <p><code>ground</code> reads <code>.context/ingest/grounding-sources.md</code> and runs the equivalent of a fresh fetch + re-extract pass for each listed source, useful when an upstream source has changed.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-5-browse-the-kb-locally","level":2,"title":"Step 5: Browse the KB Locally","text":"<p><code>.context/kb/</code> is a tree of Markdown files: topic pages live under <code>topics/<slug>/index.md</code> and cross-cutting artifacts (<code>glossary.md</code>, <code>evidence-index.md</code>, <code>outstanding-questions.md</code>, <code>domain-decisions.md</code>, <code>contradictions.md</code>, <code>timeline.md</code>, <code>source-map.md</code>, <code>source-coverage.md</code>, <code>relationship-map.md</code>) sit alongside them. Drop a minimal <code>zensical.toml</code> into <code>.context/kb/</code> and hand it to <code>ctx serve</code>:</p> <pre><code>ctx serve .context/kb/\n</code></pre> <p>The KB renders the same way the docs site you are reading right now does. Use the in-place evidence-index links to jump from a topic page to its <code>EV-###</code> rows and back. The site build is read-only: no skill or CLI writes through it.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-6-wrap-up-with-a-handover","level":2,"title":"Step 6: Wrap Up with a Handover","text":"<p>Run <code>/ctx-wrap-up</code> at session end; it owns the ceremony and delegates to the handover step (<code>/ctx-handover</code>) as its final action:</p> <pre><code>/ctx-wrap-up \"Cursor Hooks deep dive\"\n</code></pre> <p>The handover artifact lands at <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). It folds postdated closeouts into a <code>## Folded closeouts</code> section and archives the source closeout files under <code>.context/archive/closeouts/</code>. The next session's <code>/ctx-remember</code> reads the latest handover and folds any closeouts whose <code>generated-at</code> postdates it.</p> <p>The legitimate direct-invocation cases for <code>/ctx-handover</code> are <code>--no-fold</code> for a mid-session checkpoint, or recovery when a prior session ended before its wrap-up step. For the underlying CLI, see <code>ctx handover write</code>.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#how-it-ladders-together","level":2,"title":"How It Ladders Together","text":"<pre><code>sources you supply\n       │\n       ▼\n/ctx-kb-ingest (mode-declared, source-coverage advanced)\n       │\n       ├──▶ topic-page  ──▶ .context/kb/topics/<slug>/index.md\n       ├──▶ evidence    ──▶ .context/kb/evidence-index.md (EV-###)\n       ├──▶ side rails  ──▶ glossary.md, contradictions.md,\n       │                    outstanding-questions.md, timeline.md,\n       │                    source-map.md, relationship-map.md\n       └──▶ closeout    ──▶ .context/ingest/closeouts/<TS>-...md\n                               │\n                               ▼\n                       (next session)\n                               │\n                               ▼\n                  /ctx-wrap-up → /ctx-handover folds\n                  → .context/handovers/<TS>-<slug>.md\n                  + archives source closeouts under\n                  .context/archive/closeouts/\n                               │\n                               ▼\n                  /ctx-remember reads handover + postdated\n                  unfolded closeouts as the recall surface\n</code></pre>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#what-the-editorial-pipeline-is-not","level":2,"title":"What the Editorial Pipeline Is NOT","text":"<ul> <li>Not a substitute for <code>DECISIONS.md</code>. Project-level   architectural decisions stay in <code>.context/DECISIONS.md</code>. The   kb's <code>domain-decisions.md</code> is a kb-scoped artifact (different   schema, different write authority, different lifecycle).</li> <li>Not a substitute for <code>LEARNINGS.md</code>. Learnings have author   intent; kb claims have evidence backing. They're different   truth bases; do not cross-feed.</li> <li>Not for casual notes. Use <code>/ctx-kb-note</code> or <code>ctx kb note   \"<text>\"</code> to park a finding for the next ingest pass.</li> </ul>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#reference","level":2,"title":"Reference","text":"<ul> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code> (laid   down by <code>ctx init</code>)</li> <li>Skills reference:   <code>/ctx-kb-ingest</code>,   <code>/ctx-kb-ask</code>,   <code>/ctx-kb-site-review</code>,   <code>/ctx-kb-ground</code>,   <code>/ctx-kb-note</code>,   <code>/ctx-handover</code></li> <li>Related recipes:   Typical KB Session,   Recover an Aborted Session</li> </ul>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/building-skills/","level":1,"title":"Building Project Skills","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-problem","level":2,"title":"The Problem","text":"<p>You have workflows your agent needs to repeat across sessions: a deploy checklist, a review protocol, a release process. Each time, you re-explain the steps. The agent gets it mostly right but forgets edge cases you corrected last time.</p> <p>Skills solve this by encoding domain knowledge into a reusable document the agent loads automatically when triggered. A skill is not code - it is a structured prompt that captures what took you sessions to learn.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-skill-create\n</code></pre> <p>The skill-creator walks you through: identify a repeating workflow, draft a skill, test with realistic prompts, iterate until it triggers correctly and produces good output.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-skill-create</code> Skill Interactive skill creation and improvement workflow <code>ctx init</code> Command Deploys template skills to <code>.claude/skills/</code> on first setup","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-1-identify-a-repeating-pattern","level":3,"title":"Step 1: Identify a Repeating Pattern","text":"<p>Good skill candidates:</p> <ul> <li>Checklists you repeat: deploy steps, release prep, code review</li> <li>Decisions the agent gets wrong: if you keep correcting the same   behavior, encode the correction</li> <li>Multi-step workflows: anything with a sequence of commands and   conditional branches</li> <li>Domain knowledge: project-specific terminology, architecture   constraints, or conventions the agent cannot infer from code alone</li> </ul> <p>Not good candidates: one-off instructions, things the platform already handles (file editing, git operations), or tasks too narrow to reuse.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-2-create-the-skill","level":3,"title":"Step 2: Create the Skill","text":"<p>Invoke the skill-creator:</p> <pre><code>You: \"I want a skill for our deploy process\"\n\nAgent: [Asks about the workflow: what steps, what tools,\n        what edge cases, what the output should look like]\n</code></pre> <p>Or capture a workflow you just did:</p> <pre><code>You: \"Turn what we just did into a skill\"\n\nAgent: [Extracts the steps from conversation history,\n        confirms understanding, drafts the skill]\n</code></pre> <p>The skill-creator produces a <code>SKILL.md</code> file in <code>.claude/skills/your-skill/</code>.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-3-test-with-realistic-prompts","level":3,"title":"Step 3: Test with Realistic Prompts","text":"<p>The skill-creator proposes 2-3 test prompts - the kind of thing a real user would say. It runs each one and shows the result alongside a baseline (same prompt without the skill) so you can compare.</p> <pre><code>Agent: \"Here are test prompts I'd try:\n        1. 'Deploy to staging'\n        2. 'Ship the hotfix'\n        3. 'Run the release checklist'\n        Want to adjust these?\"\n</code></pre>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-4-iterate-on-the-description","level":3,"title":"Step 4: Iterate on the Description","text":"<p>The <code>description</code> field in frontmatter determines when a skill triggers. Claude tends to undertrigger - descriptions need to be specific and slightly \"pushy\":</p> <pre><code># Weak - too vague, will undertrigger\ndescription: \"Use for deployments\"\n\n# Strong - covers situations and synonyms\ndescription: >-\n  Use when deploying to staging or production, running the release\n  checklist, or when the user says 'ship it', 'deploy this', or\n  'push to prod'. Also use after merging to main when a deploy\n  is expected.\n</code></pre> <p>The skill-creator helps you tune this iteratively.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-5-deploy-as-template-optional","level":3,"title":"Step 5: Deploy as Template (Optional)","text":"<p>If the skill should be available to all projects (not just this one), place it in <code>internal/assets/claude/skills/</code> so <code>ctx init</code> deploys it to new projects automatically.</p> <p>Most project-specific skills stay in <code>.claude/skills/</code> and travel with the repo.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#skill-anatomy","level":2,"title":"Skill Anatomy","text":"<pre><code>my-skill/\n  SKILL.md         # Required: frontmatter + instructions (<500 lines)\n  scripts/         # Optional: deterministic code the skill can execute\n  references/      # Optional: detail loaded on demand (not always)\n  assets/          # Optional: output templates, not loaded into context\n</code></pre> <p>Key sections in <code>SKILL.md</code>:</p> Section Purpose Required? Frontmatter Name, description (trigger) Yes When to Use Positive triggers Yes When NOT to Use Prevents false activations Yes Process Steps and commands Yes Examples Good/bad output pairs Recommended Quality Checklist Verify before reporting completion For complex skills","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tips","level":2,"title":"Tips","text":"<ul> <li>Description is everything. A great skill with a vague description   never fires. Spend time on trigger coverage - synonyms, concrete   situations, edge cases.</li> <li>Stay under 500 lines. If your skill is growing past this, move   detail into <code>references/</code> files and point to them from <code>SKILL.md</code>.</li> <li>Do not duplicate the platform. If the agent already knows how to   do something (edit files, run git commands), do not restate it. Tag   paragraphs as Expert/Activation/Redundant and delete Redundant ones.</li> <li>Explain why, not just what. \"Sort by date because users want   recent results first\" beats \"ALWAYS sort by date.\" The agent   generalizes from reasoning better than from rigid rules.</li> <li>Test negative triggers. Make sure the skill does not fire on   unrelated prompts. A skill that activates too broadly becomes noise.</li> </ul>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#next-up","level":2,"title":"Next Up","text":"<p>Parallel Agent Development with Git Worktrees ->: Split work across multiple agents using git worktrees.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: full listing of all bundled and   project-local skills</li> <li>Guide Your Agent: how commands, skills, and   conversational patterns work together</li> <li>Design Before Coding: the four-skill chain for   front-loading design work</li> </ul>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/claude-code-permissions/","level":1,"title":"Claude Code Permission Hygiene","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code's <code>.claude/settings.local.json</code> controls what the agent can do without asking. Over time, this file accumulates one-off permissions from individual sessions: Exact commands with hardcoded paths, duplicate entries, and stale skill references. </p> <p>A noisy \"allowlist\" makes it harder to spot dangerous permissions and  increases the surface area for unintended behavior.</p> <p>Since <code>settings.local.json</code> is <code>.gitignore</code>d, it drifts independently of your codebase. There is no PR review, no CI check: just whatever you clicked \"Allow\" on.</p> <p>This recipe shows what a well-maintained permission file looks like and how to keep it clean.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                            # seeds safe defaults\n/ctx-drift                          # detects missing/stale permissions\n/ctx-permission-sanitize               # audits for dangerous patterns\n</code></pre> <p>See Recommended Defaults for the full list.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx init</code> Populates default <code>ctx</code> permissions <code>/ctx-drift</code> Detects missing or stale permission entries <code>/ctx-permission-sanitize</code> Audits for dangerous patterns (security-focused)","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#recommended-defaults","level":2,"title":"Recommended Defaults","text":"<p>After running <code>ctx init</code>, your <code>settings.local.json</code> will have the <code>ctx</code> defaults pre-populated. Here is an opinionated safe starting point for a Go project using <code>ctx</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(/tmp/ctx-*:*)\",\n      \"Bash(CGO_ENABLED=0 go build:*)\",\n      \"Bash(CGO_ENABLED=0 go test:*)\",\n      \"Bash(ctx:*)\",\n      \"Bash(git add:*)\",\n      \"Bash(git branch:*)\",\n      \"Bash(git check-ignore:*)\",\n      \"Bash(git checkout:*)\",\n      \"Bash(git commit:*)\",\n      \"Bash(git diff:*)\",\n      \"Bash(git log:*)\",\n      \"Bash(git remote:*)\",\n      \"Bash(git restore:*)\",\n      \"Bash(git show:*)\",\n      \"Bash(git stash:*)\",\n      \"Bash(git status:*)\",\n      \"Bash(git tag:*)\",\n      \"Bash(go build:*)\",\n      \"Bash(go fmt:*)\",\n      \"Bash(go test:*)\",\n      \"Bash(go vet:*)\",\n      \"Bash(golangci-lint run:*)\",\n      \"Bash(grep:*)\",\n      \"Bash(ls:*)\",\n      \"Bash(make:*)\",\n      \"Skill(ctx-convention-add)\",\n      \"Skill(ctx-decision-add)\",\n      \"Skill(ctx-learning-add)\",\n      \"Skill(ctx-task-add)\",\n      \"Skill(ctx-agent)\",\n      \"Skill(ctx-archive)\",\n      \"Skill(ctx-blog)\",\n      \"Skill(ctx-blog-changelog)\",\n      \"Skill(absorb)\",\n      \"Skill(ctx-commit)\",\n      \"Skill(ctx-drift)\",\n      \"Skill(ctx-implement)\",\n      \"Skill(ctx-journal-enrich)\",\n      \"Skill(ctx-journal-enrich-all)\",\n      \"Skill(ctx-loop)\",\n      \"Skill(ctx-next)\",\n      \"Skill(ctx-pad)\",\n      \"Skill(ctx-prompt-audit)\",\n      \"Skill(ctx-history)\",\n      \"Skill(ctx-reflect)\",\n      \"Skill(ctx-remember)\",\n      \"Skill(ctx-status)\",\n      \"Skill(ctx-worktree)\",\n      \"WebSearch\"\n    ],\n    \"deny\": [\n      \"Bash(sudo *)\",\n      \"Bash(git push *)\",\n      \"Bash(git push)\",\n      \"Bash(rm -rf /*)\",\n      \"Bash(rm -rf ~*)\",\n      \"Bash(curl *)\",\n      \"Bash(wget *)\",\n      \"Bash(chmod 777 *)\",\n      \"Read(**/.env)\",\n      \"Read(**/.env.*)\",\n      \"Read(**/*credentials*)\",\n      \"Read(**/*secret*)\",\n      \"Read(**/*.pem)\",\n      \"Read(**/*.key)\",\n      \"Edit(**/.env)\",\n      \"Edit(**/.env.*)\"\n    ]\n  }\n}\n</code></pre> <p>This Is a Starting Point, Not a Mandate</p> <p>Your project may need more or fewer entries. </p> <p>The goal is intentional permissions: Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#design-principles","level":3,"title":"Design Principles","text":"<p>Use wildcards for trusted binaries: If you trust the binary (your own project's CLI, <code>make</code>, <code>go</code>), a single wildcard like <code>Bash(ctx:*)</code> beats twenty subcommand entries. It reduces noise and means new subcommands work without re-prompting.</p> <p>Keep <code>git</code> commands granular: Unlike <code>ctx</code> or <code>make</code>, git has both safe commands (<code>git log</code>, <code>git status</code>) and destructive ones (<code>git reset --hard</code>, <code>git clean -f</code>). Listing safe commands individually prevents accidentally pre-approving dangerous ones.</p> <p>Pre-approve all <code>ctx-</code> skills: Skills shipped with <code>ctx</code> (<code>Skill(ctx-*)</code>) are safe to pre-approve. They are part of your project and you control their content. This prevents the agent from prompting on every skill invocation.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#default-deny-rules","level":3,"title":"Default Deny Rules","text":"<p><code>ctx init</code> automatically populates <code>permissions.deny</code> with rules that block dangerous operations. Deny rules are evaluated before allow rules: A denied pattern always prompts the user, even if it also matches an allow entry.</p> <p>The defaults block:</p> Pattern Why <code>Bash(sudo *)</code> Cannot enter password; will hang <code>Bash(git push *)</code> Must be explicit user action <code>Bash(rm -rf /*)</code> etc. Recursive delete of system/home directories <code>Bash(curl *)</code> / <code>wget</code> Arbitrary network requests <code>Bash(chmod 777 *)</code> World-writable permissions <code>Read/Edit(**/.env*)</code> Secrets and credentials <code>Read(**/*.pem, *.key)</code> Private keys <p>Read/Edit Deny Rules</p> <p><code>Read()</code> and <code>Edit()</code> deny rules have known upstream enforcement issues (<code>claude-code#6631,#24846</code>). </p> <p>They are included as defense-in-depth and intent documentation.</p> <p>Blocked by default deny rules: no action needed, <code>ctx init</code> handles these:</p> Pattern Risk <code>Bash(git push:*)</code> Must be explicit user action <code>Bash(sudo:*)</code> Privilege escalation <code>Bash(rm -rf:*)</code> Recursive delete with no confirmation <code>Bash(curl:*)</code> / <code>Bash(wget:*)</code> Arbitrary network requests <p>Requires manual discipline: Never add these to <code>allow</code>:</p> Pattern Risk <code>Bash(git reset:*)</code> Can discard uncommitted work <code>Bash(git clean:*)</code> Deletes untracked files <code>Skill(ctx-permission-sanitize)</code> Edits this file: self-modification vector <code>Skill(release)</code> Runs the release pipeline: high impact","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#hooks-regex-safety-net","level":2,"title":"Hooks: Regex Safety Net","text":"<p>Deny rules handle prefix-based blocking natively. Hooks complement them by catching patterns that require regex matching: Things deny rules can't express.</p> <p>The <code>ctx</code> plugin ships these blocking hooks:</p> Hook What it blocks <code>ctx system block-non-path-ctx</code> Running <code>ctx</code> from wrong path <p>Project-local hooks (not part of the plugin) catch regex edge cases:</p> Hook What it blocks <code>block-dangerous-commands.sh</code> Mid-command <code>sudo</code>/<code>git push</code> (after <code>&&</code>), copies to bin dirs, absolute-path <code>ctx</code> <p>Pre-Approved + Hook-Blocked = Silent Block</p> <p>If you pre-approve a command that a hook blocks, the user never sees the confirmation dialog. The agent gets a block response and must handle it, which is confusing.</p> <p>It's better not to pre-approve commands that hooks are designed to intercept.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-maintenance-workflow","level":2,"title":"The Maintenance Workflow","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#after-busy-sessions","level":3,"title":"After Busy Sessions","text":"<p>Permissions accumulate fastest during debugging and exploration sessions. After a session where you clicked \"Allow\" many times:</p> <ol> <li>Open <code>.claude/settings.local.json</code> in your editor;</li> <li>Look for entries at the bottom of the allowlist (new entries append there);</li> <li>Delete anything that looks session-specific:<ul> <li>Exact commands with hardcoded paths,</li> <li>Commands with literal string arguments,</li> <li>Entries that duplicate an existing wildcard.</li> </ul> </li> </ol> <p>See the Sanitize Permissions runbook for a step-by-step procedure.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#periodically","level":3,"title":"Periodically","text":"<p>Run <code>/ctx-drift</code> to catch permission drift:</p> <ul> <li>Missing <code>Bash(ctx:*)</code> wildcard;</li> <li>Missing <code>Skill(ctx-*)</code> entries for installed skills;</li> <li>Stale <code>Skill(ctx-*)</code> entries for removed skills;</li> <li>Granular <code>Bash(ctx <subcommand>:*)</code> entries that should be consolidated.</li> </ul> <p>Run <code>/ctx-permission-sanitize</code> to catch security issues:</p> <ul> <li>Hook bypass patterns</li> <li>Destructive commands</li> <li>Overly broad permissions</li> <li>Injection vectors</li> </ul>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#when-adding-new-skills","level":3,"title":"When Adding New Skills","text":"<p>If you create a custom <code>ctx-*</code> skill, add its <code>Skill()</code> entry to the allowlist manually. </p> <p><code>ctx init</code> only populates the default permissions: It won't pick up custom skills.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#golden-image-snapshots","level":3,"title":"Golden Image Snapshots","text":"<p>If manual cleanup is too tedious, use a golden image to automate it: </p> <p>Snapshot a curated permission set, then restore at session start to automatically  drop session-accumulated permissions. See the Permission Snapshots recipe for the full workflow.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#adapting-for-other-languages","level":2,"title":"Adapting for Other Languages","text":"<p>The recommended defaults above are Go-specific. For other stacks, swap the build/test tooling:</p> <p>Node.js / TypeScript:</p> <pre><code>\"Bash(npm run:*)\",\n\"Bash(npm test:*)\",\n\"Bash(npx:*)\",\n\"Bash(node:*)\"\n</code></pre> <p>Python:</p> <pre><code>\"Bash(pytest:*)\",\n\"Bash(python:*)\",\n\"Bash(pip show:*)\",\n\"Bash(ruff:*)\"\n</code></pre> <p>Rust:</p> <pre><code>\"Bash(cargo build:*)\",\n\"Bash(cargo test:*)\",\n\"Bash(cargo clippy:*)\",\n\"Bash(cargo fmt:*)\"\n</code></pre> <p>The <code>ctx</code>, <code>git</code>, and skill entries remain the same across all stacks.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#next-up","level":2,"title":"Next Up","text":"<p>Permission Snapshots →: Save and restore permission baselines for reproducible setups.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#see-also","level":2,"title":"See Also","text":"<ul> <li>Setting Up <code>ctx</code> Across AI Tools: full setup recipe   including <code>settings.local.json</code> creation</li> <li>Context Health: keeping <code>.context/</code> files accurate</li> <li>Sanitize Permissions runbook:   manual cleanup procedure</li> </ul>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/configuration-profiles/","level":1,"title":"Configuration Profiles","text":"","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#configuration-profiles","level":1,"title":"Configuration Profiles","text":"<p>Switch between dev and base runtime configurations without editing <code>.ctxrc</code> by hand. Useful when you want verbose logging and webhook notifications during development, then clean defaults for normal sessions.</p> <p>Uses: <code>ctx config switch</code>, <code>ctx config status</code>, <code>/ctx-config</code></p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#how-it-works","level":2,"title":"How It Works","text":"<p>The <code>ctx</code> repo ships two source profiles committed to git:</p> File Profile Description <code>.ctxrc.base</code> base All defaults, notifications off <code>.ctxrc.dev</code> dev Verbose logging, webhook notifications on <p>The working copy (<code>.ctxrc</code>) is gitignored. Switching profiles copies the source file over <code>.ctxrc</code>, so your runtime configuration is always a clean snapshot of one of the two sources.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#switching-profiles","level":2,"title":"Switching Profiles","text":"<pre><code># Switch to dev (verbose logging, notifications)\nctx config switch dev\n\n# Switch to base (defaults)\nctx config switch base\n\n# Toggle to the opposite profile\nctx config switch\n\n# \"prod\" is an alias for \"base\"\nctx config switch prod\n</code></pre> <p>The detection heuristic checks for an uncommented <code>notify:</code> line in <code>.ctxrc</code>: present means dev, absent means base.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#checking-the-active-profile","level":2,"title":"Checking the Active Profile","text":"<pre><code>ctx config status\n</code></pre> <p>Output examples:</p> <pre><code>active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n</code></pre>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#typical-workflow","level":2,"title":"Typical Workflow","text":"<ol> <li>Start of a debugging session: switch to dev for verbose    logging and webhook notifications so you can trace hook    activity and get push alerts.</li> </ol> <pre><code>ctx config switch dev\n</code></pre> <ol> <li> <p>Work through the issue: hooks log verbosely, webhooks fire    on key events (commits, ceremony nudges, drift warnings).</p> </li> <li> <p>Done debugging: switch back to base to silence the noise.</p> </li> </ol> <pre><code>ctx config switch base\n</code></pre>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#customizing-profiles","level":2,"title":"Customizing Profiles","text":"<p>Edit the source files directly:</p> <ul> <li><code>.ctxrc.dev</code>: add any <code>.ctxrc</code> keys you want active during   development (e.g., <code>log_level: debug</code>, <code>notify.events</code>,   <code>notify.webhook_url</code>).</li> <li><code>.ctxrc.base</code>: keep this minimal. It represents your   \"production\" defaults.</li> </ul> <p>After editing a source file, re-run <code>ctx config switch <profile></code> to apply the changes to the working copy.</p> <p>Commit Your Profiles</p> <p>Both <code>.ctxrc.base</code> and <code>.ctxrc.dev</code> should be committed to git so team members share the same profile definitions. The working copy <code>.ctxrc</code> stays gitignored.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#using-the-skill","level":2,"title":"Using the Skill","text":"<p>In a Claude Code session, say any of:</p> <ul> <li>\"switch to dev mode\"</li> <li>\"switch to base\"</li> <li>\"what profile am I on?\"</li> <li>\"toggle verbose logging\"</li> </ul> <p>The <code>/ctx-config</code> skill handles the rest.</p> <p>See also: <code>ctx config</code> reference, Configuration</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/context-health/","level":1,"title":"Detecting and Fixing Drift","text":"","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> files drift: you rename a package, delete a module, or finish a sprint, and suddenly <code>ARCHITECTURE.md</code> references paths that no longer exist, <code>TASKS.md</code> is 80 percent completed checkboxes, and <code>CONVENTIONS.md</code> describes patterns you stopped using two months ago.</p> <p>Stale context is worse than no context: </p> <p>An AI tool that trusts outdated references will hallucinate confidently.</p> <p>This recipe shows how to detect drift, fix it, and keep your <code>.context/</code> directory lean and accurate.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx drift                      # detect problems\nctx drift --fix                # auto-fix the easy ones\nctx sync --dry-run && ctx sync # reconcile after refactors\nctx compact --archive          # archive old completed tasks\nctx fmt                        # normalize line widths\nctx status                     # verify\n</code></pre> <p>Or just ask your agent: \"Is our context clean?\"</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, every command above fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx drift</code> Command Detect stale paths, missing files, violations <code>ctx drift --fix</code> Command Auto-fix simple issues <code>ctx sync</code> Command Reconcile context with codebase structure <code>ctx compact</code> Command Archive completed tasks, clean up empty sections <code>ctx fmt</code> Command Normalize context files to 80-char line width <code>ctx status</code> Command Quick health overview <code>/ctx-drift</code> Skill Structural plus semantic drift detection <code>/ctx-architecture</code> Skill Refresh <code>ARCHITECTURE.md</code> from actual codebase <code>/ctx-status</code> Skill In-session context summary <code>/ctx-prompt-audit</code> Skill Audit prompt quality and token efficiency","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-workflow","level":2,"title":"The Workflow","text":"<p>The best way to maintain context health is conversational: Ask your agent, guide it, and let it detect problems, explain them, and fix them with your approval. CLI commands exist for CI pipelines, scripting, and fine-grained control. </p> <p>For day-to-day maintenance, talk to your agent.</p> <p>Your Questions Reinforce the Pattern</p> <p>Asking \"is our context clean?\" does two things:</p> <ul> <li>It triggers a drift check right now</li> <li>It reinforces the habit</li> </ul> <p>This is reinforcement, not enforcement.</p> <p>Do not wait for the agent to be proactive on its own: </p> <p>Guide your agent, especially in early sessions.</p> <p>Over time, you will ask less and the agent will start offering more.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-1-ask-your-agent","level":3,"title":"Step 1: Ask Your Agent","text":"<p>The simplest way to check context health:</p> <pre><code>Is our context clean?\nAnything stale?\nHow healthy are our context files?\n</code></pre> <p>Or invoke the skill directly:</p> <pre><code>/ctx-drift\n</code></pre> <p>The agent performs two layers of analysis:</p> <p>Layer 1, structural checks (via <code>ctx drift</code>): Dead paths, missing files, completed task counts, constitution violations. Fast and programmatic.</p> <p>Layer 2, semantic analysis (agent-driven): Does <code>CONVENTIONS.md</code> describe patterns the code no longer follows? Does <code>DECISIONS.md</code> contain entries whose rationale no longer applies? Are there learnings about bugs that are now fixed? This is where the agent adds value the CLI cannot: It reads both context files and source code and compares them.</p> <p>The agent reports both layers together, explains each finding in plain language, and offers to fix what it can.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-2-maintenance-at-session-start","level":3,"title":"Step 2: Maintenance at Session Start","text":"<p>You do not need to ask explicitly. </p> <p>Using Claude Code</p> <p><code>ctx</code> ships with Claude Code hooks that remind the agent  at the right time to take initiative. </p> <p>Checking context health at the session start, offering to persist learnings  before you quit, and flagging drift when it matters. The agent stays  proactive without you having to prompt it:</p> <pre><code>Agent: Good morning. I've loaded the context files. A few things\n       before we start:\n\n       - ARCHITECTURE.md references `pkg/auth/` which is now empty\n       - DECISIONS.md hasn't been updated in 40 days\n       - There are 18 completed tasks ready for archival\n\n       Want me to run a quick maintenance pass, or should we jump\n       straight into today's work?\n</code></pre> <p>☝️️ this is what persistent, initiative-driven sessions feel like when context is  treated as a system instead of a prompt.</p> <p>If the agent does not offer this on its own, a gentle nudge is enough:</p> <pre><code>Anything stale before we start?\nHow's the context looking?\n</code></pre> <p>This turns maintenance from a scheduled chore into a conversation that happens when it matters.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-3-real-time-detection-during-work","level":3,"title":"Step 3: Real-Time Detection during Work","text":"<p>Agents can notice drift while working: When a mismatch is directly in the path of their current task. If an agent reads <code>ARCHITECTURE.md</code> to find where to add a handler and <code>internal/handlers/</code> doesn't exist, it will notice because the stale reference blocks its work:</p> <pre><code>Agent: ARCHITECTURE.md references `internal/handlers/` but that directory\n       doesn't exist. I'll look at the actual source tree to find where\n       handlers live now.\n</code></pre> <p>This happens reliably when the drift intersects the task. What is less reliable is the agent generalizing from one mismatch to \"there might be more stale references; let me run drift detection\" That leap requires the agent to know <code>/ctx-drift</code> exists and to decide the current task should pause for maintenance.</p> <p>If you want that behavior, reinforce it:</p> <pre><code>Good catch. Yes, run /ctx-drift and clean up any other stale references.\n</code></pre> <p>Over time, agents that have seen this pattern will start offering proactively. But do not expect it from a cold start.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-4-archival-and-cleanup","level":3,"title":"Step 4: Archival and Cleanup","text":"<p><code>ctx drift</code> detects when <code>TASKS.md</code> has more than 10 completed items and flags it as a staleness warning. Running <code>ctx drift --fix</code> archives completed tasks automatically. </p> <p>You can also run <code>/ctx-archive</code> to compact on demand.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#knowledge-health-flow","level":3,"title":"Knowledge Health Flow","text":"<p>Over time, LEARNINGS.md and DECISIONS.md accumulate entries that overlap or partially repeat each other. The <code>check-persistence</code> hook detects when entry counts exceed a configurable threshold and surfaces a nudge:</p> <p>\"LEARNINGS.md has 25+ entries. Consider running /ctx-consolidate to merge overlapping items.\"</p> <p>The consolidation workflow:</p> <ol> <li>Review: <code>/ctx-consolidate</code> groups entries by keyword similarity    and presents candidate merges for your approval.</li> <li>Merge: Approved groups are combined into single entries that    preserve the key information from each original.</li> <li>Archive: Originals move to <code>.context/archive/</code>, not deleted --    the full history is preserved in git and the archive directory.</li> <li>Verify: Run <code>ctx drift</code> after consolidation to confirm no    cross-references were broken by the merge.</li> </ol> <p>This replaces ad-hoc cleanup with a repeatable, nudge-driven cycle: detect accumulation, review candidates, merge with approval, archive originals.</p> <p>See also: Knowledge Capture for the recording workflow that feeds into this maintenance cycle.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-doctor-the-superset-check","level":2,"title":"<code>ctx doctor</code>: The Superset Check","text":"<p><code>ctx doctor</code> combines drift detection with hook auditing, configuration checks, event logging status, and token size reporting in a single command. If you want one command that covers structural health, hooks, and state:</p> <pre><code>ctx doctor          # everything in one pass\nctx doctor --json   # machine-readable for scripting\n</code></pre> <p>Use <code>/ctx-doctor</code> Too</p> <p>For agent-driven diagnosis that adds semantic analysis on top of the structural checks, use <code>/ctx-doctor</code>. </p> <p>See the Troubleshooting recipe for the full workflow.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#cli-reference","level":2,"title":"CLI Reference","text":"<p>The conversational approach above uses CLI commands under the hood. When you need direct control, use the commands directly.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift","level":3,"title":"<code>ctx drift</code>","text":"<p>Scan context files for structural problems:</p> <pre><code>ctx drift\n</code></pre> <p>Sample output:</p> <pre><code>Drift Report\n============\n\nWarnings (3):\n  ARCHITECTURE.md:14  path \"internal/api/router.go\" does not exist\n  ARCHITECTURE.md:28  path \"pkg/auth/\" directory is empty\n  CONVENTIONS.md:9    path \"internal/handlers/\" not found\n\nViolations (1):\n  TASKS.md            31 completed tasks (recommend archival)\n\nStaleness:\n  DECISIONS.md        last modified 45 days ago\n  LEARNINGS.md        last modified 32 days ago\n\nExit code: 1 (warnings found)\n</code></pre> Level Meaning Action Warning Stale path references, missing files Fix or remove Violation Constitution rule heuristic failures, heavy clutter Fix soon Staleness Files not updated recently Review content <p>Exit codes: <code>0</code> equals clean, <code>1</code> equals warnings, <code>3</code> equals violations.</p> <p>For CI integration:</p> <pre><code>ctx drift --json | jq '.warnings | length'\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift-fix","level":3,"title":"<code>ctx drift --fix</code>","text":"<p>Auto-fix mechanical issues:</p> <pre><code>ctx drift --fix\n</code></pre> <p>This handles removing dead path references, updating unambiguous renames,  clearing empty sections. Issues requiring judgment are flagged but left for you.</p> <p>Run <code>ctx drift</code> again afterward to confirm what remains.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-sync","level":3,"title":"<code>ctx sync</code>","text":"<p>After a refactor, reconcile context with the actual codebase structure:</p> <pre><code>ctx sync --dry-run   # preview first\nctx sync             # apply\n</code></pre> <p><code>ctx sync</code> scans for structural changes, compares with <code>ARCHITECTURE.md</code>,  checks for new dependencies worth documenting, and identifies context referring  to code that no longer exists.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-compact","level":3,"title":"<code>ctx compact</code>","text":"<p>Consolidate completed tasks and clean up empty sections:</p> <pre><code>ctx compact            # move completed tasks to Completed section,\n                       # remove empty sections\nctx compact --archive  # also archive old tasks to .context/archive/\n</code></pre> <ul> <li>Tasks: moves completed items (with all subtasks done) into the Completed   section of <code>TASKS.md</code></li> <li>All files: removes empty sections left behind</li> <li>With <code>--archive</code>: writes tasks older than 7 days to   <code>.context/archive/tasks-YYYY-MM-DD.md</code></li> </ul> <p>Without <code>--archive</code>, nothing is deleted: Tasks are reorganized in place.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-fmt","level":3,"title":"<code>ctx fmt</code>","text":"<p>Normalize context file line widths:</p> <pre><code>ctx fmt              # wrap long lines to 80 chars\nctx fmt --check      # CI: exit 1 if files need formatting\n</code></pre> <p>Long task descriptions, decision rationale, and learning entries accumulate as single-line entries. <code>ctx fmt</code> wraps them at word boundaries with 2-space continuation indent for list items. Headings, tables, and comments are preserved.</p> <p>Idempotent: safe to run repeatedly.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-status","level":3,"title":"<code>ctx status</code>","text":"<p>Quick health overview:</p> <pre><code>ctx status --verbose\n</code></pre> <p>Shows file counts, token estimates, modification times, and drift warnings in a single glance.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-prompt-audit","level":3,"title":"<code>/ctx-prompt-audit</code>","text":"<p>Checks whether your context files are readable, compact, and token-efficient for the model.</p> <pre><code>/ctx-prompt-audit\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Conversational approach (recommended):</p> <pre><code>Is our context clean?  -> agent runs structural plus semantic checks\nFix what you can       -> agent auto-fixes and proposes edits\nArchive the done tasks -> agent runs ctx compact --archive\nHow's token usage?     -> agent checks ctx status\n</code></pre> <p>CLI approach (for CI, scripts, or direct control):</p> <pre><code>ctx drift                      # 1. Detect problems\nctx drift --fix                # 2. Auto-fix the easy ones\nctx sync --dry-run && ctx sync # 3. Reconcile after refactors\nctx compact --archive          # 4. Archive old completed tasks\nctx fmt                        # 5. Normalize line widths\nctx status                     # 6. Verify\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tips","level":2,"title":"Tips","text":"<p>Agents cross-reference context files with source code during normal work. When drift intersects their current task, they will notice: a renamed package, a deleted directory, a path that doesn't resolve. But they rarely generalize from one mismatch to a full audit on their own. Reinforce the pattern: when an agent mentions a stale reference, ask it to run <code>/ctx-drift</code>. Over time, it starts offering.</p> <p>When an agent says \"this reference looks stale,\" it is usually right.</p> <p>Semantic drift is more damaging than structural drift:  <code>ctx drift</code> catches dead paths. But <code>CONVENTIONS.md</code> describing a pattern your  code stopped following three weeks ago is worse. When you ask  \"is our context clean?\", the agent can do both checks.</p> <p>Use <code>ctx status</code> as a quick check: It shows file counts, token estimates, and drift warnings in a single glance. Good for a fast \"is everything ok?\" before diving into work.</p> <p>Drift detection in CI: add <code>ctx drift --json</code> to your CI pipeline and fail on exit code 3 (violations). This catches constitution-level problems before they reach upstream.</p> <p>Do not over-compact: Completed tasks have historical value. The <code>--archive</code> flag preserves them in <code>.context/archive/</code> so you can search past work without cluttering active context.</p> <p>Sync is cautious by default: Use <code>--dry-run</code> after large refactors, then apply.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#next-up","level":2,"title":"Next Up","text":"<p>Claude Code Permission Hygiene →: Recommended permission defaults and maintenance workflow for Claude Code.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#see-also","level":2,"title":"See Also","text":"<ul> <li>Troubleshooting: full diagnostic workflow using   <code>ctx doctor</code>, event logs, and <code>/ctx-doctor</code></li> <li>Tracking Work Across Sessions: task lifecycle and archival</li> <li>Persisting Decisions, Learnings, and Conventions:   keeping knowledge files current</li> <li>The Complete Session: where maintenance fits in the daily workflow</li> <li>CLI Reference: full flag documentation for all commands</li> <li>Context Files: structure and purpose of each <code>.context/</code> file</li> </ul>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/customizing-hook-messages/","level":1,"title":"Customizing Hook Messages","text":"","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> hooks speak <code>ctx</code>'s language, not your project's. The QA gate says \"lint the ENTIRE project\" and \"make build,\" but your Python project uses <code>pytest</code> and <code>ruff</code>. The post-commit nudge suggests running lints, but your project uses <code>npm test</code>. You could remove the hook entirely, but then you lose the logic (counting, state tracking, adaptive frequency) just to change the words.</p> <p>How do you customize what hooks say without removing what they do?</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx hook message list                     # see all hooks and their messages\nctx hook message show qa-reminder gate    # view the current template\nctx hook message edit qa-reminder gate    # copy default to .context/ for editing\nctx hook message reset qa-reminder gate   # revert to embedded default\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root: hook message overrides live in your <code>.context/</code> directory, so <code>ctx</code> needs to know which one. If you skip the <code>eval</code>, <code>ctx hook message ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose <code>ctx hook message list</code> CLI command Show all hook messages with category and override status <code>ctx hook message show</code> CLI command Print the effective message template <code>ctx hook message edit</code> CLI command Copy embedded default to <code>.context/</code> for editing <code>ctx hook message reset</code> CLI command Delete user override, revert to default","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#how-it-works","level":2,"title":"How It Works","text":"<p>Hook messages use a 3-tier fallback:</p> <ol> <li>User override: <code>.context/hooks/messages/{hook}/{variant}.txt</code></li> <li>Embedded default: compiled into the <code>ctx</code> binary</li> <li>Hardcoded fallback: belt-and-suspenders safety net</li> </ol> <p>The hook logic (when to fire, counting, state tracking, cooldowns) is unchanged. Only the content (what text gets emitted) comes from the template. You customize what the hook says without touching how it decides to speak.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#finding-the-original-templates","level":3,"title":"Finding the Original Templates","text":"<p>The default templates live in the <code>ctx</code> source tree at:</p> <pre><code>internal/assets/hooks/messages/{hook}/{variant}.txt\n</code></pre> <p>You can also browse them on GitHub: <code>internal/assets/hooks/messages/</code></p> <p>Or use <code>ctx hook message show</code> to print any template without digging through source code:</p> <pre><code>ctx hook message show qa-reminder gate        # QA gate instructions\nctx hook message show check-persistence nudge  # persistence nudge\nctx hook message show post-commit nudge        # post-commit reminder\n</code></pre> <p>The <code>show</code> output includes the template source and available variables -- everything you need to write a replacement.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables","level":3,"title":"Template Variables","text":"<p>Some messages use Go <code>text/template</code> variables for dynamic content:</p> <pre><code>No context files updated in {{.PromptsSinceNudge}}+ prompts.\nHave you discovered learnings, made decisions,\nestablished conventions, or completed tasks\nworth persisting?\n</code></pre> <p>The <code>show</code> and <code>edit</code> commands list available variables for each message. When writing a replacement, keep the same <code>{{.VariableName}}</code> placeholders to preserve dynamic content. Variables that you omit render as <code><no value></code>: no error, but the output may look odd.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#intentional-silence","level":3,"title":"Intentional Silence","text":"<p>An empty template file (0 bytes or whitespace-only) means \"don't emit a message\". The hook still runs its logic but produces no output. This lets you silence specific messages without removing the hook from <code>hooks.json</code>.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-python-project-qa-gate","level":2,"title":"Example: Python Project QA Gate","text":"<p>The default QA gate says \"lint the ENTIRE project\" and references <code>make lint</code>. For a Python project, you want <code>pytest</code> and <code>ruff</code>:</p> <pre><code># See the current default\nctx hook message show qa-reminder gate\n\n# Copy it to .context/ for editing\nctx hook message edit qa-reminder gate\n\n# Edit the override\n</code></pre> <p>Replace the content in <code>.context/hooks/messages/qa-reminder/gate.txt</code>:</p> <pre><code>HARD GATE! DO NOT COMMIT without completing ALL of these steps first:\n(1) Run the full test suite: pytest -x\n(2) Run the linter: ruff check .\n(3) Verify a clean working tree\nRun tests and linter BEFORE every git commit, no exceptions.\n</code></pre> <p>The hook still fires on every <code>Edit</code> call. The logic is identical. Only the instructions changed.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-silencing-ceremony-nudges","level":2,"title":"Example: Silencing Ceremony Nudges","text":"<p>The ceremony check nudges you to use <code>/ctx-remember</code> and <code>/ctx-wrap-up</code>. If your team has a different workflow and finds these noisy:</p> <pre><code>ctx hook message edit check-ceremonies both\nctx hook message edit check-ceremonies remember\nctx hook message edit check-ceremonies wrapup\n</code></pre> <p>Then empty each file:</p> <pre><code>echo -n \"\" > .context/hooks/messages/check-ceremonies/both.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/remember.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/wrapup.txt\n</code></pre> <p>The hooks still track ceremony usage internally, but they no longer emit any visible output.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-javascript-project-post-commit","level":2,"title":"Example: JavaScript Project Post-Commit","text":"<p>The default post-commit nudge mentions generic \"lints and tests.\" For a JavaScript project:</p> <pre><code>ctx hook message edit post-commit nudge\n</code></pre> <p>Replace with:</p> <pre><code>Commit succeeded. 1. Offer context capture to the user: Decision (design\nchoice?), Learning (gotcha?), or Neither. 2. Ask the user: \"Want me to\nrun npm test and eslint before you push?\" Do NOT push. The user pushes\nmanually.\n</code></pre>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-two-categories","level":2,"title":"The Two Categories","text":"<p>Not all messages are equal. The <code>list</code> command shows each message's category:</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#customizable-17-messages","level":3,"title":"Customizable (17 Messages)","text":"<p>Messages that are opinions: project-specific wording that benefits from customization. These are the primary targets for override.</p> Hook Variant Description check-freshness stale Technology constant freshness warning check-ceremonies both Both ceremonies missing check-ceremonies remember Start-of-session ceremony check-ceremonies wrapup End-of-session ceremony check-context-size checkpoint Context capacity warning check-context-size oversize Injection oversize nudge check-context-size window Context window usage warning (>80%) check-journal both Unimported sessions + unenriched entries check-journal unenriched Unenriched journal entries check-journal unimported Unimported sessions check-knowledge warning Knowledge file growth check-map-staleness stale Architecture map staleness check-persistence nudge Context persistence nudge post-commit nudge Post-commit context capture qa-reminder gate Pre-commit QA gate","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#ctx-specific-10-messages","level":3,"title":"ctx-Specific (10 Messages)","text":"<p>Messages specific to <code>ctx</code>'s own development workflow. You can customize them, but <code>edit</code> will warn you first.</p> Hook Variant Description block-dangerous-commands cp-to-bin Block copy to bin dirs block-dangerous-commands install-to-local-bin Block copy to ~/.local/bin block-dangerous-commands mid-git-push Block git push block-dangerous-commands mid-sudo Block sudo block-non-path-ctx absolute-path Block absolute path invocation block-non-path-ctx dot-slash Block ./ctx invocation block-non-path-ctx go-run Block go run invocation check-reminders reminders Pending reminders relay check-resources alert Resource pressure alert check-version key-rotation Key rotation nudge check-version mismatch Version mismatch","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables-reference","level":2,"title":"Template Variables Reference","text":"Hook Variant Variables check-freshness stale <code>{{.StaleFiles}}</code> check-context-size checkpoint (none) check-context-size oversize <code>{{.TokenCount}}</code> check-context-size window <code>{{.TokenCount}}</code>, <code>{{.Percentage}}</code> check-ceremonies both, remember, wrapup (none) check-journal both <code>{{.UnimportedCount}}</code>, <code>{{.UnenrichedCount}}</code> check-journal unenriched <code>{{.UnenrichedCount}}</code> check-journal unimported <code>{{.UnimportedCount}}</code> check-knowledge warning <code>{{.FileWarnings}}</code> check-map-staleness stale <code>{{.LastRefreshDate}}</code>, <code>{{.ModuleCount}}</code> check-persistence nudge <code>{{.PromptsSinceNudge}}</code> check-reminders reminders <code>{{.ReminderList}}</code> check-resources alert <code>{{.AlertMessages}}</code> check-version key-rotation <code>{{.KeyAgeDays}}</code> check-version mismatch <code>{{.BinaryVersion}}</code>, <code>{{.PluginVersion}}</code> post-commit nudge (none) qa-reminder gate (none) block-dangerous-commands all variants (none) block-non-path-ctx all variants (none) <p>Templates that reference undefined variables render <code><no value></code>:  no error, graceful degradation.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tips","level":2,"title":"Tips","text":"<ul> <li>Override files are version-controlled: they live in <code>.context/</code>   alongside your other context files. Team members get the same   customized messages.</li> <li>Start with <code>show</code>: always check the current default before editing.   The embedded template is the baseline your override replaces.</li> <li>Use <code>reset</code> to undo: if a customization causes confusion, reset   reverts to the embedded default instantly.</li> <li>Empty file = silence: you don't need to delete the hook. An empty   override file silences the message while preserving the hook's logic.</li> <li>JSON output for scripting: <code>ctx hook message list --json</code> returns   structured data for automation.</li> </ul>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hook Output Patterns: understanding   VERBATIM relays, agent directives, and hard gates</li> <li>Auditing System Hooks: verifying hooks   are running and auditing their output</li> <li>Configuration: project-level settings   via <code>.ctxrc</code></li> </ul>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/design-before-coding/","level":1,"title":"Design Before Coding","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-problem","level":2,"title":"The Problem","text":"<p>You start coding a feature. Halfway through, you realize the approach doesn't handle a key edge case. You refactor. Then you discover the CLI interface doesn't fit the existing patterns. More refactoring.</p> <p>The design work happened during implementation, mixed in with debugging and trial-and-error. The result works, but the spec was never written down, the trade-offs were never recorded, and the next session has no idea why things are shaped this way.</p> <p>How do you front-load design so the implementation is straightforward?</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-brainstorm          # explore the design space\n/ctx-spec                # write the spec document\n/ctx-task-add            # break it into tasks\n/ctx-implement           # execute step-by-step\n</code></pre> <p>Four skills, used in sequence. Each produces an artifact that feeds the next.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-brainstorm</code> Skill Structured design dialogue: explore approaches <code>/ctx-spec</code> Skill Scaffold and fill out a spec from the template <code>/ctx-task-add</code> Skill Add implementation tasks to TASKS.md <code>/ctx-implement</code> Skill Execute a plan step-by-step with verification <code>/ctx-decision-add</code> Skill Record design choices made during brainstorming","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-1-brainstorm-the-design","level":3,"title":"Step 1: Brainstorm the Design","text":"<p>Start with <code>/ctx-brainstorm</code> when you have a rough idea but haven't committed to an approach. The skill follows a gated process:</p> <ol> <li>Understand context: what exists today, what's the gap</li> <li>Clarify the idea: one question at a time, not a wall of questions</li> <li>Surface non-functional requirements: performance, security, compatibility</li> <li>Lock understanding: confirm before exploring solutions</li> <li>Explore 2-3 approaches: with trade-offs for each</li> <li>Stress-test: poke holes in the chosen approach</li> <li>Present the design: detailed enough to implement from</li> </ol> <pre><code>You: \"I want to add RSS feed generation to the blog.\"\n\nAgent: \"Before we design, let me understand: is this for the journal\n  site, the docs site, or both?\"\n\nYou: \"Journal site only.\"\n\nAgent: [asks about update frequency, content scope, existing infra...]\n       [presents 3 approaches with trade-offs]\n\nYou: \"Option 2 - generate at build time.\"\n\nAgent: \"Let me stress-test that. What happens when...\"\n       [probes edge cases]\n       \"Design looks solid. Want me to record the approach as a\n       decision and write a spec?\"\n</code></pre> <p>The brainstorm produces a validated design in conversation. It doesn't produce a file - that's what the spec is for.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-2-write-the-spec","level":3,"title":"Step 2: Write the Spec","text":"<p>Once the design is clear, use <code>/ctx-spec</code> to produce the written artifact. The skill reads the project's spec template (<code>specs/tpl/spec-template.md</code>) and walks through each section:</p> <pre><code>You: /ctx-spec (rss feed generation)\n\nAgent: \"Creating specs/rss-feed.md from template.\"\n\n       \"Problem: What user-visible problem does this solve? Why now?\"\n\nYou: \"Users can't subscribe to blog updates. They have to check\n      the site manually.\"\n\nAgent: [writes the Problem section, moves to Approach]\n\n       \"Approach: High-level, how does this work?\"\n\nYou: \"Generate feed.xml during ctx journal site. Atom format.\"\n\nAgent: [writes Approach, moves to Happy Path, then Edge Cases...]\n</code></pre> <p>The skill spends extra time on Edge Cases and Error Handling: These are where specs earn their value. The skill pushes for at least three edge cases and won't accept \"none\" without a challenge.</p> <p>Sections that don't apply can be skipped. The result is a complete spec at <code>specs/{feature-name}.md</code>.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-3-break-into-tasks","level":3,"title":"Step 3: Break into Tasks","text":"<p>After the spec is written, the skill offers to create tasks:</p> <pre><code>Agent: \"Spec written to specs/rss-feed.md. Want me to break this\n  into tasks in TASKS.md?\"\n\nYou: \"Yes.\"\n\nAgent: [creates a Phase section referencing the spec, adds tasks]\n       \"Added Phase 0.8: RSS/Atom Feed Generation with 4 tasks:\n         - [ ] Implement feed.xml generation in ctx journal site\n         - [ ] Add --feed flag and .ctxrc feed config\n         - [ ] Write tests for feed generation\n         - [ ] Add recipe and update docs\"\n</code></pre> <p>Each task is independently completable and references the spec via <code>Spec: specs/rss-feed.md</code> in the Phase header.</p> <p>You can also invoke <code>/ctx-task-add</code> directly at any point to add individual tasks.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-4-implement-step-by-step","level":3,"title":"Step 4: Implement Step-by-Step","text":"<p>Pick up tasks with <code>/ctx-implement</code>. The skill loads the spec, breaks work into atomic steps, and checkpoints after every 3-5 steps with a build and test verification:</p> <pre><code>You: /ctx-implement (specs/rss-feed.md)\n\nAgent: \"Loaded spec. Breaking into steps:\n         1. Add FeedEntry type to journal package\n         2. Implement feed.xml template\n         3. Wire into site generation pipeline\n         [builds and tests after step 3]\n         4. Add --feed flag to ctx journal site\n         5. Add .ctxrc feed configuration\n         [builds and tests after step 5]\n         ...\"\n</code></pre> <p>If a build or test fails, the agent stops, diagnoses, and fixes before continuing.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#when-to-skip-steps","level":2,"title":"When to Skip Steps","text":"<p>Not every feature needs all four steps. Use your judgment:</p> Situation Start at Vague idea, multiple valid approaches Step 1: Brainstorm Clear approach, need to document it Step 2: Spec Spec already exists, need to plan work Step 3: Tasks Tasks exist, ready to code Step 4: Implement <p>A brainstorm without a spec is fine for small decisions. A spec without a brainstorm is fine when the design is obvious. The full chain is for features complex enough to warrant front-loaded design.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need skill names. Natural language works:</p> You say What happens \"Let's think through this feature\" <code>/ctx-brainstorm</code> \"Spec this out\" <code>/ctx-spec</code> \"Write a design doc for...\" <code>/ctx-spec</code> \"Break this into tasks\" <code>/ctx-task-add</code> \"Implement the spec\" <code>/ctx-implement</code> \"Let's design before we build\" Starts at brainstorm","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tips","level":2,"title":"Tips","text":"<ul> <li>Brainstorm first when uncertain. If you can articulate the approach in   two sentences, skip to spec. If you can't, brainstorm.</li> <li>Specs prevent scope creep. The Non-Goals section is as important as the   approach. Writing down what you won't do keeps implementation focused.</li> <li>Edge cases are the point. A spec that only describes the happy path   isn't a spec - it's a wish. The <code>/ctx-spec</code> skill pushes for at least 3   edge cases because that's where designs break.</li> <li>Record decisions during brainstorming. When you choose between   approaches, the agent offers to persist the trade-off via   <code>/ctx-decision-add</code>. Accept - future sessions need to know why, not   just what.</li> <li>Specs are living documents. Update them when implementation reveals   new constraints. A spec that diverges from reality is worse than no spec.</li> <li>The spec template is customizable. Edit <code>specs/tpl/spec-template.md</code>   to match your project's needs. The <code>/ctx-spec</code> skill reads whatever   template it finds there.</li> </ul>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: /ctx-brainstorm:   structured design dialogue</li> <li>Skills Reference: /ctx-spec:   spec scaffolding from template</li> <li>Skills Reference: /ctx-implement:   step-by-step execution with verification</li> <li>Tracking Work Across Sessions: task lifecycle   and archival</li> <li>Importing Claude Code Plans: turning ephemeral plans   into permanent specs</li> <li>Persisting Decisions, Learnings, and Conventions:   capturing design trade-offs</li> </ul>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/external-context/","level":1,"title":"Keeping Context in a Separate Repo","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> files contain project-specific decisions, learnings, conventions, and tasks. By default, they live in <code>.context/</code> inside the project tree, and that works well when the context can be public.</p> <p>But sometimes you need the context outside the project:</p> <ul> <li>Open-source projects with private context: Your architectural notes,   internal task lists, and scratchpad entries shouldn't ship with the public   repo.</li> <li>Compliance or IP concerns: Context files reference sensitive design   rationale that belongs in a separate access-controlled repository.</li> <li>Personal preference: You want to keep notes separate from code.</li> </ul> <p><code>ctx</code> supports this by letting you point <code>CTX_DIR</code> anywhere. This recipe shows how to set that up and how to tell your AI assistant where to find the context.</p> <p>One <code>.context/</code> per project</p> <p>The parent of the context directory is the project root by contract. <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook all read the codebase at <code>filepath.Dir(ContextDir())</code>. Pointing two projects at the same directory corrupts their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use <code>ctx hub</code>, not a shared <code>.context/</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tldr","level":2,"title":"TL;DR","text":"<p>Create the external context directory, initialize it, and bind it:</p> <pre><code>mkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\ncd ~/repos/myproject\n\n# Bind CTX_DIR to the external location, then init creates files there.\nexport CTX_DIR=~/repos/myproject-context/.context\nctx init\n</code></pre> <p>All <code>ctx</code> commands now use the external directory. If you share the setup across shells, add the <code>export CTX_DIR=...</code> line to your shell rc, or source a per-project <code>.envrc</code> with direnv.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#what-works-what-quietly-degrades","level":2,"title":"What Works, What Quietly Degrades","text":"<p>The single-source-anchor contract states that <code>filepath.Dir(CTX_DIR)</code> is the project root. When the context lives outside the project tree, <code>ctx</code> still resolves correctly for every operation that reads or writes inside <code>.context/</code>. But any operation that scans the codebase scans the wrong tree, and does so silently:</p> Operation Behavior with external <code>.context/</code> <code>ctx status</code>, <code>agent</code>, <code>add</code> ✅ Works. Operates on files inside <code>CTX_DIR</code>. Journal, scratchpad, hub ✅ Works. Same reason. <code>ctx sync</code> ⚠️ Scans the context repo, not the code repo. <code>ctx drift</code> ⚠️ Same. Reports nothing useful. Memory-drift hook (<code>MEMORY.md</code>) ⚠️ Looks for <code>MEMORY.md</code> next to the external <code>.context/</code>, not the code. <p>Nothing errors. The code-aware operations just find an empty or unrelated tree where the project root should be.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#workaround-symlink-the-context-into-the-code-tree","level":3,"title":"Workaround: symlink the <code>.context/</code> into the code tree","text":"<p>If you want both the privacy of an external git repo and working <code>ctx sync</code> / <code>drift</code> / memory-drift, symlink the external <code>.context/</code> into the code repo and point <code>CTX_DIR</code> at the symlink:</p> <pre><code># External repo holds the real files\nmkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\n\n# Symlink it into the code repo\nln -s ~/repos/myproject-context/.context ~/repos/myproject/.context\n\n# Bind CTX_DIR to the symlink path; ctx init will follow it\nexport CTX_DIR=~/repos/myproject/.context\nctx init\n</code></pre> <p>Now <code>filepath.Dir(CTX_DIR)</code> is the code repo, so code-aware operations scan the right tree. The actual files still live in the external repo and commit there. Add <code>.context</code> to the code repo's <code>.gitignore</code> (or <code>.git/info/exclude</code>) so the symlink itself isn't tracked by the code repo.</p> <p>The basename guard is permissive about symlinks: it checks the declared name, not the resolved target, so a <code>.context</code> symlink pointing anywhere is accepted as long as the declared basename is <code>.context</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> CLI command Initialize context directory <code>ctx activate</code> CLI command Emit <code>export CTX_DIR=...</code> for the shell <code>CTX_DIR</code> Env variable Declare context directory per-session <code>.ctxrc</code> Config file Per-project configuration <code>/ctx-status</code> Skill Verify context is loading correctly","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-1-create-the-private-context-repo","level":3,"title":"Step 1: Create the Private Context Repo","text":"<p>Create a separate repository for your context files. This can live anywhere: a private GitHub repo, a shared drive, a sibling directory:</p> <pre><code># Create the context repo\nmkdir -p ~/repos/myproject-context\ncd ~/repos/myproject-context\ngit init\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-2-initialize-ctx-pointing-at-it","level":3,"title":"Step 2: Initialize <code>ctx</code> Pointing at It","text":"<p>From your project root, declare <code>CTX_DIR</code> pointing to the external location, then initialize:</p> <pre><code>cd ~/repos/myproject\nCTX_DIR=~/repos/myproject-context/.context ctx init\n</code></pre> <p>This creates the canonical <code>.context/</code> file set inside <code>~/repos/myproject-context/</code> instead of <code>~/repos/myproject/.context/</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-3-make-it-stick","level":3,"title":"Step 3: Make It Stick","text":"<p>Declaring <code>CTX_DIR</code> on every command is tedious. Pick one of these methods to make the configuration permanent. The context directory itself must be declared via <code>CTX_DIR</code>; <code>.ctxrc</code> does not carry the path.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-a-ctx_dir-environment-variable-recommended","level":4,"title":"Option A: <code>CTX_DIR</code> Environment Variable (Recommended)","text":"<pre><code># Direct path. Works for ctx status / agent / add but degrades\n# code-aware operations. See \"What Works, What Quietly Degrades\".\nexport CTX_DIR=~/repos/myproject-context/.context\n\n# Or, with the symlink approach above, point at the symlink path\n# inside the code repo so code-aware operations stay healthy.\nexport CTX_DIR=~/repos/myproject/.context\n</code></pre> <p>Put either form in your shell profile (<code>~/.bashrc</code>, <code>~/.zshrc</code>) or a direnv <code>.envrc</code>.</p> <p>For a single session, run <code>eval \"$(ctx activate)\"</code> from any directory inside the project where exactly one <code>.context/</code> candidate is visible (the symlink counts). <code>activate</code> does not accept a path argument; bind a specific path by exporting <code>CTX_DIR</code> directly instead.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-b-ctxrc-for-other-settings","level":4,"title":"Option B: <code>.ctxrc</code> for Other Settings","text":"<p>Put any settings (token budget, priority order, freshness files) in a <code>.ctxrc</code> at the project root (<code>dirname(CTX_DIR)</code>), which here is the parent of the external <code>.context/</code>:</p> <pre><code># ~/repos/myproject-context/.ctxrc\ntoken_budget: 16000\n</code></pre> <p><code>.ctxrc</code> is always read from the parent of <code>CTX_DIR</code>, so this file is picked up whenever <code>CTX_DIR</code> points at <code>~/repos/myproject-context/.context</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#resolution","level":4,"title":"Resolution","text":"<p><code>ctx</code> reads the context directory from a single channel: the <code>CTX_DIR</code> environment variable. When <code>CTX_DIR</code> is unset, <code>ctx</code> errors with a \"no context directory specified\" hint pointing at <code>ctx activate</code> and this recipe. When set, the value must be an absolute path with <code>.context</code> as its basename; relative paths and other names are rejected on first use.</p> <p>See Activating a Context Directory for the full recipe.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-4-agent-auto-discovery-via-bootstrap","level":3,"title":"Step 4: Agent Auto-Discovery via Bootstrap","text":"<p>When context lives outside the project tree, your AI assistant needs to know where to find it. The <code>ctx system bootstrap</code> command resolves the configured context directory and communicates it to the agent automatically:</p> <pre><code>$ ctx system bootstrap\nctx system bootstrap\n====================\n\ncontext_dir: /home/user/repos/myproject-context/.context\n\nFiles:\n  CONSTITUTION.md, TASKS.md, DECISIONS.md, ...\n</code></pre> <p>The <code>CLAUDE.md</code> template generated by <code>ctx init</code> already instructs the agent to run <code>ctx system bootstrap</code> at session start. Because <code>CTX_DIR</code> is inherited by child processes, your agent picks up the external path automatically.</p> <p>Here is the relevant section from <code>CLAUDE.md</code> for reference:</p> <pre><code><!-- CLAUDE.md -->\n1. **Run `ctx system bootstrap`**: CRITICAL, not optional.\n   This tells you where the context directory is. If it returns any\n   error, relay the error output to the user verbatim, point them at\n   https://ctx.ist/recipes/activating-context/ for setup, and STOP.\n   Do not try to recover; the user decides.\n</code></pre> <p>Moreover, every nudge (context checkpoint, persistence reminder, etc.) also includes a <code>Context: /home/user/repos/myproject-context/.context</code> footer, so the agent remains anchored to the correct directory even in long sessions.</p> <p>Export <code>CTX_DIR</code> in your shell profile so every hook process inherits it:</p> <pre><code>export CTX_DIR=~/repos/myproject-context/.context\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-5-share-with-teammates","level":3,"title":"Step 5: Share with Teammates","text":"<p>Teammates clone both repos and export <code>CTX_DIR</code>:</p> <pre><code># Clone the project\ngit clone git@github.com:org/myproject.git\ncd myproject\n\n# Clone the private context repo\ngit clone git@github.com:org/myproject-context.git ~/repos/myproject-context\nexport CTX_DIR=~/repos/myproject-context/.context\n</code></pre> <p>If teammates use different paths, each developer sets their own <code>CTX_DIR</code>.</p> <p>For encryption key distribution across the team, see the Syncing Scratchpad Notes recipe.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-6-day-to-day-sync","level":3,"title":"Step 6: Day-to-Day Sync","text":"<p>The external context repo has its own git history. Treat it like any other repo: commit and push after sessions:</p> <pre><code>cd ~/repos/myproject-context\n\n# After a session\ngit add -A\ngit commit -m \"Session: refactored auth module, added rate-limit learning\"\ngit push\n</code></pre> <p>Your AI assistant can do this too. When ending a session:</p> <pre><code>You: \"Save what we learned and push the context repo.\"\n\nAgent: [runs ctx learning add, then commits and pushes the context repo]\n</code></pre> <p>You can also set up a post-session habit: project code gets committed to the project repo, context gets committed to the context repo.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember the flags; simply ask your assistant:</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#set-up-your-system-using-natural-language","level":3,"title":"Set Up Your System Using Natural Language","text":"<pre><code>You: \"Set up ctx to use ~/repos/myproject-context as the context directory.\"\n\nAgent: \"I'll set CTX_DIR to that path, run ctx init to materialize\n       it, and show you the export line to add to your shell\n       profile. Want me to seed the core context files too?\"\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#configure-separate-repo-for-context-folder-using-natural-language","level":3,"title":"Configure Separate Repo for <code>.context</code> Folder Using Natural Language","text":"<pre><code>You: \"My context is in a separate repo. Can you load it?\"\n\nAgent: [reads CTX_DIR, loads context from the external dir]\n       \"Loaded. You have 3 pending tasks, last session was about the auth\n       refactor.\"\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tips","level":2,"title":"Tips","text":"<ul> <li>Start simple. If you don't need external context yet, don't set it up.   The default <code>.context/</code> in-tree is the easiest path. Move to an external   repo when you have a concrete reason.</li> <li>One context repo per project. Sharing a single context directory across   multiple projects corrupts journals, state, and secrets. Use <code>ctx hub</code> for   cross-project knowledge sharing.</li> <li>Export <code>CTX_DIR</code> in your shell profile so hooks and tools inherit the   path without per-command flags.</li> <li>Commit both repos at session boundaries. Context without code history   (or code without context history) loses half the value.</li> </ul>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#next-up","level":2,"title":"Next Up","text":"<p>The Complete Session →: Walk through a full <code>ctx</code> session from start to finish.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#see-also","level":2,"title":"See Also","text":"<ul> <li>Setting Up <code>ctx</code> Across AI Tools: initial setup recipe</li> <li>Syncing Scratchpad Notes Across Machines: distribute   encryption keys when context is shared</li> <li>CLI Reference: full command list and global options</li> </ul>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/guide-your-agent/","level":1,"title":"Guide Your Agent","text":"<p>Commands vs. Skills</p> <p>Commands (<code>ctx status</code>, <code>ctx task add</code>) run in your terminal.</p> <p>Skills (<code>/ctx-reflect</code>, <code>/ctx-next</code>) run inside your AI coding assistant.</p> <p>Recipes combine both.</p> <p>Think of commands as structure and skills as behavior.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#proactive-behavior","level":2,"title":"Proactive Behavior","text":"<p>These recipes show explicit commands and skills, but agents trained on the <code>ctx</code> playbook are proactive: They offer to save learnings after debugging, record decisions after trade-offs, create follow-up tasks after completing work, and suggest what to work on next.</p> <p>Your questions train the agent. Asking \"what have we learned?\" or \"is our context clean?\" does two things:</p> <ul> <li>It triggers the workflow right now,</li> <li>and it reinforces the pattern.</li> </ul> <p>The more you guide, the more the agent habituates the behavior and begins offering on its own.</p> <p>Each recipe includes a Conversational Approach section showing these natural-language patterns.</p> <p>Tip</p> <p>Don't wait passively for proactive behavior: especially in early sessions.</p> <p>Ask, guide, reinforce. Over time, you ask less and the agent offers more.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#next-up","level":2,"title":"Next Up","text":"<p>Setup Across AI Tools →: Initialize <code>ctx</code> and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session   lifecycle from start to finish</li> <li>Prompting Guide: general tips for   working effectively with AI coding assistants</li> </ul>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/hook-output-patterns/","level":1,"title":"Hook Output Patterns","text":"","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code hooks can output text, JSON, or nothing at all. But the format of that output determines who sees it and who acts on it. </p> <p>Choose the wrong pattern, and your carefully crafted warning gets silently  absorbed by the agent, or your agent-directed nudge gets dumped on the user  as noise.</p> <p>This recipe catalogs the known hook output patterns and explains when to use each one.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#tldr","level":2,"title":"TL;DR","text":"<p>Eight patterns from full control to full invisibility: </p> <ul> <li>hard gate (<code>exit 2</code>), </li> <li>VERBATIM relay (agent MUST show), </li> <li>agent directive (context injection), </li> <li>and silent side-effect (background work).</li> </ul> <p>Most hooks belong in the middle.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-spectrum","level":2,"title":"The Spectrum","text":"<p>These patterns form a spectrum based on who decides what the user sees:</p> Pattern Who decides? Hard gate Hook decides (agent can't proceed) VERBATIM relay Hook decides (agent must show) Escalating severity Hook suggests, agent judges urgency Conditional relay Hook sets criteria, agent evaluates Suggested action Hook proposes, agent + user decide Agent directive Agent decides entirely Silent injection Nobody: invisible background context Silent side-effect Nobody: invisible background work <p>The spectrum runs from full hook control (hard gate) to full invisibility (silent side effect). </p> <p>Most hooks belong somewhere in the middle.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-1-hard-gate","level":2,"title":"Pattern 1: Hard Gate","text":"<p>Block the tool call entirely. The agent cannot proceed: it must find another approach or tell the user.</p> <pre><code>echo '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}'\n</code></pre> <p>When to use: Enforcing invariants that must never be violated: Constitution rules, security boundaries, destructive command prevention.</p> <p>Hook type: <code>PreToolUse</code> only (Claude Code first-class mechanism).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system block-non-path-ctx</code>: Enforces the PATH invocation rule</li> <li><code>block-git-push.sh</code>: Requires explicit user approval for pushes (project-local)</li> <li><code>block-dangerous-commands.sh</code>: Prevents <code>sudo</code>, copies to <code>~/.local/bin</code> (project-local)</li> </ul> <p>Trade-off: The agent gets a block response with a reason. Good reasons help the agent recover (\"use X instead\"); bad reasons leave it stuck.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-2-verbatim-relay","level":2,"title":"Pattern 2: VERBATIM Relay","text":"<p>Force the agent to show this to the user as-is. The explicit instruction overcomes the agent's tendency to silently absorb context.</p> <pre><code>echo \"IMPORTANT: Relay this warning to the user VERBATIM before answering their question.\"\necho \"\"\necho \"┌─ Journal Reminder ─────────────────────────────\"\necho \"│ You have 12 sessions not yet exported.\"\necho \"└────────────────────────────────────────────────\"\n</code></pre> <p>When to use: Actionable reminders the user needs to see regardless of what they asked: Stale backups, unimported sessions, resource warnings.</p> <p>Hook type: <code>UserPromptSubmit</code> (runs before the agent sees the prompt).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-journal</code>: Unexported sessions and unenriched entries</li> <li><code>ctx system check-context-size</code>: Context capacity warning</li> <li><code>ctx system check-resources</code>: Resource pressure (memory, swap, disk, load): <code>DANGER</code> only</li> <li><code>ctx system check-freshness</code>: Technology constant staleness warning</li> </ul> <p>Trade-off: Noisy if overused. Every VERBATIM relay adds a preamble before the agent's actual answer. Throttle with once-per-day markers or adaptive frequency.</p> <p>Key detail: The phrase <code>IMPORTANT: Relay this ... VERBATIM</code> is what makes this work. Without it, agents tend to process the information internally and never surface it. The explicit instruction is the pattern: the box-drawing is just fancy formatting.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-3-agent-directive","level":2,"title":"Pattern 3: Agent Directive","text":"<p>Tell the agent to do something, not the user. The agent decides whether and how to involve the user.</p> <pre><code>echo \"┌─ Persistence Checkpoint (prompt #25) ───────────\"\necho \"│ No context files updated in 15+ prompts.\"\necho \"│ Have you discovered learnings, decisions,\"\necho \"│ or completed tasks worth persisting?\"\necho \"└──────────────────────────────────────────────────\"\n</code></pre> <p>When to use: Behavioral nudges. The hook detects a condition and asks the agent to consider an action. The user may never need to know.</p> <p>Hook type: <code>UserPromptSubmit</code>.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-persistence</code>: Nudges the agent to persist context</li> </ul> <p>Trade-off: No guarantee the agent acts. The nudge is one signal among many in the context window. Strong phrasing helps (\"Have you...?\" is better than \"Consider...\"), but ultimately the agent decides.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-4-silent-context-injection","level":2,"title":"Pattern 4: Silent Context Injection","text":"<p>Load context with no visible output. The agent gets enriched without either party noticing.</p> <pre><code>ctx agent --budget 4000 >/dev/null || true\n</code></pre> <p>When to use: Background context loading that should be invisible. The agent benefits from the information, but neither it, nor the user needs to know it happened.</p> <p>Hook type: <code>PreToolUse</code> with <code>.*</code> matcher (runs on every tool call).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li>The <code>ctx agent</code> <code>PreToolUse</code> hook: injects project context silently</li> </ul> <p>Trade-off: Adds latency to every tool call. Keep the injected content small and fast to generate.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-5-silent-side-effect","level":2,"title":"Pattern 5: Silent Side-Effect","text":"<p>Do work, produce no output: Housekeeping that needs no acknowledgment.</p> <pre><code>find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n</code></pre> <p>When to use: Cleanup, log rotation, temp file management. Anything where the action is the point and nobody needs to know it happened.</p> <p>Hook type: Any hook where output is irrelevant.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li>Log rotation, marker file cleanup, state directory maintenance</li> </ul> <p>Trade-off: None, if the action is truly invisible. If it can fail in a way that matters, consider logging.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-6-conditional-relay","level":3,"title":"Pattern 6: Conditional Relay","text":"<p>Tell the agent to relay only if a condition holds in context.</p> <pre><code>echo \"If the user's question involves modifying .context/ files,\"\necho \"relay this warning VERBATIM:\"\necho \"\"\necho \"┌─ Context Integrity ─────────────────────────────\"\necho \"│ CONSTITUTION.md has not been verified in 7 days.\"\necho \"└────────────────────────────────────────────────\"\necho \"\"\necho \"Otherwise, proceed normally.\"\n</code></pre> <p>When to use: Warnings that only matter in certain contexts. Avoids noise when the user is doing unrelated work.</p> <p>Trade-off: Depends on the agent's judgment about when the condition holds. More fragile than VERBATIM relay, but less noisy.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-7-suggested-action","level":3,"title":"Pattern 7: Suggested Action","text":"<p>Give the agent a specific command to propose to the user.</p> <pre><code>echo \"┌─ Stale Dependencies ──────────────────────────\"\necho \"│ go.sum is 30+ days newer than go.mod.\"\necho \"│ Suggested: run \\`go mod tidy\\`\"\necho \"│ Ask the user before proceeding.\"\necho \"└───────────────────────────────────────────────\"\n</code></pre> <p>When to use: The hook detects a fixable condition and knows the fix. Goes beyond a nudge: Gives the agent a concrete next step. The agent still asks for permission but knows exactly what to propose.</p> <p>Trade-off: The suggestion might be wrong or outdated. The \"ask the user before proceeding\" part is critical.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-8-escalating-severity","level":3,"title":"Pattern 8: Escalating Severity","text":"<p>Different urgency tiers with different relay expectations.</p> <pre><code># INFO: agent processes silently, mentions if relevant\necho \"INFO: Last test run was 3 days ago.\"\n\n# WARN: agent should mention to user at next natural pause\necho \"WARN: 12 uncommitted changes across 3 branches.\"\n\n# CRITICAL: agent must relay immediately, before any other work\necho \"CRITICAL: Relay VERBATIM before answering. Disk usage at 95%.\"\n</code></pre> <p>When to use: When you have multiple hooks producing output and need to avoid overwhelming the user. <code>INFO</code> gets absorbed, <code>WARN</code> gets mentioned, <code>CRITICAL</code> interrupts.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-resources</code>: Uses two tiers (<code>WARNING</code>/<code>DANGER</code>) internally   but only fires the VERBATIM relay at <code>DANGER</code> level: <code>WARNING</code> is silent.   See <code>ctx system</code> for the user-facing command that shows both tiers.</li> </ul> <p>Trade-off: Requires agent training or convention to recognize the tiers. Without a shared protocol, the prefixes are just text.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#choosing-a-pattern","level":2,"title":"Choosing a Pattern","text":"<pre><code>Is the agent about to do something forbidden?\n  └─ Yes → Hard gate\n\nDoes the user need to see this regardless of what they asked?\n  └─ Yes → VERBATIM relay\n  └─ Sometimes → Conditional relay\n\nShould the agent consider an action?\n  └─ Yes, with a specific fix → Suggested action\n  └─ Yes, open-ended → Agent directive\n\nIs this background context the agent should have?\n  └─ Yes → Silent injection\n\nIs this housekeeping?\n  └─ Yes → Silent side-effect\n</code></pre>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#design-tips","level":2,"title":"Design Tips","text":"<p>Throttle aggressively: VERBATIM relays that fire every prompt will be ignored or resented. Use once-per-day markers (<code>touch $REMINDED</code>), adaptive frequency (every Nth prompt), or staleness checks (only fire if condition persists).</p> <p>Include actionable commands: \"You have 12 unimported sessions\" is less useful than \"You have 12 unimported sessions. Run: <code>ctx journal import --all</code>.\" Give the user (or agent) the exact next step.</p> <p>Use box-drawing for visual structure: The <code>┌─ ─┐ │ └─ ─┘</code> pattern makes hook output visually distinct from agent prose. It also signals \"this is machine-generated, not agent opinion.\"</p> <p>Test the silence path: Most hook runs should produce no output (the condition isn't met). Make sure the common case is fast and silent.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"<p>Lessons from 19 days of hook debugging in <code>ctx</code>. Every one of these was encountered, debugged, and fixed in production.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#silent-misfire-wrong-key-name","level":3,"title":"Silent Misfire: Wrong Key Name","text":"<pre><code>{ \"PreToolUseHooks\": [ ... ] }\n</code></pre> <p>The key is <code>PreToolUse</code>, not <code>PreToolUseHooks</code>. Claude Code validates silently: A misspelled key means the hook is ignored with no error. Always test with a debug <code>echo</code> first to confirm the hook fires before adding real logic.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#json-escaping-breaks-shell-commands","level":3,"title":"JSON Escaping Breaks Shell Commands","text":"<p>Go's <code>json.Marshal</code> escapes <code>></code>, <code><</code>, and <code>&</code> as Unicode sequences (<code>\\u003e</code>) by default. This breaks shell commands in generated config:</p> <pre><code>\"command\": \"ctx agent 2\\u003e/dev/null\"\n</code></pre> <p>Fix: use <code>json.Encoder</code> with <code>SetEscapeHTML(false)</code> when generating hook configuration.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#stdin-not-environment-variables","level":3,"title":"<code>stdin</code>, Not Environment Variables","text":"<p>Hook input arrives as JSON via <code>stdin</code>, not environment variables:</p> <pre><code># Wrong:\nCOMMAND=\"$CLAUDE_TOOL_INPUT\"\n\n# Right:\nHOOK_INPUT=$(cat)\nCOMMAND=$(echo \"$HOOK_INPUT\" | jq -r '.tool_input.command // empty')\n</code></pre>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#regex-overfitting","level":3,"title":"Regex Overfitting","text":"<p>A regex meant to catch <code>ctx</code> as a binary will also match <code>ctx</code> as a directory component:</p> <pre><code># Too broad: blocks: git -C /home/jose/WORKSPACE/ctx status\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# Narrow to binary only:\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n</code></pre> <p>Test hook regexes against paths that contain the target string as a substring, not just as the final component.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#repetition-fatigue","level":3,"title":"Repetition Fatigue","text":"<p>Injecting context on every tool call sounds safe. In practice, after seeing the same context injection fifteen times, the agent treats it as background noise: Conventions stated in the injected context get violated because salience has been destroyed by repetition.</p> <p>Fix: cooldowns. <code>ctx agent --session $PPID --cooldown 10m</code> injects at most once per ten minutes per session using a tombstone file in <code>/tmp/</code>. This is not an optimization; it is a correction for a design flaw. Every injection consumes attention budget: 50 tool calls at 4,000 tokens each means 200,000 tokens of repeated context, most of it wasted.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#hardcoded-paths","level":3,"title":"Hardcoded Paths","text":"<p>A username rename (<code>parallels</code> to <code>jose</code>) broke every hook at once. Use <code>$CLAUDE_PROJECT_DIR</code> instead of absolute paths:</p> <pre><code>\"command\": \"\\\"$CLAUDE_PROJECT_DIR\\\"/.claude/hooks/block-git-push.sh\"\n</code></pre> <p>If the platform provides a runtime variable for paths, always use it.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#next-up","level":2,"title":"Next Up","text":"<p>Webhook Notifications →: Get push notifications when loops complete, hooks fire, or agents hit milestones.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#see-also","level":2,"title":"See Also","text":"<ul> <li>Customizing Hook Messages: override   what hooks say without changing what they do</li> <li>Claude Code Permission Hygiene: how   permissions and hooks work together</li> <li>Defense in Depth:   why hooks matter for agent security</li> </ul>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/","level":1,"title":"Hook Sequence Diagrams","text":"","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#hook-lifecycle","level":2,"title":"Hook Lifecycle","text":"<p>This page documents the <code>ctx</code> system hooks: the built-in <code>ctx system *</code> subcommands that Claude Code invokes via <code>.claude/hooks.json</code> at lifecycle events. These are owned by <code>ctx</code> itself, not authored by users.</p> <p>Not to Be Confused with <code>ctx trigger</code></p> <p><code>ctx</code> has three distinct hook-like layers:</p> <ul> <li><code>ctx system</code> hooks (this page): built-in, owned   by <code>ctx</code>, wired into Claude Code via   <code>internal/assets/claude/hooks/hooks.json</code>.</li> <li><code>ctx trigger</code>: user-authored shell scripts in   <code>.context/hooks/<type>/*.sh</code>. See   <code>ctx trigger</code> reference and the   trigger authoring recipe.</li> <li>Claude Code hooks configured directly in   <code>.claude/settings.local.json</code>, tool-specific, not   portable across AI tools.</li> </ul> <p>This page is only about the first category.</p> <p>Every <code>ctx system</code> hook is a Go binary invoked by Claude Code at one of three lifecycle events: <code>PreToolUse</code> (before a tool runs, can block), <code>PostToolUse</code> (after a tool completes), or <code>UserPromptSubmit</code> (on every user prompt, before any tools run). Hooks receive JSON on stdin and emit JSON or plain text on stdout.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#pretooluse-hooks","level":2,"title":"PreToolUse Hooks","text":"<p>These fire before a tool executes. They can block, gate, or inject context.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#context-load-gate","level":3,"title":"Context-Load-Gate","text":"<p>Matcher: <code>.*</code> (all tools)</p> <p>Injects the full context packet on first tool use of a session. One-shot per session.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as context-load-gate\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Git as git log\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized\n    alt not initialized\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check ctx-loaded-{session} marker\n    alt marker exists\n        Hook-->>CC: (silent exit, already fired)\n    end\n    Hook->>State: Create marker (one-shot guard)\n    Hook->>State: Prune stale session files\n    loop Each file in ReadOrder\n        alt GLOSSARY or TASK\n            Note over Hook: Skip (Task mentioned in footer only)\n        else DECISION or LEARNING\n            Hook->>Ctx: Extract index table only\n        else other files\n            Hook->>Ctx: Read full content\n        end\n        Hook->>Hook: Estimate tokens per file\n    end\n    Hook->>Git: Detect changes since last session\n    Hook->>Hook: Build injection (files + changes + token counts)\n    Hook-->>CC: JSON {additionalContext: injection}\n    Hook->>Hook: Send webhook (metadata only)\n    Hook->>State: Write oversize flag if tokens > threshold</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-non-path-ctx","level":3,"title":"Block-Non-Path-ctx","text":"<p>Matcher: <code>Bash</code></p> <p>Blocks <code>./ctx</code>, <code>go run ./cmd/ctx</code>, or absolute-path <code>ctx</code> invocations. Constitutionally enforced.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-non-path-ctx\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Test regex: relative-path, go-run, absolute-path\n    alt no match\n        Hook-->>CC: (silent exit)\n    end\n    alt absolute-path + test exception\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason + constitution suffix}\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#qa-reminder","level":3,"title":"Qa-Reminder","text":"<p>Matcher: <code>Bash</code></p> <p>Gate nudge before any git command. Reminds agent to lint/test.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as qa-reminder\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check command contains \"git\"\n    alt no git command\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, gate, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: QA gate}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#specs-nudge","level":3,"title":"Specs-Nudge","text":"<p>Matcher: <code>EnterPlanMode</code></p> <p>Nudges agent to save plans/specs when new implementation detected.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as specs-nudge\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: specs nudge}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#posttooluse-hooks","level":2,"title":"PostToolUse Hooks","text":"<p>These fire after a tool completes. They observe, nudge, and track state.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#post-commit","level":3,"title":"Post-Commit","text":"<p>Matcher: <code>Bash</code></p> <p>Fires after <code>git commit</code> (not amend). Nudges for context capture and checks version drift.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as post-commit\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"git commit\"?\n    alt not a git commit\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"--amend\"?\n    alt is amend\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: post-commit nudge}\n    Hook->>Hook: Relay(message)\n    Hook->>Hook: CheckVersionDrift()</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-task-completion","level":3,"title":"Check-Task-Completion","text":"<p>Matcher: <code>Edit</code>, <code>Write</code></p> <p>Configurable-interval nudge after edits. Per-session counter resets after firing.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-task-completion\n    participant State as .context/state/\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read task nudge interval\n    alt interval <= 0 (disabled)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read per-session counter\n    Hook->>Hook: Increment counter\n    alt counter < interval\n        Hook->>State: Write counter\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Reset counter to 0\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: JSON {additionalContext: task nudge}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#userpromptsubmit-hooks","level":2,"title":"UserPromptSubmit Hooks","text":"<p>These fire on every user prompt, before any tools run. They perform health checks, track state, and nudge for housekeeping.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-context-size","level":3,"title":"Check-Context-Size","text":"<p>Adaptive context window monitoring. Fires checkpoints, window warnings, and billing alerts based on prompt count and token usage.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-context-size\n    participant State as .context/state/\n    participant Session as Session JSONL\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized\n    Hook->>Hook: Read input, resolve session ID\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: Pause acknowledgment message\n    end\n    Hook->>State: Increment session prompt counter\n    Hook->>Session: Read token info (tokens, model, window)\n\n    rect rgb(255, 240, 240)\n        Note over Hook: Billing check (independent, never suppressed)\n        alt tokens >= billing threshold (one-shot)\n            Hook->>Tpl: LoadMessage(hook, billing, vars)\n            Hook-->>CC: Billing warning nudge box\n            Hook->>Hook: NudgeAndRelay(billing message)\n        end\n    end\n\n    Hook->>State: Check wrap-up marker\n    alt wrapped up recently (< 2h)\n        Hook->>State: Write stats (event: suppressed)\n        Hook-->>CC: (silent exit)\n    end\n\n    rect rgb(240, 248, 255)\n        Note over Hook: Adaptive frequency check\n        alt count > 30 and count % 3 == 0\n            Note over Hook: High frequency trigger\n        else count > 15 and count % 5 == 0\n            Note over Hook: Medium frequency trigger\n        else\n            Hook->>State: Write stats (event: silent)\n            Hook-->>CC: (silent exit)\n        end\n    end\n\n    alt context window >= 80%\n        Hook->>Tpl: LoadMessage(hook, window, vars)\n        Hook-->>CC: Window warning nudge box\n        Hook->>Hook: NudgeAndRelay(window message)\n    else checkpoint trigger\n        Hook->>Tpl: LoadMessage(hook, checkpoint)\n        Hook-->>CC: Checkpoint nudge box\n        Hook->>Hook: NudgeAndRelay(checkpoint message)\n    end\n    Hook->>State: Write session stats</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-ceremonies","level":3,"title":"Check-Ceremonies","text":"<p>Daily check for <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> usage in recent journal entries.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-ceremonies\n    participant State as .context/state/\n    participant Journal as Journal files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Read recent files (lookback window)\n    alt no journal files\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Scan for /ctx-remember and /ctx-wrap-up\n    alt both ceremonies present\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Note over Hook: variant: both | remember | wrapup\n    Hook-->>CC: Nudge box (missing ceremonies)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-freshness","level":3,"title":"Check-Freshness","text":"<p>Daily check for technology-dependent constants that may need review.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-freshness\n    participant State as .context/state/\n    participant FS as Filesystem\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>FS: Stat tracked files (5 source files)\n    alt all files modified within 6 months\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {StaleFiles})\n    Hook-->>CC: Nudge box (stale file list + review URL)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-journal","level":3,"title":"Check-Journal","text":"<p>Daily check for unimported sessions and unenriched journal entries.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-journal\n    participant State as .context/state/\n    participant Journal as Journal dir\n    participant Claude as Claude projects dir\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Check dir exists\n    Hook->>Claude: Check dir exists\n    alt either dir missing\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Get newest entry mtime\n    Hook->>Claude: Count .jsonl files newer than journal\n    Hook->>Journal: Count unenriched entries\n    alt unimported == 0 and unenriched == 0\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, {counts})\n    Note over Hook: variant: both | unimported | unenriched\n    Hook-->>CC: Nudge box (counts)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-knowledge","level":3,"title":"Check-Knowledge","text":"<p>Daily check for knowledge file entry/line counts exceeding configured thresholds.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-knowledge\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read thresholds (decisions, learnings, conventions)\n    alt all thresholds disabled (0)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Ctx: Parse DECISIONS.md entry count\n    Hook->>Ctx: Parse LEARNINGS.md entry count\n    Hook->>Ctx: Count CONVENTIONS.md lines\n    Hook->>Hook: Compare against thresholds\n    alt all within limits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, warning, {FileWarnings})\n    Hook-->>CC: Nudge box (file warnings)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-map-staleness","level":3,"title":"Check-Map-Staleness","text":"<p>Daily check for architecture map age and relevant code changes.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-map-staleness\n    participant State as .context/state/\n    participant Tracking as map-tracking.json\n    participant Git as git log\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tracking: Read map-tracking.json\n    alt missing, invalid, or opted out\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Parse LastRun date\n    alt map not stale (< N days)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Git: Count commits touching internal/ since LastRun\n    alt no relevant commits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {date, count})\n    Hook-->>CC: Nudge box (last refresh + commit count)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-memory-drift","level":3,"title":"Check-Memory-Drift","text":"<p>Per-session check for MEMORY.md changes since last sync.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-memory-drift\n    participant State as .context/state/\n    participant Mem as memory.Discover\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check session tombstone\n    alt already nudged this session\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: DiscoverMemoryPath(projectRoot)\n    alt auto memory not active\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: HasDrift(contextDir, sourcePath)\n    alt no drift\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: Nudge box (drift reminder)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch session tombstone</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-persistence","level":3,"title":"Check-Persistence","text":"<p>Tracks context file modification and nudges when edits happen without persisting context. Adaptive threshold based on prompt count.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-persistence\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read persistence state {Count, LastNudge, LastMtime}\n    alt first prompt (no state)\n        Hook->>State: Initialize state {Count:1, LastNudge:0, LastMtime:now}\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Increment Count\n    Hook->>Ctx: Get current context mtime\n    alt context modified since LastMtime\n        Hook->>State: Reset LastNudge = Count, update LastMtime\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: sinceNudge = Count - LastNudge\n    Hook->>Hook: PersistenceNudgeNeeded(Count, sinceNudge)?\n    alt threshold not reached\n        Hook->>State: Write state\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, vars)\n    Hook-->>CC: Nudge box (prompt count, time since last persist)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Update LastNudge = Count, write state</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-reminders","level":3,"title":"Check-Reminders","text":"<p>Per-prompt check for due reminders. No throttle.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-reminders\n    participant Store as Reminders store\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Store: ReadReminders()\n    alt load error\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter by due date (After <= today)\n    alt no due reminders\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, reminders, {list})\n    Hook-->>CC: Nudge box (reminder list + dismiss hints)\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-resources","level":3,"title":"Check-Resources","text":"<p>Checks system resources (memory, swap, disk, load). Fires on every prompt. No initialization required.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-resources\n    participant Sys as sysinfo\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: HookPreamble (parse input, check pause)\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Sys: Collect snapshot (memory, swap, disk, load)\n    Hook->>Sys: Evaluate thresholds per metric\n    alt max severity < Danger\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter alerts to Danger level only\n    Hook->>Hook: Build alertMessages from danger alerts\n    Hook->>Tpl: LoadMessage(hook, alert, {alertMessages}, fallback)\n    Hook-->>CC: Nudge box (danger alerts)\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-version","level":3,"title":"Check-Version","text":"<p>Daily binary-vs-plugin version comparison with piggybacked key rotation check.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-version\n    participant State as .context/state/\n    participant Config as Binary + Plugin version\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read binary version\n    alt dev build\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read plugin version\n    alt plugin version not found or parse error\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Compare major.minor\n    alt versions match\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, mismatch, {versions})\n    Hook-->>CC: Nudge box (version mismatch)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle\n    Hook->>Hook: CheckKeyAge() (piggybacked)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#heartbeat","level":3,"title":"Heartbeat","text":"<p>Silent per-prompt pulse. Tracks prompt count, context modification, and token usage. The agent never sees this hook's output.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as heartbeat\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Notify as Webhook + EventLog\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Increment heartbeat counter\n    Hook->>Ctx: Get latest context file mtime\n    Hook->>State: Compare with last recorded mtime\n    Hook->>State: Update mtime record\n    Hook->>State: Read session token info\n    Hook->>Notify: Send heartbeat notification\n    Hook->>Notify: Append to event log\n    Hook->>State: Write heartbeat log entry\n    Note over Hook: No stdout - agent never sees this</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#project-local-hooks","level":2,"title":"Project-Local Hooks","text":"<p>These hooks are configured in <code>settings.local.json</code> and are not shipped with ctx. They are specific to individual developer setups.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-dangerous-commands","level":3,"title":"Block-Dangerous-Commands","text":"<p>Lifecycle: PreToolUse. Matcher: <code>Bash</code></p> <p>Blocks dangerous shell patterns (sudo, git push, cp to bin). No initialization or pause checks: always active.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-dangerous-commands\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Note over Hook: Cascade: first matching regex wins\n    Hook->>Hook: Test MidSudo regex\n    alt match\n        Hook->>Hook: variant = sudo\n    end\n    Hook->>Hook: Test MidGitPush regex (if no variant)\n    alt match\n        Hook->>Hook: variant = git-push\n    end\n    Hook->>Hook: Test CpMvToBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = cp-to-bin\n    end\n    Hook->>Hook: Test InstallToLocalBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = install-to-bin\n    end\n    alt no variant matched\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason}\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#throttling-summary","level":2,"title":"Throttling Summary","text":"Hook Lifecycle Throttle Type Scope context-load-gate PreToolUse One-shot marker Per session block-non-path-ctx PreToolUse None Every match qa-reminder PreToolUse None Every git command specs-nudge PreToolUse None Every prompt post-commit PostToolUse None Every git commit check-task-completion PostToolUse Configurable interval Per session check-context-size UserPromptSubmit Adaptive counter Per session check-ceremonies UserPromptSubmit Daily marker Once per day check-freshness UserPromptSubmit Daily marker Once per day check-journal UserPromptSubmit Daily marker Once per day check-knowledge UserPromptSubmit Daily marker Once per day check-map-staleness UserPromptSubmit Daily marker Once per day check-memory-drift UserPromptSubmit Session tombstone Once per session check-persistence UserPromptSubmit Adaptive counter Per session check-reminders UserPromptSubmit None Every prompt check-resources UserPromptSubmit None Every prompt check-version UserPromptSubmit Daily marker Once per day heartbeat UserPromptSubmit None Every prompt block-dangerous-commands PreToolUse * None Every match <p>* Project-local hook (settings.local.json), not shipped with ctx.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#state-file-reference","level":2,"title":"State File Reference","text":"<p>All state files live in <code>.context/state/</code>.</p> File Pattern Hook Purpose <code>ctx-loaded-{session}</code> context-load-gate One-shot injection marker <code>ctx-paused-{session}</code> (all) Session pause marker <code>ctx-wrapped-up</code> check-context-size Suppress nudges after wrap-up (2h expiry) <code>freshness-checked</code> check-freshness Daily throttle <code>ceremony-reminded</code> check-ceremonies Daily throttle <code>journal-reminded</code> check-journal Daily throttle <code>knowledge-reminded</code> check-knowledge Daily throttle <code>map-staleness-reminded</code> check-map-staleness Daily throttle <code>version-checked</code> check-version Daily throttle <code>memory-drift-nudged-{session}</code> check-memory-drift Per-session tombstone <code>ctx-context-count-{session}</code> check-context-size Prompt counter <code>stats-{session}.jsonl</code> check-context-size Session stats log <code>persist-{session}</code> check-persistence Counter + mtime state <code>ctx-task-count-{session}</code> check-task-completion Prompt counter <code>heartbeat-count-{session}</code> heartbeat Prompt counter <code>heartbeat-mtime-{session}</code> heartbeat Last context mtime","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hub-cluster/","level":1,"title":"HA Cluster","text":"","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#ctx-hub-high-availability-cluster","level":1,"title":"<code>ctx</code> Hub: High-Availability Cluster","text":"<p>Run multiple hub nodes with Raft-based leader election for redundancy. Any follower can take over if the leader dies.</p> <p>This recipe assumes you've read the <code>ctx</code> Hub overview and the Multi-machine setup. HA only makes sense in the \"small trusted team\" story; a personal cross-project brain on one workstation does not need three Raft peers.</p> <p>Raft-Lite</p> <p><code>ctx</code> uses Raft only for leader election, not for data consensus. Entry replication happens via sequence-based gRPC sync on the append-only JSONL store. This is simpler than full Raft log replication and is possible because the store is append-only and clients are idempotent. The implication: a write accepted by the leader is durable on the leader immediately; followers catch up asynchronously. If the leader crashes between accepting a write and replicating it, that write can be lost. Do not use the hub as a bank ledger.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#topology","level":2,"title":"Topology","text":"<p>A minimum HA cluster is three nodes. Two is worse than one: it doubles failure probability without providing quorum.</p> <pre><code>         +-------------+\n         |  client(s)  |\n         +------+------+\n                |\n    +-----------+-----------+\n    |           |           |\n+---v---+   +---v---+   +---v---+\n| hub A |   | hub B |   | hub C |\n| :9900 |   | :9900 |   | :9900 |\n+-------+   +-------+   +-------+\n    ^           ^           ^\n    +-----------+-----------+\n        Raft (leader election)\n        gRPC (data sync)\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-1-bootstrap-the-first-node","level":2,"title":"Step 1: Bootstrap the First Node","text":"<pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n</code></pre> <p>The node starts a Raft election as soon as it sees its peers.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-2-start-the-other-nodes","level":2,"title":"Step 2: Start the Other Nodes","text":"<p>On <code>hub-b.lan</code>:</p> <pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-c.lan:9900\n</code></pre> <p>On <code>hub-c.lan</code>:</p> <pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-b.lan:9900\n</code></pre> <p>After a few seconds, one node wins the election and becomes the leader. The other two are followers.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-3-verify-cluster-state","level":2,"title":"Step 3: Verify Cluster State","text":"<p>From any node:</p> <pre><code>ctx hub status\n</code></pre> <p>Expected output:</p> <pre><code>role:       leader\npeers:      hub-a.lan:9900 (leader)\n            hub-b.lan:9900 (follower, in-sync)\n            hub-c.lan:9900 (follower, in-sync)\nentries:    1248\nuptime:     3h42m\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-4-register-clients-with-failover-peers","level":2,"title":"Step 4: Register Clients with Failover Peers","text":"<p>The <code>ctx hub *</code> commands above run on the hub nodes themselves and don't need a project. The <code>ctx connection *</code> commands below are different: they live inside a project (the encrypted hub config is stored at <code>.context/.connect.enc</code>), so you have to tell <code>ctx</code> which project first.</p> <p>When registering a client, give it the full peer list:</p> <pre><code># In the project directory on the client:\neval \"$(ctx activate)\"\nctx connection register hub-a.lan:9900 \\\n  --token ctx_adm_... \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n</code></pre> <p>If the leader becomes unreachable, the client reconnects to the next peer. Followers redirect to the current leader, so writes always land on the right node.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#runtime-membership-changes","level":2,"title":"Runtime Membership Changes","text":"<p>Add a new peer without downtime:</p> <pre><code>ctx hub peer add hub-d.lan:9900\n</code></pre> <p>Remove a decommissioned peer:</p> <pre><code>ctx hub peer remove hub-c.lan:9900\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#planned-maintenance","level":2,"title":"Planned Maintenance","text":"<p>Before taking a leader offline, hand off leadership:</p> <pre><code>ssh hub-a.lan 'ctx hub stepdown'\n</code></pre> <p><code>stepdown</code> triggers a new election among the remaining followers before the leader goes offline. In-flight clients briefly pause, then reconnect to the new leader.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#failure-modes-at-a-glance","level":2,"title":"Failure Modes at a Glance","text":"Event What happens Leader crashes New election; clients reconnect to new leader Follower crashes No write impact; catches up on restart Network partition (majority) Majority side keeps serving; minority read-only Network partition (split) No quorum; all nodes read-only Disk full on leader Writes rejected; read traffic continues <p>For the full list, see Hub failure modes.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#see-also","level":2,"title":"See Also","text":"<ul> <li>Multi-machine recipe: single-node   deployment</li> <li>Hub operations: backup and   maintenance</li> <li>Hub security model: TLS, tokens</li> </ul>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-getting-started/","level":1,"title":"Getting Started","text":"","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#ctx-hub-getting-started","level":1,"title":"<code>ctx</code> Hub: Getting Started","text":"<p>Stand up a single-node <code>ctx</code> Hub on localhost, register two projects, publish a decision from one, and see it appear in the other, all in under five minutes.</p> <p>Read This First</p> <p>If you haven't already, skim the <code>ctx</code> Hub overview. It explains the mental model, names the two user stories (personal vs small team), and (importantly) lists what the hub does not do. This recipe assumes you already know you want the feature.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-youll-get-out-of-this-recipe","level":2,"title":"What You'll Get out of This Recipe","text":"<p>By the end, you will have:</p> <ol> <li>A local hub process running on port <code>9900</code>.</li> <li>Two project directories both registered with the <code>ctx</code> Hub.</li> <li>A decision published from project <code>alpha</code> that appears    automatically in project <code>beta</code>'s <code>.context/hub/</code> and in    <code>ctx agent --include-hub</code> output.</li> </ol> <p>Concretely, the payoff this unlocks: a lesson you record in one project becomes visible to your agent the next time you open another project, without touching local files in the second project or opening another editor window.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-this-recipe-does-not-cover","level":2,"title":"What This Recipe Does Not Cover","text":"<ul> <li>Sharing <code>.context/journal/</code>, <code>.context/pad</code>, or any other   local state. The hub only fans out <code>decision</code>, <code>learning</code>,   <code>convention</code>, and <code>task</code> entries. Everything else stays local.</li> <li>Multi-user attribution. The hub identifies projects, not   people.</li> <li>Running over a LAN; see   Multi-machine setup.</li> <li>Redundancy; see HA cluster.</li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li><code>ctx</code> installed and on <code>PATH</code></li> <li>Two project directories, each already initialized with   <code>ctx init</code></li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"<p>In a dedicated terminal:</p> <pre><code>ctx hub start\n</code></pre> <p>On first run, the hub generates an admin token and prints it to stdout. Copy it; you'll need it for each project registration:</p> <pre><code>ctx hub listening on :9900\nadmin token: ctx_adm_7f3a1c2d...\ndata dir: ~/.ctx/hub-data/\n</code></pre> <p>The admin token is written to <code>~/.ctx/hub-data/admin.token</code> so you can recover it later. Treat it like a password.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-2-register-the-first-project","level":2,"title":"Step 2: Register the First Project","text":"<p><code>ctx hub start</code> above runs on the hub server and doesn't need a project. Step 2 is different: the encrypted hub config is stored inside a project at <code>.context/.connect.enc</code>, so you have to tell <code>ctx</code> which project first.</p> <pre><code>cd ~/projects/alpha\neval \"$(ctx activate)\"\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\n</code></pre> <p>This stores an encrypted connection config in <code>.context/.connect.enc</code>. The admin token is exchanged for a per-project client token; the admin token itself is never persisted in the project.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-3-choose-what-to-receive","level":2,"title":"Step 3: Choose What to Receive","text":"<pre><code>ctx connection subscribe decision learning convention\n</code></pre> <p>Only the entry types you subscribe to will be delivered by <code>sync</code> and <code>listen</code>.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-4-publish-a-decision","level":2,"title":"Step 4: Publish a Decision","text":"<p>Either use <code>ctx add --share</code> to write locally and push to the <code>ctx</code> Hub:</p> <pre><code>ctx decision add \"Use UTC timestamps everywhere\" --share \\\n  --context \"We had timezone drift between the API and journal\" \\\n  --rationale \"Single source of truth avoids conversion bugs\" \\\n  --consequence \"The UI does conversion at render time\"\n</code></pre> <p>Or publish an existing entry directly:</p> <pre><code>ctx connection publish decision \"Use UTC timestamps everywhere\"\n</code></pre>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-5-register-a-second-project-and-sync","level":2,"title":"Step 5: Register a Second Project and Sync","text":"<pre><code>cd ~/projects/beta\neval \"$(ctx activate)\"   # bind CTX_DIR for this project\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\nctx connection subscribe decision learning convention\nctx connection sync\n</code></pre> <p>The decision from <code>alpha</code> now appears in <code>~/projects/beta/.context/hub/decisions.md</code> with an origin tag and timestamp.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-6-watch-entries-arrive-live","level":2,"title":"Step 6: Watch Entries Arrive Live","text":"<p>Instead of re-running <code>sync</code>, stream new entries as they land:</p> <pre><code>ctx connection listen\n</code></pre> <p>Leave this running in a terminal; every <code>--share</code> publish from any registered project will appear in <code>.context/hub/</code> immediately.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-7-feed-shared-knowledge-into-the-agent","level":2,"title":"Step 7: Feed Shared Knowledge into the Agent","text":"<p>Once entries exist in <code>.context/hub/</code>, include them in the agent context packet:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are added as a dedicated tier in the budget-aware assembly, scored by recency and type relevance.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#auto-sync-on-session-start","level":2,"title":"Auto-Sync on Session Start","text":"<p>After <code>register</code>, the <code>check-hub-sync</code> hook pulls new entries at the start of each session (daily throttled). Most users never need to call <code>ctx connection sync</code> manually.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Multi-machine hub: run the hub   on a LAN host and connect from other workstations.</li> <li>HA cluster: Raft-based leader   election for high availability.</li> <li>Hub operations: daemon mode,   backup, log rotation, JSONL store layout.</li> <li>Hub security model: token   lifecycle, encryption at rest, threat model.</li> <li><code>ctx connect</code> reference and   <code>ctx hub start</code> reference.</li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-multi-machine/","level":1,"title":"Multi-Machine","text":"","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#ctx-hub-multi-machine","level":1,"title":"<code>ctx</code> Hub: Multi-Machine","text":"<p>Run the hub on a LAN host and connect from project directories on other workstations. This recipe is the Story 2 (\"small trusted team\") shape described in the <code>ctx</code> Hub overview; read that first if you haven't, especially the trust-model warnings.</p> <p>This recipe assumes you've already walked through Getting Started and understand what flows through the hub (decisions, learnings, conventions, tasks, not journals, scratchpad, or raw context files).</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#topology","level":2,"title":"Topology","text":"<pre><code>+------------------+        +------------------+\n| workstation A    |        | workstation B    |\n|  ~/projects/x    |        |  ~/projects/y    |\n|  ctx connection  |        |  ctx connection  |\n+---------+--------+        +---------+--------+\n          |                           |\n          +-----------+   +-----------+\n                      v   v\n              +-------------------+\n              | LAN host \"nexus\"  |\n              | ctx hub start     |\n              | --daemon          |\n              | :9900             |\n              +-------------------+\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-1-start-the-daemon-on-the-lan-host","level":2,"title":"Step 1: Start the Daemon on the LAN Host","text":"<p>On the machine that will hold the hub (call it <code>nexus</code>):</p> <pre><code>ctx hub start --daemon --port 9900\n</code></pre> <p>The daemon writes a PID file to <code>~/.ctx/hub-data/hub.pid</code>. Stop it later with:</p> <pre><code>ctx hub stop\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-2-firewall-and-port","level":2,"title":"Step 2: Firewall and Port","text":"<p>Open port <code>9900/tcp</code> on <code>nexus</code> to the LAN only. Never expose the hub to the public internet without a reverse proxy and TLS in front of it (see Hub security model).</p> <p>Typical LAN allowlist rules:</p> firewalldufwnftables <pre><code>sudo firewall-cmd --zone=internal \\\n  --add-port=9900/tcp --permanent\nsudo firewall-cmd --reload\n</code></pre> <pre><code>sudo ufw allow from 192.168.1.0/24 to any port 9900 proto tcp\n</code></pre> <pre><code>sudo nft add rule inet filter input ip saddr 192.168.1.0/24 \\\n  tcp dport 9900 accept\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-3-retrieve-the-admin-token","level":2,"title":"Step 3: Retrieve the Admin Token","text":"<p>The daemon prints the admin token to stdout on first run. Running as a daemon, that output goes to the log instead:</p> <pre><code>cat ~/.ctx/hub-data/admin.token\n</code></pre> <p>Copy the token over a trusted channel (SSH, password manager, or an encrypted note). Do not email it or put it in chat.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-4-register-projects-from-each-workstation","level":2,"title":"Step 4: Register Projects from Each Workstation","text":"<p>The <code>ctx hub *</code> commands above run on the LAN host (<code>nexus</code>) and don't need a project. Step 4 is different: each workstation registers from inside a project (the encrypted hub config and the fan-out inbox both live under <code>.context/</code>), so you have to tell <code>ctx</code> which project first.</p> <p>On workstation <code>A</code>:</p> <pre><code>cd ~/projects/x\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n</code></pre> <p>On workstation <code>B</code>:</p> <pre><code>cd ~/projects/y\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n</code></pre> <p>Each registration exchanges the admin token for a per-project client token. Only the client token is persisted in <code>.context/.connect.enc</code>, encrypted with the same AES-256-GCM scheme <code>ctx</code> uses for notification credentials.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-5-verify","level":2,"title":"Step 5: Verify","text":"<p>From either workstation:</p> <pre><code>ctx connection status\n</code></pre> <p>You should see the <code>ctx</code> Hub address, role (<code>leader</code> for single-node), subscription filters, and the sequence number you're synced to.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#tls-recommended","level":2,"title":"TLS (Recommended)","text":"<p>For anything beyond a trusted home LAN, terminate TLS in front of the hub. The hub speaks gRPC, so the reverse proxy must speak HTTP/2:</p> <pre><code>server {\n    listen 443 ssl http2;\n    server_name nexus.example.com;\n\n    ssl_certificate     /etc/letsencrypt/live/nexus.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/nexus.example.com/privkey.pem;\n\n    location / {\n        grpc_pass grpc://127.0.0.1:9900;\n    }\n}\n</code></pre> <p>Point <code>ctx connection register</code> at the public hostname and port 443.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#handling-daemon-restarts","level":2,"title":"Handling Daemon Restarts","text":"<p>The hub is append-only JSONL, so restarts are safe. Clients keep their last-seen sequence in <code>.context/hub/.sync-state.json</code> and pick up exactly where they left off on the next <code>sync</code> or <code>listen</code> reconnect.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#see-also","level":2,"title":"See Also","text":"<ul> <li>HA cluster recipe: for redundancy</li> <li>Hub operations: backup, rotation</li> <li>Hub failure modes</li> <li>Hub security model</li> </ul>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-overview/","level":1,"title":"Overview","text":"","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#ctx-hub-overview","level":1,"title":"<code>ctx</code> Hub: Overview","text":"<p>Start here before the other hub recipes. This page answers what the hub is, who it's for, why you'd run one, and, equally important, what it is not.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#mental-model-in-one-paragraph","level":2,"title":"Mental Model in One Paragraph","text":"<p>The hub is a fan-out channel for structured knowledge entries across projects. When you publish a decision, learning, convention, or task with <code>--share</code>, the hub stores it in an append-only log and delivers it to every other project subscribed to that type. The next time your agent loads context in any of those projects, shared entries can be included in the context packet alongside local ones.</p> <p>That's the whole feature. It is a project-to-project knowledge bus for a small, curated set of entry types. It is not a shared memory, a shared journal, or a multi-user database.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-flows-through-the-hub","level":2,"title":"What Flows through the Hub","text":"<p>Only four entry types:</p> Type What it is <code>decision</code> Architectural decisions with rationale <code>learning</code> Gotchas, lessons, surprising behaviors <code>convention</code> Coding patterns and standards <code>task</code> Work items worth sharing across projects <p>Each entry is an immutable record with a content blob, the publishing project's name as <code>Origin</code>, a timestamp, and a hub-assigned sequence number. Once published, entries are never rewritten.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-does-not-flow-through-the-hub","level":2,"title":"What Does Not Flow through the Hub","text":"<p>This is the part new users get wrong most often:</p> <ul> <li>Session journals (<code>~/.claude/</code> logs, <code>.context/journal/</code>)   stay local. The hub does not sync your AI session history.</li> <li>Scratchpad (<code>.context/pad</code>) stays local. Encrypted notes   never leave the machine they were written on.</li> <li>Local context files as a whole (<code>TASKS.md</code>,   <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>) are not   mirrored wholesale. Only entries you explicitly <code>--share</code>, or   publish later with <code>ctx connection publish</code>, cross the boundary.</li> <li>Anything under <code>.context/</code> that isn't one of the four entry   types above. Configuration, state, logs, memory, journal   metadata: all local.</li> </ul> <p>If you were expecting \"now my agent in project B can see everything my agent did in project A,\" that's not this feature. Local session density still lives on the local machine.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#two-user-stories","level":2,"title":"Two User Stories","text":"<p>The hub makes sense in two different shapes. Pick the one that matches your situation; the mechanics are identical but the trust model and threat surface are very different.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-1-personal-cross-project-brain","level":3,"title":"Story 1: Personal Cross-Project Brain","text":"<p>One developer, many projects, one hub, usually on localhost.</p> <p>You're working across several projects on the same machine (or a handful of machines you own). You want a lesson learned debugging project A to show up when you open project B a week later, without re-discovering it. You want a convention you codified in one project to be visible as-you-type in another.</p> <p>Concrete payoff:</p> <ul> <li><code>ctx learning add --share \"...\"</code> in project A →   <code>ctx agent --include-hub</code> in project B shows that learning   in the next context packet.</li> <li>A decision recorded in your personal \"dotfiles\" project is   instantly visible to every other project on your workstation.</li> <li>Cross-project conventions (e.g., \"use UTC timestamps   everywhere\") live in one place and propagate.</li> </ul> <p>Trust model: high, because you trust every participant since every participant is you. Run the hub on localhost or on your own LAN, use the default single-node setup, don't worry about TLS.</p> <p>Start here: Getting Started for the one-time setup, then Personal cross-project brain for the day-to-day workflow.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-2-small-trusted-team","level":3,"title":"Story 2: Small Trusted Team","text":"<p>A few teammates, projects they each own, one hub on a LAN host they all trust.</p> <p>Your team has a handful of services and you want a shared \"things we've learned the hard way\" stream. Someone on the platform team records a convention about timestamp handling; everyone else's agents see it the next session. An on-call engineer records a learning from a 3 AM incident; the rest of the team inherits the lesson without needing to read the postmortem.</p> <p>Concrete payoff:</p> <ul> <li>Team conventions propagate without needing a wiki or chat.</li> <li>Lessons from one team member become available to everyone   else's agent context packets automatically.</li> <li>Cross-project decisions (shared libraries, deployment   patterns, naming rules) live in a single log the whole team   reads.</li> </ul> <p>Trust model: the hub assumes everyone holding a client token is friendly. There is no per-user attribution you can rely on, <code>Origin</code> is self-asserted by the publishing client, and there is no read ACL beyond the subscription filter. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.</p> <p>Operational shape: run the hub on a LAN host (or a three-node HA cluster for redundancy), put TLS in front of it for anything beyond a home LAN, distribute client tokens over a trusted channel.</p> <p>Start here: Multi-machine setup for the deployment, Team knowledge bus for the day-to-day team workflow, then HA cluster if you need redundancy.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#identity-projects-not-users","level":2,"title":"Identity: Projects, Not Users","text":"<p>The hub has no concept of users. Its unit of identity is the project. <code>ctx connection register</code> binds a hub token to a project directory, not to a person. Two developers working on the same project share either:</p> <ul> <li>The same <code>.connect.enc</code>, copied between machines over a   trusted channel, or</li> <li>Different project names (<code>alpha@laptop-a</code>,   <code>alpha@laptop-b</code>), because the hub rejects duplicate   registrations of the same project name.</li> </ul> <p>Either works; neither gives you per-human attribution. If you need \"who wrote this,\" the hub is the wrong tool.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#when-not-to-use-it","level":2,"title":"When Not to Use It","text":"<ul> <li>Solo, single-project work. Local <code>.context/</code> files are   enough. The hub adds operational surface for no payoff.</li> <li>Untrusted participants. The hub assumes everyone with a   client token is friendly. It is not hardened against hostile   insiders or compromised tokens.</li> <li>Compliance-sensitive environments. There is no audit   trail that can prove who published what, only which   project published what, and <code>Origin</code> is self-asserted.</li> <li>Secrets or PII. Entry content is stored plaintext on the   hub and fanned out to every subscribed client. Don't publish   anything you wouldn't paste in a team chat.</li> <li>Wholesale journal sharing. See \"what does not flow\"   above. If that's what you want, this feature won't provide   it. Talk to us in the issue tracker about what would.</li> </ul>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#how-entries-reach-your-agent","level":2,"title":"How Entries Reach Your Agent","text":"<p>Once a project is registered and subscribed, entries arrive by three mechanisms:</p> <ol> <li><code>ctx connection sync</code>: an on-demand pull, replays    everything new since the last sequence you saw.</li> <li><code>ctx connection listen</code>: a long-lived gRPC stream that    writes new entries to <code>.context/hub/</code> as they arrive.</li> <li><code>check-hub-sync</code> hook: runs at session start, daily    throttled, so most users never call <code>sync</code> manually.</li> </ol> <p>Once entries exist in <code>.context/hub/</code>, <code>ctx agent --include-hub</code> adds a dedicated tier to the budget-aware context packet, scored by recency and type relevance. That's the end of the pipeline.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#where-to-go-next","level":2,"title":"Where to Go Next","text":"If you're… Read Trying it for yourself on one machine Getting Started A solo developer using the hub day-to-day Personal cross-project brain Setting up for a small team on a LAN Multi-machine setup A small team using the hub day-to-day Team knowledge bus Running redundant nodes HA cluster Operating a hub in production Operations Assessing the security posture Security model Debugging a hub in trouble Failure modes Just reading the commands <code>ctx connect</code>, <code>ctx serve</code>, <code>ctx hub</code>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-personal/","level":1,"title":"Personal Cross-Project Brain","text":"","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#personal-cross-project-brain","level":1,"title":"Personal Cross-Project Brain","text":"<p>This recipe shows how one developer uses a <code>ctx</code> Hub across their own projects day-to-day, the \"Story 1\" shape from the Hub overview. You're not setting up infrastructure for a team; you're making a lesson you learned last Tuesday in project A automatically surface when you open project B next Thursday.</p> <p>Prerequisites: a working <code>ctx</code> Hub on localhost (see Getting Started for the roughly five-minute setup). This recipe assumes the hub is already running and you've registered at least two projects.</p> <p>Activate Each Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> after each <code>cd <project></code> (or wire it into direnv). The hub server (<code>ctx hub start</code>, etc.) runs on the server and doesn't need this; the commands in this recipe (<code>ctx add --share</code>, <code>ctx agent --include-hub</code>, <code>ctx connection ...</code>) live inside a project and do. If you skip the <code>eval</code>, they'll fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#the-core-loop","level":2,"title":"The Core Loop","text":"<p>Every day, the same three verbs matter:</p> <ol> <li>Record: notice a decision, learning, or    convention and capture it with <code>ctx add --share</code>.</li> <li>Subscribe: every project you care about is    subscribed to the types you want delivered (set once    with <code>ctx connection subscribe</code>).</li> <li>Load: your agent picks up shared entries on next    session start via the auto-sync hook, or explicitly    via <code>ctx agent --include-hub</code>.</li> </ol> <p>That's the whole workflow. The rest of this recipe fills in the concrete moments where each verb matters.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#a-realistic-day","level":2,"title":"A Realistic Day","text":"<p>You have three projects on your workstation:</p> <ul> <li><code>~/projects/api</code>, a Go service you're actively   developing</li> <li><code>~/projects/cli</code>, a companion CLI that consumes the   API</li> <li><code>~/projects/dotfiles</code>, your personal conventions and   cross-project learnings</li> </ul> <p>All three are registered with a single hub running on <code>localhost:9900</code> (started once at boot, or via a systemd user unit; see Hub operations). All three subscribe to <code>decision</code>, <code>learning</code>, and <code>convention</code>.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#0900-start-work-on-api","level":3,"title":"09:00 - Start Work on <code>api</code>","text":"<p>You <code>cd ~/projects/api</code> and start a Claude Code session. Behind the scenes, the plugin's <code>PreToolUse</code> hook calls <code>ctx agent --budget 8000 --include-hub</code> before the first tool call. Agent loads:</p> <ul> <li>Local <code>.context/</code> (TASKS, DECISIONS, LEARNINGS, etc.)</li> <li>Foundation steering files (always-inclusion)</li> <li>Everything you've shared from the other two projects</li> </ul> <p>So the \"use UTC timestamps everywhere\" decision you recorded in <code>dotfiles</code> last week is already in Claude's context for this session, without any manual <code>sync</code>.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1030-you-discover-a-gotcha","level":3,"title":"10:30 - You Discover a Gotcha","text":"<p>While debugging, you find that the API's retry loop silently drops the last error when the transport times out. This is the kind of thing you'd normally add to <code>LEARNINGS.md</code> in <code>api/</code>. But it's useful across every Go service you'll ever write, not just this one. So:</p> <pre><code>ctx learning add --share \\\n  --context \"Go http.Client retries mask the final error\" \\\n  --lesson  \"Transport timeouts don't surface as errors when the retry loop re-assigns err without wrapping. Check for context.DeadlineExceeded on the request context instead.\" \\\n  --application \"Any retry loop over http.Client.Do that uses a per-attempt timeout\"\n</code></pre> <p>The <code>--share</code> flag does two things:</p> <ol> <li>Writes the learning to <code>api/.context/LEARNINGS.md</code>    locally (as a normal <code>ctx learning add</code> would).</li> <li>Publishes the same entry to the <code>ctx</code> Hub, which stores it    in the append-only JSONL and fans it out to every    subscribed client.</li> </ol> <p>Within seconds, <code>cli/.context/hub/learnings.md</code> and <code>dotfiles/.context/hub/learnings.md</code> both contain a copy of this learning (the <code>ctx connection listen</code> daemon picks it up from the <code>ctx</code> Hub's Listen stream).</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1200-you-switch-to-cli","level":3,"title":"12:00 - You Switch to <code>cli</code>","text":"<p><code>cd ~/projects/cli</code>, open a new session. The agent packet for <code>cli</code> now includes the learning you just recorded in <code>api</code>, because <code>cli</code> is subscribed to <code>learning</code> and the entry has already been synced into <code>cli/.context/hub/learnings.md</code>.</p> <p>You don't have to re-explain the retry-loop gotcha. Claude already sees it.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1400-you-codify-a-convention","level":3,"title":"14:00 - You Codify a Convention","text":"<p>You've been writing error messages in <code>api</code> and decided you want a consistent pattern: lowercase start, no trailing period, single-sentence. This is a convention, not a decision; it applies to every Go project you touch. Record it in <code>dotfiles</code> (since that's your \"personal standards\" project), and share it:</p> <pre><code>cd ~/projects/dotfiles\nctx convention add --share \\\n  \"Error messages: lowercase start, no trailing period, single sentence (follows Go's stdlib style)\"\n</code></pre> <p>The convention lands in <code>dotfiles/CONVENTIONS.md</code> locally and fans out to <code>api</code> and <code>cli</code> via the hub. The next Claude Code session in either project gets the convention injected into the steering-adjacent slot of the agent packet.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1630-end-of-day","level":3,"title":"16:30 - End of Day","text":"<p>You didn't run <code>ctx connection sync</code> once. You didn't <code>git push</code> anything between projects. You didn't remember to tell your agent about the retry-loop gotcha in the new project. The hub did all of it for you.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-the-workflow-actually-looks-like","level":2,"title":"What the Workflow Actually Looks Like","text":"<p>Stripped of prose, the day's commands were:</p> <pre><code># Morning: nothing. Agent loads --include-hub automatically.\n\n# Mid-morning: record a learning that should cross projects\nctx learning add --share \\\n  --context \"...\" --lesson \"...\" --application \"...\"\n\n# Afternoon: codify a convention in the \"standards\" project\nctx convention add --share \"...\"\n\n# Evening: nothing. Everything's already propagated.\n</code></pre> <p>The hub is passive infrastructure. You never talk to it directly; you talk through it by using <code>--share</code> on commands you were already running.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#tips-for-solo-use","level":2,"title":"Tips for Solo Use","text":"<p>Pick a \"standards\" project. One of your projects should play the role of \"canonical source for rules you want everywhere.\" Your dotfiles, a personal scratch repo, or a dedicated <code>ctx-standards</code> project all work. Record cross-cutting conventions there and let the hub propagate them to everything else.</p> <p>Subscribe to <code>task</code> only if you want cross-project todos. The four subscribable types are <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Tasks are usually project-local; subscribing makes every hub-shared task from every project show up in every other project's agent packet. That's probably not what you want. Skip <code>task</code> in <code>ctx connection subscribe</code> unless you have a specific reason.</p> <p>Run the hub as a user-level daemon so you don't have to remember to start it. On Linux with systemd:</p> <pre><code># ~/.config/systemd/user/ctx-hub.service\n[Unit]\nDescription=ctx Hub (personal)\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/ctx hub start\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n</code></pre> <pre><code>systemctl --user enable --now ctx-hub.service\n</code></pre> <p>Don't overthink subscription filters. For personal use, subscribe every project to all four types at first (or three, if you skip <code>task</code>). Tune later if the context packets get noisy.</p> <p>Local storage is fine; no TLS needed. The hub runs on localhost. No one else is on the network. Skip the TLS setup from the Multi-machine recipe; it's relevant when the hub is on a LAN host serving multiple workstations, not when it's a personal daemon.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"<p>Not a setup guide. For the one-time hub install and project registration, use Getting Started.</p> <p>Not a team guide. If you're sharing across humans, not just across your own projects, read Team knowledge bus instead; the trust model and operational concerns are different.</p> <p>Not production operations. For backup, log rotation, failure recovery, and HA, see Hub operations and Hub failure modes.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hub overview: when to use the Hub   and when not to.</li> <li>Team knowledge bus: the multi-human   companion recipe.</li> <li><code>ctx connect</code>: the client-side   commands used above (<code>subscribe</code>, <code>publish</code>, <code>sync</code>,   <code>listen</code>, <code>status</code>).</li> <li><code>ctx add</code>: the <code>--share</code> flag   reference.</li> <li><code>ctx hub</code>: operator commands for   starting, stopping, and inspecting the hub.</li> </ul>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-team/","level":1,"title":"Team Knowledge Bus","text":"","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#team-knowledge-bus","level":1,"title":"Team Knowledge Bus","text":"<p>This recipe shows how a small trusted team uses a <code>ctx</code> Hub as a shared knowledge bus, the \"Story 2\" shape from the Hub overview. You're not building a wiki, you're not replacing your issue tracker, and you're not running a multi-tenant service. You're connecting 3-10 developers who trust each other so that lessons, decisions, and conventions flow between them without ceremony.</p> <p>Prerequisites:</p> <ul> <li>A running <code>ctx</code> Hub on a LAN host or internal server   everyone on the team can reach. See   Multi-machine setup for the   deployment guide.</li> <li>Each team member has <code>ctx</code> installed and has   <code>ctx connection register</code>-ed their working projects with   the hub.</li> <li>Each project on each workstation has been activated for   the shell with <code>eval \"$(ctx activate)\"</code>. The hub server   (<code>ctx hub start</code>, etc.) doesn't need this, but the   client side (<code>ctx connection ...</code>, <code>ctx add --share</code>)   lives in a project and does. If you skip activation,   those client commands fail with <code>Error: no context   directory specified</code>. See   Activating a Context Directory.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#trust-model-read-this-first","level":2,"title":"Trust Model: Read This First","text":"<p>The hub assumes everyone holding a client token is friendly. There's no per-user attribution you can rely on, no read ACL beyond subscription filters, and <code>Origin</code> is self-asserted by the publishing client. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.</p> <p>If your team is:</p> <ul> <li>✅ 3-10 engineers, all known to each other, all   trusted with production access</li> <li>✅ On a single internal network or behind a VPN</li> <li>✅ Comfortable with \"the hub assumes friendly   participants\"</li> </ul> <p>…this recipe fits. If your team is:</p> <ul> <li>❌ Larger than ~15, with turnover</li> <li>❌ Includes contractors, untrusted agents, or   compromised-workstation concerns</li> <li>❌ Needs audit trails that prove who published what</li> <li>❌ Requires per-team-member isolation</li> </ul> <p>…you're in \"Story 3\" territory, which the hub does not support today. Use a wiki or a dedicated knowledge platform instead.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#the-teams-three-verbs","level":2,"title":"The Team's Three Verbs","text":"<p>Everyone on the team does three things, same as in the personal recipe, but with different social expectations:</p> <ol> <li>Record: when you learn something that would save    a teammate time, capture it with <code>ctx add --share</code>.</li> <li>Subscribe: every engineer's project directories    subscribe to the types the team cares about.</li> <li>Load: agents pick up shared entries automatically    via the auto-sync hook and the <code>--include-hub</code> flag    in the PreToolUse hook pipeline.</li> </ol> <p>The operational shape is identical to solo use. What's different is the culture around publishing: when do you <code>--share</code>, and what belongs on the hub vs. in your local <code>.context/</code>.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-goes-on-the-hub-team-rules-of-thumb","level":2,"title":"What Goes on the Hub (Team Rules of Thumb)","text":"<p>Share it if it's true for more than one person. The central question: \"would the next teammate who hits this problem save time if they already knew this?\" If yes, <code>--share</code>. If no, record it locally and move on.</p> <p>Decisions:</p> <ul> <li>✅ Cross-service decisions (database choice, auth   model, deployment pattern, monitoring stack).</li> <li>✅ Policy decisions that apply to all services   (naming, API versioning, error-message format).</li> <li>❌ Internal implementation decisions inside a single   service (\"chose a map over a slice here because lookups   dominate\").</li> <li>❌ One-off tactical calls for a specific PR.</li> </ul> <p>Learnings:</p> <ul> <li>✅ Gotchas, surprising behavior, flaky infrastructure   quirks, anything you'd tell a teammate over coffee   with \"watch out for X\".</li> <li>✅ Lessons from incidents, right after the postmortem   is the highest-value time to share.</li> <li>❌ Internal debugging notes that only make sense with   context from your current branch.</li> </ul> <p>Conventions:</p> <ul> <li>✅ Repo layout, commit message format, pre-commit   hooks, review expectations.</li> <li>✅ Language-level style decisions that apply across   services.</li> <li>❌ Per-service idioms (\"in <code>billing/</code> we prefer…\").</li> </ul> <p>Tasks: almost always project-local. Don't subscribe to <code>task</code> unless the team has a specific reason (e.g., a cross-cutting migration you want visible everywhere).</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#a-realistic-week","level":2,"title":"A Realistic Week","text":"<p>Monday, 3 AM incident, shared learning</p> <p>On-call engineer Alice gets paged: the payment service starts returning 500s after a dependency update. After an hour she finds the culprit: a breaking change in a transitive gRPC dep that only manifests under high concurrency. Postmortem on Tuesday, but right now she records the learning:</p> <pre><code>ctx learning add --share \\\n  --context \"Payment service 3 AM incident, 2026-04-03\" \\\n  --lesson  \"grpc-go v1.62+ changes DialContext behavior under high \\\n  concurrency: connections from a single channel can deadlock if the \\\n  server emits GOAWAY mid-stream. Symptom: 500 errors cluster in \\\n  30s bursts, no error in grpc client logs.\" \\\n  --application \"Any service on grpc-go. Pin to v1.61 or patch with \\\n  keepalive: https://github.com/grpc/grpc-go/issues/...\" \n</code></pre> <p>By Tuesday morning, every other engineer's agent context packet contains this learning. When Bob starts work on the <code>ledger</code> service (which also uses grpc-go), his Claude Code session already knows about the gotcha without Bob having to read the incident channel.</p> <p>Wednesday, cross-service decision</p> <p>The team agrees on a new pattern for API versioning: header-based instead of URL-based. Platform lead Carol records the decision:</p> <pre><code>ctx decision add --share \\\n  --context \"Need consistent API versioning across all 6 services. \\\n  Current URL-based /v1/ isn't working for gradual rollouts.\" \\\n  --rationale \"Header-based versioning lets us route by header at the \\\n  edge, which makes canary rollouts trivial. URL-based versioning \\\n  forces clients to update their paths.\" \\\n  --consequence \"All new endpoints use X-API-Version header. \\\n  Existing /v1/ endpoints stay. Deprecation schedule in q3.\" \\\n  \"Use header-based API versioning for new endpoints\"\n</code></pre> <p>Every engineer's next session knows about this decision automatically. When Dave starts adding endpoints to the <code>inventory</code> service on Thursday, Claude already prompts him for the header pattern instead of defaulting to <code>/v1/</code>.</p> <p>Friday, convention drift caught at review</p> <p>Dave notices that his PR auto-formatted some error messages to end with periods. He recalls the team convention is \"no trailing period\" but can't remember where it was documented. He runs <code>ctx connection status</code>, sees the hub is healthy, greps his local <code>.context/hub/conventions.md</code>, and finds:</p> <pre><code>## [2026-03-12] Error message format\nLowercase start, no trailing period, single sentence.\n</code></pre> <p>He fixes the PR. No lookup on the wiki, no question in chat, no context-switch penalty.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#workflow-tips-for-teams","level":2,"title":"Workflow Tips for Teams","text":"<p>Designate a \"champion\" for decisions. The team lead or platform engineer should be the person who explicitly <code>--share</code>s cross-cutting decisions. Other team members share learnings freely but should ask \"should this be a decision?\" in review before <code>--share</code>ing a decision. This keeps the decision stream signal-rich.</p> <p>Publish postmortem learnings immediately, not after the meeting. The postmortem itself is a document; the actionable rules that come out of it belong on the hub, and they should land within an hour of the incident. \"Share fast, edit later\" is the rule.</p> <p>Delete noisy entries, don't tolerate them. The hub is append-only, but the <code>.context/hub/</code> mirror on each client is just Markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate <code>entries.jsonl</code> (see Hub operations). Noisy shared feeds lose trust fast.</p> <p>Don't subscribe every project to every type. For backend engineers, subscribing to <code>decision + learning + convention</code> is usually right. For platform or DevOps projects, adding <code>task</code> makes sense. For a prototype or experiment project, subscribing only to <code>convention</code> might be enough.</p> <p>Run a single hub, not one per team. If two teams need to share knowledge, they should share a hub. Splitting hubs by team creates silos, which is often exactly the thing you were trying to solve.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#operational-concerns","level":2,"title":"Operational Concerns","text":"<p>The team recipe assumes someone owns the hub host. That person (or a small group) is responsible for:</p> <ul> <li>Uptime: the hub is infrastructure; treat it like   any other internal service you run. See   Hub operations.</li> <li>Backups: <code>entries.jsonl</code> is the source of truth.   Snapshot it to the same backup tier as your other   internal data.</li> <li>Upgrades: cadence the team agrees on. Major   upgrades may require everyone to re-register, so do   them at natural breaks.</li> <li>Failures: see   Hub failure modes   for the standard oncall playbook.</li> </ul> <p>Optional but recommended: run a 3-node Raft cluster so the hub survives individual node failures. See HA cluster. For teams under 10 people, a single-node hub with daily backups is usually fine.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#token-management","level":2,"title":"Token Management","text":"<p>Every team member has a client token stored in their <code>.context/.connect.enc</code>. Rules of thumb:</p> <ul> <li>One token per engineer per project. Not one token   per team; not one shared token. Each engineer   registers each of their working projects separately.</li> <li>Token compromise = revoke immediately. When an   engineer leaves, their tokens should be removed from   <code>clients.json</code> on the hub. This is a manual operation   today; see Hub security for the   revocation steps.</li> <li>No checked-in tokens. <code>.context/.connect.enc</code> is   encrypted with the local machine key, but don't push   it to shared repos; it's per-workstation.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"<p>Not a wiki replacement. The hub is for structured entries, not prose. Put your architecture overviews, onboarding docs, and design discussions in a real wiki.</p> <p>Not an audit log. <code>Origin</code> on the hub is self-asserted. If compliance requires provenance, the hub is the wrong tool.</p> <p>Not a ticket system. Task sharing works, but mature teams already have Jira/Linear/Github Issues. Don't try to replace those with hub tasks; use the hub for lightweight cross-project todos that your existing tracker doesn't capture well.</p> <p>Not a production service for end users. This is internal team infrastructure. Do not expose the hub to customers, partners, or the open internet.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hub overview: when to use the   hub and when not to.</li> <li>Personal cross-project brain:   the single-developer companion recipe.</li> <li>Multi-machine setup:   standing up the hub on a LAN host.</li> <li>HA cluster: optional redundancy   for larger teams.</li> <li>Hub operations: backup,   rotation, monitoring.</li> <li>Hub security: threat model   and hardening checklist.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/import-plans/","level":1,"title":"Importing Claude Code Plans","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code plan files (<code>~/.claude/plans/*.md</code>) are ephemeral: They have structured context, approach, and file lists, but they're orphaned after the session ends. The filenames are UUIDs, so you can't tell what's in them without opening each one.</p> <p>How do you turn a useful plan into a permanent project spec?</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tldr","level":2,"title":"TL;DR","text":"<pre><code>You: /ctx-plan-import\nAgent: [lists plans with dates and titles]\n       1. 2026-02-28  Add authentication middleware\n       2. 2026-02-27  Refactor database connection pool\nYou: \"import 1\"\nAgent: [copies to specs/add-authentication-middleware.md]\n</code></pre> <p>Plans are copied (not moved) to <code>specs/</code>, slugified by their H1 heading.</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-plan-import</code> Skill List, filter, and import plan files to specs <code>/ctx-task-add</code> Skill Optionally add a task referencing the spec","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-1-list-available-plans","level":3,"title":"Step 1: List Available Plans","text":"<p>Invoke the skill and it lists plans with modification dates and titles:</p> <pre><code>You: /ctx-plan-import\n\nAgent: Found 3 plan files:\n         1. 2026-02-28  Add authentication middleware\n         2. 2026-02-27  Refactor database connection pool\n         3. 2026-02-25  Import plans skill\n       Which plans would you like to import?\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-2-filter-optional","level":3,"title":"Step 2: Filter (Optional)","text":"<p>You can narrow the list with arguments:</p> Argument Effect <code>--today</code> Only plans modified today <code>--since YYYY-MM-DD</code> Only plans modified on or after the date <code>--all</code> Import everything without prompting (none) Interactive selection <pre><code>You: /ctx-plan-import --today\nYou: /ctx-plan-import --since 2026-02-27\nYou: /ctx-plan-import --all\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-3-select-and-import","level":3,"title":"Step 3: Select and Import","text":"<p>Pick one or more plans by number:</p> <pre><code>You: \"import 1 and 3\"\n\nAgent: Imported 2 plan(s):\n         ~/.claude/plans/abc123.md -> specs/add-authentication-middleware.md\n         ~/.claude/plans/ghi789.md -> specs/import-plans-skill.md\n       Want me to add tasks referencing these specs?\n</code></pre> <p>The agent reads the H1 heading from each plan and slugifies it for the filename. If a plan has no H1 heading, the original filename (minus extension) is used as the slug.</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-4-add-follow-up-tasks-optional","level":3,"title":"Step 4: Add Follow-Up Tasks (Optional)","text":"<p>If you say yes, the agent creates tasks in <code>TASKS.md</code> that reference the imported specs:</p> <pre><code>You: \"yes, add tasks\"\n\nAgent: [runs /ctx-task-add for each spec]\n       Added:\n         - [ ] Implement authentication middleware (spec: specs/add-authentication-middleware.md)\n         - [ ] Import plans skill (spec: specs/import-plans-skill.md)\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember the exact skill name:</p> You say What happens \"import my plans\" <code>/ctx-plan-import</code> (interactive) \"save today's plans as specs\" <code>/ctx-plan-import --today</code> \"import all plans from this week\" <code>/ctx-plan-import --since ...</code> \"turn that plan into a spec\" <code>/ctx-plan-import</code> (filtered)","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tips","level":2,"title":"Tips","text":"<ul> <li>Plans are copied, not moved: The originals stay in <code>~/.claude/plans/</code>.   Claude Code manages that directory; <code>ctx</code> doesn't delete from it.</li> <li>Conflict handling: If <code>specs/{slug}.md</code> already exists, the agent   asks whether to overwrite or pick a different name.</li> <li>Specs are project memory: Once imported, specs are tracked in git   and available to future sessions. Reference them from <code>TASKS.md</code> phase   headers with <code>Spec: specs/slug.md</code>.</li> <li>Pair with <code>/ctx-implement</code>: After importing a plan as a spec, use   <code>/ctx-implement</code> to execute it step-by-step with verification.</li> </ul>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: /ctx-plan-import:   full skill description</li> <li>The Complete Session: where plan import fits   in the session flow</li> <li>Tracking Work Across Sessions: managing tasks   that reference imported specs</li> </ul>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/knowledge-capture/","level":1,"title":"Persisting Decisions, Learnings, and Conventions","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-problem","level":2,"title":"The Problem","text":"<p>You debug a subtle issue, discover the root cause, and move on.</p> <p>Three weeks later, a different session hits the same issue. The knowledge existed briefly in one session's memory but was never written down.</p> <p>Architectural decisions suffer the same fate: you weigh trade-offs, pick an approach, and six sessions later the AI suggests the alternative you already rejected.</p> <p>How do you make sure important context survives across sessions?</p> <p>Prefer Skills to Raw Commands</p> <p>Use <code>/ctx-decision-add</code> and <code>/ctx-learning-add</code> instead of raw <code>ctx add</code> commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx add ...</code> / <code>ctx reindex</code> / <code>ctx decision ...</code> / <code>ctx learning ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-reflect               # surface items worth persisting\n/ctx-decision-add \"Title\"  # record with context/rationale/consequence\n/ctx-learning-add \"Title\"  # record with context/lesson/application\n</code></pre> <p>Or just tell your agent: \"What have we learned this session?\"</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx decision add</code> Command Record an architectural decision <code>ctx learning add</code> Command Record a gotcha, tip, or lesson <code>ctx convention add</code> Command Record a coding pattern or standard <code>ctx reindex</code> Command Rebuild both quick-reference indices <code>ctx decision reindex</code> Command Rebuild the DECISIONS.md index <code>ctx learning reindex</code> Command Rebuild the LEARNINGS.md index <code>/ctx-decision-add</code> Skill AI-guided decision capture with validation <code>/ctx-learning-add</code> Skill AI-guided learning capture with validation <code>/ctx-convention-add</code> Skill AI-guided convention recording with placement <code>/ctx-reflect</code> Skill Surface items worth persisting at breakpoints","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-workflow","level":2,"title":"The Workflow","text":"<p>Decision, Learning, or Convention?</p> <ul> <li>If you chose between alternatives, it is a decision.</li> <li>If you discovered something surprising, it is a learning.</li> <li>If you are codifying a repeated pattern, it is a convention.</li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-1-understand-what-to-persist","level":3,"title":"Step 1: Understand What to Persist","text":"<p>Three context files serve different purposes:</p> <p>Decisions (<code>DECISIONS.md</code>) answer \"why is it this way?\" They record trade-offs between alternatives with structured fields: context, rationale, and consequences. Decisions prevent re-debating settled questions.</p> <p>Learnings (<code>LEARNINGS.md</code>) answer \"what did we discover the hard way?\" They record gotchas and debugging insights specific to this project with structured fields: context, lesson, and application. </p> <p>Learnings prevent repeating past mistakes.</p> <p>Conventions (<code>CONVENTIONS.md</code>) answer \"how do we do things here?\" They record patterns and standards. No structured fields required: just a name, a rule, and an example. Conventions keep code consistent across sessions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-2-record-decisions","level":3,"title":"Step 2: Record Decisions","text":"<p>Decisions have three structured fields: </p> <ul> <li>context, </li> <li>rationale, </li> <li>and consequences.</li> </ul> <p>The easiest way to record one is to tell your agent:</p> <pre><code>You: \"Save that as a decision: we picked file-based cooldown\n      tokens over env vars.\"\n\nAgent: \"Recorded in DECISIONS.md:\n  - Decision: Use file-based cooldown tokens instead of env vars\n  - Context: Hook subprocesses cannot persist env vars to parent shell\n  - Rationale: File tokens survive across processes. Simpler than\n    IPC. Cleanup is automatic via TTL.\n  - Consequence: Tombstone files accumulate in /tmp. Cannot share\n    state across machines.\"\n</code></pre> <p>The agent was present for the trade-off discussion. It fills in the structured fields from what it observed, which is hard to reconstruct later as a terminal one-liner.</p> <p>When multiple options were considered, the agent includes rejected alternatives in the rationale automatically.</p> <p>The <code>/ctx-decision-add</code> skill also guides you through the fields interactively. For quick decisions, it supports a Y-statement:</p> <p>\"In the context of [situation], facing [constraint], we decided for [choice] and against [alternatives], to achieve [benefit], accepting that [trade-off].\"</p> CLI Command for Scripting and Automation <p>When no agent is in the loop (CI pipelines, shell scripts, hooks), use the CLI directly with structured flags:</p> <pre><code>ctx decision add \"Use file-based cooldown tokens instead of env vars\" \\\n  --context \"Hook subprocesses cannot persist env vars to parent shell\" \\\n  --rationale \"File tokens survive across processes. Simpler than IPC. Cleanup is automatic via TTL.\" \\\n  --consequence \"Tombstone files accumulate in /tmp. Cannot share state across machines.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-3-record-learnings","level":3,"title":"Step 3: Record Learnings","text":"<p>Learnings have three structured fields: context, lesson, and application.</p> <p>Again, your agent is the best tool for this. It was there when you discovered the gotcha and can express it more completely than a backslash-escaped terminal command:</p> <pre><code>You: \"That subprocess thing was painful. Save it as a learning.\"\n\nAgent: \"Added to LEARNINGS.md:\n  - Learning: Claude Code hooks run in a subprocess\n  - Context: Set env var in PreToolUse hook, but it was not visible\n    in the main session\n  - Lesson: Hook scripts execute in a child process. Env changes do\n    not propagate to parent.\n  - Application: Use tombstone files for hook-to-session\n    communication. Never rely on hook env vars.\"\n</code></pre> <p>You said one sentence. The agent produced a structured entry with all three fields filled from the session context.</p> <p>That is the point: the agent has the full conversation and can be far more expressive than what you would type at a command prompt.</p> <p>The <code>/ctx-learning-add</code> skill applies three quality filters:</p> <ol> <li>Could someone Google this in 5 minutes?</li> <li>Is it specific to this codebase?</li> <li>Did it take real effort to discover?</li> </ol> <p>All three must pass.</p> <p>Learnings capture principles and heuristics, not code snippets.</p> CLI Command for Scripting and Automation <p>When no agent is in the loop:</p> <pre><code>ctx learning add \"Claude Code hooks run in a subprocess\" \\\n  --context \"Set env var in PreToolUse hook, but it was not visible in the main session\" \\\n  --lesson \"Hook scripts execute in a child process. Env changes do not propagate to parent.\" \\\n  --application \"Use tombstone files for hook-to-session communication. Never rely on hook env vars.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-4-record-conventions","level":3,"title":"Step 4: Record Conventions","text":"<p>Conventions are simpler: just a name, a rule, and optionally a section.</p> <p>These are short enough that either approach works:</p> <pre><code>You: \"We've been using kebab-case for every CLI flag. Codify that.\"\n\nAgent: \"Added to CONVENTIONS.md under Naming:\n  'Use kebab-case for all CLI flag names.'\"\n</code></pre> <p>Or from the terminal:</p> <pre><code>ctx convention add \"Use kebab-case for all CLI flag names\" --section \"Naming\"\n</code></pre> <p>Conventions work best for rules that come up repeatedly. Codify a pattern the third time you see it, not the first.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-5-reindex-after-manual-edits","level":3,"title":"Step 5: Reindex After Manual Edits","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> maintain a quick-reference index at the top: a compact table of date and title for each entry. The index updates automatically via <code>ctx add</code>, but falls out of sync after hand edits.</p> <pre><code>ctx reindex\n</code></pre> <p>This single command regenerates both indices. You can also reindex individually with <code>ctx decision reindex</code> or <code>ctx learning reindex</code>.</p> <p>Run reindex after any manual edit. The index lets AI tools scan all entries without reading the full file, which matters when token budgets are tight.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-6-use-ctx-reflect-to-surface-what-to-capture","level":3,"title":"Step 6: Use <code>/ctx-reflect</code> to Surface What to Capture","text":"<p>Keep It Conversational</p> <p><code>/ctx-reflect</code> is not the only way to trigger reflection.</p> <p>Agents trained on the <code>ctx</code> playbook naturally surface persist-worthy items at breakpoints, even without invoking the skill explicitly.</p> <p>A conversational prompt like \"anything worth saving?\" or \"let's wrap up\" can trigger the same review.</p> <p>The skill provides a structured checklist, but the behavior is available through natural conversation.</p> <p>At natural breakpoints (after completing a feature, fixing a bug, or before ending a session) use <code>/ctx-reflect</code> to identify items worth persisting.</p> <pre><code>/ctx-reflect\n</code></pre> <p>The skill walks through learnings, decisions, tasks, and session notes, skipping categories with nothing to report. The output includes specific commands for each suggested persist:</p> <pre><code>This session implemented file-based cooldown for `ctx agent` and\ndiscovered that hook subprocesses cannot set env vars in the parent.\n\nI'd suggest persisting:\n- **Learning**: Hook subprocesses cannot propagate env vars\n  `ctx learning add \"...\" --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Decision**: File-based cooldown tokens over env vars\n  `ctx decision add \"...\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n</code></pre> <p>The skill always asks before persisting.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-7-the-conversational-approach","level":3,"title":"Step 7: The Conversational Approach","text":"<p>The commands shown in Steps 2 to 4 are precise, but you rarely need to type them yourself. When working with an agent that has loaded the <code>ctx</code> playbook, natural language triggers the same operations.</p> <p>You ask, the agent acts:</p> You say What the agent does \"What have we learned this session?\" Reviews recent work, identifies learnings, offers to persist \"Save that as a decision\" Captures the trade-off you just discussed with structured fields \"Any gotchas worth remembering?\" Scans recent work for non-obvious lessons \"Record that convention\" Adds the pattern you just agreed on to <code>CONVENTIONS.md</code> \"Let's wrap up\" Runs through the reflect checklist conversationally <p>Example exchange:</p> <pre><code>You: \"What did we learn from that debugging session?\"\n\nAgent: \"We discovered three things worth persisting:\n  1. Hook subprocesses can't propagate env vars to the parent. We\n     spent 20 minutes on this before switching to tombstone files.\n  2. Tombstone files need a TTL or they accumulate in /tmp.\n  3. The cooldown logic must check file mtime, not just existence.\n  Want me to save these as learnings?\"\n\nYou: \"Yes, all of them.\"\n\nAgent: \"Done. Added all three to LEARNINGS.md with structured fields.\"\n</code></pre> <p>The agent can also be proactive without being asked. The playbook encourages agents to persist context at natural milestones.</p> <p>You may see:</p> <ul> <li>After fixing a tricky bug: \"I noticed this was a non-obvious issue.   Want me to save it as a learning?\"</li> <li>After choosing between approaches: \"We just made a trade-off between   file-based tokens and IPC. Should I record this as a decision?\"</li> <li>At session milestones: the agent checks what is worth persisting and offers a   brief summary.</li> </ul> <p>When agents run unattended (no human in the loop), they may persist directly:</p> <ul> <li>\"I've added the subprocess env var gotcha to <code>LEARNINGS.md</code> so we don't hit it again.\"</li> <li>\"Recorded the decision to use file-based tokens over env vars in <code>DECISIONS.md</code>.\"</li> <li>\"Marked the cooldown task done and added a follow-up for TTL cleanup.\"</li> </ul> <p>This is behavior by design.</p> <p>The playbook's self-check prompt, \"If this session ended right now, would the next session know what happened?\" drives agents to persist early and often rather than waiting for explicit instructions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#command-line-approach-scripting-and-automation","level":3,"title":"Command-Line Approach (Scripting and Automation)","text":"<pre><code># Decision: record the trade-off\nctx decision add \"Use PostgreSQL over SQLite\" \\\n  --context \"Need concurrent multi-user access\" \\\n  --rationale \"SQLite locks on writes; Postgres handles concurrency\" \\\n  --consequence \"Requires a database server; team needs Postgres training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Learning: record the gotcha\nctx learning add \"SQL migrations must be idempotent\" \\\n  --context \"Deploy failed when migration ran twice after rollback\" \\\n  --lesson \"CREATE TABLE without IF NOT EXISTS fails on retry\" \\\n  --application \"Always use IF NOT EXISTS guards in migrations\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Convention: record the pattern\nctx convention add \"API handlers return structured errors\" --section \"API\"\n\n# Reindex after manual edits\nctx reindex\n\n# Reflect at breakpoints (from AI assistant)\n# /ctx-reflect\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#conversational-approach-interactive-sessions","level":3,"title":"Conversational Approach (Interactive Sessions)","text":"<pre><code>You: \"We just picked Postgres over SQLite for the concurrency\n      reasons. Save that?\"\n\nAgent: \"Recorded. Added to DECISIONS.md:\n  - Decision: Use PostgreSQL over SQLite\n  - Context: Need concurrent multi-user access\n  - Rationale: SQLite locks on writes; Postgres handles concurrency\n  - Consequence: Requires a database server; team needs Postgres\n    training\"\n\nYou: \"Also that migration thing. The deploy blew up when the\n      migration ran twice.\"\n\nAgent: \"Got it. Added to LEARNINGS.md:\n  - Learning: SQL migrations must be idempotent\n  - Lesson: CREATE TABLE without IF NOT EXISTS fails on retry\n  - Application: Always use IF NOT EXISTS guards in migrations\"\n\nYou: \"Good. Anything else worth saving before we wrap up?\"\n\nAgent: \"We've been using structured error returns in every API\n  handler this session. Want me to codify that as a convention?\"\n\nYou: \"Do it.\"\n\nAgent: \"Done. Added 'API handlers return structured errors' to\n  CONVENTIONS.md under the API section.\"\n</code></pre> <p>Both approaches produce the same structured entries in the same context files.</p> <ul> <li>The conversational approach is the natural fit for interactive sessions; </li> <li>the CLI commands are better suited for scripts, hooks, and automation pipelines.</li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tips","level":2,"title":"Tips","text":"<ul> <li>Record decisions at the moment of choice. The alternatives you considered and   the reasons you rejected them fade quickly. Capture trade-offs while they are   fresh.</li> <li>Learnings should fail the Gemini test. If someone could find it in a 5-minute   Gemini search, it does not belong in <code>LEARNINGS.md</code>.</li> <li>Conventions earn their place through repetition. Add a convention the third   time you see a pattern, not the first.</li> <li>Use <code>/ctx-reflect</code> at natural breakpoints. The checklist catches items you   might otherwise lose.</li> <li>Keep the entries self-contained. Each entry should make sense on its own. A   future session may load only one due to token budget constraints.</li> <li>Reindex after every hand edit. It takes less than a second. A stale index   causes AI tools to miss entries.</li> <li>Prefer the structured fields. The verbosity forces clarity. A decision without   a rationale is just a fact. A learning without an application is just a story.</li> <li>Talk to your agent, do not type commands. In interactive sessions, the   conversational approach is the recommended way to capture knowledge. Say   \"save that as a learning\" or \"any decisions worth recording?\" and let the   agent handle the structured fields. Reserve the CLI commands for scripting,   automation, and CI/CD pipelines where there is no agent in the loop.</li> <li>Trust the agent's proactive instincts. Agents trained on the <code>ctx</code> playbook will   offer to persist context at milestones. A brief \"want me to save this?\" is   cheaper than re-discovering the same lesson three sessions later.</li> <li> <p>Relax provenance per-project if <code>--session-id</code>, <code>--branch</code>, or <code>--commit</code>   are impractical (e.g., manual notes outside an AI session). Add to <code>.ctxrc</code>:</p> <pre><code>provenance_required:\n  session_id: false   # allow entries without --session-id\n  branch: true        # still require --branch\n  commit: true        # still require --commit\n</code></pre> <p>Default is all three required. Only human config relaxes: Agents cannot bypass, and that's by design.</p> </li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#next-up","level":2,"title":"Next Up","text":"<p>Tracking Work Across Sessions →: Add, prioritize, complete, and archive tasks across sessions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#see-also","level":2,"title":"See Also","text":"<ul> <li>Tracking Work Across Sessions: managing the tasks that   decisions and learnings support</li> <li>The Complete Session: full session lifecycle including   reflection and context persistence</li> <li>Detecting and Fixing Drift: keeping knowledge files   accurate as the codebase evolves</li> <li>CLI Reference: full documentation for <code>ctx add</code>,   <code>ctx decision</code>, <code>ctx learning</code></li> <li>Context Files: format and conventions for <code>DECISIONS.md</code>,   <code>LEARNINGS.md</code>, and <code>CONVENTIONS.md</code></li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/memory-bridge/","level":1,"title":"Bridging Claude Code Auto Memory","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code maintains per-project auto memory at <code>~/.claude/projects/<slug>/memory/MEMORY.md</code>. This file is:</p> <ul> <li>Outside the repo - not version-controlled, not portable</li> <li>Machine-specific - tied to one <code>~/.claude/</code> directory</li> <li>Invisible to <code>ctx</code> - context loading and hooks don't read it</li> </ul> <p>Meanwhile, <code>ctx</code> maintains structured context files (DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) that are git-tracked, portable, and token-budgeted - but Claude Code doesn't automatically write to them.</p> <p>The two systems hold complementary knowledge with no bridge between them.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx memory sync          # Mirror MEMORY.md into .context/memory/mirror.md\nctx memory status        # Check for drift\nctx memory diff          # See what changed since last sync\n</code></pre> <p>The <code>check-memory-drift</code> hook nudges automatically when MEMORY.md changes - you don't need to remember to sync manually.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx memory ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx memory sync</code> CLI command Copy MEMORY.md to mirror, archive previous <code>ctx memory status</code> CLI command Show drift, timestamps, line counts <code>ctx memory diff</code> CLI command Show changes since last sync <code>ctx memory import</code> CLI command Classify and promote entries to .context/ files <code>ctx memory publish</code> CLI command Push curated .context/ content to MEMORY.md <code>ctx memory unpublish</code> CLI command Remove published block from MEMORY.md <code>ctx system check-memory-drift</code> Hook Nudge when MEMORY.md has changed (once/session)","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#how-it-works","level":2,"title":"How It Works","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#discovery","level":3,"title":"Discovery","text":"<p>Claude Code encodes project paths as directory names under <code>~/.claude/projects/</code>. The encoding replaces <code>/</code> with <code>-</code> and prefixes with <code>-</code>:</p> <pre><code>/home/jose/WORKSPACE/ctx  →  ~/.claude/projects/-home-jose-WORKSPACE-ctx/\n</code></pre> <p><code>ctx memory</code> uses this encoding to locate MEMORY.md automatically from your project root - no configuration needed.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#mirroring","level":3,"title":"Mirroring","text":"<p>When you run <code>ctx memory sync</code>:</p> <ol> <li>The previous mirror is archived to <code>.context/memory/archive/mirror-<timestamp>.md</code></li> <li>MEMORY.md is copied to <code>.context/memory/mirror.md</code></li> <li>Sync state is updated in <code>.context/state/memory-import.json</code></li> </ol> <p>The mirror is git-tracked, so it travels with the project. Archives provide a fallback for projects that don't use git.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#drift-detection","level":3,"title":"Drift Detection","text":"<p>The <code>check-memory-drift</code> hook compares MEMORY.md's modification time against the mirror. When drift is detected, the agent sees:</p> <pre><code>┌─ Memory Drift ────────────────────────────────────────────────\n│ MEMORY.md has changed since last sync.\n│ Run: ctx memory sync\n│ Context: .context\n└────────────────────────────────────────────────────────────────\n</code></pre> <p>The nudge fires once per session to avoid noise.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#typical-workflow","level":2,"title":"Typical Workflow","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#at-session-start","level":3,"title":"At Session Start","text":"<p>If the hook fires a drift nudge, sync before diving into work:</p> <pre><code>ctx memory diff     # Review what changed\nctx memory sync     # Mirror the changes\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#periodic-check","level":3,"title":"Periodic Check","text":"<pre><code>ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#dry-run","level":3,"title":"Dry Run","text":"<p>Preview what sync would do without writing:</p> <pre><code>ctx memory sync --dry-run\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#storage-layout","level":2,"title":"Storage Layout","text":"<pre><code>.context/\n├── memory/\n│   ├── mirror.md                          # Raw copy of MEMORY.md (often git-tracked)\n│   └── archive/\n│       ├── mirror-2026-03-05-143022.md    # Timestamped pre-sync snapshots\n│       └── mirror-2026-03-04-220015.md\n├── state/\n│   └── memory-import.json                 # Sync tracking state\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#edge-cases","level":2,"title":"Edge Cases","text":"Scenario Behavior Auto memory not active <code>sync</code> exits 1 with message. <code>status</code> reports \"not active\". Hook skips silently. First sync (no mirror) Creates mirror without archiving. MEMORY.md is empty Syncs to empty mirror (valid). Not initialized Init guard rejects (same as all <code>ctx</code> commands).","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#importing-entries","level":2,"title":"Importing Entries","text":"<p>Once you've synced, you can classify and promote entries into structured <code>.context/</code> files:</p> <pre><code>ctx memory import --dry-run    # Preview classification\nctx memory import              # Actually promote entries\n</code></pre> <p>Each entry is classified by keyword heuristics:</p> Keywords Target <code>always use</code>, <code>prefer</code>, <code>never use</code>, <code>standard</code> CONVENTIONS.md <code>decided</code>, <code>chose</code>, <code>trade-off</code>, <code>approach</code> DECISIONS.md <code>gotcha</code>, <code>learned</code>, <code>watch out</code>, <code>bug</code>, <code>caveat</code> LEARNINGS.md <code>todo</code>, <code>need to</code>, <code>follow up</code> TASKS.md Everything else Skipped <p>Entries that don't match any pattern are skipped - they stay in the mirror for manual review. Deduplication (hash-based) prevents re-importing the same entry on subsequent runs.</p> <p>Review Before Importing</p> <p>Use <code>--dry-run</code> first. The heuristic classifier is deliberately simple - it may misclassify ambiguous entries. Review the plan, then import.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-workflow","level":3,"title":"Full Workflow","text":"<pre><code>ctx memory sync                # 1. Mirror MEMORY.md\nctx memory import --dry-run    # 2. Preview what would be imported\nctx memory import              # 3. Promote entries to .context/ files\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#publishing-context-to-memorymd","level":2,"title":"Publishing Context to <code>MEMORY.md</code>","text":"<p>Push curated <code>.context/</code> content back into MEMORY.md so Claude Code sees structured project context on session start - without needing hooks.</p> <pre><code>ctx memory publish --dry-run    # Preview what would be published\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter line budget\n</code></pre> <p>Published content is wrapped in markers:</p> <pre><code><!-- ctx:published -->\n# Project Context (managed by ctx)\n\n## Pending Tasks\n- [ ] Implement feature X\n...\n<!-- ctx:end -->\n</code></pre> <p>Rules:</p> <ul> <li><code>ctx</code> owns everything between the markers</li> <li>Claude owns everything outside the markers</li> <li><code>ctx memory import</code> reads only outside the markers</li> <li><code>ctx memory publish</code> replaces only inside the markers</li> </ul> <p>To remove the published block entirely:</p> <pre><code>ctx memory unpublish\n</code></pre> <p>Publish at Wrap-Up, Not on Commit</p> <p>The best time to publish is during session wrap-up, after persisting decisions and learnings. Never auto-publish - give yourself a chance to review what's going into MEMORY.md.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-bidirectional-workflow","level":3,"title":"Full Bidirectional Workflow","text":"<pre><code>ctx memory sync                 # 1. Mirror MEMORY.md\nctx memory import --dry-run     # 2. Check what Claude wrote\nctx memory import               # 3. Promote entries to .context/\nctx memory publish --dry-run    # 4. Check what would be published\nctx memory publish              # 5. Push context to MEMORY.md\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/multi-tool-setup/","level":1,"title":"Setup Across AI Tools","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-problem","level":2,"title":"The Problem","text":"<p>You have installed <code>ctx</code> and want to set it up with your AI coding assistant so that context persists across sessions. Different tools have different integration depths. For example: </p> <ul> <li>Claude Code supports native hooks that load and save context automatically.</li> <li>Cursor injects context via its system prompt.</li> <li>Aider reads context files through its <code>--read</code> flag.</li> </ul> <p>This recipe walks through the complete setup for each tool, from initialization through verification, so you end up with a working memory layer regardless of which AI tool you use.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tldr","level":2,"title":"TL;DR","text":"<pre><code>cd your-project\nctx init                      # creates .context/\neval \"$(ctx activate)\"        # bind CTX_DIR for this shell\nsource <(ctx completion zsh)  # shell completion (or bash/fish)\n\n# ## Claude Code (automatic after plugin install) ##\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n\n# ## OpenCode ##\nctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n\n# ## Cursor / Aider / Copilot / Windsurf ##\nctx setup cursor # or: aider, copilot, windsurf\n\n# ## Companion tools (highly recommended) ##\nnpx gitnexus analyze          # code knowledge graph\n# Add Gemini Search MCP server for grounded web search\n</code></pre> <p>Activate the Project Once Per Shell</p> <p>Run <code>eval \"$(ctx activate)\"</code> after <code>ctx init</code>. The <code>ctx setup</code>, <code>ctx init</code>, and <code>ctx completion</code> commands work without it, but if you skip the <code>eval</code>, most others (<code>ctx agent</code>, <code>ctx load</code>, <code>ctx watch</code>, <code>ctx journal ...</code>) fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Create a <code>.ctxrc</code> in your project root to configure token budgets, context directory, drift thresholds, and more.</p> <p>Then start your AI tool and ask: \"Do you remember?\"</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx init</code> Create <code>.context/</code> directory, templates, and permissions <code>ctx setup</code> Generate integration configuration for a specific AI tool <code>ctx agent</code> Print a token-budgeted context packet for AI consumption <code>ctx load</code> Output assembled context in read order (for manual pasting) <code>ctx watch</code> Auto-apply context updates from AI output (non-native tools) <code>ctx completion</code> Generate shell autocompletion for bash, zsh, or fish <code>ctx journal import</code> Import sessions to editable journal Markdown","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-1-initialize-ctx","level":3,"title":"Step 1: Initialize <code>ctx</code>","text":"<p>Run <code>ctx init</code> in your project root. This creates the <code>.context/</code> directory with all template files and seeds <code>ctx</code> permissions in <code>settings.local.json</code>.</p> <pre><code>cd your-project\nctx init\n</code></pre> <p>This produces the following structure:</p> <pre><code>.context/\n  CONSTITUTION.md     # Hard rules the AI must never violate\n  TASKS.md            # Current and planned work\n  CONVENTIONS.md      # Code patterns and standards\n  ARCHITECTURE.md     # System overview\n  DECISIONS.md        # Architectural decisions with rationale\n  LEARNINGS.md        # Lessons learned, gotchas, tips\n  GLOSSARY.md         # Domain terms and abbreviations\n  AGENT_PLAYBOOK.md   # How AI tools should use this system\n</code></pre> <p>Using a Different <code>.context</code> Directory</p> <p>The <code>.context/</code> directory doesn't have to live inside your project. Point <code>ctx</code> to an external folder by exporting <code>CTX_DIR</code> (the only declaration channel).</p> <p>Useful when context must stay private while the code is public, or when you want to commit notes to a separate repo.</p> <p>Caveats (the recipe covers both with workarounds):</p> <ul> <li>Code-aware operations degrade silently. <code>ctx sync</code>, <code>ctx drift</code>,   and the memory-drift hook read the codebase from   <code>dirname(CTX_DIR)</code>. With an external <code>.context/</code>, that's the   context repo, not your code repo. They scan the wrong tree without   erroring. The recipe shows a symlink workaround that keeps both   healthy.</li> <li>One <code>.context/</code> per project, always. Sharing one directory   across multiple projects corrupts journals, state, and secrets.   For cross-project knowledge sharing (CONSTITUTION, CONVENTIONS,   ARCHITECTURE, etc.) use <code>ctx hub</code>, not a   shared <code>.context/</code>.</li> </ul> <p>See External Context for the full recipe and Configuration for the resolver details.</p> <p>For Claude Code, install the <code>ctx</code> plugin to get hooks and skills:</p> <pre><code>claude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n</code></pre> <p>If you only need the core files (useful for lightweight setups), use the <code>--minimal</code> flag:</p> <pre><code>ctx init --minimal\n</code></pre> <p>This creates only <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>CONSTITUTION.md</code>.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-2-generate-tool-specific-hooks","level":3,"title":"Step 2: Generate Tool-Specific Hooks","text":"<p>If you are using a tool other than Claude Code (which is configured automatically by <code>ctx init</code>), generate its integration configuration:</p> <pre><code># For Cursor\nctx setup cursor\n\n# For Aider\nctx setup aider\n\n# For GitHub Copilot\nctx setup copilot\n\n# For Windsurf\nctx setup windsurf\n</code></pre> <p>Each command prints the configuration you need. How you apply it depends on the tool.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#claude-code","level":4,"title":"Claude Code","text":"<p>No action needed. Just install <code>ctx</code> from the Marketplace as <code>ActiveMemory/ctx</code>.</p> <p>Claude Code Is a First-Class Citizen</p> <p>With the <code>ctx</code> plugin installed, Claude Code gets hooks and skills automatically. The <code>PreToolUse</code> hook runs <code>ctx agent --budget 4000</code> on every tool call (with a 10-minute cooldown so it only fires once per window).</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#opencode","level":4,"title":"OpenCode","text":"<p>Run the one-liner from the project root:</p> <pre><code>ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>This deploys a lifecycle plugin, slash command skills, <code>AGENTS.md</code>, and registers the <code>ctx</code> MCP server globally. See <code>ctx</code> for OpenCode for full details.</p> <p>OpenCode Is a First-Class Citizen</p> <p>With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, and persists at session end, with no manual steps needed.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#vs-code","level":4,"title":"VS Code","text":"<p>Install the <code>ctx</code> extension from the VS Code Marketplace (publisher: <code>activememory</code>). Then, from your project root:</p> <pre><code>ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>Open Copilot Chat and type <code>@ctx /init</code> to verify. The extension auto-downloads the <code>ctx</code> CLI if it isn't on PATH. See <code>ctx</code> for VS Code for full details.</p> <p>VS Code Is a First-Class Citizen</p> <p>The extension carries its own runtime. No <code>ctx setup</code> step is needed. It registers a <code>@ctx</code> chat participant with 45 slash commands, automatic hooks (file save, git commit, <code>.context/</code> change, dependency-file edit), and a reminder status-bar indicator. Unlike embedded harnesses, the extension ships through its own pipeline to the VS Code Marketplace.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#cursor","level":4,"title":"Cursor","text":"<p>Add the system prompt snippet to <code>.cursor/settings.json</code>:</p> <pre><code>{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and .context/CONVENTIONS.md before responding. Follow rules in .context/CONSTITUTION.md.\"\n}\n</code></pre> <p>Context files appear in Cursor's file tree. You can also paste a context packet directly into chat:</p> <pre><code>ctx agent --budget 4000 | xclip    # Linux\nctx agent --budget 4000 | pbcopy   # macOS\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#aider","level":4,"title":"Aider","text":"<p>Create <code>.aider.conf.yml</code> so context files are loaded on every session:</p> <pre><code>read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n</code></pre> <p>Then start Aider normally:</p> <pre><code>aider\n</code></pre> <p>Or specify files on the command line:</p> <pre><code>aider --read .context/TASKS.md --read .context/CONVENTIONS.md\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-3-set-up-shell-completion","level":3,"title":"Step 3: Set Up Shell Completion","text":"<p>Shell completion lets you tab-complete <code>ctx</code> subcommands and flags, which is especially useful while learning the CLI.</p> <pre><code># Bash (add to ~/.bashrc)\nsource <(ctx completion bash)\n\n# Zsh (add to ~/.zshrc)\nsource <(ctx completion zsh)\n\n# Fish\nctx completion fish > ~/.config/fish/completions/ctx.fish\n</code></pre> <p>After sourcing, typing <code>ctx a<TAB></code> completes to <code>ctx agent</code>, and <code>ctx journal <TAB></code> shows <code>list</code>, <code>show</code>, and <code>export</code>.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-4-verify-the-setup-works","level":3,"title":"Step 4: Verify the Setup Works","text":"<p>Start a fresh session in your AI tool and ask:</p> <p>\"Do you remember?\"</p> <p>A correctly configured tool responds with specific context: current tasks from <code>TASKS.md</code>, recent decisions, and previous session topics. It should not say \"I don't have memory\" or \"Let me search for files.\"</p> <p>This question checks the passive side of memory. A properly set-up agent is also proactive: it treats context maintenance as part of its job:</p> <ul> <li>After a debugging session, it offers to save a learning.</li> <li>After a trade-off discussion, it asks whether to record the decision.</li> <li>After completing a task, it suggests follow-up items.</li> </ul> <p>The \"do you remember?\" check verifies both halves: recall and responsibility.</p> <p>For example, after resolving a tricky bug, a proactive agent might say:</p> <pre><code>That Redis timeout issue was subtle. Want me to save this as a *learning*\nso we don't hit it again?\n</code></pre> <p>If you see behavior like this, the setup is working end to end.</p> <p>In Claude Code, you can also invoke the <code>/ctx-status</code> skill:</p> <pre><code>/ctx-status\n</code></pre> <p>This prints a summary of all context files, token counts, and recent activity, confirming that hooks are loading context.</p> <p>If context is not loading, check the basics:</p> Symptom Fix <code>ctx: command not found</code> Ensure <code>ctx</code> is in your PATH: <code>which ctx</code> Hook errors Verify plugin is installed: <code>claude /plugin list</code> Context not refreshing Cooldown may be active; wait 10 minutes or set <code>--cooldown 0</code>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-5-enable-watch-mode-for-non-native-tools","level":3,"title":"Step 5: Enable Watch Mode for Non-Native Tools","text":"<p>Tools like Aider, Copilot, and Windsurf do not support native hooks for saving context automatically. For these, run <code>ctx watch</code> alongside your AI tool.</p> <p>Pipe the AI tool's output through <code>ctx watch</code>:</p> <pre><code># Terminal 1: Run Aider with output logged\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch the log for context updates\nctx watch --log /tmp/aider.log\n</code></pre> <p>Or for any generic tool:</p> <pre><code>your-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n</code></pre> <p>When the AI emits structured update commands, <code>ctx watch</code> parses and applies them automatically:</p> <pre><code><context-update type=\"learning\"\n  context=\"Debugging rate limiter\"\n  lesson=\"Redis MULTI/EXEC does not roll back on error\"\n  application=\"Wrap rate-limit checks in Lua scripts instead\"\n>Redis Transaction Behavior</context-update>\n</code></pre> <p>To preview changes without modifying files:</p> <pre><code>ctx watch --dry-run --log /tmp/ai.log\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-6-import-session-transcripts-optional","level":3,"title":"Step 6: Import Session Transcripts (Optional)","text":"<p>If you want to browse past session transcripts, import them to the journal:</p> <pre><code>ctx journal import --all\n</code></pre> <p>This converts raw session data into editable Markdown files in <code>.context/journal/</code>. You can then enrich them with metadata using <code>/ctx-journal-enrich-all</code> inside your AI assistant.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Here is the condensed setup for all three tools:</p> <pre><code># ## Common (run once per project) ##\ncd your-project\nctx init\nsource <(ctx completion zsh)       # or bash/fish\n\n# ## Claude Code (automatic, just verify) ##\n# Start Claude Code, then ask: \"Do you remember?\"\n\n# ## OpenCode ##\nctx setup opencode --write\n# Start OpenCode, then ask: \"Do you remember?\"\n\n# ## Cursor ##\nctx setup cursor\n# Add the system prompt to .cursor/settings.json\n# Paste context: ctx agent --budget 4000 | pbcopy\n\n# ## Aider ##\nctx setup aider\n# Create .aider.conf.yml with read: paths\n# Run watch mode alongside: ctx watch --log /tmp/aider.log\n\n# ## Verify any Tool ##\n# Ask your AI: \"Do you remember?\"\n# Expect: specific tasks, decisions, recent context\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>ctx init</code> (not <code>--minimal</code>) for your first project. The full   template set gives the agent more to work with, and you can always delete   files later.</li> <li>For Claude Code, the token budget is configured in the plugin's <code>hooks.json</code>.   To customize, adjust the <code>--budget</code> flag in the <code>ctx agent</code> hook command.</li> <li>The <code>--session $PPID</code> flag isolates cooldowns per Claude Code process, so   parallel sessions do not suppress each other.</li> <li>Commit your <code>.context/</code> directory to version control. Several <code>ctx</code> features   (journals, changelogs, blog generation) rely on git history.</li> <li>For Cursor and Copilot, keep <code>CONVENTIONS.md</code> visible. These tools treat   open files as higher-priority context.</li> <li>Run <code>ctx drift</code> periodically to catch stale references before they confuse   the agent.</li> <li>The agent playbook instructs the agent to persist context at natural   milestones (completed tasks, decisions, gotchas). In practice, this   works best when you reinforce the habit: a quick \"anything worth saving?\"   after a debugging session goes a long way.</li> </ul>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#companion-tools-highly-recommended","level":2,"title":"Companion Tools (Highly Recommended)","text":"<p><code>ctx</code> skills can leverage external MCP servers for web search and code intelligence. <code>ctx</code> works without them, but they significantly improve agent behavior across sessions. The investment is small and the benefits compound. Skills like <code>/ctx-code-review</code>, <code>/ctx-explain</code>, and <code>/ctx-refactor</code> all become noticeably better with these tools connected.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gemini-search","level":3,"title":"Gemini Search","text":"<p>Provides grounded web search with citations. Used by skills and the agent playbook as the preferred search backend (faster and more accurate than built-in web search).</p> <p>Setup: Add the Gemini Search MCP server to your Claude Code settings. See the Gemini Search MCP documentation for installation.</p> <p>Verification: <pre><code># The agent checks this automatically during /ctx-remember\n# Manual test: ask the agent to search for something\n</code></pre></p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gitnexus","level":3,"title":"GitNexus","text":"<p>Provides a code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Used by skills like <code>/ctx-refactor</code> (impact analysis) and <code>/ctx-code-review</code> (dependency awareness).</p> <p>Setup: Add the GitNexus MCP server to your Claude Code settings, then index your project:</p> <pre><code>npx gitnexus analyze\n</code></pre> <p>Verification: <pre><code># The agent checks this automatically during /ctx-remember\n# If the index is stale, it will suggest rehydrating\n</code></pre></p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#suppressing-the-check","level":3,"title":"Suppressing the Check","text":"<p>If you don't use companion tools and want to skip the availability check at session start, add to <code>.ctxrc</code>:</p> <pre><code>companion_check: false\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#future-direction","level":3,"title":"Future Direction","text":"<p>The companion tool integration is evolving toward a pluggable model: bring your own search engine, bring your own code intelligence. The current integration is MCP-based and limited to Gemini Search and GitNexus. If you use a different search or code intelligence tool, skills will degrade gracefully to built-in capabilities.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#next-up","level":2,"title":"Next Up","text":"<p>Keeping Context in a Separate Repo →: Store context files outside the project tree for multi-repo or open source setups.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session lifecycle recipe</li> <li>Multilingual Session Parsing: configure session header prefixes for other languages</li> <li>CLI Reference: all commands and flags</li> <li>Integrations: detailed per-tool integration docs</li> </ul>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multilingual-sessions/","level":1,"title":"Multilingual Session Parsing","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#the-problem","level":2,"title":"The Problem","text":"<p>Your team works across languages. Session files written by AI tools might use headers like <code># Oturum: 2026-01-15 - API Düzeltme</code> (Turkish) or <code># セッション: 2026-01-15 - テスト</code> (Japanese) instead of <code># Session: 2026-01-15 - Fix API</code>.</p> <p>By default, <code>ctx</code> only recognizes <code>Session:</code> as a session header prefix. Files with other prefixes are silently skipped during journal import and journal generation: They look like regular Markdown, not sessions.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#tldr","level":2,"title":"TL;DR","text":"<p>Add recognized prefixes to <code>.ctxrc</code>:</p> <pre><code>session_prefixes:\n  - \"Session:\"      # English (include to keep default)\n  - \"Oturum:\"       # Turkish\n  - \"セッション:\"     # Japanese\n</code></pre> <p>Restart your session. All configured prefixes are now recognized.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#how-it-works","level":2,"title":"How It Works","text":"<p>The Markdown session parser detects session files by looking for an H1 header that starts with a known prefix followed by a date:</p> <pre><code># Session: 2026-01-15 - Fix API Rate Limiting\n# Oturum: 2026-01-15 - API Düzeltme\n# セッション: 2026-01-15 - テスト\n</code></pre> <p>The list of recognized prefixes comes from <code>session_prefixes</code> in <code>.ctxrc</code>. When the key is absent or empty, <code>ctx</code> falls back to the built-in default: <code>[\"Session:\"]</code>.</p> <p>Date-only headers (<code># 2026-01-15 - Morning Work</code>) are always recognized regardless of prefix configuration.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#configuration","level":2,"title":"Configuration","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#adding-a-language","level":3,"title":"Adding a Language","text":"<p>Add the prefix with a trailing colon to your <code>.ctxrc</code>:</p> <pre><code>session_prefixes:\n  - \"Session:\"\n  - \"Sesión:\"       # Spanish\n</code></pre> <p>Include Session: Explicitly</p> <p>When you override <code>session_prefixes</code>, the default is replaced, not extended. If you still want English headers recognized, include <code>\"Session:\"</code> in your list.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#team-setup","level":3,"title":"Team Setup","text":"<p>Commit <code>.ctxrc</code> to the repo so all team members share the same prefix list. This ensures <code>ctx journal import</code> and journal generation pick up sessions from all team members regardless of language.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#common-prefixes","level":3,"title":"Common Prefixes","text":"Language Prefix English <code>Session:</code> Turkish <code>Oturum:</code> Spanish <code>Sesión:</code> French <code>Session:</code> German <code>Sitzung:</code> Japanese <code>セッション:</code> Korean <code>세션:</code> Portuguese <code>Sessão:</code> Chinese <code>会话:</code>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#verifying","level":3,"title":"Verifying","text":"<p>After configuring, test with <code>ctx journal source</code>. Sessions with the new prefixes should appear in the output.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> from the project root. If you skip it, <code>ctx journal ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#what-this-does-not-do","level":2,"title":"What This Does NOT Do","text":"<ul> <li>Change the interface language: <code>ctx</code> output is always English.   This setting only controls which session files <code>ctx</code> can parse.</li> <li>Generate headers: <code>ctx</code> never writes session headers. The prefix   list is recognition-only (input, not output).</li> <li>Affect JSONL sessions: Claude Code JSONL transcripts don't use   header prefixes. This only applies to Markdown session files in   <code>.context/sessions/</code>.</li> </ul>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#see-also","level":2,"title":"See Also","text":"<p>See also: Setup Across AI Tools - complete multi-tool setup including Markdown session configuration.</p> <p>See also: CLI Reference - full <code>.ctxrc</code> field reference including <code>session_prefixes</code>.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/parallel-worktrees/","level":1,"title":"Parallel Agent Development with Git Worktrees","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-problem","level":2,"title":"The Problem","text":"<p>You have a large backlog (10, 20, 30 open tasks) and many of them are independent: docs work that doesn't touch Go code, a new package that doesn't overlap with existing ones, test coverage for a stable module.</p> <p>Running one agent at a time means serial execution. You want 3-4 agents working in parallel, each on its own track, without stepping on each other's files.</p> <p>Git worktrees solve this. </p> <p>Each worktree is a separate working directory with its own branch, but they  share the same <code>.git</code> object database. Combined with <code>ctx</code>'s persistent context,  each agent session picks up the full project state and works independently.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-worktree                                   # 1. group tasks by file overlap\ngit worktree add ../myproject-docs -b work/docs # 2. create worktrees\ncd ../myproject-docs && claude                  # 3. launch agents (one per track)\n/ctx-worktree teardown docs                     # 4. merge back and clean up\n</code></pre> <p><code>TASKS.md</code> will conflict on merge: Accept all <code>[x]</code> completions from both sides.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-worktree</code> Skill Create, list, and tear down worktrees <code>/ctx-next</code> Skill Pick tasks from the backlog for each track <code>git worktree</code> Command Underlying git worktree management <code>git merge</code> Command Merge completed tracks back to main","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-1-assess-the-backlog","level":3,"title":"Step 1: Assess the Backlog","text":"<p>Start in your main checkout. Ask the agent to analyze your tasks and group them by blast radius: which files and directories each task touches.</p> <pre><code>/ctx-worktree\nLook at TASKS.md and group the pending tasks into 2-3 independent\ntracks based on which files they'd touch. Show me the grouping\nbefore creating anything.\n</code></pre> <p>The agent reads <code>TASKS.md</code>, estimates file overlap, and proposes groups:</p> <pre><code>Proposed worktree groups:\n\n  work/docs   # recipe updates, blog post (touches: docs/)\n  work/crypto # scratchpad encryption infra (touches: internal/crypto/)\n  work/tests  # journal test coverage (touches: internal/cli/journal/)\n</code></pre>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-2-create-the-worktrees","level":3,"title":"Step 2: Create the Worktrees","text":"<p>Once you approve the grouping, the agent creates worktrees as sibling directories:</p> <pre><code>Create the worktrees for those three groups.\n</code></pre> <p>Behind the scenes:</p> <pre><code>git worktree add ../myproject-docs -b work/docs\ngit worktree add ../myproject-crypto -b work/crypto\ngit worktree add ../myproject-tests -b work/tests\n</code></pre> <p>Each worktree is a full working copy on its own branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-3-launch-agents","level":3,"title":"Step 3: Launch Agents","text":"<p>Open a separate terminal (or editor window) for each worktree and start a Claude Code session:</p> <pre><code># Terminal 1\ncd ../myproject-docs\nclaude\n\n# Terminal 2\ncd ../myproject-crypto\nclaude\n\n# Terminal 3\ncd ../myproject-tests\nclaude\n</code></pre> <p>Each agent sees the full project, including <code>.context/</code>, and can work independently. </p> <p>Do Not Initialize Context in Worktrees</p> <p>Do not run <code>ctx init</code> in worktrees: The <code>.context</code> directory is already tracked in <code>git</code>.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-4-work","level":3,"title":"Step 4: Work","text":"<p>Each agent works through its assigned tasks. They can read <code>TASKS.md</code> to know what's assigned to their track, use <code>/ctx-next</code> to pick the next item, and commit normally on their <code>work/*</code> branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-5-merge-back","level":3,"title":"Step 5: Merge Back","text":"<p>As each track finishes, return to the main checkout and merge:</p> <pre><code>/ctx-worktree teardown docs\n</code></pre> <p>The agent checks for uncommitted changes, merges <code>work/docs</code> into your current branch, removes the worktree, and deletes the branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-6-handle-tasksmd-conflicts","level":3,"title":"Step 6: Handle <code>TASKS.md</code> Conflicts","text":"<p><code>TASKS.md</code> will almost always conflict when merging: Multiple agents will mark different tasks as <code>[x]</code>. This is expected and easy to resolve:</p> <p>Accept all completions from both sides. No task should go from <code>[x]</code> back to <code>[ ]</code>. The merge resolution is always additive.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-7-cleanup","level":3,"title":"Step 7: Cleanup","text":"<p>After all tracks are merged, verify everything is clean:</p> <pre><code>/ctx-worktree list\n</code></pre> <p>Should show only the main working tree. All <code>work/*</code> branches should be gone.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't have to use the skill directly for every step. These natural prompts work:</p> <ul> <li>\"I have a big backlog. Can we split it across worktrees?\"</li> <li>\"Which of these tasks can run in parallel without conflicts?\"</li> <li>\"Merge the docs track back in.\"</li> <li>\"Clean up all the worktrees, we're done.\"</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#what-works-differently-in-worktrees","level":2,"title":"What Works Differently in Worktrees","text":"<p>The encryption key lives at <code>~/.ctx/.ctx.key</code> (user-level, outside the project). Because all worktrees on the same machine share this path, <code>ctx pad</code> and <code>ctx hook notify</code> work in worktrees automatically - no special setup needed.</p> <p>One thing to watch:</p> <ul> <li>Journal enrichment: <code>ctx journal import</code> and <code>ctx journal enrich</code>   write files relative to the current working directory. Enrichments   created in a worktree stay there and are discarded on teardown.   Enrich journals on the main branch after merging: the JSONL session   logs are always intact, and you don't lose any data.</li> </ul> <p>Context Files Will Merge Just Fine</p> <p>Tracked context files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>) work normally; <code>git</code> handles them.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tips","level":2,"title":"Tips","text":"<ul> <li>3-4 worktrees max. Beyond that, merge complexity outweighs the   parallelism benefit. The skill enforces this limit.</li> <li>Group by package or directory, not by priority. Two high-priority   tasks that touch the same files must be in the same track.</li> <li><code>TASKS.md</code> will conflict on merge. This is normal. Accept all <code>[x]</code>   completions: The resolution is always additive.</li> <li>Don't run <code>ctx init</code> in worktrees. The <code>.context/</code> directory is   tracked in git. Running init overwrites shared context files.</li> <li>Name worktrees by concern, not by number. <code>work/docs</code> and   <code>work/crypto</code> are more useful than <code>work/track-1</code> and <code>work/track-2</code>.</li> <li>Commit frequently in each worktree. Smaller commits make merge   conflicts easier to resolve.</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#next-up","level":2,"title":"Next Up","text":"<p>Back to the beginning: Guide Your Agent →</p> <p>Or explore the full recipe list.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#see-also","level":2,"title":"See Also","text":"<ul> <li>Running an Unattended AI Agent: for serial   autonomous loops instead of parallel tracks</li> <li>Tracking Work Across Sessions: managing the   task backlog that feeds into parallelization</li> <li>The Complete Session: the complete session workflow   end-to-end, with examples</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/permission-snapshots/","level":1,"title":"Permission Snapshots","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code's <code>.claude/settings.local.json</code> accumulates one-off permissions every time you click \"Allow\". After busy sessions the file is full of session-specific entries that expand the agent's surface area beyond intent.</p> <p>Since <code>settings.local.json</code> is <code>.gitignore</code>d, there is no PR review or CI check. The file drifts independently on every machine, and there is no built-in way to reset to a known-good state.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-permission-sanitize               # audit for dangerous patterns\nctx permission snapshot            # save golden image\n# ... sessions accumulate cruft ...\nctx permission restore             # reset to golden state\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx permission ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-solution","level":2,"title":"The Solution","text":"<p>Save a curated <code>settings.local.json</code> as a golden image, then restore from it to drop session-accumulated permissions. The golden file (<code>.claude/settings.golden.json</code>) is committed to version control and shared with the team.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx permission snapshot</code> Save settings.local.json as golden image <code>ctx permission restore</code> Reset settings.local.json from golden image <code>/ctx-permission-sanitize</code> Audit for dangerous patterns before snapshotting","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#step-by-step","level":2,"title":"Step by Step","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#1-curate-your-permissions","level":3,"title":"1. Curate Your Permissions","text":"<p>Start with a clean <code>settings.local.json</code>. Optionally run <code>/ctx-permission-sanitize</code> to remove dangerous patterns first.</p> <p>Review the file manually. Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.</p> <p>See the Permission Hygiene recipe for recommended defaults.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#2-take-a-snapshot","level":3,"title":"2. Take a Snapshot","text":"<pre><code>ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n</code></pre> <p>This creates a byte-for-byte copy. No re-encoding, no indent changes.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#3-commit-the-golden-file","level":3,"title":"3. Commit the Golden File","text":"<pre><code>git add .claude/settings.golden.json\ngit commit -m \"Add permission golden image\"\n</code></pre> <p>The golden file is not gitignored (unlike <code>settings.local.json</code>). This is intentional: it becomes a team-shared baseline.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#4-auto-restore-at-the-session-start","level":3,"title":"4. Auto-Restore at the Session Start","text":"<p>Add this instruction to your <code>CLAUDE.md</code>:</p> <pre><code>## On Session Start\n\nRun `ctx permission restore` to reset permissions to the golden image.\n</code></pre> <p>The agent will restore the golden image at the start of every session, automatically dropping any permissions accumulated during previous sessions.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#5-update-when-intentional-changes-are-made","level":3,"title":"5. Update When Intentional Changes Are Made","text":"<p>When you add a new permanent permission (not a one-off debugging entry):</p> <pre><code># Edit settings.local.json with the new permission\n# Then update the golden image:\nctx permission snapshot\ngit add .claude/settings.golden.json\ngit commit -m \"Update permission golden image: add cargo test\"\n</code></pre>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember exact commands. These natural-language prompts work with agents trained on the <code>ctx</code> playbook:</p> What you say What happens \"Save my current permissions as baseline\" Agent runs <code>ctx permission snapshot</code> \"Reset permissions to the golden image\" Agent runs <code>ctx permission restore</code> \"Clean up my permissions\" Agent runs <code>/ctx-permission-sanitize</code> then snapshot \"What permissions did I accumulate?\" Agent diffs local vs golden","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#next-up","level":2,"title":"Next Up","text":"<p>Turning Activity into Content →: Generate blog posts, changelogs, and journal sites from your project activity.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#see-also","level":2,"title":"See Also","text":"<ul> <li>Permission Hygiene: recommended defaults and   maintenance workflow</li> <li>CLI Reference: <code>ctx</code> permission:   full command documentation</li> </ul>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/publishing/","level":1,"title":"Turning Activity into Content","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-problem","level":2,"title":"The Problem","text":"<p>Your <code>.context/</code> directory is full of decisions, learnings, and session history.</p> <p>Your <code>git log</code> tells the story of a project evolving.</p> <p>But none of this is visible to anyone outside your terminal.</p> <p>You want to turn this raw activity into:</p> <ul> <li>a browsable journal site,</li> <li>blog posts,</li> <li>changelog posts.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx journal import --all             # 1. import sessions to markdown\n\n/ctx-journal-enrich-all             # 2. add metadata and tags\n\nctx journal site --serve            # 3. build and serve the journal\n\n/ctx-blog about the caching layer   # 4. draft a blog post\n/ctx-blog-changelog v0.1.0 \"v0.2\"   # 5. write a changelog post\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx journal ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Read on for details on each stage.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx journal import</code> Command Import session JSONL to editable Markdown <code>ctx journal site</code> Command Generate a static site from journal entries <code>ctx journal obsidian</code> Command Generate an Obsidian vault from journal entries <code>ctx serve</code> Command Serve any zensical directory (default: journal) <code>ctx site feed</code> Command Generate Atom feed from finalized blog posts <code>make journal</code> Makefile Shortcut for import + site rebuild <code>/ctx-journal-enrich-all</code> Skill Full pipeline: import if needed, then batch-enrich (recommended) <code>/ctx-journal-enrich</code> Skill Add metadata, summaries, and tags to one entry <code>/ctx-blog</code> Skill Draft a blog post from recent project activity <code>/ctx-blog-changelog</code> Skill Write a themed post from a commit range","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-1-import-sessions-to-markdown","level":3,"title":"Step 1: Import Sessions to Markdown","text":"<p>Raw session data lives as JSONL files in Claude Code's internal storage. The first step is converting these into readable, editable Markdown.</p> <pre><code># Import all sessions from the current project\nctx journal import --all\n\n# Import from all projects (if you work across multiple repos)\nctx journal import --all --all-projects\n\n# Import a single session by ID or slug\nctx journal import abc123\nctx journal import gleaming-wobbling-sutherland\n</code></pre> <p>Imported files land in <code>.context/journal/</code> as individual Markdown files with session metadata and the full conversation transcript.</p> <p><code>--all</code> is safe by default: Only new sessions are imported. Existing files are skipped. Use <code>--regenerate</code> to re-import existing files (YAML frontmatter is preserved). Use <code>--regenerate --keep-frontmatter=false -y</code> to regenerate everything including frontmatter.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-2-enrich-entries-with-metadata","level":3,"title":"Step 2: Enrich Entries with Metadata","text":"<p>Raw entries have timestamps and conversations but lack the structured metadata that makes a journal searchable. Use <code>/ctx-journal-enrich-all</code> to process your entire backlog at once:</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.</p> <p>For large backlogs (20+ entries), it can spawn subagents to process entries in parallel.</p> <p>To enrich a single entry instead:</p> <pre><code>/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich 2026-01-24\n</code></pre> <p>After enrichment, an entry gains YAML frontmatter:</p> <pre><code>---\ntitle: \"Implement Redis caching for API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n</code></pre> <p>This metadata powers better navigation in the journal site: </p> <ul> <li>titles replace slugs, </li> <li>summaries appear in the index, </li> <li>and search covers topics and technologies.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-3-generate-the-journal-site","level":3,"title":"Step 3: Generate the Journal Site","text":"<p>With entries exported and enriched, generate the static site:</p> <pre><code># Generate site files\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally (opens at http://localhost:8000)\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n</code></pre> <p>The site is generated in <code>.context/journal-site/</code> by default. It uses zensical for static site generation (<code>pipx install zensical</code>).</p> <p>Or use the Makefile shortcut that combines export and rebuild:</p> <pre><code>make journal\n</code></pre> <p>This runs <code>ctx journal import --all</code> followed by <code>ctx journal site --build</code>, then reminds you to enrich before rebuilding. To serve the built site, use <code>make journal-serve</code> or <code>ctx serve</code> (serve-only, no regeneration).</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#alternative-export-to-obsidian-vault","level":3,"title":"Alternative: Export to Obsidian Vault","text":"<p>If you use Obsidian for knowledge management, generate a vault instead of (or alongside) the static site:</p> <pre><code>ctx journal obsidian\nctx journal obsidian --output ~/vaults/ctx-journal\n</code></pre> <p>This produces an Obsidian-ready directory with wikilinks, MOC (Map of Content) pages for topics/files/types, and a \"Related Sessions\" footer on each entry for graph connectivity. Open the output directory in Obsidian as a vault.</p> <p>The vault uses the same enriched source entries as the static site. Both outputs can coexist: The static site goes to <code>.context/journal-site/</code>, the vault to <code>.context/journal-obsidian/</code>.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-4-draft-blog-posts-from-activity","level":3,"title":"Step 4: Draft Blog Posts from Activity","text":"<p>When your project reaches a milestone worth sharing, use <code>/ctx-blog</code> to draft a post from recent activity. The skill gathers context from multiple sources: <code>git log</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, completed tasks, and journal entries.</p> <pre><code>/ctx-blog about the caching layer we just built\n/ctx-blog last week's refactoring work\n/ctx-blog lessons learned from the migration\n</code></pre> <p>The skill gathers recent commits, decisions, and learnings; identifies a narrative arc; drafts an outline for approval; writes the full post; and saves it to <code>docs/blog/YYYY-MM-DD-slug.md</code>.</p> <p>Posts are written in first person with code snippets, commit references, and an honest discussion of what went wrong.</p> <p>The Output Is <code>zensical</code>-Flavored Markdown</p> <p>The blog skills produce Markdown tuned for a zensical site: <code>topics:</code> frontmatter (zensical's tag field), a <code>docs/blog/</code> output path, and a banner image reference. </p> <p>The content is still standard Markdown and can be adapted to other  static site generators, but the defaults assume a <code>zensical</code>  project structure.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-5-write-changelog-posts-from-commit-ranges","level":3,"title":"Step 5: Write Changelog Posts from Commit Ranges","text":"<p>For release notes or \"what changed\" posts, <code>/ctx-blog-changelog</code> takes a starting commit and a theme, then analyzes everything that changed:</p> <pre><code>/ctx-blog-changelog 040ce99 \"building the journal system\"\n/ctx-blog-changelog HEAD~30 \"what's new in v0.2.0\"\n/ctx-blog-changelog v0.1.0 \"the road to v0.2.0\"\n</code></pre> <p>The skill diffs the commit range, identifies the most-changed files, and constructs a narrative organized by theme rather than chronology, including a key commits table and before/after comparisons.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-6-generate-the-blog-feed","level":3,"title":"Step 6: Generate the Blog Feed","text":"<p>After publishing blog posts, generate the Atom feed so readers and automation can discover new content:</p> <pre><code>ctx site feed\n</code></pre> <p>This scans <code>docs/blog/</code> for finalized posts (<code>reviewed_and_finalized: true</code>), extracts title, date, author, topics, and summary, and writes a valid Atom 1.0 feed to <code>site/feed.xml</code>. The feed is also generated automatically as part of <code>make site</code>.</p> <p>The feed is available at ctx.ist/feed.xml.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-conversational-approach","level":2,"title":"The Conversational Approach","text":"<p>You can also drive your publishing anytime with natural language:</p> <pre><code>\"write about what we did this week\"\n\"turn today's session into a blog post\"\n\"make a changelog post covering everything since the last release\"\n\"enrich the last few journal entries\"\n</code></pre> <p>The agent has full visibility into your <code>.context/</code> state (tasks completed, decisions recorded, learnings captured), so its suggestions are grounded in what actually happened.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>The full pipeline from raw transcripts to published content:</p> <pre><code># 1. Import all sessions\nctx journal import --all\n\n# 2. In Claude Code: enrich all entries with metadata\n/ctx-journal-enrich-all\n\n# 3. Build and serve the journal site\nmake journal\nmake journal-serve\n\n# 3b. Or generate an Obsidian vault\nctx journal obsidian\n\n# 4. In Claude Code: draft a blog post\n/ctx-blog about the features we shipped this week\n\n# 5. In Claude Code: write a changelog post\n/ctx-blog-changelog v0.1.0 \"what's new in v0.2.0\"\n</code></pre> <p>The journal pipeline is idempotent at every stage. You can rerun <code>ctx journal import --all</code> without losing enrichment. You can rebuild the site as many times as you want.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tips","level":2,"title":"Tips","text":"<ul> <li>Import regularly. Run <code>ctx journal import --all</code> after each session to keep   your journal current. Only new sessions are imported: Existing files are   skipped by default.</li> <li>Use batch enrichment. <code>/ctx-journal-enrich-all</code> filters noise (suggestion   sessions, trivial sessions, multipart continuations) so you do not have to   decide what is worth enriching.</li> <li>Keep journal files in <code>.gitignore</code>. Session journals can contain sensitive   data: file contents, commands, internal discussions, and error messages with   stack traces. Add <code>.context/journal/</code> and <code>.context/journal-site/</code> to   <code>.gitignore</code>.</li> <li>Use <code>/ctx-blog</code> for narrative posts and <code>/ctx-blog-changelog</code> for release   posts. One finds a story in recent activity, the other explains a commit   range by theme.</li> <li>Edit the drafts. These skills produce drafts, not final posts. Review the   narrative, add your perspective, and remove anything that does not serve the   reader.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#next-up","level":2,"title":"Next Up","text":"<p>Running an Unattended AI Agent →: Set up an AI agent that works through tasks overnight without you at the keyboard.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#see-also","level":2,"title":"See Also","text":"<ul> <li>Session Journal: journal system, enrichment schema</li> <li>CLI Reference: <code>ctx</code> journal: import, list, show session history</li> <li>CLI Reference: <code>ctx</code> journal site: static site generation</li> <li>CLI Reference: <code>ctx</code> journal obsidian: Obsidian vault export</li> <li>CLI Reference: <code>ctx</code> serve: serve-only (no regeneration)</li> <li>Browsing and Enriching Past Sessions: journal browsing workflow</li> <li>The Complete Session: capturing context during a session</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/recover-aborted-session/","level":1,"title":"Recover an Aborted KB Session","text":"","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#the-problem","level":2,"title":"The Problem","text":"<p>You ran one or more <code>/ctx-kb-ingest</code> passes, then the session ended before <code>/ctx-wrap-up</code>. Maybe you closed the laptop, the connection dropped, or you just forgot the wrap-up step.</p> <p>You come back the next day and ask \"do you remember?\" and the agent picks up the previous handover, but the editorial work since the last handover seems to be missing from the readback.</p> <p>It isn't missing. It's unfolded. Here's how the pipeline handles it and how to close the loop manually.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-remember                                       # picks up the unfolded \n                                                    # closeouts automatically\n/ctx-handover \"recovery: fold the orphan closeouts\" # direct invocation is \n                                                    # appropriate for recovery\n</code></pre> <p>The recovery path is the one legitimate place to invoke <code>/ctx-handover</code> directly. Normally <code>/ctx-wrap-up</code> owns session-end and delegates to the handover step; the abort broke that path, so a hand-rolled handover invocation is how you close the loop without re-running the full wrap-up ceremony.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#how-the-fold-mechanism-survives-an-abort","level":2,"title":"How the Fold Mechanism Survives an Abort","text":"<p>Two artifacts make abort-recovery work without any cleanup:</p> <ol> <li> <p>Closeouts are immutable once written. Every editorial    pass writes a closeout under    <code>.context/ingest/closeouts/<TS>-<mode>-closeout.md</code> before    the pass reports <code>done</code>. If the session dies, the closeout    is already on disk.</p> </li> <li> <p><code>/ctx-remember</code> folds unfolded closeouts into the    readback. The skill always reads the latest handover.    When <code>.context/kb/</code> exists, it additionally reads any    closeouts whose <code>generated-at</code> postdates the handover.    The <code>## What changed</code> and <code>## Source-coverage updates</code>    sections from each unfolded closeout are surfaced in    recall.</p> </li> </ol> <p>So an aborted session never loses editorial work; it just delays the handover fold by one session.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-1-confirm-the-orphan-closeouts","level":2,"title":"Step 1: Confirm the Orphan Closeouts","text":"<pre><code>ls -la .context/ingest/closeouts/\n</code></pre> <p>Files there with <code>generated-at</code> postdating your latest handover are the unfolded ones. You can read any closeout directly to see what it claims about its pass:</p> <pre><code>cat .context/ingest/closeouts/<TS>-ingest-closeout.md\n</code></pre> <p>Look at:</p> <ul> <li>The Pass-mode body block (<code>Declared / Reason /   Definition of done / Result</code>): what the pass committed to   and whether it claimed success or <code>deferred</code>.</li> <li>The Source-coverage updates section: what state   transitions hit the ledger.</li> <li>The Next pass hint: the exact resumption invocation   the closeout recommends, if the pass deferred.</li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-2-run-ctx-remember","level":2,"title":"Step 2: Run <code>/ctx-remember</code>","text":"<pre><code>/ctx-remember\n</code></pre> <p>The readback will include the editorial-state summary as part of the standard readback shape. If everything looks consistent, proceed to Step 3.</p> <p>If the readback surfaces something surprising (a closeout claiming <code>topic-page: produced</code> for a slug whose file is missing, a <code>comprehensive</code> ledger advance against a source whose page is <code>speculative</code>, etc.), fix the underlying inconsistency before folding. (Doctor advisories for these shapes are on the Phase-7 backlog.)</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-3-write-the-recovery-handover","level":2,"title":"Step 3: Write the Recovery Handover","text":"<p>This step is the one legitimate direct invocation of <code>/ctx-handover</code>. In normal session-end the call goes through <code>/ctx-wrap-up</code>; here the prior session aborted, so you reach for the handover step directly to retire the orphan closeouts:</p> <pre><code>/ctx-handover \"recovery: fold orphan closeouts from yesterday\"\n</code></pre> <p>Or via the CLI:</p> <pre><code>ctx handover write \"recovery: fold orphan closeouts from yesterday\" \\\n  --summary \"Folded N orphan closeouts from the aborted session.\" \\\n  --next \"Resume <topic> per the closeout's Next pass hint.\"\n</code></pre> <p>The handover:</p> <ul> <li>Reads the latest handover cursor.</li> <li>Finds all closeouts whose <code>generated-at</code> is after the cursor.</li> <li>Folds their summaries into a <code>## Folded closeouts</code> section.</li> <li>Archives the source closeout files under   <code>.context/archive/closeouts/</code> (closeouts are   append-never-rewrite; archival moves bytes but does not   modify them).</li> </ul> <p>After the handover lands, the orphan closeouts are now durably tied to a session boundary; the next <code>/ctx-remember</code> reads just the new handover (and any closeouts postdating it), without re-folding the recovered ones.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#edge-cases","level":2,"title":"Edge Cases","text":"Case Behavior Closeout has malformed frontmatter Handover fold skips it with a warning to stderr. Hand-edit the malformed file (typically a missing <code>generated-at</code>) and re-run <code>ctx handover write</code> to fold it next time. Closeout's <code>generated-at</code> is before the last handover but was never folded Treated as already-folded (silently skipped; the cursor is the source of truth). If you genuinely want to re-fold it, hand-edit the closeout's <code>generated-at</code> forward. You aborted during an ingest pass, before its closeout was written No closeout exists; the pass left no recall residue. Treat the source(s) as un-ingested and re-run <code>/ctx-kb-ingest</code>. The source-coverage ledger row may show stale residue from a prior pass; the next ingest will advance it correctly. Multiple sessions piled up unfolded closeouts One handover run folds them all in a single shot. The fold is cursor-driven, not session-driven. You want recall without consuming closeouts <code>ctx handover write ... --no-fold</code> writes a handover with frontmatter but leaves the closeouts in place. The next handover (without <code>--no-fold</code>) folds everything postdating the latest handover cursor.","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#when-this-matters","level":2,"title":"When This Matters","text":"<ul> <li>After a network drop / laptop close mid-session.</li> <li>When you ran <code>/ctx-kb-ingest</code> from a sub-agent that   finished without calling <code>/ctx-handover</code>.</li> <li>After porting work from another environment (e.g. you   rsynced <code>.context/ingest/closeouts/</code> from a different   machine) and want to integrate the work into the destination   project's recall thread.</li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Typical KB Session</li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/scratchpad-sync/","level":1,"title":"Syncing Scratchpad Notes Across Machines","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-problem","level":2,"title":"The Problem","text":"<p>You work from multiple machines: a desktop and a laptop, or a local machine and a remote dev server.</p> <p>The scratchpad entries are encrypted. The ciphertext (<code>.context/scratchpad.enc</code>) travels with git, but the encryption key lives outside the project at <code>~/.ctx/.ctx.key</code> and is never committed. Without the key on each machine, you cannot read or write entries.</p> <p>How do you distribute the key and keep the scratchpad in sync?</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                                                  # 1. generates key\neval \"$(ctx activate)\"                                    # 2. bind CTX_DIR\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key        # 3. copy key\nchmod 600 ~/.ctx/.ctx.key                                 # 4. secure it\n# Normal git push/pull syncs the encrypted scratchpad.enc\n# On conflict: ctx pad resolve → rebuild → git add + commit\n</code></pre> <p>Activate Each Machine</p> <p>Run <code>eval \"$(ctx activate)\"</code> from the project root on every machine that reads or writes the scratchpad: after each <code>ctx init</code>, or after each clone on machine B. If you skip it, <code>ctx pad ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Finding Your Key File</p> <p>The key is always at <code>~/.ctx/.ctx.key</code> - one key, one machine.</p> <p>Treat the Key like a Password</p> <p>The scratchpad key is the only thing protecting your encrypted entries.</p> <p>Store a backup in a secure enclave such as a password manager, and treat it with the same care you would give passwords, certificates, or API tokens.</p> <p>Anyone with the key can decrypt every scratchpad entry.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> CLI command Initialize context (generates the key automatically) <code>ctx pad add</code> CLI command Add a scratchpad entry <code>ctx pad rm</code> CLI command Remove entries by stable ID (supports ranges) <code>ctx pad edit</code> CLI command Edit a scratchpad entry <code>ctx pad resolve</code> CLI command Show both sides of a merge conflict <code>ctx pad merge</code> CLI command Merge entries from other scratchpad files <code>ctx pad import</code> CLI command Bulk-import lines from a file <code>ctx pad export</code> CLI command Export blob entries to a directory <code>scp</code> Shell Copy the key file between machines <code>git push</code> / <code>git pull</code> Shell Sync the encrypted file via <code>git</code> <code>/ctx-pad</code> Skill Natural language interface to pad commands","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-1-initialize-on-machine-a","level":3,"title":"Step 1: Initialize on Machine A","text":"<p>Run <code>ctx init</code> on your first machine. The key is created automatically at <code>~/.ctx/.ctx.key</code>:</p> <pre><code>ctx init\n# ...\n# Created ~/.ctx/.ctx.key (0600)\n# Created .context/scratchpad.enc\n</code></pre> <p>The key lives outside the project directory and is never committed. The <code>.enc</code> file is tracked in git.</p> <p>Key Folder Change (v0.7.0+)</p> <p>If you built <code>ctx</code> from source or upgraded past v0.6.0, the key location changed to <code>~/.ctx/.ctx.key</code>. Check these legacy folders and copy your key manually:</p> <pre><code># Old locations (pick whichever exists)\nls ~/.local/ctx/keys/        # pre-v0.7.0 user-level\nls .context/.ctx.key         # pre-v0.6.0 project-local\n\n# Copy to the new location\nmkdir -p ~/.ctx && chmod 700 ~/.ctx\ncp <old-key-path> ~/.ctx/.ctx.key\nchmod 600 ~/.ctx/.ctx.key\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-2-copy-the-key-to-machine-b","level":3,"title":"Step 2: Copy the Key to Machine B","text":"<p>Use any secure transfer method. The key is always at <code>~/.ctx/.ctx.key</code>:</p> <pre><code># scp - create the target directory first\nssh user@machine-b \"mkdir -p ~/.ctx && chmod 700 ~/.ctx\"\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key\n\n# Or use a password manager, USB drive, etc.\n</code></pre> <p>Set permissions on Machine B:</p> <pre><code>chmod 600 ~/.ctx/.ctx.key\n</code></pre> <p>Secure the Transfer</p> <p>The key is a raw 256-bit AES key. Anyone with the key can decrypt the scratchpad. Use an encrypted channel (SSH, password manager, vault). </p> <p>Never paste it in plaintext over email or chat.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-3-normal-pushpull-workflow","level":3,"title":"Step 3: Normal Push/Pull Workflow","text":"<p>The encrypted file is committed, so standard git sync works:</p> <pre><code># Machine A: add entries and push\nctx pad add \"staging API key: sk-test-abc123\"\ngit add .context/scratchpad.enc\ngit commit -m \"Update scratchpad\"\ngit push\n\n# Machine B: pull and read\ngit pull\nctx pad\n#   1. staging API key: sk-test-abc123\n</code></pre> <p>Both machines have the same key, so both can decrypt the same <code>.enc</code> file.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-4-read-and-write-from-either-machine","level":3,"title":"Step 4: Read and Write from Either Machine","text":"<p>Once the key is distributed, all <code>ctx pad</code> commands work identically on both machines. Entries added on Machine A are visible on Machine B after a <code>git pull</code>, and vice versa.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-5-handle-merge-conflicts","level":3,"title":"Step 5: Handle Merge Conflicts","text":"<p>If both machines add entries between syncs, pulling will create a merge conflict on <code>.context/scratchpad.enc</code>. Git cannot merge binary (encrypted) content automatically.</p> <p>The fastest approach is <code>ctx pad merge</code>: It reads both conflict sides, deduplicates, and writes the union:</p> <pre><code># Extract theirs to a temp file, then merge it in\ngit show :3:.context/scratchpad.enc > /tmp/theirs.enc\ngit checkout --ours .context/scratchpad.enc\nctx pad merge /tmp/theirs.enc\n\n# Done: Commit the resolved scratchpad:\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n</code></pre> <p>Alternatively, use <code>ctx pad resolve</code> to inspect both sides manually:</p> <pre><code>ctx pad resolve\n# === Ours (this machine) ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n#\n# === Theirs (incoming) ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n</code></pre> <p>Then reconstruct the merged scratchpad:</p> <pre><code># Start fresh with all entries from both sides\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\n# Mark the conflict resolved\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#merge-conflict-walkthrough","level":2,"title":"Merge Conflict Walkthrough","text":"<p>Here's a full scenario showing how conflicts arise and how to resolve them:</p> <p>1. Both machines start in sync (1 entry):</p> <pre><code>Machine A: 1. staging API key: sk-test-abc123\nMachine B: 1. staging API key: sk-test-abc123\n</code></pre> <p>2. Both add entries independently:</p> <pre><code>Machine A adds: \"check DNS after deploy\"\nMachine B adds: \"new endpoint: api.example.com/v2\"\n</code></pre> <p>3. Machine A pushes first. Machine B pulls and gets a conflict:</p> <pre><code>git pull\n# CONFLICT (content): Merge conflict in .context/scratchpad.enc\n</code></pre> <p>4. Machine B runs <code>ctx pad resolve</code>:</p> <pre><code>ctx pad resolve\n# === Ours ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n#\n# === Theirs ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n</code></pre> <p>5. Rebuild with entries from both sides and commit:</p> <pre><code># Clear and rebuild (or use the skill to guide you)\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\ngit add .context/scratchpad.enc\ngit commit -m \"Merge scratchpad: keep entries from both machines\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#conversational-approach","level":3,"title":"Conversational Approach","text":"<p>When working with an AI assistant, you can resolve conflicts naturally:</p> <pre><code>You: \"I have a scratchpad merge conflict. Can you resolve it?\"\n\nAgent: \"Let me extract theirs and merge it in.\"\n       [runs git show :3:.context/scratchpad.enc > /tmp/theirs.enc]\n       [runs git checkout --ours .context/scratchpad.enc]\n       [runs ctx pad merge /tmp/theirs.enc]\n       \"Merged 2 new entries (1 duplicate skipped). Want me to\n       commit the resolution?\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tips","level":2,"title":"Tips","text":"<ul> <li>Back up the key: If you lose it, you lose access to all encrypted   entries. Store a copy in your password manager.</li> <li>One key per project: Each <code>ctx init</code> generates a unique key.   Don't reuse keys across projects.</li> <li>Keys work in worktrees: Because the key lives at <code>~/.ctx/.ctx.key</code>   (outside the project), git worktrees on the same machine share the key   automatically. No special setup needed.</li> <li>Plaintext fallback for non-sensitive projects: If encryption adds   friction and you have nothing sensitive, set <code>scratchpad_encrypt: false</code>   in <code>.ctxrc</code>. Merge conflicts become trivial text merges.</li> <li>Never commit the key: The key is stored outside the project at   <code>~/.ctx/.ctx.key</code> and should never be copied into the repository.</li> </ul>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#next-up","level":2,"title":"Next Up","text":"<p>Hook Output Patterns →: Choose the right output pattern for your Claude Code hooks.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#see-also","level":2,"title":"See Also","text":"<ul> <li>Scratchpad: feature overview, all commands, when   to use scratchpad vs context files</li> <li>Persisting Decisions, Learnings, and Conventions:   for structured knowledge that outlives the scratchpad</li> </ul>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-with-claude/","level":1,"title":"Using the Scratchpad","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-problem","level":2,"title":"The Problem","text":"<p>During a session you accumulate quick notes, reminders, intermediate values, and sometimes sensitive tokens. They don't fit <code>TASKS.md</code> (not work items) or <code>DECISIONS.md</code> (not decisions). They don't have the structured fields that <code>LEARNINGS.md</code> requires.</p> <p>Without somewhere to put them, they get lost between sessions.</p> <p>How do you capture working memory that persists across sessions without polluting your structured context files?</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx pad add \"check DNS propagation after deploy\"\nctx pad         # list entries\nctx pad show 1  # print entry (pipe-friendly)\n</code></pre> <p>Entries are encrypted at rest and travel with <code>git</code>. </p> <p>Use the <code>/ctx-pad</code> skill to manage entries from inside your AI session.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx pad ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx pad</code> CLI command List all scratchpad entries <code>ctx pad show N</code> CLI command Output raw text of entry N (pipe-friendly) <code>ctx pad add</code> CLI command Add a new entry <code>ctx pad edit</code> CLI command Replace, append to, or prepend to an entry <code>ctx pad add --file</code> CLI command Ingest a file as a blob entry <code>ctx pad show N --out</code> CLI command Extract a blob entry to a file <code>ctx pad rm</code> CLI command Remove entries by stable ID (supports ranges) <code>ctx pad normalize</code> CLI command Reassign entry IDs as 1..N <code>ctx pad mv</code> CLI command Reorder entries <code>ctx pad --tag</code> CLI command Filter entries by tag <code>ctx pad tags</code> CLI command List all tags with counts <code>ctx pad import</code> CLI command Bulk-import lines from a file (or stdin) <code>ctx pad export</code> CLI command Export all blob entries to a directory <code>/ctx-pad</code> Skill Natural language interface to all pad commands","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-1-add-a-note-naturally","level":3,"title":"Step 1: Add a Note Naturally","text":"<p>You don't need to remember any syntax. Just tell your assistant what to jot down:</p> <pre><code>You: \"jot down: check DNS propagation after deploy\"\n\nAgent: \"Added to scratchpad:\n  1. check DNS propagation after deploy\"\n</code></pre> <p>Behind the scenes, the agent runs <code>ctx pad add \"check DNS propagation after deploy\"</code>.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-2-view-your-scratchpad","level":3,"title":"Step 2: View Your Scratchpad","text":"<pre><code>You: \"show my scratchpad\"\n\nAgent: \"Your scratchpad has 3 entries:\n  1. check DNS propagation after deploy\n  2. staging API endpoint: api.staging.example.com/v2\n  3. retry limit should be 5, not 3\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-3-edit-an-entry","level":3,"title":"Step 3: Edit an Entry","text":"<pre><code>You: \"update entry 2 to include the new endpoint\"\n\nAgent: \"Updated entry 2:\n  was: staging API endpoint: api.staging.example.com/v2\n  now: staging API endpoint: api.prod.example.com/v2\"\n</code></pre> <p>For partial edits, the agent can use <code>--append</code> or <code>--prepend</code>:</p> <pre><code>You: \"append the port number to entry 2\"\n\nAgent: \"Updated entry 2:\n  staging API endpoint: api.prod.example.com/v2 :8443\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-4-compose-entries","level":3,"title":"Step 4: Compose Entries","text":"<p>Use <code>ctx pad show N</code> to pipe entry content into other operations:</p> <pre><code>You: \"append the content of entry 3 to entry 1\"\n\nAgent: [runs ctx pad edit 1 --append \"$(ctx pad show 3)\"]\n       \"Done. Entry 1 is now:\n        check DNS propagation after deploy retry limit should be 5, not 3\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-5-reorder-by-priority","level":3,"title":"Step 5: Reorder by Priority","text":"<pre><code>You: \"move entry 4 to the top\"\n\nAgent: [runs ctx pad mv 4 1]\n       \"Moved entry 4 to position 1. Scratchpad reordered.\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-6-clean-up","level":3,"title":"Step 6: Clean Up","text":"<pre><code>You: \"remove entry 2, it's done\"\n\nAgent: [runs ctx pad rm 2]\n       \"Removed entry 2. 3 entries remaining.\n        (IDs are stable; remaining entries keep their IDs.)\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-7-store-a-file-as-a-blob","level":3,"title":"Step 7: Store a File as a Blob","text":"<p>The scratchpad can hold small files (up to 64 KB) as encrypted blob entries. The file is base64-encoded and stored alongside a label you provide:</p> <pre><code># Ingest a file: the first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# List shows the label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-8-extract-a-blob","level":3,"title":"Step 8: Extract a Blob","text":"<p>Use <code>show --out</code> to write the decoded file back to disk:</p> <pre><code># Write blob entry to a file\nctx pad show 2 --out ./recovered-deploy.yaml\n\n# Or print to stdout (for piping)\nctx pad show 2 | head -5\n</code></pre> <p>Blob entries are encrypted identically to text entries: They're just base64-encoded before encryption. The <code>--out</code> flag decodes and writes the raw bytes.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-9-bulk-import-notes","level":3,"title":"Step 9: Bulk Import Notes","text":"<p>When you have a file with many notes (one per line), import them in bulk instead of adding one at a time:</p> <pre><code># Import from a file: Each non-empty line becomes an entry\nctx pad import notes.txt\n\n# Or pipe from stdin\ngrep TODO *.go | ctx pad import -\n</code></pre> <p>All entries are written in a single encrypt/write cycle, regardless of how many lines the file contains.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-10-export-blobs-to-disk","level":3,"title":"Step 10: Export Blobs to Disk","text":"<p>Export all blob entries to a directory as individual files. Each blob's label becomes the filename:</p> <pre><code># Export to a directory (created if needed)\nctx pad export ./ideas\n\n# Preview what would be exported\nctx pad export --dry-run ./ideas\n\n# Force overwrite existing files\nctx pad export --force ./backup\n</code></pre> <p>When a file already exists, a unix timestamp is prepended to the filename to avoid collisions. Use <code>--force</code> to overwrite instead.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-11-tag-entries-for-organization","level":3,"title":"Step 11: Tag Entries for Organization","text":"<p>Tags let you categorize entries without any structure beyond a <code>#word</code> token in the text. Add them when creating or editing entries:</p> <pre><code>You: \"jot down: check DNS propagation #later\"\nYou: \"tag entry 2 as urgent\"\n\nAgent: [runs ctx pad edit 2 --tag urgent]\n       \"Updated entry 2.\"\n</code></pre> <p>Filter your scratchpad by tag:</p> <pre><code>You: \"show me everything tagged later\"\n\nAgent: [runs ctx pad --tag later]\n       \"  1. check DNS propagation #later\n        3. review PR feedback #later #ci\"\n</code></pre> <p>Entry IDs are stable; they don't shift when other entries are deleted, so <code>ctx pad rm 3</code> always targets the same entry regardless of deletions or active filters. Use <code>ctx pad normalize</code> to reassign IDs as 1..N.</p> <p>Exclude a tag with <code>~</code>:</p> <pre><code>ctx pad --tag ~later         # everything NOT tagged #later\nctx pad --tag later --tag ci # entries with BOTH tags (AND logic)\n</code></pre> <p>See what tags you're using:</p> <pre><code>You: \"what tags do I have?\"\n\nAgent: [runs ctx pad tags]\n       \"ci       1\n        later    2\n        urgent   1\"\n</code></pre> <p>Tags work on blob entries too; they're extracted from the label:</p> <pre><code>ctx pad add \"deploy config #prod\" --file ./deploy.yaml\nctx pad --tag prod\n#   1. deploy config #prod [BLOB]\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#using-ctx-pad-in-a-session","level":2,"title":"Using <code>/ctx-pad</code> in a Session","text":"<p>Invoke the <code>/ctx-pad</code> skill first, then describe what you want in natural language. Without the skill prefix, the agent may route your request to <code>TASKS.md</code> or another context file instead of the scratchpad.</p> <pre><code>You: /ctx-pad jot down: check DNS after deploy\nYou: /ctx-pad show my scratchpad\nYou: /ctx-pad delete entry 3\n</code></pre> <p>Once the skill is active, it translates intent into commands:</p> You say (after <code>/ctx-pad</code>) What the agent does \"jot down: check DNS after deploy\" <code>ctx pad add \"check DNS after deploy\"</code> \"remember this: retry limit is 5\" <code>ctx pad add \"retry limit is 5\"</code> \"show my scratchpad\" / \"what's on my pad\" <code>ctx pad</code> \"show me entry 3\" <code>ctx pad show 3</code> \"delete the third one\" / \"remove entry 3\" <code>ctx pad rm 3</code> \"remove entries 3 through 5\" <code>ctx pad rm 3-5</code> \"renumber my scratchpad\" <code>ctx pad normalize</code> \"change entry 2 to ...\" <code>ctx pad edit 2 \"new text\"</code> \"append ' +important' to entry 3\" <code>ctx pad edit 3 --append \" +important\"</code> \"prepend 'URGENT:' to entry 1\" <code>ctx pad edit 1 --prepend \"URGENT: \"</code> \"prioritize entry 4\" / \"move to the top\" <code>ctx pad mv 4 1</code> \"import my notes from notes.txt\" <code>ctx pad import notes.txt</code> \"export all blobs to ./ideas\" <code>ctx pad export ./ideas</code> \"show entries tagged later\" <code>ctx pad --tag later</code> \"show everything except later\" <code>ctx pad --tag ~later</code> \"what tags do I have\" <code>ctx pad tags</code> \"tag entry 5 as urgent\" <code>ctx pad edit 5 --tag urgent</code> <p>When in Doubt, Use the CLI Directly</p> <p>The <code>ctx pad</code> commands work the same whether you run them yourself or let the skill invoke them. </p> <p>If the agent misroutes a request, fall back to <code>ctx pad add \"...\"</code> in your terminal.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#when-to-use-scratchpad-vs-context-files","level":2,"title":"When to Use Scratchpad vs Context Files","text":"Situation Use Temporary reminders (\"check X after deploy\") Scratchpad Session-start reminders (\"remind me next session\") <code>ctx remind</code> Working values during debugging (ports, endpoints, counts) Scratchpad Sensitive tokens or API keys (short-term storage) Scratchpad Quick notes that don't fit anywhere else Scratchpad Work items with completion tracking <code>TASKS.md</code> Trade-offs between alternatives with rationale <code>DECISIONS.md</code> Reusable lessons with context/lesson/application <code>LEARNINGS.md</code> Codified patterns and standards <code>CONVENTIONS.md</code> <p>Decision Guide</p> <ul> <li>If it has structured fields (context, rationale, lesson, application),   it belongs in a context file like <code>DECISIONS.md</code> or <code>LEARNINGS.md</code>.</li> <li>If it's a work item you'll mark done, it belongs in <code>TASKS.md</code>.</li> <li>If you want a message relayed VERBATIM at the next session start,   it belongs in <code>ctx remind</code>.</li> <li>If it's a quick note, reminder, or working value (especially if it's   sensitive or ephemeral) it belongs on the scratchpad.</li> </ul> <p>Scratchpad Is Not a Junk Drawer</p> <p>The scratchpad is for working memory, not long-term storage.</p> <p>If a note is still relevant after several sessions, promote it:</p> <p>A persistent reminder becomes a task, a recurring value becomes a convention, a hard-won insight becomes a learning.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tips","level":2,"title":"Tips","text":"<ul> <li>Entries persist across sessions: The scratchpad is committed   (encrypted) to git, so entries survive session boundaries. Pick up   where you left off.</li> <li>Entries are numbered and reorderable: Use <code>ctx pad mv</code> to put   high-priority items at the top.</li> <li><code>ctx pad show N</code> enables unix piping: Output raw entry text   with no numbering prefix. Compose with <code>--append</code>, <code>--prepend</code>, or   other shell tools.</li> <li>Never mention the key file contents to the AI: The agent knows   how to use <code>ctx pad</code> commands but should never read or print   the encryption key (<code>~/.ctx/.ctx.key</code>) directly.</li> <li>Encryption is transparent: You interact with plaintext; the   encryption/decryption happens automatically on every read/write.</li> </ul>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#next-up","level":2,"title":"Next Up","text":"<p>Syncing Scratchpad Notes Across Machines →: Distribute encryption keys and scratchpad data across environments.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#see-also","level":2,"title":"See Also","text":"<ul> <li>Scratchpad: feature overview, all commands,   encryption details, plaintext override</li> <li>Persisting Decisions, Learnings, and Conventions:   for structured knowledge that outlives the scratchpad</li> <li>The Complete Session: full session lifecycle   showing how the scratchpad fits into the broader workflow</li> </ul>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/","level":1,"title":"Scrutinizing a Plan","text":"<p>When you have a plan and want it attacked, not validated, the <code>/ctx-plan</code> skill runs an adversarial interview. It surfaces what's weak, missing, or unexamined before you commit.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#when-to-use-it","level":2,"title":"When to Use It","text":"<ul> <li>Before starting a multi-day implementation.</li> <li>After writing a spec but before opening the first PR.</li> <li>When the team aligned suspiciously fast on a complex change.</li> <li>When you've drafted something and the obvious next step is to   hand it to a senior reviewer.</li> </ul> <p><code>/ctx-plan</code> is not a brainstorming tool. Use <code>/ctx-brainstorm</code> for that. <code>/ctx-plan</code> assumes the plan exists and asks: does it survive contact with reality?</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#stop-conditions","level":2,"title":"Stop Conditions","text":"<p>The skill runs depth-first through assumptions, tradeoffs, and failure modes. Stop when one of these is true:</p> <ul> <li>Plan converges. The bet is clear, the sequencing is justified,   and you can articulate what would falsify it.</li> <li>Plan dissolves. The interview surfaces a load-bearing   assumption you can't defend; the plan needs rework, not more   scrutiny.</li> <li>You hit information you don't have. Pause, gather it,   resume.</li> </ul> <p>If the agent loops on a single angle (asking variations of the same question), say so. The skill is designed to walk depth-first, not exhaust one branch.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#worked-example","level":2,"title":"Worked Example","text":"<pre><code>You: /ctx-plan\n\nI want to migrate our queue from RabbitMQ to NATS. I've drafted a\ntwo-week plan: spike NATS in staging, port one consumer, port the\nrest, decommission RabbitMQ.\n\nAgent: One question at a time. What breaks if your message ordering\n       guarantees aren't equivalent across the two systems?\n\nYou: NATS gives us per-subject ordering by default; we don't actually\n     need cross-subject ordering, so I think we're fine.\n\nAgent: How would you notice if you were wrong? What's the loudest\n       symptom of a stray out-of-order message in production today?\n\nYou: ...probably a duplicate notification. Which we already see\n     occasionally. So we wouldn't notice immediately.\n\nAgent: Then your migration's correctness check needs to be stronger\n       than \"no incidents.\" Before you port consumer two, what\n       evidence would convince you that consumer one's ordering\n       behavior is correct under load?\n\nYou: Right. Add a dedicated assertion test in staging that replays\n     a known-out-of-order sequence and verifies the consumer\n     dedupes correctly. Otherwise we're just hoping.\n\nAgent: Good. Next: what would change your mind about NATS itself?\n       What rejected alternative is closest to coming back?\n</code></pre> <p>The interview keeps going until the bet is articulated, the falsifiable conditions are written down, and the rejected alternatives have a recall trigger.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#output","level":2,"title":"Output","text":"<p><code>/ctx-plan</code> produces a clearer plan, not a document. Persist the deltas via:</p> <ul> <li><code>/ctx-spec</code> if the conclusions belong in a feature spec.</li> <li><code>/ctx-decision-add</code> if a tradeoff resolved into an   architectural decision.</li> <li><code>/ctx-learning-add</code> if you discovered a project-specific   gotcha during the interview.</li> </ul> <p>The skill itself is in <code>internal/assets/claude/skills/ctx-plan/SKILL.md</code>; the working contract lives there, the recipe is the on-ramp.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#see-also","level":2,"title":"See Also","text":"<ul> <li>Design Before Coding: the   brainstorming counterpart, used before a plan exists.</li> <li><code>ctx-spec</code>: scaffolds a feature spec from   the project template.</li> </ul>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/session-archaeology/","level":1,"title":"Browsing and Enriching Past Sessions","text":"","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-problem","level":2,"title":"The Problem","text":"<p>After weeks of AI-assisted development you have dozens of sessions scattered across JSONL files in <code>~/.claude/projects/</code>. Finding the session where you debugged the Redis connection pool, or remembering what you decided about the caching strategy three Tuesdays ago, often means grepping raw JSON.</p> <p>There is no table of contents, no search, and no summaries.</p> <p>This recipe shows how to turn that raw session history into a browsable, searchable, and enriched journal site you can navigate in your browser.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tldr","level":2,"title":"TL;DR","text":"<p>Export and Generate</p> <pre><code>ctx journal import --all\nctx journal site --serve\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx journal ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Enrich</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>Rebuild</p> <pre><code>ctx journal site --serve\n</code></pre> <p>Read on for what each stage does and why.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx journal source</code> Command List parsed sessions with metadata <code>ctx journal source --show</code> Command Inspect a specific session in detail <code>ctx journal import</code> Command Import sessions to editable journal Markdown <code>ctx journal site</code> Command Generate a static site from journal entries <code>ctx journal obsidian</code> Command Generate an Obsidian vault from journal entries <code>ctx journal schema check</code> Command Validate JSONL files and report schema drift <code>ctx journal schema dump</code> Command Print the embedded JSONL schema definition <code>ctx serve</code> Command Serve any zensical directory (default: journal) <code>/ctx-history</code> Skill Browse sessions inside your AI assistant <code>/ctx-journal-enrich</code> Skill Add frontmatter metadata to a single entry <code>/ctx-journal-enrich-all</code> Skill Full pipeline: import if needed, then batch-enrich","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-workflow","level":2,"title":"The Workflow","text":"<p>The session journal follows a four-stage pipeline.</p> <p>Each stage is idempotent and safe to re-run:</p> <p>By default, each stage skips entries that have already been processed.</p> <pre><code>import -> enrich -> rebuild\n</code></pre> Stage Tool What it does Skips if Where Import <code>ctx journal import --all</code> Converts session JSONL to Markdown File already exists (safe default) CLI or agent Enrich <code>/ctx-journal-enrich-all</code> Adds frontmatter, summaries, topic tags Frontmatter already present Agent only Rebuild <code>ctx journal site --build</code> Generates browsable static HTML N/A CLI only Obsidian <code>ctx journal obsidian</code> Generates Obsidian vault with wikilinks N/A CLI only <p>Where Do You Run Each Stage?</p> <p>Import (Steps 1 to 3) works equally well from the terminal or inside your AI assistant via <code>/ctx-history</code>. The CLI is fine here: the agent adds no special intelligence, it just runs the same command.</p> <p>Enrich (Step 4) requires the agent: it reads conversation content and produces structured metadata.</p> <p>Rebuild and serve (Step 5) is a terminal operation that starts a long-running server.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-1-list-your-sessions","level":3,"title":"Step 1: List Your Sessions","text":"<p>Start by seeing what sessions exist for the current project:</p> <pre><code>ctx journal source\n</code></pre> <p>Sample output:</p> <pre><code>Sessions (newest first)\n=======================\n\n  Slug                           Project   Date         Duration  Turns  Tokens\n  gleaming-wobbling-sutherland   ctx       2026-02-07   1h 23m    47     82,341\n  twinkly-stirring-kettle        ctx       2026-02-06   0h 45m    22     38,102\n  bright-dancing-hopper          ctx       2026-02-05   2h 10m    63     124,500\n  quiet-flowing-dijkstra         ctx       2026-02-04   0h 18m    11     15,230\n  ...\n</code></pre> <p>Slugs Look Cryptic?</p> <p>These auto-generated slugs (<code>gleaming-wobbling-sutherland</code>) are hard to recognize later.</p> <p>Use <code>/ctx-journal-enrich</code> to add human-readable titles, topic tags, and summaries to exported journal entries, making them easier to find.</p> <p>Filter by project or tool if you work across multiple codebases:</p> <pre><code>ctx journal source --project ctx --limit 10\nctx journal source --tool claude-code\nctx journal source --all-projects\n</code></pre>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-2-inspect-a-specific-session","level":3,"title":"Step 2: Inspect a Specific Session","text":"<p>Before exporting everything, inspect a single session to see its metadata and conversation summary:</p> <pre><code>ctx journal source --show --latest\n</code></pre> <p>Or look up a specific session by its slug, partial ID, or UUID:</p> <pre><code>ctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show twinkly\nctx journal source --show abc123\n</code></pre> <p>Add <code>--full</code> to see the complete message content instead of the summary view:</p> <pre><code>ctx journal source --show --latest --full\n</code></pre> <p>This is useful for checking what happened before deciding whether to export and enrich it.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-3-import-sessions-to-the-journal","level":3,"title":"Step 3: Import Sessions to the Journal","text":"<p>Import converts raw session data into editable Markdown files in <code>.context/journal/</code>:</p> <pre><code># Import all sessions from the current project\nctx journal import --all\n\n# Import a single session\nctx journal import gleaming-wobbling-sutherland\n\n# Include sessions from all projects\nctx journal import --all --all-projects\n</code></pre> <p><code>--keep-frontmatter=false</code> Discards Enrichments</p> <p><code>--keep-frontmatter=false</code> discards enriched YAML frontmatter during regeneration.</p> <p>Back up your journal before using this flag.</p> <p>Each imported file contains session metadata (date, time, duration, model, project, git branch), a tool usage summary, and the full conversation transcript.</p> <p>Re-importing is safe. Running <code>ctx journal import --all</code> only imports new sessions: Existing files are never touched. Use <code>--dry-run</code> to preview what would be imported without writing anything.</p> <p>To re-import existing files (e.g., after a format improvement), use <code>--regenerate</code>: Conversation content is regenerated while preserving any YAML frontmatter you or the enrichment skill has added. You'll be prompted before any files are overwritten.</p> <p><code>--regenerate</code> Replaces the Markdown Body</p> <p><code>--regenerate</code> preserves YAML frontmatter but replaces the entire Markdown body with freshly generated content from the source JSONL.</p> <p>If you manually edited the conversation transcript (added notes, redacted sensitive content, restructured sections), those edits will be lost.</p> <p>BACK UP YOUR JOURNAL FIRST.</p> <p>To protect entries you've hand-edited, you can explicitly lock them:</p> <pre><code>ctx journal lock <pattern>\n</code></pre> <p>Locked entries are always skipped, regardless of flags.</p> <p>If you prefer to add <code>locked: true</code> directly in frontmatter during enrichment, run <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>:</p> <pre><code>ctx journal sync\n</code></pre> <p>See <code>ctx journal lock --help</code> and <code>ctx journal sync --help</code> for details.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-4-enrich-with-metadata","level":3,"title":"Step 4: Enrich with Metadata","text":"<p>Raw imports have timestamps and transcripts but lack the semantic metadata that makes sessions searchable: topics, technology tags, outcome status, and summaries. The <code>/ctx-journal-enrich*</code> skills add this structured frontmatter.</p> <p>Locked entries are skipped by enrichment skills, just as they are by import. Lock entries you want to protect before running batch enrichment.</p> <p>Batch enrichment (recommended):</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and  summaries from the conversation.</p> <p>It shows you a grouped summary before applying changes so you can scan quickly rather than reviewing one by one.</p> <p>For large backlogs (20+ entries), the skill can spawn subagents to process entries in parallel.</p> <p>Single-entry enrichment:</p> <pre><code>/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-02-06\n</code></pre> <p>Each enriched entry gets YAML frontmatter like this:</p> <pre><code>---\ntitle: \"Implement Redis caching middleware\"\ndate: 2026-02-06\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/api/middleware/cache.go\n---\n</code></pre> <p>The skill also generates a summary and can extract decisions,  learnings, and tasks mentioned during the session.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-5-generate-and-serve-the-site","level":3,"title":"Step 5: Generate and Serve the Site","text":"<p>With imported and enriched journal files, generate the static site:</p> <pre><code># Generate site structure only\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate, build, and serve locally\nctx journal site --serve\n</code></pre> <p>Then open <code>http://localhost:8000</code> to browse.</p> <p>The site includes a date-sorted index, individual session pages with full conversations, search (press <code>/</code>), dark mode, and enriched titles in the navigation when frontmatter exists.</p> <p>You can also serve an already-generated site without regenerating using <code>ctx serve</code> (serve-only, no regeneration).</p> <p>The site generator requires <code>zensical</code> (<code>pipx install zensical</code>).</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#where-does-agent-add-value","level":2,"title":"Where Does Agent Add Value?","text":"<p>Export, list, and show are mechanical:  The agent runs the same CLI commands you would, so you can stay  in your terminal for those.</p> <p>The agent earns its keep in enrich. It reads conversation content, understands what happened, and produces structured metadata. That is agent work, not CLI work.</p> <p>You can also ask your agent to browse sessions conversationally instead of remembering flags:</p> <pre><code>What did we work on last week?\nShow me the session about Redis.\nImport everything to the journal.\n</code></pre> <p>This is convenient but not required: <code>ctx journal source</code> gives you the same inventory.</p> <p>Where the agent genuinely helps is chaining the pipeline:</p> <pre><code>You:   What happened last Tuesday?\nAgent: Last Tuesday you worked on two sessions:\n       - bright-dancing-hopper (2h 10m): refactored the middleware\n         pipeline and added Redis caching\n       - quiet-flowing-dijkstra (18m): quick fix for a nil pointer\n         in the config loader\n       Want me to export and enrich them?\nYou:   Yes, do it.\nAgent: Exports both, enriches, then proposes frontmatter.\n</code></pre> <p>The value is staying in one context while the agent runs import -> enrich without you manually switching tools.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A typical pipeline from raw sessions to a browsable site:</p> <pre><code># Terminal: import and generate\nctx journal import --all\nctx journal site --serve\n</code></pre> <pre><code># AI assistant: enrich\n/ctx-journal-enrich-all\n</code></pre> <pre><code># Terminal: rebuild with enrichments\nctx journal site --serve\n</code></pre> <p>If your project includes <code>Makefile.ctx</code> (deployed by <code>ctx init</code>), use <code>make journal</code> to combine import and rebuild stages. Then enrich inside Claude Code, then <code>make journal</code> again to pick up enrichments.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#session-retention-and-cleanup","level":2,"title":"Session Retention and Cleanup","text":"<p>Claude Code does not keep JSONL transcripts forever. Understanding its cleanup behavior helps you avoid losing session history.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#default-behavior","level":3,"title":"Default Behavior","text":"<p>Claude Code retains session transcripts for approximately 30 days. After that, JSONL files are automatically deleted during cleanup. Once deleted, <code>ctx journal</code> can no longer see those sessions - the data is gone.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-cleanupperioddays-setting","level":3,"title":"The <code>cleanupPeriodDays</code> Setting","text":"<p>Claude Code exposes a <code>cleanupPeriodDays</code> setting in its configuration (<code>~/.claude/settings.json</code>) that controls retention:</p> Value Behavior <code>30</code> (default) Transcripts older than 30 days are deleted <code>60</code>, <code>90</code>, etc. Extends the retention window <code>0</code> Disables writing new transcripts entirely - not \"keep forever\" <p>Setting <code>cleanupPeriodDays</code> To 0</p> <p>Setting this to <code>0</code> does not mean \"never delete.\" It disables transcript creation altogether. No new JSONL files are written, which means <code>ctx journal</code> sees nothing new. This is rarely what you want.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#why-journal-import-matters","level":3,"title":"Why Journal Import Matters","text":"<p>The journal import pipeline (Steps 1-4 above) is your archival mechanism. Imported Markdown files in <code>.context/journal/</code> persist independently of Claude Code's cleanup cycle. Even after the source JSONL files are deleted, your journal entries remain.</p> <p>Recommendation: import regularly - weekly, or after any session worth revisiting. A quick <code>ctx journal import --all</code> takes seconds and ensures nothing falls through the 30-day window.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#quick-archival-checklist","level":3,"title":"Quick Archival Checklist","text":"<ol> <li>Run <code>ctx journal import --all</code> at least weekly</li> <li>Enrich high-value sessions with <code>/ctx-journal-enrich</code> before the    details fade from your own memory</li> <li>Lock enriched entries (<code>ctx journal lock <pattern></code>) to protect them    from accidental regeneration</li> <li>Rebuild the journal site periodically to keep it current</li> </ol>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>/ctx-history</code> inside your AI assistant. If you want to quickly check what happened in a recent session without leaving your editor, <code>/ctx-history</code> lets you browse interactively without importing.</li> <li>Large sessions may be split automatically. Sessions with 200+ messages can be split into multiple parts (<code>session-abc123.md</code>, <code>session-abc123-p2.md</code>, <code>session-abc123-p3.md</code>) with navigation links between them. The site generator can handle this.</li> <li>Suggestion sessions can be separated. Claude Code can generate short suggestion sessions for autocomplete. These may appear under a separate section in the site index, so they do not clutter your main session list.</li> <li>Your agent is a good session browser. You do not need to remember slugs, dates, or flags. Ask \"what did we do yesterday?\" or \"find the session about Redis\"  and it can map the question to recall commands.</li> </ul> <p>Journal Files Are Sensitive</p> <p>Journal files MUST be <code>.gitignore</code>d.</p> <p>Session transcripts can contain sensitive data such as file contents, commands, error messages with stack traces, and potentially API keys.</p> <p>Add <code>.context/journal/</code>, <code>.context/journal-site/</code>, and  <code>.context/journal-obsidian/</code> to your <code>.gitignore</code>.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#next-up","level":2,"title":"Next Up","text":"<p>Persisting Decisions, Learnings, and Conventions →: Record decisions, learnings, and conventions so they survive across sessions.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: where session saving fits in the daily workflow</li> <li>Turning Activity into Content: generating blog posts from session history</li> <li>Session Journal: full documentation of the journal system</li> <li>CLI Reference: <code>ctx</code> journal: all journal subcommands and flags</li> <li>CLI Reference: <code>ctx</code> serve: serve-only (no regeneration)</li> <li>Context Files: the <code>.context/</code> directory structure</li> </ul>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-ceremonies/","level":1,"title":"Session Ceremonies","text":"","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#the-problem","level":2,"title":"The Problem","text":"<p>Sessions have two critical moments: the start and the end.</p> <ul> <li>At the start, you need the agent to load context and confirm it knows what is going on. </li> <li>At the end, you need to capture whatever the session produced before the conversation disappears.</li> </ul> <p>Most <code>ctx</code> skills work conversationally: \"jot down: check DNS after deploy\" is as good as <code>/ctx-pad add \"check DNS after deploy\"</code>. But session boundaries are different. They are well-defined moments with specific requirements, and partial execution is costly.</p> <p>If the agent only half-loads context at the start, it works from stale assumptions. If it only half-persists at the end, learnings and decisions are lost.</p> <p>This Is One of the Few Times Being Explicit Matters</p> <p>Session ceremonies are the two bookend skills that mark these boundaries. </p> <p>They are the exception to the conversational rule:</p> <p>Invoke <code>/ctx-remember</code> and <code>/ctx-wrap-up</code>  explicitly as slash commands.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tldr","level":2,"title":"TL;DR","text":"<p>Start: <code>/ctx-remember</code>: load context, get a structured readback.</p> <p>End: <code>/ctx-wrap-up</code>: review session, propose candidates, persist approved items.</p> <p>Use the slash commands, not conversational triggers, for completeness.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#explicit-invocation-matters","level":2,"title":"Explicit Invocation Matters","text":"<p>Most <code>ctx</code> skills encourage natural language. These two are different:</p> <p>Well-defined moments: Sessions have clear boundaries. A slash command marks the boundary unambiguously.</p> <p>Ambiguity risk: \"Do you remember?\" could mean many things. <code>/ctx-remember</code> means exactly one thing: load context and present a structured readback.</p> <p>Completeness: Conversational triggers risk partial execution. The agent might load some files but skip the session history, or persist one learning but forget to check for uncommitted changes. The slash command runs the full ceremony.</p> <p>Muscle memory: Typing <code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end becomes a habit, like opening and closing braces.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-remember</code> Skill Load context and present structured readback <code>/ctx-wrap-up</code> Skill Gather session signal, propose and persist context <code>/ctx-commit</code> Skill Commit with context capture (offered by wrap-up) <code>ctx agent</code> CLI Load token-budgeted context packet <code>ctx journal source</code> CLI List recent sessions <code>ctx add</code> CLI Persist learnings, decisions, conventions, tasks","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-start-ctx-remember","level":2,"title":"Session Start: <code>/ctx-remember</code>","text":"<p>Invoke at the beginning of every session:</p> <pre><code>/ctx-remember\n</code></pre> <p>The skill silently:</p> <ol> <li>Loads the context packet via <code>ctx agent --budget 4000</code></li> <li>Reads <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code></li> <li>Checks recent sessions via <code>ctx journal source --limit 3</code></li> </ol> <p>Then presents a structured readback with four sections:</p> <ul> <li>Last session: topic, date, what was accomplished</li> <li>Active work: pending and in-progress tasks</li> <li>Recent context: 1-2 relevant decisions or learnings</li> <li>Next step: suggestion or question about what to focus on</li> </ul> <p>The readback should feel like recall, not a file system tour. If the agent says \"Let me check if there are files...\" instead of a confident summary, the skill is not working correctly.</p> <p>What about 'do you remember?'</p> <p>The conversational trigger still works. But <code>/ctx-remember</code> guarantees the full ceremony runs: </p> <ul> <li>context packet, </li> <li>file reads, </li> <li>session history,</li> <li>and all four readback sections. </li> </ul> <p>The conversational version may cut corners.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-end-ctx-wrap-up","level":2,"title":"Session End: <code>/ctx-wrap-up</code>","text":"<p>Invoke before ending a session where meaningful work happened:</p> <pre><code>/ctx-wrap-up\n</code></pre> <p>The skill runs four phases:</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-1-gather-signal","level":3,"title":"Phase 1: Gather Signal","text":"<p>Silently checks <code>git diff --stat</code>, recent commits, and scans the conversation for themes: architectural choices, gotchas, patterns established, follow-up work identified.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-2-propose-candidates","level":3,"title":"Phase 2: Propose Candidates","text":"<p>Presents a structured list grouped by type:</p> <pre><code>## Session Wrap-Up\n\n### Learnings (2 candidates)\n1. **PyMdownx details extension breaks pre/code rendering**\n   - Context: Journal site showed broken code blocks inside details tags\n   - Lesson: details extension wraps content in <details> HTML, which\n     interferes with <pre><code> rendering\n   - Application: Use fenced code blocks instead of indented code inside\n     admonitions when details extension is active\n\n2. **Hook subprocesses cannot propagate env vars**\n   - Context: Set env var in PreToolUse hook, invisible in main session\n   - Lesson: Hooks execute in child processes; env changes don't propagate\n   - Application: Use tombstone files for hook-to-session communication\n\n### Decisions (1 candidate)\n1. **File-based cooldown tokens over env vars**\n   - Context: Need session-scoped cooldown for ctx agent auto-loading\n   - Rationale: File tokens survive across processes, simpler than IPC\n   - Consequence: Tombstone files accumulate in /tmp; need TTL cleanup\n\nPersist all? Or select which to keep?\n</code></pre> <p>Each candidate has complete structured fields, not just a title. Empty categories are omitted.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-3-persist","level":3,"title":"Phase 3: Persist","text":"<p>After you approve (all, some, or modified), the skill runs the appropriate <code>ctx add</code> commands and reports results.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#nudge-suppression","level":3,"title":"Nudge Suppression","text":"<p>After persisting, the skill marks the session as wrapped up via <code>ctx system mark-wrapped-up</code>. This suppresses context checkpoint nudges for 2 hours so the wrap-up ceremony itself does not trigger noisy reminders.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-4-commit-offer","level":3,"title":"Phase 4: Commit Offer","text":"<p>If there are uncommitted changes, offers to run <code>/ctx-commit</code>. Does not auto-commit.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#when-to-skip","level":2,"title":"When to Skip","text":"<p>Not every session needs ceremonies.</p> <p>Skip <code>/ctx-remember</code> when:</p> <ul> <li>You are doing a quick one-off lookup (reading a file, checking a value)</li> <li>Context was already loaded this session via <code>/ctx-agent</code></li> <li>You are continuing immediately after a previous session and context is   still fresh</li> </ul> <p>Skip <code>/ctx-wrap-up</code> when:</p> <ul> <li>Nothing meaningful happened (only read files, answered a question)</li> <li>You already persisted everything manually during the session</li> <li>The session was trivial (typo fix, quick config change)</li> </ul> <p>A good heuristic: if the session produced something a future session should know about, run <code>/ctx-wrap-up</code>. If not, just close.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#quick-reference","level":2,"title":"Quick Reference","text":"<pre><code># Session start\n/ctx-remember\n\n# ... do work ...\n\n# Session end\n/ctx-wrap-up\n</code></pre> <p>That is the complete ceremony. Two commands, bookending your session.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#relationship-to-other-skills","level":2,"title":"Relationship to Other Skills","text":"Skill When Purpose <code>/ctx-remember</code> Session start Load and confirm context <code>/ctx-reflect</code> Mid-session breakpoints Checkpoint at milestones <code>/ctx-wrap-up</code> Session end Full session review and persist <code>/ctx-commit</code> After completing work Commit with context capture <p><code>/ctx-reflect</code> is for mid-session checkpoints. <code>/ctx-wrap-up</code> is for end-of-session: it is more thorough, covers the full session arc, and includes the commit offer. If you already ran <code>/ctx-reflect</code> recently, <code>/ctx-wrap-up</code> avoids proposing the same candidates again.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tips","level":2,"title":"Tips","text":"<ul> <li>Make it a habit: The value of ceremonies compounds over sessions. Each <code>/ctx-wrap-up</code> makes the next <code>/ctx-remember</code> richer.</li> <li>Trust the candidates: The agent scans the full conversation. It often catches learnings you forgot about.</li> <li>Edit before approving: If a proposed candidate is close but not quite right, tell the agent what to change. Do not settle for a vague learning when a precise one is possible.</li> <li>Do not force empty ceremonies: If <code>/ctx-wrap-up</code> finds nothing worth persisting, that is fine. A session that only read files and answered questions does not need artificial learnings.</li> </ul>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#next-up","level":2,"title":"Next Up","text":"<p>Browsing and Enriching Past Sessions →: Export session history to a browsable journal and enrich entries with metadata.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: the full session workflow   that ceremonies bookend</li> <li>Persisting Decisions, Learnings, and Conventions:   deep dive on what gets persisted during wrap-up</li> <li>Detecting and Fixing Drift: keeping context files   accurate between ceremonies</li> <li>Pausing Context Hooks: skip ceremonies entirely   for quick tasks that don't need them</li> </ul>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-changes/","level":1,"title":"Reviewing Session Changes","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-changed-while-you-were-away","level":2,"title":"What Changed While You Were Away?","text":"<p>Between sessions, teammates commit code, context files get updated, and decisions pile up. <code>ctx change</code> gives you a single-command summary of everything that moved since your last session.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#quick-start","level":2,"title":"Quick Start","text":"<pre><code># Auto-detects your last session and shows what changed\nctx change\n\n# Check what changed in the last 48 hours\nctx change --since 48h\n\n# Check since a specific date\nctx change --since 2026-03-10\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx change</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#how-reference-time-works","level":2,"title":"How Reference Time Works","text":"<p><code>ctx change</code> needs a reference point to compare against. It tries these sources in order:</p> <ol> <li><code>--since</code> flag: explicit duration (<code>24h</code>, <code>72h</code>) or date    (<code>2026-03-10</code>, RFC3339 timestamp)</li> <li>Session markers: <code>ctx-loaded-*</code> files in <code>.context/state/</code>;    picks the second-most-recent (your previous session start)</li> <li>Event log: last <code>context-load-gate</code> event from    <code>.context/state/events.jsonl</code></li> <li>Fallback: 24 hours ago</li> </ol> <p>The marker-based detection means <code>ctx change</code> usually just works without any flags: it knows when you last loaded context and shows everything after that.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-it-reports","level":2,"title":"What It Reports","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#context-file-changes","level":3,"title":"Context File Changes","text":"<p>Any <code>.md</code> file in <code>.context/</code> modified after the reference time:</p> <pre><code>### Context File Changes\n- `TASKS.md` - modified 2026-03-11 14:30\n- `DECISIONS.md` - modified 2026-03-11 09:15\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#code-changes","level":3,"title":"Code Changes","text":"<p>Git activity since the reference time:</p> <pre><code>### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#integrating-into-session-start","level":2,"title":"Integrating into Session Start","text":"<p>Pair <code>ctx change</code> with the <code>/ctx-remember</code> ceremony for a complete session-start picture:</p> <pre><code># 1. Load context (this also creates the session marker)\nctx agent --budget 4000\n\n# 2. See what changed since your last session\nctx change\n</code></pre> <p>Or script it:</p> <pre><code># .context/hooks/session-start.sh\nctx agent --budget 4000\necho \"---\"\nctx change\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#team-workflows","level":2,"title":"Team Workflows","text":"<p>When multiple people share a <code>.context/</code> directory, <code>ctx change</code> shows who changed what:</p> <pre><code># After pulling from remote\ngit pull\nctx change --since 72h\n</code></pre> <p>This surfaces context file changes from teammates that you might otherwise miss in the commit log.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#tips","level":2,"title":"Tips","text":"<ul> <li>No changes? If nothing shows up, the reference time might be   wrong. Use <code>--since 48h</code> to widen the window.</li> <li>Works without git. Context file changes are detected by   filesystem mtime, not git. Code changes require git.</li> <li>Hook integration. The <code>context-load-gate</code> hook writes the   session marker that <code>ctx change</code> uses for auto-detection. If   you're not using the <code>ctx</code> plugin, markers won't exist and it falls   back to the event log or 24h window.</li> </ul>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-lifecycle/","level":1,"title":"The Complete Session","text":"","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-problem","level":2,"title":"The Problem","text":"<p>\"What does a full <code>ctx</code> session look like from start to finish?\"</p> <p>You have <code>ctx</code> installed and your <code>.context/</code> directory initialized, but the individual commands and skills feel disconnected.</p> <p>How do they fit together into a coherent workflow?</p> <p>This recipe walks through a complete session, from opening your editor to persisting context before you close it, so you can see how each piece connects.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tldr","level":2,"title":"TL;DR","text":"<ol> <li>Load: <code>/ctx-remember</code>: load context, get structured readback.</li> <li>Orient: <code>/ctx-status</code>: check file health and token usage.</li> <li>Pick: <code>/ctx-next</code>: choose what to work on.</li> <li>Work: implement, test, iterate.</li> <li>Commit: <code>/ctx-commit</code>: commit and capture decisions/learnings.</li> <li>Reflect: <code>/ctx-reflect</code>: identify what to persist (at milestones)</li> <li>Wrap up: <code>/ctx-wrap-up</code>: end-of-session ceremony.</li> </ol> <p>Read on for the full walkthrough with examples.</p> <p>Before You Start: Activate the Project</p> <p><code>ctx</code> commands (and the skills that call them) require <code>CTX_DIR</code> to be declared for the shell you're working in; <code>ctx</code> does not walk the filesystem to find <code>.context/</code>. Once per shell (or via your shell rc / direnv):</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>If you skip this, every skill below will surface an error naming the fix. See Activating a Context Directory for the full recipe.</p> <p>What Is a Readback?</p> <p>A readback is a structured summary where the agent plays back what it knows:</p> <ul> <li>last session,</li> <li>active tasks,</li> <li>recent decisions.</li> </ul> <p>This way, you can confirm it loaded the right context.</p> <p>The term \"readback\" comes from aviation, where pilots repeat instructions back to air traffic control to confirm they heard correctly.</p> <p>Same idea in <code>ctx</code>: The agent tells you what it \"thinks\" is going on, and you correct anything that's off before the work begins.</p> <ul> <li>Last session: topic, date, what was accomplished</li> <li>Active work: pending and in-progress tasks</li> <li>Recent context: 1-2 decisions or learnings that matter now</li> <li>Next step: suggestion or question about what to focus on</li> </ul>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx status</code> CLI command Quick health check on context files <code>ctx agent</code> CLI command Load token-budgeted context packet <code>ctx journal source</code> CLI command List previous sessions <code>ctx journal source --show</code> CLI command Inspect a specific session in detail <code>/ctx-remember</code> Skill Recall project context with structured readback <code>/ctx-agent</code> Skill Load full context packet inside the assistant <code>/ctx-status</code> Skill Show context summary with commentary <code>/ctx-next</code> Skill Suggest what to work on with rationale <code>/ctx-commit</code> Skill Commit code and prompt for context capture <code>/ctx-reflect</code> Skill Structured reflection checkpoint <code>/ctx-history</code> Skill Browse session history inside your AI assistant","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-workflow","level":2,"title":"The Workflow","text":"<p>The session lifecycle has seven steps. You will not always use every step (for example, a quick bugfix might skip reflection, and a research session might skip committing), but the full arc looks like this:</p> <p>Load context > Orient > Pick a Task > Work > Commit > Reflect</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-1-load-context","level":3,"title":"Step 1: Load Context","text":"<p>Start every session by loading what you know. The fastest way is a single prompt:</p> <pre><code>Do you remember what we were working on?\n</code></pre> <p>This triggers the <code>/ctx-remember</code> skill. Behind the scenes, the assistant runs <code>ctx agent --budget 4000</code>, reads the files listed in the context packet (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>), checks <code>ctx journal source --limit 3</code> for recent sessions, and then presents a structured readback.</p> <p>The readback should feel like a recall, not a file system tour. If you see \"Let me check if there are files...\" instead of a confident summary, the context system is not loaded properly.</p> <p>As an alternative, if you want raw data instead of a readback, run <code>ctx status</code> in your terminal or invoke <code>/ctx-status</code> for a summarized health check showing file counts, token usage, and recent activity.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-2-orient","level":3,"title":"Step 2: Orient","text":"<p>After loading context, verify you understand the current state.</p> <pre><code>/ctx-status\n</code></pre> <p>The status output shows which context files are populated, how many tokens they consume, and which files were recently modified. Look for:</p> <ul> <li>Empty core files: <code>TASKS.md</code> or <code>CONVENTIONS.md</code> with no content means   the context is sparse</li> <li>High token count (over 30k): the context is bloated and might   need <code>ctx compact</code></li> <li>No recent activity: files may be stale and need updating</li> </ul> <p>If the status looks healthy and the readback from Step 1 gave you enough context, skip ahead.</p> <p>If something seems off (stale tasks, missing decisions...), spend a minute reading the relevant file before proceeding.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"<p>With context loaded, choose a task. You can pick one yourself, or ask the assistant to recommend:</p> <pre><code>/ctx-next\n</code></pre> <p>The skill reads <code>TASKS.md</code>, checks recent sessions to avoid re-suggesting completed work, and presents 1-3 ranked recommendations with rationale.</p> <p>It prioritizes in-progress tasks over new starts (finishing is better than starting), respects explicit priority tags, and favors momentum: continuing a thread from a recent session is cheaper than context-switching.</p> <p>If you already know what you want to work on, state it directly:</p> <pre><code>Let's work on the session enrichment feature.\n</code></pre>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-4-do-the-work","level":3,"title":"Step 4: Do the Work","text":"<p>This is the main body of the session: write code, fix bugs, refactor, research: whatever the task requires.</p> <p>During this phase, a few <code>ctx</code>-specific patterns help:</p> <p>Check decisions before choosing: when you face a design choice, check if a prior decision covers it.</p> <pre><code>Is this consistent with our decisions?\n</code></pre> <p>Constrain scope: keep the assistant focused on the task at hand.</p> <pre><code>Only change files in internal/cli/session/. Nothing else.\n</code></pre> <p>Use <code>/ctx-implement</code> for multistep plans: if the task has multiple steps, this skill executes them one at a time with build/test verification between each step.</p> <p>Context monitoring runs automatically: the <code>check-context-size</code> hook monitors context capacity at adaptive intervals. Early in a session it stays silent. After 16+ prompts it starts monitoring, and past 30 prompts it checks frequently. If context capacity is running high, it will suggest saving unsaved work. No manual invocation is needed.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-5-commit-with-context","level":3,"title":"Step 5: Commit with Context","text":"<p>When the work is ready, use the context-aware commit instead of raw <code>git commit</code>:</p> <pre><code>/ctx-commit\n</code></pre> <p>The Agent May Recommend Committing</p> <p>You do not always need to invoke <code>/ctx-commit</code> explicitly.</p> <p>After a commit, the agent may proactively offer to capture context:</p> <p>\"We just made a trade-off there. Want me to record it as a decision?\"</p> <p>This is normal: The Agent Playbook encourages persisting at milestones, and a commit is a natural milestone.</p> <p>As an alternative, you can ask the assistant \"can we commit this?\" and it will pick up the <code>/ctx-commit</code> skill for you.</p> <p>The skill runs a pre-commit build check (for Go projects, <code>go build</code>), reviews the staged changes, drafts a commit message focused on \"why\" rather than \"what\", and then commits.</p> <p>After the commit succeeds, it prompts you:</p> <pre><code>**Any context to capture?**\n\n- **Decision**: Did you make a design choice or trade-off?\n- **Learning**: Did you hit a gotcha or discover something?\n- **Neither**: No context to capture; we are done.\n</code></pre> <p>If you made a decision, the skill records it with <code>ctx decision add</code>. If you learned something, it records it with <code>ctx learning add</code> including context, lesson, and application fields. This is the bridge between committing code and remembering why the code looks the way it does.</p> <p>If source code changed in areas that affect documentation, the skill also offers to check for doc drift.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-6-reflect","level":3,"title":"Step 6: Reflect","text":"<p>At natural breakpoints (after finishing a feature, resolving a complex bug, or before switching tasks) pause to reflect:</p> <pre><code>/ctx-reflect\n</code></pre> <p>Agents Reflect at Milestones</p> <p>Agents often reflect without explicit invocation.</p> <p>After completing a significant piece of work, the agent may naturally surface items worth persisting:</p> <p>\"We discovered that <code>$PPID</code> resolves differently inside hooks. Should I save that as a learning?\"</p> <p>This is the agent following the Work-Reflect-Persist cycle from the Agent Playbook.</p> <p>You do not need to say <code>/ctx-reflect</code> for this to happen; the agent treats milestones as reflection triggers on its own.</p> <p>The skill works through a checklist: learnings discovered, decisions made, tasks completed or created, and whether there are items worth persisting. It then presents a summary with specific items to persist, each with the exact command to run:</p> <pre><code>I would suggest persisting:\n\n- **Learning**: `$PPID` in PreToolUse hooks resolves to the Claude Code PID\n  `ctx learning add --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Task**: mark \"Add cooldown to ctx agent\" as done\n- **Decision**: tombstone-based cooldown with 10m default\n  `ctx decision add \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n</code></pre> <p>The skill asks before persisting anything. You choose what to keep.</p> <p>Not every commit needs reflection. A typo fix does not. But when you have been debugging for an hour and finally understand the root cause, that is worth a reflection checkpoint.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-7-persist-before-ending","level":3,"title":"Step 7: Persist Before Ending","text":"<p>Before ending the session, run the wrap-up ceremony to capture outstanding learnings, decisions, conventions, and tasks:</p> <pre><code>/ctx-wrap-up\n</code></pre> <p>Ceremony Skills</p> <p><code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end are ceremony skills: Invoke them as explicit slash commands for completeness, not conversationally.</p> <p>See Session Ceremonies for details on why these two skills are the exception to the conversational rule.</p> <p>The skill gathers signal from <code>git diff</code>, recent commits, and the conversation, then proposes structured candidates for your approval. After you select which to keep, it persists them via <code>ctx add</code> and offers <code>/ctx-commit</code> if uncommitted changes remain.</p> <p>As its final step, <code>/ctx-wrap-up</code> writes a handover under <code>.context/handovers/<TS>-<slug>.md</code> — a former-agent-to-next-agent note with a past-tense summary and a future-tense \"first action for the next session\". The filename is timestamped so concurrent agent runs never overwrite each other. The next <code>/ctx-remember</code> reads this file as the authoritative recall surface; skipping <code>/ctx-wrap-up</code> means the next session has no handover to read and recall degrades to probabilistic reconstruction from canonical files plus journal.</p> <p>Session transcripts are automatically captured by Claude Code and can be browsed later with <code>ctx journal source</code> and <code>ctx journal source --show</code>.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#conversational-session-flow","level":2,"title":"Conversational Session Flow","text":"<p>The steps above describe the mechanics. In practice, a session with a <code>ctx</code>-aware agent feels more like a conversation than a command sequence.</p> <p>The agent knows the lifecycle and moves through it naturally, often without explicit invocation. Here is a realistic example:</p> <p>You: Do you remember what we were working on?</p> <p>Agent (loads context silently, presents structured readback):</p> <pre><code>Last session (Feb 7, caching-layer): we implemented the in-memory cache with\nTTL expiration and wrote tests for the eviction policy. \n\nYou have 3 pending tasks; the biggest one is adding Redis as a backend.\n\nRecent decision:<br>\nwe chose LRU over LFU because the access pattern is mostly recency-based.\n\nWant to continue with the Redis backend, or tackle something else?\n</code></pre> <p>You: Let's work on the Redis caching backend.</p> <p>Agent (picks up the task, begins implementation):</p> <pre><code>Got it. I see the task in TASKS.md: \"Add Redis backend for cache layer.\"\n\nI will start with the connection pool setup. Only touching\n`internal/cache/redis/` for now.\n</code></pre> <p>(The agent works... writes code, runs tests, iterates.)</p> <p>Agent (after implementing the connection pool and choosing a retry strategy):</p> <pre><code>The connection pool is working. I went with exponential backoff with jitter\nfor retries instead of fixed intervals; it handles thundering herd better\nunder reconnection storms. That is a design choice worth recording. \n\nWant me to save it as a decision?\n</code></pre> <p>You: Yes, go ahead.</p> <p>Agent:</p> <pre><code>Done. Added to DECISIONS.md: \"Exponential backoff with jitter for Redis\nreconnection, to avoid thundering herd on pool recovery.\"\n</code></pre> <p>(More work happens... the agent implements the cache read/write paths.)</p> <p>You: What else did we learn during this session?</p> <p>Agent:</p> <pre><code>A few things worth capturing:\n\n* **Learning**: Redis `SET` with `NX` and `PX` flags handles atomic\n  set-if-not-exists with TTL in a single call. No need for a separate\n  `EXPIRE` command. We initially did it in two calls and hit a race condition.\n* **Learning**: The `go-redis` library's `PoolSize` default is 10 per CPU.\n  For our workload, explicit sizing at 20 was more predictable.\n* **Task update**: The connection pool subtask is done; the read/write path\n  is implemented but needs integration tests.\n\nWant me to persist these?\n</code></pre> <p>You: Yes. And let's wrap up.</p> <p>Agent (reflects and persists... all in one flow):</p> <pre><code>Persisted:\n\n* 2 learnings added to LEARNINGS.md\n* Marked \"connection pool setup\" done in TASKS.md\n* Added \"write integration tests for Redis cache\" as a new task\n\nContext is up to date. You are good to close.\n</code></pre> <p>Notice What Happened</p> <p>In the above workflow, the user never typed <code>/ctx-reflect</code> or <code>ctx learning add</code>.</p> <p>The agent moved through Load, Orient, Pick, Work, Commit, and Reflect driven by natural conversation.</p> <p>\"Let's wrap up\" was enough to trigger the full reflect-and-persist flow.</p> <p>The agent surfaced persist-worthy items at milestones: after a design choice, after discovering a gotcha: without waiting to be asked.</p> <p>This is the intended experience. </p> <p>The commands and skills still exist for when you want precise control, but  the agent is a proactive partner in the lifecycle, not a passive executor  of slash commands.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Quick-reference checklist for a complete session:</p> <ul> <li> Load: <code>/ctx-remember</code>: load context and confirm readback</li> <li> Orient: <code>/ctx-status</code>: check file health and token usage</li> <li> Pick: <code>/ctx-next</code>: choose what to work on</li> <li> Work: implement, test, iterate (scope with \"only change X\")</li> <li> Commit: <code>/ctx-commit</code>: commit and capture decisions/learnings</li> <li> Reflect: <code>/ctx-reflect</code>: identify what to persist (at milestones)</li> <li> Wrap up: <code>/ctx-wrap-up</code>: end-of-session ceremony</li> </ul> <p>Conversational equivalents: you can drive the same lifecycle with plain language:</p> Step Slash command Natural language Load <code>/ctx-remember</code> \"Do you remember?\" / \"What were we working on?\" Orient <code>/ctx-status</code> \"How's our context looking?\" Pick <code>/ctx-next</code> \"What should we work on?\" / \"Let's do the caching task\" Work (none) \"Only change files in internal/cache/\" Commit <code>/ctx-commit</code> \"Commit this\" / \"Ship it\" Reflect <code>/ctx-reflect</code> \"What did we learn?\" / (agent offers at milestones) Wrap up <code>/ctx-wrap-up</code> (use the slash command for completeness) <p>The agent understands both columns.</p> <p>In practice, most sessions use a mix:</p> <ul> <li>Explicit Commands when you want precision;</li> <li>Natural Language when you want flow and agentic autonomy.</li> </ul> <p>The agent will also initiate steps on its own (particularly \"Reflect\") when it recognizes a milestone.</p> <p>Short sessions (quick bugfix) might only use: Load, Work, Commit.</p> <p>Long sessions should Reflect after each major milestone and persist learnings and decisions before ending.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tips","level":2,"title":"Tips","text":"<p>Persist early if context is running low. A hook monitors context capacity and notifies you when it gets high, but do not wait for the notification. If you have been working for a while and have unpersisted learnings, persist proactively.</p> <p>Browse previous sessions by topic. If you need context from a prior session, <code>ctx journal source --show auth</code> will match by keyword. You do not need to remember the exact date or slug.</p> <p>Reflection is optional but valuable. You can skip <code>/ctx-reflect</code> for small changes, but always persist learnings and decisions before ending a session where you did meaningful work. These are what the next session loads.</p> <p>Let the hook handle context loading. The <code>PreToolUse</code> hook runs <code>ctx agent</code> automatically with a cooldown, so context loads on first tool use without you asking. The <code>/ctx-remember</code> prompt at session start is for your benefit (to get a readback), not because the assistant needs it.</p> <p>The agent is a proactive partner, not a passive tool. A <code>ctx</code>-aware agent follows the Agent Playbook: it watches for milestones (completed tasks, design decisions, discovered gotchas) and offers to persist them without being asked. If you finish a tricky debugging session, it may say \"That root cause is worth saving as a learning. Want me to record it?\" before you think to ask. This is by design.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#next-up","level":2,"title":"Next Up","text":"<p>Session Ceremonies →: The two bookend rituals for every session: <code>/ctx-remember</code> at the start, <code>/ctx-wrap-up</code> at the end.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#see-also","level":2,"title":"See Also","text":"<ul> <li>Session Ceremonies: why <code>/ctx-remember</code> and   <code>/ctx-wrap-up</code> are explicit slash commands, not conversational</li> <li>CLI Reference: full documentation for all <code>ctx</code> commands</li> <li>Prompting Guide: effective prompts for ctx-enabled projects</li> <li>Tracking Work Across Sessions: deep dive on task management</li> <li>Persisting Decisions, Learnings, and Conventions:   deep dive on knowledge capture</li> <li>Detecting and Fixing Drift: keeping context files accurate</li> <li>Pausing Context Hooks: shortcut the full lifecycle   for quick tasks that don't need ceremony overhead</li> </ul>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-pause/","level":1,"title":"Pausing Context Hooks","text":"","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#the-problem","level":2,"title":"The Problem","text":"<p>Not every session needs the full ceremony. Quick investigations, one-off questions, small fixes unrelated to active project work: These tasks don't benefit from persistence nudges, ceremony reminders, or knowledge checks. Every hook still fires, consuming tokens and attention on work that won't produce learnings or decisions worth capturing.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tldr","level":2,"title":"TL;DR","text":"Command What it does <code>ctx hook pause</code> or <code>/ctx-pause</code> Silence all nudge hooks for this session <code>ctx hook resume</code> or <code>/ctx-resume</code> Restore normal hook behavior <p>Pause is session-scoped: It only affects the current session. Other sessions (same project, different terminal) are unaffected.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-gets-paused","level":2,"title":"What Gets Paused","text":"<p>All nudge and reminder hooks go silent:</p> <ul> <li>Context size checkpoints</li> <li>Ceremony adoption nudges</li> <li>Persistence reminders</li> <li>Journal maintenance reminders</li> <li>Knowledge growth nudges</li> <li>Map staleness nudges</li> <li>Version update nudges</li> <li>Resource pressure warnings</li> <li>QA reminders</li> <li>Post-commit nudges</li> <li>Specs nudges</li> <li>Backup age warnings</li> <li>Context load gate</li> <li>Pending reminders relay</li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-still-fires","level":2,"title":"What Still Fires","text":"<p>Security hooks always run, even when paused:</p> <ul> <li><code>block-non-path-ctx</code>: prevents <code>./ctx</code> invocations</li> <li><code>block-dangerous-commands</code>: blocks <code>sudo</code>, force push, etc.</li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#workflow","level":2,"title":"Workflow","text":"<pre><code># 1. Session starts: Context loads normally.\n\n# 2. You realize this is a quick task\nctx hook pause\n\n# 3. Work without interruption: hooks are silent\n\n# 4. Session evolves into real work? Resume first\nctx hook resume\n\n# 5. Now wrap up normally\n# /ctx-wrap-up\n</code></pre>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#graduated-reminder","level":2,"title":"Graduated Reminder","text":"<p>Paused hooks aren't completely invisible. A minimal indicator appears so you always know the state:</p> Paused turns What you see 1-5 <code>ctx:paused</code> 6+ <code>ctx:paused (N turns): resume with /ctx-resume</code> <p>This prevents the \"forgot I paused\" problem during long sessions.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tips","level":2,"title":"Tips","text":"<ul> <li> <p>Resume before wrapping up. If your quick task turns into real work,   resume hooks before running <code>/ctx-wrap-up</code>. The wrap-up ceremony needs   active hooks to capture learnings properly.</p> </li> <li> <p>Initial context load is unaffected. The ~8k token startup injection   (CLAUDE.md, playbook, constitution) happens before any command runs.   Pause only affects hooks that fire during the session.</p> </li> <li> <p>Use for quick investigations. Debugging a stack trace? Checking a   git log? Answering a colleague's question? Pause, do the work, close   the session. No ceremony needed.</p> </li> <li> <p>Don't use for real work. If you're implementing features, fixing   bugs, or making decisions: keep hooks active. The nudges exist to   prevent context loss.</p> </li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#see-also","level":2,"title":"See Also","text":"<p>See also: Session Ceremonies: the bookend rituals that pause lets you skip when they aren't needed.</p> <p>See also: Customizing Hook Messages: if you want to change what hooks say rather than silencing them entirely.</p> <p>See also: The Complete Session: the full session workflow that pause shortcuts for quick tasks.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-reminders/","level":1,"title":"Session Reminders","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-problem","level":2,"title":"The Problem","text":"<p>You're deep in a session and realize: \"I need to refactor the swagger definitions next time.\" You could add a task, but this isn't a work item: it's a note to future-you. You could jot it on the scratchpad, but scratchpad entries don't announce themselves.</p> <p>How do you leave a message that your next session opens with?</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind list\nctx remind dismiss 1       # or batch: ctx remind dismiss 1 3-5\n</code></pre> <p>Reminders surface automatically at session start: VERBATIM, every session, until you dismiss them.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx remind ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx remind</code> CLI command Add a reminder (default action) <code>ctx remind list</code> CLI command Show all pending reminders <code>ctx remind dismiss</code> CLI command Remove a reminder by ID (or <code>--all</code>) <code>/ctx-remind</code> Skill Natural language interface to reminders","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-1-leave-a-reminder","level":3,"title":"Step 1: Leave a Reminder","text":"<p>Tell your agent what to remember, or run it directly:</p> <pre><code>You: \"remind me to refactor the swagger definitions\"\n\nAgent: [runs ctx remind \"refactor the swagger definitions\"]\n       \"Reminder set:\n         + [1] refactor the swagger definitions\"\n</code></pre> <p>Or from the terminal:</p> <pre><code>ctx remind \"refactor the swagger definitions\"\n</code></pre>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-2-set-a-date-gate-optional","level":3,"title":"Step 2: Set a Date Gate (Optional)","text":"<p>If the reminder shouldn't fire until a specific date:</p> <pre><code>You: \"remind me to check the deploy logs after Tuesday\"\n\nAgent: [runs ctx remind \"check the deploy logs\" --after 2026-02-25]\n       \"Reminder set:\n         + [2] check the deploy logs  (after 2026-02-25)\"\n</code></pre> <p>The reminder stays silent until that date, then fires every session.</p> <p>The agent converts natural language dates (\"tomorrow\", \"next week\", \"after the release on Friday\") to <code>YYYY-MM-DD</code>. If it's ambiguous, it asks.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-3-start-a-new-session","level":3,"title":"Step 3: Start a New Session","text":"<p>Next session, the reminder appears automatically before anything else:</p> <pre><code>┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n</code></pre> <p>No action needed: The <code>check-reminders</code> hook fires on <code>UserPromptSubmit</code> and the agent relays the box verbatim.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-4-dismiss-when-done","level":3,"title":"Step 4: Dismiss When Done","text":"<p>After you've acted on a reminder (or decided to skip it):</p> <pre><code>You: \"dismiss reminder 1\"\n\nAgent: [runs ctx remind dismiss 1]\n       \"Dismissed:\n         - [1] refactor the swagger definitions\"\n\n# Batch dismiss also works:\n# \"dismiss reminders 3, 5 through 7\"\n# → ctx remind dismiss 3 5-7\n</code></pre> <p>Or clear everything:</p> <pre><code>ctx remind dismiss --all\n</code></pre>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-5-check-whats-pending","level":3,"title":"Step 5: Check What's Pending","text":"<pre><code>ctx remind list\n</code></pre> <pre><code>  [1] refactor the swagger definitions\n  [3] review auth token expiry logic\n  [4] check deploy logs  (after 2026-02-25, not yet due)\n</code></pre> <p>Date-gated reminders that haven't reached their date show <code>(not yet due)</code>.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#using-ctx-remind-in-a-session","level":2,"title":"Using <code>/ctx-remind</code> in a Session","text":"<p>Invoke the <code>/ctx-remind</code> skill, then describe what you want:</p> <pre><code>You: /ctx-remind remind me to update the API docs\nYou: /ctx-remind what reminders do I have?\nYou: /ctx-remind dismiss reminder 3\n</code></pre> You say (after <code>/ctx-remind</code>) What the agent does \"remind me to update the API docs\" <code>ctx remind \"update the API docs\"</code> \"remind me next week to check staging\" <code>ctx remind \"check staging\" --after 2026-03-02</code> \"what reminders do I have?\" <code>ctx remind list</code> \"dismiss reminder 3\" <code>ctx remind dismiss 3</code> \"dismiss reminders 3, 5 through 7\" <code>ctx remind dismiss 3 5-7</code> \"clear all reminders\" <code>ctx remind dismiss --all</code>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#reminders-vs-scratchpad-vs-tasks","level":2,"title":"Reminders vs Scratchpad vs Tasks","text":"You want to... Use Leave a note that announces itself next session <code>ctx remind</code> Jot down a quick value or sensitive token <code>ctx pad</code> Track work with status and completion <code>TASKS.md</code> Record a decision or lesson for all sessions Context files <p>Decision guide:</p> <ul> <li>If it should announce itself at session start → <code>ctx remind</code></li> <li>If it's a quiet note you'll check manually → <code>ctx pad</code></li> <li>If it's a work item you'll mark done → <code>TASKS.md</code></li> </ul> <p>Reminders Are Sticky Notes, Not Tasks</p> <p>A reminder has no status, no priority, no lifecycle. It's a message to \"future you\" that fires until dismissed. </p> <p>If you need tracking, use a task in <code>TASKS.md</code>.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tips","level":2,"title":"Tips","text":"<ul> <li>Reminders fire every session: Unlike nudges (which throttle to   once per day), reminders repeat until you dismiss them. This is   intentional: You asked to be reminded.</li> <li>Date gating is session-scoped, not clock-scoped: <code>--after   2026-02-25</code> means \"don't show until sessions on or after Feb 25.\"   It does not mean \"alarm at midnight on Feb 25.\"</li> <li>The agent handles date parsing: Say \"next week\" or \"after   Friday\": The agent converts it to <code>YYYY-MM-DD</code>. The CLI only   accepts the explicit date format.</li> <li>Reminders are committed to git: They travel with the repo.   If you switch machines, your reminders follow.</li> <li>IDs never reuse: After dismissing reminder 3, the next reminder   gets ID 4 (or higher). No confusion from recycled numbers.</li> </ul>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#next-up","level":2,"title":"Next Up","text":"<p>Using the Scratchpad →: For quiet notes and sensitive values that don't need session-start announcements.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#see-also","level":2,"title":"See Also","text":"<ul> <li>CLI Reference: <code>ctx</code> remind: full   command syntax and flags</li> <li>The Complete Session: how reminders fit into   the session lifecycle</li> <li>Managing Tasks: for work items that need status   tracking</li> </ul>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/state-maintenance/","level":1,"title":"State Directory Maintenance","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-problem","level":2,"title":"The Problem","text":"<p>Every session creates tombstone files in <code>.context/state/</code> - small markers that suppress repeat hook nudges (\"already checked context size\", \"already sent persistence reminder\"). Over days and weeks, these accumulate into hundreds of files from long-dead sessions.</p> <p>The files are harmless individually, but the clutter makes it harder to reason about state, and stale global tombstones can suppress nudges across sessions entirely.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx prune --dry-run     # preview what would be removed\nctx prune               # prune files older than 7 days\nctx prune --days 1      # more aggressive: keep only today\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx prune</code> / <code>ctx status</code> fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose <code>ctx prune</code> Command Remove old per-session state files <code>ctx status</code> Command Quick health overview including state dir","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#understanding-state-files","level":2,"title":"Understanding State Files","text":"<p>State files fall into two categories:</p> <p>Session-scoped (contain a UUID in the filename): Created per-session to suppress repeat nudges. Safe to prune once the session ends. Examples:</p> <pre><code>context-check-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\nheartbeat-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\npersistence-nudge-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\n</code></pre> <p>Global (no UUID): Persist across sessions. <code>ctx prune</code> preserves these automatically. Some are legitimate state (<code>events.jsonl</code>, <code>memory-import.json</code>); others may be stale tombstones that need manual review.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-workflow","level":2,"title":"The Workflow","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-1-preview","level":3,"title":"Step 1: Preview","text":"<p>Always dry-run first to see what would be removed:</p> <pre><code>ctx prune --dry-run\n</code></pre> <p>The output shows each file, its age, and a summary:</p> <pre><code>  would prune: context-check-abc123... (age: 3d)\n  would prune: heartbeat-abc123... (age: 3d)\n\nDry run - would prune 150 files (skip 70 recent, preserve 14 global)\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-2-prune","level":3,"title":"Step 2: Prune","text":"<p>Choose an age threshold. The default is 7 days:</p> <pre><code>ctx prune               # older than 7 days\nctx prune --days 3      # older than 3 days\nctx prune --days 1      # older than 1 day (aggressive)\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-3-review-global-files","level":3,"title":"Step 3: Review Global Files","text":"<p>After pruning, check what <code>prune</code> preserved:</p> <pre><code>ls .context/state/ | grep -v '[0-9a-f]\\{8\\}-[0-9a-f]\\{4\\}'\n</code></pre> <p>Legitimate global files (keep):</p> <ul> <li><code>events.jsonl</code> - event log</li> <li><code>memory-import.json</code> - import tracking state</li> </ul> <p>Stale global tombstones (safe to delete):</p> <ul> <li>Files like <code>backup-reminded</code>, <code>ceremony-reminded</code>, <code>version-checked</code>   with no session UUID are one-shot markers. If they are from a previous   session, they are stale and can be removed manually.</li> </ul> <pre><code>rm .context/state/backup-reminded .context/state/ceremony-reminded\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-4-verify","level":3,"title":"Step 4: Verify","text":"<pre><code>ls .context/state/ | wc -l    # should be manageable\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#when-to-prune","level":2,"title":"When to Prune","text":"<ul> <li>Weekly: <code>ctx prune</code> with default 7-day threshold</li> <li>After heavy parallel work: Multiple concurrent sessions create   many tombstones. Prune with <code>--days 1</code> afterward.</li> <li>When state directory exceeds ~100 files: A sign that pruning   hasn't run recently</li> </ul>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tips","level":2,"title":"Tips","text":"<p>Pruning active sessions is safe but noisy: If you prune a file belonging to a still-running session, the corresponding hook will re-fire its nudge on the next prompt. Minor UX annoyance, not data loss.</p> <p>No context files are stored in state: The state directory contains only tombstones, counters, and diagnostic data. Nothing in <code>.context/state/</code> affects your decisions, learnings, tasks, or conventions.</p> <p>Test artifacts sneak in: Files like <code>context-check-statstest</code> or <code>heartbeat-unknown</code> are artifacts from development or testing. They lack UUIDs so <code>prune</code> preserves them. Delete manually.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#see-also","level":2,"title":"See Also","text":"<ul> <li>Detecting and Fixing Drift: broader context   maintenance including drift detection and archival</li> <li>Troubleshooting: diagnostic workflow using   <code>ctx doctor</code> and event logs</li> <li>CLI Reference: system: full flag documentation   for <code>ctx prune</code> and related commands</li> </ul>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/steering/","level":1,"title":"Writing Steering Files","text":"","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#writing-steering-files","level":1,"title":"Writing Steering Files","text":"<p>Steering files tell your AI assistant how to behave, not what was decided or how the codebase is written. This recipe walks through writing a steering file from scratch, validating which prompts will trigger it, and syncing it out to your configured AI tools.</p> <p>Before You Start</p> <p>If you're unsure whether a rule belongs in <code>steering/</code>, <code>DECISIONS.md</code>, or <code>CONVENTIONS.md</code>, read the \"Steering vs decisions vs conventions\" admonition on the <code>ctx steering</code> reference page. The short version: if the rule is \"the AI should always do X when asked about Y,\" that's steering. Otherwise it's probably a decision or convention.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#start-here-customize-the-foundation-files","level":2,"title":"Start Here: Customize the Foundation Files","text":"<p><code>ctx init</code> scaffolds four foundation steering files for you the first time you initialize a project:</p> File Purpose <code>.context/steering/product.md</code> Product context, goals, target users <code>.context/steering/tech.md</code> Tech stack, constraints, key dependencies <code>.context/steering/structure.md</code> Directory layout, naming conventions <code>.context/steering/workflow.md</code> Branch strategy, commit rules, pre-commit <p>Each file opens with an inline HTML comment that explains the three inclusion modes, what <code>priority</code> means, and the <code>tools</code> scope. The comment is invisible in rendered Markdown but visible when you edit the file. Delete it once the file is yours.</p> <p>All four default to <code>inclusion: always</code> and <code>priority: 10</code>, so they fire on every AI tool call until you customize them. If you're reading this recipe and haven't touched them yet, open each one now and replace the placeholder bullet list with actual rules for your project. That's the highest-leverage five minutes you can spend in a new <code>ctx</code> setup.</p> <p>What to fill in, by file:</p> <p><code>product.md</code>: The elevator pitch plus hard scope:</p> <ul> <li>One-sentence product description.</li> <li>Primary users and their top job-to-be-done.</li> <li>Two or three \"this is explicitly out of scope\" items   so the AI doesn't wander.</li> </ul> <p><code>tech.md</code>: Technology and constraints:</p> <ul> <li>Languages and versions (<code>Go 1.22</code>, <code>Node 20</code>, etc.).</li> <li>Frameworks and key libraries.</li> <li>Runtime and deployment target.</li> <li>Hard constraints: \"no CGO\", \"no network at test time\",   \"no external DB for unit tests\". These are the things   that burn agents when they don't know them.</li> </ul> <p><code>structure.md</code>: Layout and naming:</p> <ul> <li>Top-level directories and their purpose.</li> <li>Where new files should go (and where they should NOT).</li> <li>Naming conventions for packages, files, types.</li> </ul> <p><code>workflow.md</code>: Process rules:</p> <ul> <li>Branch strategy (main-only, trunk-based, feature   branches).</li> <li>Commit message format, signed-off-by requirement.</li> <li>Pre-commit and pre-push checks.</li> <li>Review expectations.</li> </ul> <p>After editing, the next AI tool call in Claude Code will pick up the new rules automatically via the plugin's <code>PreToolUse</code> hook, with no sync step and no restart. Other tools (Cursor, Cline, Kiro) need <code>ctx steering sync</code> to export into their native format.</p> <p>Prefer a Bare <code>.context/steering/</code> Directory?</p> <p>Re-run <code>ctx init --no-steering-init</code> and delete the scaffolded files. <code>ctx init</code> leaves existing files alone, so the flag is only needed if you want to opt out of the initial scaffold.</p> <p>The rest of this recipe walks through creating an additional, scenario-specific steering file beyond the four foundation defaults.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#scenario","level":2,"title":"Scenario","text":"<p>You're working on a project with a strict input-validation policy: every new API handler must validate request bodies before touching the database. You want the AI to flag this concern automatically whenever it's asked to write an HTTP handler, without you having to remind it every session.</p> <p>Claude Code Users: Pick <code>always</code>, Not <code>auto</code></p> <p>This walkthrough uses <code>inclusion: auto</code> because the scenario is a scoped rule that matches a specific kind of prompt. That works natively on Cursor, Cline, and Kiro (they resolve the <code>description</code> keyword match themselves).</p> <p>On Claude Code, <code>auto</code> does not fire through the plugin's <code>PreToolUse</code> hook. The hook passes an empty prompt to <code>ctx agent</code>, so only <code>always</code> files match. Claude can still reach an <code>auto</code> file by calling the <code>ctx_steering_get</code> MCP tool, but that requires Claude to decide to call it; there's no automatic injection.</p> <p>If Claude Code is your tool, set <code>inclusion: always</code> in Step 2 instead of <code>auto</code>. The rule will fire on every tool call regardless of topic. You may want to narrow the rule body so the extra tokens per turn aren't wasted on unrelated work.</p> <p>See the <code>ctx steering</code> reference \"Prefer <code>inclusion: always</code> for Claude Code\" section for the full trade-off.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-1-scaffold-the-file","level":2,"title":"Step 1: Scaffold the File","text":"<pre><code>ctx steering add api-validation\n</code></pre> <p>That creates <code>.context/steering/api-validation.md</code> with default frontmatter:</p> <pre><code>---\nname: api-validation\ndescription:\ninclusion: manual\ntools: []\npriority: 50\n---\n</code></pre> <p>The defaults are deliberately conservative: <code>inclusion: manual</code> means the file won't be applied until you opt in, which keeps the rules out of the prompt until you've reviewed them.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-2-fill-in-the-rule","level":2,"title":"Step 2: Fill in the Rule","text":"<p>Open the file and write the rule body plus a focused description. The description is what <code>inclusion: auto</code> matches against later.</p> <pre><code>---\nname: api-validation\ndescription: HTTP handler input validation and request parsing\ninclusion: auto\ntools: []\npriority: 20\n---\n\n# API request validation\n\nEvery new HTTP handler MUST:\n\n1. Parse request bodies into typed structs, never `map[string]any`.\n2. Validate required fields before any database call.\n3. Return 400 with a machine-readable error for validation failures.\n4. Use `context.Context` from the request for all downstream calls.\n\nPrefer existing validation helpers in `internal/validate/`\nrather than inline checks.\n</code></pre> <p>Notes on the choices:</p> <ul> <li><code>inclusion: auto</code>: this rule should fire automatically   on HTTP-handler-shaped prompts, not always.</li> <li><code>priority: 20</code>: lower than the default, so this rule   appears near the top of the prompt alongside other   high-priority rules.</li> <li>Description is keyword-rich (\"HTTP handler input   validation and request parsing\"); the <code>auto</code> matcher scores   prompts against these words.</li> </ul>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-3-preview-which-prompts-match","level":2,"title":"Step 3: Preview Which Prompts Match","text":"<p>Before committing the file, validate your description catches the prompts you care about:</p> <pre><code>ctx steering preview \"add an endpoint for updating user email\"\n</code></pre> <p>Expected output:</p> <pre><code>Steering files matching prompt \"add an endpoint for updating user email\":\n  api-validation       inclusion=auto     priority=20  tools=all\n</code></pre> <p>Good, the prompt matches. Try a negative case:</p> <pre><code>ctx steering preview \"fix a bug in the JSON renderer\"\n</code></pre> <p>Expected: empty match (or whatever else is currently <code>auto</code>). If <code>api-validation</code> incorrectly fires for unrelated prompts, tighten the description. If it misses prompts it should catch, add more keywords.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-4-list-to-confirm-metadata","level":2,"title":"Step 4: List to Confirm Metadata","text":"<pre><code>ctx steering list\n</code></pre> <p>Should show <code>api-validation</code> alongside any other files, with its inclusion mode and priority. If the list is wrong, check the frontmatter for typos.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-5-get-the-rules-in-front-of-the-ai","level":2,"title":"Step 5: Get the Rules in Front of the AI","text":"<p>Steering files are authored once in <code>.context/steering/</code>, but how they reach the AI depends on which tool you use. There are two delivery mechanisms:</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-a-native-rules-tools-cursor-cline-kiro","level":3,"title":"Path A: Native-Rules Tools (Cursor, Cline, Kiro)","text":"<p>These tools read a specific directory for rules. <code>ctx steering sync</code> exports your files into that directory with tool-specific frontmatter:</p> <pre><code>ctx steering sync\n</code></pre> <p>Depending on the active tool in <code>.ctxrc</code> or <code>--tool</code>:</p> Tool Target Cursor <code>.cursor/rules/</code> Cline <code>.clinerules/</code> Kiro <code>.kiro/steering/</code> <p>The sync is idempotent; unchanged files are skipped. Run it whenever you edit a steering file.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-b-claude-code-and-codex-hook-mcp","level":3,"title":"Path B: Claude Code and Codex (Hook + MCP)","text":"<p>Claude Code and Codex have no native rules primitive, so <code>ctx steering sync</code> is a no-op for them; it deliberately skips both. Instead, steering reaches these tools through two non-sync channels:</p> <ol> <li> <p><code>PreToolUse</code> hook (automatic). The <code>ctx setup    claude-code</code> plugin installs a hook that runs    <code>ctx agent --budget 8000</code> before each tool call. <code>ctx    agent</code> loads your steering files, filters them against    the active prompt, and includes matching bodies as    Tier 6 of the context packet. The packet gets injected    into Claude's context automatically.</p> </li> <li> <p><code>ctx_steering_get</code> MCP tool (on-demand). Claude can    call this MCP tool mid-task to fetch matching steering    files for a specific prompt. Automatic activation comes    from Claude's judgment, not a hook.</p> </li> </ol> <p>Both channels activate when you run:</p> <pre><code>ctx setup claude-code --write\n</code></pre> <p>That installs the plugin, wires the hook, and registers the MCP server. After that, steering files you edit are picked up on the next tool call, with no sync step needed.</p> <p>Running <code>ctx steering sync</code> with Claude Code</p> <p>It won't error; it will simply report that Claude and Codex aren't sync targets and skip them. If Claude Code is your only tool, you never need to run <code>sync</code>. If you use both Claude Code and (say) Cursor, run <code>sync</code> to keep Cursor up to date; the Claude pipeline takes care of itself via the hook.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-6-verify-the-ai-sees-it","level":2,"title":"Step 6: Verify the AI Sees It","text":"<p>Open your AI tool and ask it something the rule should fire on:</p> <p>\"Add a POST /users endpoint that accepts email and name.\"</p> <p>If the rule is working, the AI's first response should mention input validation, typed structs, and the <code>internal/validate/</code> package, because that's what the steering file told it to do.</p> <p>If nothing happens, the fix depends on which path you're on:</p> <p>Path A (Cursor/Cline/Kiro):</p> <ol> <li>Re-run <code>ctx steering preview</code> with the literal prompt to    confirm the match.</li> <li>Run <code>ctx steering list</code> and verify <code>inclusion</code> is <code>auto</code>,    not <code>manual</code>.</li> <li>Check the tool's own config directory (e.g.    <code>.cursor/rules/</code>); the file should be there after    <code>ctx steering sync</code>.</li> </ol> <p>Path B (Claude Code):</p> <ol> <li>Re-run <code>ctx steering preview</code> with the literal prompt to    confirm the match.</li> <li>Verify the plugin is installed: <code>cat .claude/hooks.json</code>    should include <code>ctx agent --budget 8000</code> under    <code>PreToolUse</code>. If not, re-run <code>ctx setup claude-code --write</code>.</li> <li>Run <code>ctx agent --budget 8000</code> manually and grep the    output for your rule body. If it's there, the data is    fine; if it's missing, the <code>inclusion</code> mode or    <code>description</code> is at fault.</li> <li>As a last resort, ask Claude directly: \"Call the    <code>ctx_steering_get</code> MCP tool with my prompt and show me    the result.\" If the MCP tool returns your rule, Claude    has access but isn't pulling it into the initial    context packet; tighten the description keywords.</li> </ol>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>Too-generic descriptions. <code>description: general coding</code> will match almost every prompt and flood the context window. Keep descriptions specific to the scenario the rule applies to.</p> <p>Overlapping rules. If two steering files match the same prompt and contradict each other, the result is confusing. Use <code>priority</code> to resolve, but better: merge the files or narrow the descriptions so they don't overlap.</p> <p>Putting decisions in steering. \"We decided to use PostgreSQL\" is a decision, not a rule for the AI to follow on every prompt. Record decisions with <code>ctx decision add</code>, not <code>ctx steering add</code>.</p> <p>Committing <code>inclusion: always</code> without thinking. Rules marked <code>always</code> fire on every prompt, consuming tier-6 budget permanently. Only use <code>always</code> for true invariants (security, safety, licensing). Everything else should be <code>auto</code> or <code>manual</code>.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx steering</code> reference: full   command, flag, and frontmatter reference.</li> <li><code>ctx setup</code>: configure which tools the   steering sync writes to.</li> <li>Authoring triggers: if you want   script-based automation, not rule-based prompt injection.</li> </ul>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/system-hooks-audit/","level":1,"title":"Auditing System Hooks","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> runs 14 system hooks behind the scenes: nudging your agent to persist context, warning about resource pressure, gating commits on QA. But these hooks are invisible by design. You never see them fire. You never know if they stopped working.</p> <p>How do you verify your hooks are actually running, audit what they do, and get alerted when they go silent?</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx system check-resources # run a hook manually\nls -la .context/logs/      # check hook execution logs\nctx hook notify setup      # get notified when hooks fire\n</code></pre> <p>Or ask your agent: \"Are our hooks running?\"</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx system <hook></code> CLI command Run a system hook manually <code>ctx sysinfo</code> CLI command Show system resource status <code>ctx usage</code> CLI command Stream or dump per-session token stats <code>ctx hook notify setup</code> CLI command Configure webhook for audit trail <code>ctx hook notify test</code> CLI command Verify webhook delivery <code>.ctxrc</code> <code>notify.events</code> Configuration Subscribe to <code>relay</code> for full hook audit <code>.context/logs/</code> Log files Local hook execution ledger","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-are-system-hooks","level":2,"title":"What Are System Hooks?","text":"<p>System hooks are plumbing commands that <code>ctx</code> registers with your AI tool (Claude Code, Cursor, etc.) via the plugin's <code>hooks.json</code>. They fire automatically at specific events during your AI session:</p> Event When Hooks <code>UserPromptSubmit</code> Before the agent sees your prompt 10 check hooks + heartbeat <code>PreToolUse</code> Before the agent uses a tool <code>block-non-path-ctx</code>, <code>qa-reminder</code> <code>PostToolUse</code> After a tool call succeeds <code>post-commit</code> <p>You never run these manually. Your AI tool runs them for you: That's the point.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-complete-hook-catalog","level":2,"title":"The Complete Hook Catalog","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#prompt-time-checks-userpromptsubmit","level":3,"title":"Prompt-Time Checks (UserPromptSubmit)","text":"<p>These fire before every prompt, but most are throttled to avoid noise.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-context-size-context-capacity-warning","level":4,"title":"<code>check-context-size</code>: Context Capacity Warning","text":"<p>What: Adaptive prompt counter. Silent for the first 15 prompts, then nudges with increasing frequency (every 5<sup>th</sup>, then every 3<sup>rd</sup>).</p> <p>Why: Long sessions lose coherence. The nudge reminds both you and the agent to persist context before the window fills up.</p> <p>Output: VERBATIM relay box with prompt count.</p> <pre><code>┌─ Context Checkpoint (prompt #20) ────────────────\n│ This session is getting deep. Consider wrapping up\n│ soon. If there are unsaved learnings, decisions, or\n│ conventions, now is a good time to persist them.\n│ ⏱ Context window: ~45k tokens (~22% of 200k)\n└──────────────────────────────────────────────────\n</code></pre> <p>Usage: Every prompt records token usage to <code>.context/state/stats-{session}.jsonl</code>. Monitor live with <code>ctx usage --follow</code> or query with <code>ctx usage --json</code>. Usage is recorded even during wrap-up suppression (event: <code>suppressed</code>).</p> <p>Billing guard: When <code>billing_token_warn</code> is set in <code>.ctxrc</code>, a one-shot warning fires if session tokens exceed the threshold. This warning is independent of all other triggers - it fires even during wrap-up suppression.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-persistence-context-staleness-nudge","level":4,"title":"<code>check-persistence</code>: Context Staleness Nudge","text":"<p>What: Tracks when <code>.context/*.md</code> files were last modified. If too many prompts pass without a write, nudges the agent to persist.</p> <p>Why: Sessions produce insights that evaporate if not recorded. This catches the \"we talked about it but never wrote it down\" failure mode.</p> <p>Output: VERBATIM relay after 20+ prompts without a context file change.</p> <pre><code>┌─ Persistence Checkpoint (prompt #20) ───────────\n│ No context files updated in 20+ prompts.\n│ Have you discovered learnings, made decisions,\n│ established conventions, or completed tasks\n│ worth persisting?\n│\n│ Run /ctx-wrap-up to capture session context.\n└──────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-ceremonies-session-ritual-adoption","level":4,"title":"<code>check-ceremonies</code>: Session Ritual Adoption","text":"<p>What: Scans your last 3 journal entries for <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> usage. Nudges once per day if missing.</p> <p>Why: Session ceremonies are the highest-leverage habit in <code>ctx</code>. This hook bootstraps the habit until it becomes automatic.</p> <p>Output: Tailored nudge depending on which ceremony is missing.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-journal-unimported-session-reminder","level":4,"title":"<code>check-journal</code>: Unimported Session Reminder","text":"<p>What: Detects unimported Claude Code sessions and unenriched journal entries. Fires once per day.</p> <p>Why: Exported sessions become searchable history. Unenriched entries lack metadata for filtering. Both decay in value over time.</p> <p>Output: VERBATIM relay with counts and exact commands.</p> <pre><code>┌─ Journal Reminder ─────────────────────────────\n│ You have 3 new session(s) not yet exported.\n│ 5 existing entries need enrichment.\n│\n│ Export and enrich:\n│   ctx journal import --all\n│   /ctx-journal-enrich-all\n└────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-resources-system-resource-pressure","level":4,"title":"<code>check-resources</code>: System Resource Pressure","text":"<p>What: Monitors memory, swap, disk, and CPU load. Only fires at DANGER severity (memory >= 90%, swap >= 75%, disk >= 95%, load >= 1.5x CPU count).</p> <p>Why: Resource exhaustion mid-session can corrupt work. This provides early warning to persist and exit.</p> <p>Output: VERBATIM relay listing critical resources.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-knowledge-knowledge-file-growth","level":4,"title":"<code>check-knowledge</code>: Knowledge File Growth","text":"<p>What: Counts entries in <code>LEARNINGS.md</code>, <code>DECISIONS.md</code>, and lines in <code>CONVENTIONS.md</code>. Fires once per day when thresholds are exceeded.</p> <p>Why: Large knowledge files dilute agent context. 35 learnings compete for attention; 15 focused ones get applied. Thresholds are configurable in <code>.ctxrc</code>.</p> <p>Default thresholds:</p> <pre><code># .ctxrc\nentry_count_learnings: 30\nentry_count_decisions: 20\nconvention_line_count: 200\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-version-binaryplugin-version-drift","level":4,"title":"<code>check-version</code>: Binary/Plugin Version Drift","text":"<p>What: Compares the <code>ctx</code> binary version against the plugin version. Fires once per day. Also checks encryption key age for rotation nudge.</p> <p>Why: Version drift means hooks reference features the binary doesn't have. The key rotation nudge prevents indefinite key reuse.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-reminders-pending-reminder-relay","level":4,"title":"<code>check-reminders</code>: Pending Reminder Relay","text":"<p>What: Reads <code>.context/reminders.json</code> and surfaces any due reminders via VERBATIM relay. No throttle: fires every session until dismissed.</p> <p>Why: Reminders are sticky notes to future-you. Unlike nudges (which throttle to once per day), reminders repeat deliberately until the user dismisses them.</p> <p>Output: VERBATIM relay box listing due reminders.</p> <pre><code>┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-freshness-technology-constant-staleness","level":4,"title":"<code>check-freshness</code>: Technology Constant Staleness","text":"<p>What: Stats files listed in <code>.ctxrc</code> <code>freshness_files</code> and warns if any haven't been modified in over 6 months. Daily throttle. Silent when no files are configured (opt-in via <code>.ctxrc</code>).</p> <p>Why: Model capabilities evolve - token budgets, attention limits, and context window sizes that were accurate 6 months ago may no longer reflect best practices. This hook reminds you to review and touch the file to confirm values are still current.</p> <p>Config (<code>.ctxrc</code>):</p> <pre><code>freshness_files:\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # optional\n</code></pre> <p>Each entry has a <code>path</code> (relative to project root), <code>desc</code> (what constants live there), and optional <code>review_url</code> (where to check current values). When <code>review_url</code> is set, the nudge includes \"Review against: {url}\". When absent, just \"Touch the file to mark it as reviewed.\"</p> <p>Output: VERBATIM relay listing stale files, silent otherwise.</p> <pre><code>┌─ Technology Constants Stale ──────────────────────\n│   config/thresholds.yaml (210 days ago)\n│     - Model token limits and batch sizes\n│   Review against: https://docs.example.com/limits\n│ Touch each file to mark it as reviewed.\n└───────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-map-staleness-architecture-map-drift","level":4,"title":"<code>check-map-staleness</code>: Architecture Map Drift","text":"<p>What: Checks whether <code>map-tracking.json</code> is older than 30 days and there are commits touching <code>internal/</code> since the last map refresh. Daily throttle prevents repeated nudges.</p> <p>Why: Architecture documentation drifts silently as code evolves. This hook detects structural changes that the map hasn't caught up with and suggests running <code>/ctx-architecture</code> to refresh.</p> <p>Output: VERBATIM relay when stale and modules changed, silent otherwise.</p> <pre><code>┌─ Architecture Map Stale ────────────────────────────\n│ ARCHITECTURE.md hasn't been refreshed since 2026-01-15\n│ and there are commits touching 12 modules.\n│ /ctx-architecture keeps architecture docs drift-free.\n│\n│ Want me to run /ctx-architecture to refresh?\n└─────────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#heartbeat-session-heartbeat-webhook","level":4,"title":"<code>heartbeat</code>: Session Heartbeat Webhook","text":"<p>What: Fires on every prompt. Sends a webhook notification with prompt count, session ID, context modification status, and token usage telemetry. Never produces stdout.</p> <p>Why: Other hooks only send webhooks when they \"speak\" (nudge/relay). When silent, you have no visibility into session activity. The heartbeat provides a continuous session-alive signal with token consumption data for observability dashboards or liveness monitoring.</p> <p>Output: None (webhook + event log only).</p> <p>Payload:</p> <pre><code>{\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  }\n}\n</code></pre> <p>Token fields (<code>tokens</code>, <code>context_window</code>, <code>usage_pct</code>) are included when usage data is available from the session JSONL file.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tool-time-hooks-pretooluse-posttooluse","level":3,"title":"Tool-Time Hooks (PreToolUse / PostToolUse)","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#block-non-path-ctx-path-enforcement-hard-gate","level":4,"title":"<code>block-non-path-ctx</code>: PATH Enforcement (Hard Gate)","text":"<p>What: Blocks any Bash command that invokes <code>./ctx</code>, <code>./dist/ctx</code>, <code>go run ./cmd/ctx</code>, or an absolute path to <code>ctx</code>. Only PATH invocations are allowed.</p> <p>Why: Enforces <code>CONSTITUTION.md</code>'s invocation invariant. Running a dev-built binary in production context causes version confusion and silent behavior drift.</p> <p>Output: Block response (prevents the tool call):</p> <pre><code>{\"decision\": \"block\", \"reason\": \"Use 'ctx' from PATH, not './ctx'...\"}\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#qa-reminder-pre-commit-qa-gate","level":4,"title":"<code>qa-reminder</code>: Pre-Commit QA Gate","text":"<p>What: Fires on every <code>Edit</code> tool use. Reminds the agent to lint and test the entire project before committing.</p> <p>Why: Agents tend to \"I'll test later\" and then commit untested code. Repetition is intentional: the hook reinforces the habit on every edit, not just before commits.</p> <p>Output: Agent directive with hard QA gate instructions.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#post-commit-context-capture-after-commit","level":4,"title":"<code>post-commit</code>: Context Capture After Commit","text":"<p>What: Fires after any <code>git commit</code> (excludes <code>--amend</code>). Prompts the agent to offer context capture (decision? learning?) and suggest running lints/tests before pushing.</p> <p>Why: Commits are natural reflection points. The nudge converts mechanical git operations into context-capturing opportunities.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-the-local-event-log","level":2,"title":"Auditing Hooks via the Local Event Log","text":"<p>If you don't need an external audit trail, enable the local event log for a self-contained record of hook activity:</p> <pre><code># .ctxrc\nevent_log: true\n</code></pre> <p>Once enabled, every hook that fires writes an entry to <code>.context/state/events.jsonl</code>. Query it with <code>ctx hook event</code>:</p> <pre><code>ctx hook event                    # last 50 events\nctx hook event --hook qa-reminder # filter by hook\nctx hook event --session <id>     # filter by session\nctx hook event --json | jq '.'    # raw JSONL for processing\n</code></pre> <p>The event log is local, queryable, and doesn't require any external service. For a full diagnostic workflow combining event logs with structural health checks, see Troubleshooting.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-webhooks","level":2,"title":"Auditing Hooks via Webhooks","text":"<p>The most powerful audit setup pipes all hook output to a webhook, giving you a real-time external record of what your agent is being told.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-1-set-up-the-webhook","level":3,"title":"Step 1: Set Up the Webhook","text":"<pre><code>ctx hook notify setup\n# Enter your webhook URL (Slack, Discord, ntfy.sh, IFTTT, etc.)\n</code></pre> <p>See Webhook Notifications for service-specific setup.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-2-subscribe-to-relay-events","level":3,"title":"Step 2: Subscribe to <code>relay</code> Events","text":"<pre><code># .ctxrc\nnotify:\n  events:\n    - relay   # all hook output: VERBATIM relays, directives, blocks\n    - nudge   # just the user-facing VERBATIM relays\n</code></pre> <p>The <code>relay</code> event fires for every hook that produces output. This includes:</p> Hook Event sent <code>check-context-size</code> <code>relay</code> + <code>nudge</code> <code>check-persistence</code> <code>relay</code> + <code>nudge</code> <code>check-ceremonies</code> <code>relay</code> + <code>nudge</code> <code>check-journal</code> <code>relay</code> + <code>nudge</code> <code>check-resources</code> <code>relay</code> + <code>nudge</code> <code>check-knowledge</code> <code>relay</code> + <code>nudge</code> <code>check-version</code> <code>relay</code> + <code>nudge</code> <code>check-reminders</code> <code>relay</code> + <code>nudge</code> <code>check-freshness</code> <code>relay</code> + <code>nudge</code> <code>check-map-staleness</code> <code>relay</code> + <code>nudge</code> <code>heartbeat</code> <code>heartbeat</code> only <code>block-non-path-ctx</code> <code>relay</code> only <code>post-commit</code> <code>relay</code> only <code>qa-reminder</code> <code>relay</code> only","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-3-cross-reference","level":3,"title":"Step 3: Cross-Reference","text":"<p>With <code>relay</code> enabled, your webhook receives a JSON payload every time a hook fires:</p> <pre><code>{\n  \"event\": \"relay\",\n  \"message\": \"check-persistence: No context updated in 20+ prompts\",\n  \"session_id\": \"b854bd9c\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"my-project\"\n}\n</code></pre> <p>This creates an external audit trail independent of the agent. You can now cross-verify: did the agent actually relay the checkpoint the hook told it to relay?</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#verifying-hooks-actually-fire","level":2,"title":"Verifying Hooks Actually Fire","text":"<p>Hooks are invisible. An invisible thing that breaks is indistinguishable from an invisible thing that never existed. Three verification methods, from simplest to most robust:</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-1-ask-the-agent","level":3,"title":"Method 1: Ask the Agent","text":"<p>The simplest check. After a few prompts into a session:</p> <pre><code>\"Did you receive any hook output this session? Print the last\ncontext checkpoint or persistence nudge you saw.\"\n</code></pre> <p>The agent should be able to recall recent hook output from its context window. If it says \"I haven't received any hook output\", either:</p> <ul> <li>The hooks aren't firing (check installation);</li> <li>The session is too short (hooks throttle early);</li> <li>The hooks fired but the agent absorbed them silently.</li> </ul> <p>Limitation: You are trusting the agent to report accurately. Agents sometimes confabulate or miss context. Use this as a quick smoke test, not definitive proof.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-2-check-the-webhook-trail","level":3,"title":"Method 2: Check the Webhook Trail","text":"<p>If you have <code>relay</code> events enabled, check your webhook receiver. Every hook that fires sends a timestamped notification. No notification = no fire.</p> <p>This is the ground truth. The webhook is called directly by the <code>ctx</code> binary, not by the agent. The agent cannot fake, suppress, or modify webhook deliveries.</p> <p>Compare what the webhook received against what the agent claims to have relayed. Discrepancies mean the agent is absorbing nudges instead of surfacing them.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-3-read-the-local-logs","level":3,"title":"Method 3: Read the Local Logs","text":"<p>Hooks that support logging write to <code>.context/logs/</code>:</p> <pre><code># Check context-size hook activity\ncat .context/logs/check-context-size.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] prompt#1 silent\n# [2026-02-22 09:17:33] [session:b854bd9c] prompt#16 CHECKPOINT\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 CHECKPOINT\n</code></pre> <pre><code># Check persistence nudge activity\ncat .context/logs/check-persistence.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] init count=1 mtime=1770646611\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 NUDGE since_nudge=20\n</code></pre> <p>Logs are append-only and written by the <code>ctx</code> binary, not the agent.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#detecting-silent-hook-failures","level":2,"title":"Detecting Silent Hook Failures","text":"<p>The hardest failure mode: hooks that stop firing without error. The plugin config changes, a binary update drops a hook, or a PATH issue silently breaks execution. Nothing errors: The hook just never runs.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-staleness-signal","level":3,"title":"The Staleness Signal","text":"<p>If <code>.context/logs/check-context-size.log</code> has no entries newer than 5 days but you've been running sessions daily, something is wrong. The absence of evidence is evidence of absence: but only if you control for inactivity.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#false-positive-protection","level":3,"title":"False Positive Protection","text":"<p>A naive \"hooks haven't fired in N days\" alert fires incorrectly when you simply haven't used <code>ctx</code>. The correct check needs two inputs:</p> <ol> <li>Last hook fire time: from <code>.context/logs/</code> or webhook history</li> <li>Last session activity: from journal entries or <code>ctx journal source</code></li> </ol> <p>If sessions are happening but hooks aren't firing, that's a real problem. If neither sessions nor hooks are happening, that's a vacation.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-to-check","level":3,"title":"What to Check","text":"<p>When you suspect hooks aren't firing:</p> <pre><code># 1. Verify the plugin is installed\nls ~/.claude/plugins/\n\n# 2. Check hook registration\ncat ~/.claude/plugins/ctx/hooks.json | head -20\n\n# 3. Run a hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-context-size\n\n# 4. Check for PATH issues\nwhich ctx\nctx --version\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>nudge</code>, graduate to <code>relay</code>: The <code>nudge</code> event covers   user-facing VERBATIM relays. Add <code>relay</code> when you want full visibility   into agent directives and hard gates.</li> <li>Webhooks are your trust anchor:    The agent can ignore a nudge, but it can't suppress the webhook.    If the webhook fired and the agent didn't relay, you have proof of a    compliance gap.</li> <li>Hooks are throttled by design: Most check hooks fire once per day   or use adaptive frequency. Don't expect a notification every prompt:   Silence usually means the throttle is working, not that the hook is   broken.</li> <li>Daily markers live in <code>.context/state/</code>: Throttle files are stored   in <code>.context/state/</code> alongside other project-scoped state. If you need   to force a hook to re-fire during testing, delete the corresponding   marker file.</li> <li>The QA reminder is intentionally noisy: Unlike other hooks,   <code>qa-reminder</code> fires on every <code>Edit</code> call with no throttle. This is   deliberate: The commit quality degrades when the reminder fades from   salience.</li> <li>Log files are safe to commit: <code>.context/logs/</code> contains only   timestamps, session IDs, and status keywords. No secrets, no code.</li> </ul>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#next-up","level":2,"title":"Next Up","text":"<p>Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#see-also","level":2,"title":"See Also","text":"<ul> <li>Troubleshooting: full diagnostic workflow using   <code>ctx doctor</code>, event logs, and <code>/ctx-doctor</code></li> <li>Customizing Hook Messages: override   what hooks say without changing what they do</li> <li>Webhook Notifications: setting up and   configuring the webhook system</li> <li>Hook Output Patterns: understanding   VERBATIM relays, agent directives, and hard gates</li> <li>Detecting and Fixing Drift: structural checks   that complement runtime hook auditing</li> <li>CLI Reference: full <code>ctx system</code>   command reference</li> </ul>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/task-management/","level":1,"title":"Tracking Work Across Sessions","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-problem","level":2,"title":"The Problem","text":"<p>You have work that spans multiple sessions. Tasks get added during one session, partially finished in another, and completed days later.</p> <p>Without a system, follow-up items fall through the cracks, priorities drift, and you lose track of what was done versus what still needs doing. <code>TASKS.md</code> grows cluttered with completed checkboxes that obscure the remaining work.</p> <p>How do you manage work items that span multiple sessions without losing context?</p> <p>Prefer Skills over Raw Commands</p> <p>When working with an AI agent, use <code>/ctx-task-add</code> instead of raw <code>ctx task add</code>. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx task add</code> / <code>ctx task ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tldr","level":2,"title":"TL;DR","text":"<p>Manage Tasks:</p> <pre><code>ctx task add \"Fix race condition\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add\nctx task add \"Write tests\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add to phase\nctx task complete \"race condition\"                      # mark done\nctx task snapshot \"before-refactor\"               # backup\nctx task archive                                  # clean up\n</code></pre> <p>Pick Up the Next Task:</p> <pre><code>/ctx-next # pick what's next\n</code></pre> <p>Read on for the full workflow and conversational patterns.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx task add</code> Command Add a new task to <code>TASKS.md</code> <code>ctx task complete</code> Command Mark a task as done by number or text <code>ctx task snapshot</code> Command Create a point-in-time backup of <code>TASKS.md</code> <code>ctx task archive</code> Command Move completed tasks to archive file <code>/ctx-task-add</code> Skill AI-assisted task creation with validation <code>/ctx-archive</code> Skill AI-guided archival with safety checks <code>/ctx-next</code> Skill Pick what to work on based on priorities","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-1-add-tasks-with-priorities","level":3,"title":"Step 1: Add Tasks with Priorities","text":"<p>Every piece of follow-up work gets a task. Use <code>ctx task add</code> from the terminal or <code>/ctx-task-add</code> from your AI assistant. Tasks should start with a verb and be specific enough that someone unfamiliar with the session could act on them.</p> <pre><code># High-priority bug found during code review\nctx task add \"Fix race condition in session cooldown\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Medium-priority feature work\nctx task add \"Add --format json flag to ctx status for CI integration\" --priority medium \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Low-priority cleanup\nctx task add \"Remove deprecated --raw flag from ctx load\" --priority low \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre> <p>The <code>/ctx-task-add</code> skill validates your task before recording it. It checks that the description is actionable, not a duplicate, and specific enough for someone else to pick up.</p> <p>If you say \"fix the bug,\" it will ask you to clarify which bug and where.</p> <p>Tasks Are Often Created Proactively</p> <p>In practice, many tasks are created proactively by the agent rather than by explicit CLI commands.</p> <p>After completing a feature, the agent will often identify follow-up work: tests, docs, edge cases, error handling, and offer to add them as tasks.</p> <p>You do not need to dictate <code>ctx task add</code> commands; the agent picks up on work context and suggests tasks naturally.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-2-organize-with-phase-sections","level":3,"title":"Step 2: Organize with Phase Sections","text":"<p>Tasks live in phase sections inside <code>TASKS.md</code>.</p> <p>Phases provide logical groupings that preserve order and enable replay.</p> <p>A task does not move between sections. It stays in its phase permanently, and status is tracked via checkboxes and inline tags.</p> <pre><code>## Phase 1: Core CLI\n\n- [x] Implement ctx add command\n- [x] Implement ctx task complete command\n- [ ] Add --section flag to ctx task add `#priority:medium`\n\n## Phase 2: AI Integration\n\n- [ ] Implement ctx agent cooldown `#priority:high` `#in-progress`\n- [ ] Add ctx watch XML parsing `#priority:medium`\n  - Blocked by: Need to finalize agent output format\n\n## Backlog\n\n- [ ] Performance optimization for large TASKS.md files `#priority:low`\n- [ ] Add metrics dashboard to ctx status `#priority:deferred`\n</code></pre> <p>Use <code>--section</code> when adding a task to a specific phase:</p> <pre><code>ctx task add \"Add ctx watch XML parsing\" --priority medium --section \\\n    \"Phase 2: AI Integration\" \\\n    --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre> <p>Without <code>--section</code>, the task is inserted before the first unchecked task in <code>TASKS.md</code>.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"<p>At the start of a session, or after finishing a task, use <code>/ctx-next</code> to get prioritized recommendations. </p> <p>The skill reads <code>TASKS.md</code>, checks recent sessions, and ranks candidates using  explicit priority, blocking status, in-progress state, momentum from  recent work, and phase order.</p> <p>You can also ask naturally: \"what should we work on?\" or \"what's the highest priority right now?\"</p> <pre><code>/ctx-next\n</code></pre> <p>The output looks like this:</p> <pre><code>**1. Implement ctx agent cooldown** `#priority:high`\n\n    Still in-progress from yesterday's session. The tombstone file approach is\n    half-built. Finishing is cheaper than context-switching.\n\n**2. Add --section flag to ctx task add** `#priority:medium`\n\n    Last Phase 1 item. Quick win that unblocks organized task entry.\n\n---\n\n*Based on 8 pending tasks across 3 phases.\n\nLast session: agent-cooldown (2026-02-06).*\n</code></pre> <p>In-progress tasks almost always come first: </p> <p>Finishing existing work takes priority over starting new work.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-4-complete-tasks","level":3,"title":"Step 4: Complete Tasks","text":"<p>When a task is done, mark it complete by number or partial text match:</p> <pre><code># By task number (as shown in TASKS.md)\nctx task complete 3\n\n# By partial text match\nctx task complete \"agent cooldown\"\n</code></pre> <p>The task's checkbox changes from <code>[ ]</code> to <code>[x]</code>. Tasks are never deleted: they stay in their phase section so history is preserved.</p> <p>Be Conversational</p> <p>You rarely need to run <code>ctx task complete</code> yourself during an interactive session.</p> <p>When you say something like \"the rate limiter is done\" or \"we finished that,\" the agent marks the task complete and moves on to suggesting what is next.</p> <p>The CLI commands are most useful for manual housekeeping, scripted workflows, or when you want precision.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-5-snapshot-before-risky-changes","level":3,"title":"Step 5: Snapshot Before Risky Changes","text":"<p>Before a major refactor or any change that might break things, snapshot your current task state. This creates a copy of <code>TASKS.md</code> in <code>.context/archive/</code> without modifying the original.</p> <pre><code># Default snapshot\nctx task snapshot\n\n# Named snapshot (recommended before big changes)\nctx task snapshot \"before-refactor\"\n</code></pre> <p>This creates a file like <code>.context/archive/tasks-before-refactor-2026-02-08-1430.md</code>. If the refactor goes sideways, and you need to confirm what the task state looked like before you started, the snapshot is there.</p> <p>Snapshots are cheap: Take them before any change you might want to undo or review later.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-6-archive-when-tasksmd-gets-cluttered","level":3,"title":"Step 6: Archive When <code>TASKS.md</code> Gets Cluttered","text":"<p>After several sessions, <code>TASKS.md</code> accumulates completed tasks that make it hard to see what is still pending.</p> <p>Use <code>ctx task archive</code> to move all <code>[x]</code> items to a timestamped archive file.</p> <p>Start with a dry run to preview what will be moved:</p> <pre><code>ctx task archive --dry-run\n</code></pre> <p>Then archive:</p> <pre><code>ctx task archive\n</code></pre> <p>Completed tasks move to <code>.context/archive/tasks-2026-02-08.md</code>. Phase headers are preserved in the archive for traceability. Pending tasks (<code>[ ]</code>) remain in <code>TASKS.md</code>.</p> <p>The <code>/ctx-archive</code> skill adds two safety checks before archiving: it verifies that completed tasks are genuinely cluttering the view and that nothing was marked <code>[x]</code> prematurely.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#letting-the-agent-drive","level":2,"title":"Letting the Agent Drive","text":"<p>The steps above show the CLI commands for task management.</p> <p>In practice, most task management happens conversationally:</p> <p>An agent that has loaded the context files does not need you to type <code>ctx task add</code>. It tracks work naturally and offers the right operations.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#use-natural-language-instead-of-commands","level":3,"title":"Use Natural Language Instead of Commands","text":"<p>These conversational prompts replace explicit commands during interactive sessions:</p> Instead of typing... Just say... <code>ctx task add \"Write tests for X\" --session-id ID --branch BR --commit HASH</code> \"We should add tests for this: track that?\" <code>/ctx-next</code> \"What should we work on?\" <code>ctx task complete \"rate limiting\"</code> \"The rate limiter is done, what's next?\" <code>ctx task archive</code> \"<code>TASKS.md</code> is getting long, can you clean it up?\" <code>ctx task add ... --session-id ID --branch BR --commit HASH && ctx task add ...</code> \"Add follow-ups for what we just built.\" <p>The agent translates these into the right <code>ctx</code> operations behind the scenes.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-creates-tasks-proactively","level":3,"title":"The Agent Creates Tasks Proactively","text":"<p>After completing a piece of work, the agent does not wait for you to dictate follow-up items. It identifies them from the work context and offers:</p> <pre><code>**Agent**: \"I've finished implementing the rate limiter middleware. I noticed\nthree follow-up items:\n\n1. Write integration tests for the rate limiter (the unit tests pass but\n   there's no end-to-end coverage)\n2. Add rate limit headers to API responses (X-RateLimit-Remaining, etc.)\n3. Document the rate limit configuration in the ops runbook\n\nWant me to add these as tasks?\"\n</code></pre> <p>This is how an agent behaves once it has read the Agent Playbook.</p> <p>The playbook's \"Work, Reflect, Persist\" cycle means the agent asks itself after every milestone: Did I just complete a task? Did new work emerge?</p> <p>When the answer is yes, it acts.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#asking-the-agent-what-to-capture","level":3,"title":"Asking the Agent What to Capture","text":"<p>You don't always know what's worth persisting.</p> <p>Instead of reviewing the session yourself and typing specific commands, ask the agent to do the thinking:</p> You say What happens \"What tasks came out of this session?\" Agent reviews the work, identifies follow-ups, and offers to add them \"Are there any tasks, learnings, or decisions worth adding?\" Agent scans the full session for all three types and proposes a batch \"What should we remember from today?\" Agent surfaces everything persist-worthy: not just tasks \"Anything we should track before we move on?\" Agent checks for loose ends specific to the work just completed <p>This is especially powerful after long or exploratory sessions where multiple threads were touched. </p> <p>The agent has the full conversation in context and can spot follow-ups you  might miss:</p> <pre><code>You:   \"Are there any tasks, learnings, or decisions worth adding\n        from this session?\"\n\nAgent: \"Yes. Here's what I'd suggest:\n\n  Tasks:\n  - Add retry logic to the webhook delivery (we discussed it when\n    the timeout test failed but never tracked it)\n  - Update the API docs to reflect the new rate limit headers\n\n  Learnings:\n  - net/http's default client has no timeout: we burned 15 minutes\n    on that before adding an explicit 30s timeout\n\n  Decisions:\n  - We chose token-bucket over sliding-window for rate limiting\n    because it handles bursts better and the library is simpler\n\n  Want me to add all of these?\"\n\nYou:   \"All of them.\"\n</code></pre> <p>The agent fills in structured fields from what it observed during the session. You get the same quality entries as if you typed verbose commands, without needing to remember what happened or how to format it.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-completes-tasks-and-moves-on","level":3,"title":"The Agent Completes Tasks and Moves On","text":"<p>When you confirm that work is finished, or when the agent can see that it is, it marks the task done and suggests the next step:</p> <pre><code>You: \n\"The cooldown logic is working now.\"\n\nAgent: \n\"Marked 'Implement ctx agent cooldown' as done. Your highest-\npriority remaining task is 'Add --section flag to ctx task add'.\n\nIt's the last item in Phase 1 and a quick win. Want to start on that?\"\n</code></pre> <p>The agent chains completion into next-task selection.</p> <p>One sentence from you triggers two operations.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#end-of-session-capture","level":3,"title":"End-of-Session Capture","text":"<p>At the end of a session, the agent reviews what happened and catches loose ends:</p> <pre><code>You: \n\"I think we're good for today.\"\n\nAgent: \n\"Before we wrap up: during the middleware work you mentioned\nwanting to add metrics for rate limit hits. That never made it into TASKS.md.\n\nWant me to add it as a task so it's there next session?\"\n</code></pre> <p>This is the proactive persistence model at work.</p> <p>The agent monitors the conversation for mentioned-but-not-tracked work and surfaces it before context is lost.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#a-conversational-session-example","level":3,"title":"A Conversational Session Example","text":"<p>Here is what task management looks like in a real session when the agent is driving:</p> <pre><code>You:   What's left to do?\nAgent: You have 5 pending tasks across 2 phases. The highest priority is\n       \"Implement ctx agent cooldown\": it's marked in-progress from\n       yesterday and half-built. Want to pick that up?\n\nYou:   Yeah, let's finish it.\n       [... 20 minutes of implementation work ...]\n\nYou:   That's working. Tests pass.\nAgent: Nice. I'll mark \"Implement ctx agent cooldown\" as done.\n       I noticed two things from the implementation:\n       1. The tombstone file cleanup should happen on a timer, not just\n          at startup. Want me to add that as a task?\n       2. We learned that file locks don't work reliably on NFS. I'll\n          add that to LEARNINGS.md.\n\nYou:   Yes to both. What's next?\nAgent: Added the cleanup task as high priority. Next up is \"Add --section\n       flag to ctx task add\": last item in Phase 1. It should be\n       straightforward since the flag parsing pattern already exists\n       in the codebase. Ready?\n</code></pre> <p>It's All Conversational</p> <p>Notice what did not happen: The user never typed a <code>ctx</code> command.</p> <p>The agent handled task completion, follow-up creation, learning capture, and next-task selection: all from natural conversation.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<pre><code># Add a task\nctx task add \"Implement rate limiting for API endpoints\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to a specific phase\nctx task add \"Write integration tests for rate limiter\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# See what to work on\n# (from AI assistant) /ctx-next\n\n# Mark done by text\nctx task complete \"rate limiting\"\n\n# Mark done by number\nctx task complete 5\n\n# Snapshot before a risky refactor\nctx task snapshot \"before-middleware-rewrite\"\n\n# Archive completed tasks when the list gets long\nctx task archive --dry-run     # preview first\nctx task archive               # then archive\n</code></pre>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tips","level":2,"title":"Tips","text":"<ul> <li>Start tasks with a verb: \"Add,\" \"Fix,\" \"Implement,\" \"Investigate\":    not just a topic like \"Authentication.\"</li> <li>Include the why in the task description. Future sessions lack the context of   why you added the task. \"Add rate limiting\" is worse than \"Add rate limiting   to prevent abuse on the public API after the load test showed 10x traffic spikes.\"</li> <li>Use <code>#in-progress</code> sparingly. Only one or two tasks should carry this tag at   a time. If everything is in-progress, nothing is.</li> <li>Snapshot before, not after. The point of a snapshot is to capture the    state before a change, not to celebrate what you just finished.</li> <li>Archive regularly. Once completed tasks outnumber pending ones, it is time   to archive. A clean <code>TASKS.md</code> helps both you and your AI assistant focus.</li> <li>Never delete tasks. Mark them <code>[x]</code> (completed) or <code>[-]</code> (skipped with a   reason). Deletion breaks the audit trail.</li> <li>Trust the agent's task instincts. When the agent suggests follow-up items   after completing work, it is drawing on the full context of what just happened.</li> <li>Conversational prompts beat commands in interactive sessions. Saying   \"what should we work on?\" is faster and more natural than running <code>/ctx-next</code>.   Save explicit commands for scripts, CI, and unattended runs.</li> <li>Let the agent chain operations. A single statement like \"that's done, what's   next?\" can trigger completion, follow-up identification, and next-task   selection in one flow.</li> <li>Review proactive task suggestions before moving on. The best follow-ups come   from items spotted in-context right after the work completes.</li> </ul>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#next-up","level":2,"title":"Next Up","text":"<p>Using the Scratchpad →: Store short-lived sensitive notes in an encrypted scratchpad.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session lifecycle including   task management in context</li> <li>Persisting Decisions, Learnings, and Conventions:   capturing the \"why\" behind your work</li> <li>Detecting and Fixing Drift:   keeping <code>TASKS.md</code> accurate over time</li> <li>CLI Reference:   full documentation for <code>ctx add</code>, <code>ctx task complete</code>, <code>ctx task</code></li> <li>Context Files: <code>TASKS.md</code>:    format and conventions for <code>TASKS.md</code></li> </ul>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/triggers/","level":1,"title":"Authoring Lifecycle Triggers","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#authoring-lifecycle-triggers","level":1,"title":"Authoring Lifecycle Triggers","text":"<p>Triggers are executable shell scripts that fire at specific events during an AI session. They're how you express \"when the AI saves a file, also do X\" or \"before the AI edits this path, check Y first.\" This recipe walks through writing your first trigger, testing it, and enabling it safely.</p> <p>Triggers Execute Arbitrary Code</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks:</p> <ul> <li>Only enable scripts you have read and understand.</li> <li>Never enable a trigger you downloaded from the internet   without reviewing every line.</li> <li>Avoid shelling out to user-controlled values (<code>jq -r</code>   output, <code>path</code> field, <code>tool</code> field) without quoting.</li> <li>A malicious or buggy trigger can block tool calls,   corrupt context files, or exfiltrate data.</li> </ul> <p>The generated trigger template starts disabled (no executable bit) so you cannot accidentally run an unreviewed script. Enable it explicitly with <code>ctx trigger enable</code>.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#scenario","level":2,"title":"Scenario","text":"<p>You want a <code>pre-tool-use</code> trigger that blocks the AI from editing anything in <code>internal/crypto/</code> without explicit confirmation. Cryptographic code is sensitive, and accidental edits have caused outages before, and you want a hard gate.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-1-scaffold-the-script","level":2,"title":"Step 1: Scaffold the Script","text":"<pre><code>ctx trigger add pre-tool-use protect-crypto\n</code></pre> <p>That creates <code>.context/hooks/pre-tool-use/protect-crypto.sh</code> with a template:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\n# Read the JSON event from stdin.\npayload=$(cat)\n\n# Parse fields with jq.\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Your logic here.\n\n# Return a JSON result. action can be \"allow\", \"block\", or absent.\necho '{\"action\": \"allow\"}'\n</code></pre> <p>Note: the directory is <code>.context/hooks/pre-tool-use/</code>; the on-disk layout still uses <code>hooks/</code> even though the command is <code>ctx trigger</code>. If you <code>ls .context/hooks/</code>, that's where your triggers live.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-2-write-the-logic","level":2,"title":"Step 2: Write the Logic","text":"<p>Open the file and replace the template body:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\npayload=$(cat)\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Only gate write-family tools.\ncase \"$tool\" in\n  write_file|edit_file|apply_patch) ;;\n  *)\n    echo '{\"action\": \"allow\"}'\n    exit 0\n    ;;\nesac\n\n# Block any path under internal/crypto/.\ncase \"$path\" in\n  internal/crypto/*|*/internal/crypto/*)\n    jq -n --arg p \"$path\" '{\n      action: \"block\",\n      message: (\"Edits to \" + $p + \" require manual review. \" +\n                \"See CONVENTIONS.md for the crypto-change process.\")\n    }'\n    exit 0\n    ;;\nesac\n\necho '{\"action\": \"allow\"}'\n</code></pre> <p>A few things to note:</p> <ul> <li><code>set -euo pipefail</code>: any unhandled error aborts the   script. Critical for a security-relevant trigger.</li> <li>Quote everything from <code>jq</code>: the <code>path</code> field comes from   the AI tool; treat it as untrusted input.</li> <li>Explicit <code>allow</code> case: the default is allow. An   empty or missing response is a risky default.</li> <li>Use <code>jq -n --arg</code> for output construction, as it is safer than   string concatenation when the message may contain special   characters.</li> </ul>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-3-test-with-a-mock-payload","level":2,"title":"Step 3: Test with a Mock Payload","text":"<p>Before enabling the trigger, test it with a realistic mock input using <code>ctx trigger test</code>. This runs the script against a synthetic JSON payload without actually firing any AI tool.</p> <pre><code># Test the \"should block\" case\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\n</code></pre> <p>Expected: the trigger returns <code>{\"action\":\"block\", \"message\": \"...\"}</code>.</p> <pre><code># Test the \"should allow\" case\nctx trigger test pre-tool-use --tool write_file --path internal/memory/mirror.go\n</code></pre> <p>Expected: the trigger returns <code>{\"action\":\"allow\"}</code>.</p> <pre><code># Test that non-write tools pass through\nctx trigger test pre-tool-use --tool read_file --path internal/crypto/aes.go\n</code></pre> <p>Expected: <code>{\"action\":\"allow\"}</code> because the <code>case</code> statement only gates write-family tools.</p> <p>If any of these cases misbehave, fix the trigger before enabling it. The trigger is disabled at this point, so misbehavior doesn't affect real AI sessions.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-4-enable-it","level":2,"title":"Step 4: Enable It","text":"<p>Once the test cases pass, enable the trigger:</p> <pre><code>ctx trigger enable protect-crypto\n</code></pre> <p>That sets the executable bit. Next time the AI starts a <code>pre-tool-use</code> event, the trigger will fire.</p> <p>Verify it's enabled:</p> <pre><code>ctx trigger list\n</code></pre> <p>Should show <code>protect-crypto</code> under <code>pre-tool-use</code> with an enabled indicator.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-5-iterate-safely","level":2,"title":"Step 5: Iterate Safely","text":"<p>If you discover a bug after enabling, disable first, fix second:</p> <pre><code>ctx trigger disable protect-crypto\n# ...edit the script...\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\nctx trigger enable protect-crypto\n</code></pre> <p>Disabling simply clears the executable bit; the script stays on disk, and <code>ctx trigger enable</code> re-enables it without rewriting anything.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#patterns-worth-copying","level":2,"title":"Patterns Worth Copying","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#logging-not-blocking","level":3,"title":"Logging, Not Blocking","text":"<p>For auditing or analytics, return <code>{\"action\":\"allow\"}</code> always and append to a log as a side effect:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\npayload=$(cat)\necho \"$payload\" >> .context/logs/tool-use.jsonl\necho '{\"action\":\"allow\"}'\n</code></pre>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#context-injection-at-session-start","level":3,"title":"Context Injection at Session Start","text":"<p>A <code>session-start</code> trigger can prepend text to the agent's initial prompt by emitting <code>{\"action\":\"inject\", \"content\": \"...\"}</code> . This is useful for injecting daily standup notes, open PRs, or rotating TODOs without storing them in a steering file.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#chaining-triggers-of-the-same-type","level":3,"title":"Chaining Triggers of the Same Type","text":"<p>Multiple scripts in the same type directory all run. If any returns <code>action: block</code>, the block wins. Keep individual triggers single-purpose and rely on composition.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>Forgetting the shebang. Without <code>#!/usr/bin/env bash</code>, the trigger won't execute even with the executable bit set.</p> <p>Not quoting <code>$path</code>. If you use <code>$path</code> in a command substitution or a <code>case</code> glob without quoting, a file name with spaces or metacharacters will break the trigger in surprising ways.</p> <p>Enabling before testing. <code>ctx trigger enable</code> makes the script live immediately. Always <code>ctx trigger test</code> first.</p> <p>Outputting non-JSON. The trigger's stdout must be valid JSON or <code>ctx</code>'s trigger runner will log a parse error. Use <code>jq -n</code> to construct output rather than hand-writing JSON strings.</p> <p>Mixing <code>hook</code> and <code>trigger</code> vocabulary. The command is <code>ctx trigger</code> but the on-disk directory is <code>.context/hooks/</code>. The feature was renamed; the directory name lags behind. Don't let this confuse you; they refer to the same thing.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx trigger</code> reference: full   command, flag, and event-type reference.</li> <li><code>ctx steering</code>: persistent rules,   not scripts. Use steering when the thing you want is \"tell   the AI to always do X\" rather than \"run a script when Y   happens.\"</li> <li>Writing steering files: the rule-based   equivalent of this recipe.</li> </ul>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/troubleshooting/","level":1,"title":"Troubleshooting","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-problem","level":2,"title":"The Problem","text":"<p>Something isn't working: a hook isn't firing, nudges are too noisy, context seems stale, or the agent isn't following instructions. The information to diagnose it exists (across status, drift, event logs, hook config, and session history), but assembling it manually is tedious.</p> <p>How do you figure out what's wrong and fix it?</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx doctor                   # structural health check\nctx hook event --last 20  # recent hook activity\n# or ask: \"something seems off, can you diagnose?\"\n</code></pre>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx doctor</code> CLI command Structural health report <code>ctx doctor --json</code> CLI command Machine-readable health report <code>ctx hook event</code> CLI command Query local event log <code>/ctx-doctor</code> Skill Agent-driven diagnosis with analysis","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#quick-check-ctx-doctor","level":3,"title":"Quick Check: <code>ctx doctor</code>","text":"<p>Run <code>ctx doctor</code> for an instant structural health report. It checks context initialization, required files, drift, hook configuration, event logging, webhooks, reminders, task completion ratio, and context token size: all in one pass:</p> <pre><code>ctx doctor\n</code></pre> <pre><code>ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n</code></pre> <p>Warnings are non-critical but worth fixing. Errors need attention. Informational notes (○) flag optional features that aren't enabled.</p> <p>For scripting:</p> <pre><code>ctx doctor --json | jq '.warnings'\n</code></pre>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#deep-dive-ctx-doctor","level":3,"title":"Deep Dive: <code>/ctx-doctor</code>","text":"<p>When you need the agent to reason about what's wrong, use the skill. Ask naturally or invoke directly:</p> <pre><code>Why didn't my hook fire?\nSomething seems off, can you diagnose?\n/ctx-doctor\n</code></pre> <p>The agent follows a triage sequence:</p> <ol> <li>Baseline: runs <code>ctx doctor --json</code> for structural health</li> <li>Events: runs <code>ctx hook event --json --last 100</code> (if event logging enabled)</li> <li>Correlate: connects findings across both sources</li> <li>Present: structured findings with evidence</li> <li>Suggest: actionable next steps (but doesn't auto-fix)</li> </ol> <p>The skill degrades gracefully: without event logging enabled, it still runs structural checks and notes what you'd gain by enabling it.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#raw-event-inspection","level":3,"title":"Raw Event Inspection","text":"<p>For power users: <code>ctx hook event</code> with filters gives direct access to the event log.</p> <pre><code># Last 50 events (default)\nctx hook event\n\n# Events from a specific session\nctx hook event --session eb1dc9cd-0163-4853-89d0-785fbfaae3a6\n\n# Only QA reminder events\nctx hook event --hook qa-reminder\n\n# Raw JSONL for jq processing\nctx hook event --json | jq '.message'\n\n# Include rotated (older) events\nctx hook event --all --last 100\n</code></pre> <p>Filters use AND logic: <code>--hook qa-reminder --session abc123</code> returns only QA reminder events from that specific session.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#common-problems","level":2,"title":"Common Problems","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#no-context-directory-specified-for-this-project","level":3,"title":"\"No context directory specified for this project\"","text":"<p>Symptoms: Any <code>ctx</code> command fails with <code>Error: no context directory specified for this project</code> (possibly with a likely-candidate hint or a candidate list depending on what's visible from your CWD).</p> <p>Cause: <code>ctx</code> does not search the filesystem for a <code>.context/</code> directory. You have to declare which one to use before running day-to-day commands.</p> <p>Fix: bind <code>CTX_DIR</code> for the current shell:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>See Activating a Context Directory for the full recipe (one-shot <code>CTX_DIR=...</code> inline form, CI patterns, direnv setup).</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#ctx-not-initialized","level":3,"title":"\"<code>ctx</code>: Not Initialized\"","text":"<p>Symptoms: After declaring <code>CTX_DIR</code>, the command fails with <code>ctx: not initialized - run \"ctx init\" first</code>.</p> <p>Cause: The declared directory exists but hasn't been initialized with template files.</p> <p>Fix:</p> <pre><code>ctx init          # create .context/ with template files\nctx init --minimal  # or just the essentials (CONSTITUTION, TASKS, DECISIONS)\n</code></pre> <p>Commands that work without CTX_DIR or initialization: <code>ctx init</code>, <code>ctx activate</code>, <code>ctx deactivate</code>, <code>ctx setup</code>, <code>ctx doctor</code>, <code>ctx guide</code>, <code>ctx why</code>, <code>ctx config switch/status</code>, <code>ctx hub *</code>, and help-only grouping commands.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-cli-and-my-claude-code-session-disagree-on-the-project","level":3,"title":"\"My CLI and My Claude Code Session Disagree on the Project\"","text":"<p>Symptoms: A <code>!</code>-pragma or interactive <code>ctx</code> call writes to the wrong <code>.context/</code>; or you ran <code>ctx remind add</code> in shell A and the reminder shows up in project B's notifications.</p> <p>Cause: <code>CTX_DIR</code> is sourced from three different surfaces, and they can drift apart:</p> Surface Source of <code>CTX_DIR</code> Bound when Claude Code hooks <code>${CLAUDE_PROJECT_DIR}/.context</code> (injected) Every hook line; the project Claude is in <code>!</code>-pragma in chat / interactive shell Whatever the parent shell exported When you ran <code>eval \"$(ctx activate)\"</code> New shell tab opened mid-session Whatever your shellrc exports Login <p>When these drift, the per-prompt <code>check-anchor-drift</code> hook fires a verbatim warning naming both values. To fix: re-run <code>eval \"$(ctx activate)\"</code> from inside the project the Claude Code session is editing, or close the shell tab and reopen it from the right working directory.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-hook-isnt-firing","level":3,"title":"\"My Hook Isn't Firing\"","text":"<p>Symptoms: No nudges appearing, webhook silent, event log shows no entries for the expected hook.</p> <p>Diagnosis:</p> <pre><code># 1. Check if ctx is installed and on PATH\nwhich ctx && ctx --version\n\n# 2. Check if the hook is registered\ngrep \"check-persistence\" ~/.claude/plugins/ctx/hooks.json\n\n# 3. Run the hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-persistence\n\n# 4. Check event log for the hook (if enabled)\nctx hook event --hook check-persistence\n</code></pre> <p>Common causes:</p> <ul> <li>Plugin is not installed: run <code>ctx init --claude</code> to reinstall</li> <li>PATH issue: the hook invokes <code>ctx</code> from PATH; ensure it resolves</li> <li>Throttle active: most hooks fire once per day: check   <code>.context/state/</code> for daily marker files</li> <li>Hook silenced: a custom message override may be an empty file:   check <code>ctx hook message list</code> for overrides</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#too-many-nudges","level":3,"title":"\"Too Many Nudges\"","text":"<p>Symptoms: The agent is overwhelmed with hook output. Context checkpoints, persistence reminders, and QA gates fire constantly.</p> <p>Diagnosis:</p> <pre><code># Check how often hooks fired recently\nctx hook event --last 50\n\n# Count fires per hook\nctx hook event --json | jq -r '.detail.hook // \"unknown\"' \\\n  | sort | uniq -c | sort -rn\n</code></pre> <p>Common causes:</p> <ul> <li>QA reminder is noisy by design: it fires on every <code>Edit</code> call with no   throttle. This is intentional. If it's too much, silence it with an empty   override: <code>ctx hook message edit qa-reminder gate</code>, then empty the file</li> <li>Long session: context checkpoint fires with increasing frequency after   prompt 15. This is the system telling you the session is getting long:   consider wrapping up</li> <li>Short throttle window: if you deleted marker files in   <code>.context/state/</code>, daily-throttled hooks will re-fire</li> <li>Outdated Claude Code plugin: Update the plugin using Claude Code →    <code>/plugin</code> → \"Marketplace\"</li> <li><code>ctx</code> version mismatch: Build (or download) and install the    latest <code>ctx</code> vesion.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#context-seems-stale","level":3,"title":"\"Context Seems Stale\"","text":"<p>Symptoms: The agent references outdated information, paths that don't exist, or decisions that were reversed.</p> <p>Diagnosis:</p> <pre><code># Structural drift check\nctx drift\n\n# Full doctor check (includes drift + more)\nctx doctor\n\n# Check when context files were last modified\nctx status --verbose\n</code></pre> <p>Common causes:</p> <ul> <li>Drift accumulated: stale path references in <code>ARCHITECTURE.md</code> or   <code>CONVENTIONS.md</code>. Fix with <code>ctx drift --fix</code> or ask the agent to clean up.</li> <li>Task backlog: too many completed tasks diluting active context. Archive   with <code>ctx task archive</code> or <code>ctx compact --archive</code>.</li> <li>Large context files: <code>LEARNINGS.md</code> with 40+ entries competes for   attention. Consolidate with <code>/ctx-consolidate</code>.</li> <li>Missing session ceremonies: if <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> aren't   being used, context doesn't get refreshed. See   Session Ceremonies.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-agent-isnt-following-instructions","level":3,"title":"\"The Agent Isn't Following Instructions\"","text":"<p>Symptoms: The agent ignores conventions, forgets decisions, or acts contrary to <code>CONSTITUTION.md</code> rules.</p> <p>Diagnosis:</p> <pre><code># Check context token size: Is it too large for the model?\nctx doctor --json | jq '.results[] | select(.name == \"context_size\")'\n\n# Check if context is actually being loaded\nctx hook event --hook context-load-gate\n</code></pre> <p>Common causes:</p> <ul> <li>Context too large: if total tokens exceed the model's effective attention,   instructions get diluted. Check <code>ctx doctor</code> for the size check. Compact with   <code>ctx compact --archive</code>.</li> <li>Context not loading: if <code>context-load-gate</code> hasn't fired, the agent   may not have received context. Verify the hook is registered.</li> <li>Conflicting instructions: <code>CONVENTIONS.md</code> says one thing,   <code>AGENT_PLAYBOOK.md</code> says another. Review both files for consistency.</li> <li>Agent drift: the agent's behavior diverges from instructions over long   sessions. This is normal. Use <code>/ctx-reflect</code> to re-anchor, or start a new   session.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li>Event logging (optional but recommended): <code>event_log: true</code> in <code>.ctxrc</code></li> <li><code>ctx</code> initialized: <code>ctx init</code></li> </ul> <p>Event logging is not required for <code>ctx doctor</code> or <code>/ctx-doctor</code> to work. Both degrade gracefully: structural checks run regardless, and the skill notes when event data is unavailable.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>ctx doctor</code>: It's the fastest way to get a comprehensive   health picture. Save event log inspection for when you need to understand   when and how often something happened.</li> <li>Enable event logging early: The log is opt-in and low-cost (~250 bytes   per event, 1MB rotation cap). Enable it before you need it: Diagnosing   a problem without historical data is much harder.</li> <li>Use the skill for correlation: <code>ctx doctor</code> tells you what is wrong.   <code>/ctx-doctor</code> tells you why by correlating structural findings with event   patterns. The agent can spot connections that individual commands miss.</li> <li>Event log is gitignored: It's machine-local diagnostic data, not project   context. Different machines produce different event streams.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#next-up","level":2,"title":"Next Up","text":"<p>Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#see-also","level":2,"title":"See Also","text":"<ul> <li>Auditing System Hooks: the complete hook catalog   and webhook-based audit trails</li> <li>Detecting and Fixing Drift: structural and semantic   drift detection and repair</li> <li>Webhook Notifications: push notifications for   hook activity</li> <li><code>ctx doctor</code> CLI: full command reference</li> <li><code>ctx hook event</code> CLI: event log   query reference</li> <li><code>/ctx-doctor</code> skill: agent-driven   diagnosis</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/typical-kb-session/","level":1,"title":"Typical KB Session","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#the-problem","level":2,"title":"The Problem","text":"<p>You set the editorial pipeline up (Build a Knowledge Base). Now you sit down for a real research session: a transcript to ingest, a question to answer against existing evidence, a finding to capture for later. What's the actual flow?</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-remember                                    # session-start recall\n/ctx-kb-ingest ./inputs/transcript.md \"topic\"    # editorial pass\n/ctx-kb-ask \"does the kb say X?\"                 # grounded Q&A\n/ctx-kb-note \"follow-up: chase the v1.1 link\"    # park a finding\n/ctx-wrap-up                                     # ceremony → /ctx-handover\n</code></pre>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-remember</code> Skill Session-start recall (folds KB state when present) <code>/ctx-kb-ingest</code> Skill Mode-aware editorial pass <code>/ctx-kb-ask</code> Skill Q&A grounded in the kb <code>/ctx-kb-note</code> Skill Park a finding for the next ingest <code>/ctx-wrap-up</code> Skill End-of-session ceremony; delegates to the handover step <code>/ctx-handover</code> Skill Writes the per-session handover; called by <code>/ctx-wrap-up</code>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-1-session-start-recall","level":2,"title":"Step 1: Session Start (Recall)","text":"<pre><code>/ctx-remember\n</code></pre> <p><code>/ctx-remember</code> reads the latest handover under <code>.context/handovers/</code> (timestamped <code><TS>-<slug>.md</code> so concurrent agent runs never overwrite); its <code>## Summary</code> and <code>## Next session</code> are the authoritative recall surface. The five canonical files (<code>TASKS</code>, <code>DECISIONS</code>, etc.) are read as usual.</p> <p>When <code>.context/kb/</code> exists, <code>/ctx-remember</code> additionally folds editorial state into the readback: any closeouts whose <code>generated-at</code> postdates the handover are read for their <code>## What changed</code> sections (these are unfolded passes the last handover did not yet consume).</p> <p><code>SESSION_LOG.md</code> is not read at session start; it is mid-flight working memory, not a recall surface.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-2-ingest-the-sources-you-brought","level":2,"title":"Step 2: Ingest the Sources You Brought","text":"<pre><code>/ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n</code></pre> <p>The skill declares its mode up front (most often <code>topic-page</code>), resolves sources, scans the source-coverage ledger for adjacent incomplete topics, and synthesizes prose into the topic page section by section. Every cited claim mints an <code>EV-###</code> row in <code>evidence-index.md</code> with the source short-name + locator + optional <code>sha:</code> pin for in-repo files.</p> <p>The pass ends with a circuit-breaker check (file exists, cites ≥ 1 <code>EV-###</code>, site builds clean, cold-reader rubric at <code>pass</code>) and writes a closeout.</p> <p>If the skill reports <code>topic-page: deferred</code> instead of <code>produced</code>, look at the closeout's <code>Next pass hint</code>. It names the exact resumption invocation.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-3-ask-grounded-questions","level":2,"title":"Step 3: Ask Grounded Questions","text":"<pre><code>/ctx-kb-ask \"does the kb say hooks block until they exit?\"\n</code></pre> <p><code>/ctx-kb-ask</code> reads the kb's prose and answers with <code>EV-###</code> citations. If the kb cannot answer, it opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> and reports the gap rather than inventing.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-4-park-findings-for-later","level":2,"title":"Step 4: Park Findings for Later","text":"<pre><code>/ctx-kb-note \"check whether SIGTERM behavior changed in v1.2\"\n</code></pre> <p><code>/ctx-kb-note</code> appends one-liners to <code>.context/ingest/findings.md</code>, a lightweight surface for parking ideas that don't earn a full ingest pass right now. The next <code>/ctx-kb-ingest</code> can choose to absorb them.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-5-wrap-up","level":2,"title":"Step 5: Wrap Up","text":"<pre><code>/ctx-wrap-up \"Cursor Hooks: lifecycle deep dive\"\n</code></pre> <p><code>/ctx-wrap-up</code> runs the standard capture checklist (learnings, decisions, conventions, tasks) and delegates to <code>/ctx-handover</code> as its final step. In a KB session it additionally:</p> <ul> <li>Surfaces pending closeouts under   <code>.context/ingest/closeouts/</code>.</li> <li>Counts <code>open</code> rows in <code>outstanding-questions.md</code>.</li> </ul> <p>The handover artifact lands at <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). The handover folds postdated closeouts into a <code>## Folded closeouts</code> section and archives them under <code>.context/archive/closeouts/</code>. Editorial work that was incomplete at wrap-up (open <code>Q-###</code> rows, <code>topic-page: deferred</code> passes) is surfaced as recall on the next session start.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#common-shapes","level":2,"title":"Common Shapes","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#multiple-topics-in-one-session","level":3,"title":"Multiple Topics in One Session","text":"<p>Run <code>/ctx-kb-ingest</code> once per topic. Each pass writes its own closeout; the handover folds all of them at the end.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#mid-session-checkpoint","level":3,"title":"Mid-Session Checkpoint","text":"<pre><code>ctx handover write \"Mid-day checkpoint\" \\\n  --summary \"...\" --next \"...\" --no-fold\n</code></pre> <p><code>--no-fold</code> writes the handover without consuming closeouts, useful when you want a recall anchor mid-session without ending the editorial chunking.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#aborted-session","level":3,"title":"Aborted Session","text":"<p>If you close the laptop after an ingest pass but before <code>/ctx-wrap-up</code>, the closeouts stay in place. The next session's <code>/ctx-remember</code> reads them as unfolded postdated closeouts; the next wrap-up's handover step folds them normally. See Recover an Aborted Session for the failure-mode detail.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Recover an Aborted Session</li> <li>Skill: <code>/ctx-kb-ingest</code></li> <li>Skill: <code>/ctx-handover</code></li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/webhook-notifications/","level":1,"title":"Webhook Notifications","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-problem","level":2,"title":"The Problem","text":"<p>Your agent runs autonomously (loops, implements, releases) while you are away from the terminal. You have no way to know when it finishes, hits a limit, or when a hook fires a nudge.</p> <p>How do you get notified about agent activity without watching the terminal?</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx hook notify setup  # configure webhook URL (encrypted)\nctx hook notify test   # verify delivery\n# Hooks auto-notify on: session-end, loop-iteration, resource-danger\n</code></pre>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx hook notify setup</code> CLI command Configure and encrypt webhook URL <code>ctx hook notify test</code> CLI command Send a test notification <code>ctx hook notify --event <name> \"msg\"</code> CLI command Send a notification from scripts/skills <code>.ctxrc</code> <code>notify.events</code> Configuration Filter which events reach your webhook","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-1-get-a-webhook-url","level":3,"title":"Step 1: Get a Webhook URL","text":"<p>Any service that accepts HTTP POST with JSON works. Common options:</p> Service How to get a URL IFTTT Create an applet with the \"Webhooks\" trigger Slack Create an Incoming Webhook Discord Channel Settings > Integrations > Webhooks ntfy.sh Use <code>https://ntfy.sh/your-topic</code> (no signup) Pushover Use API endpoint with your user key <p>The URL contains auth tokens. <code>ctx</code> encrypts it; it never appears in plaintext in your repo.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-2-configure-the-webhook","level":3,"title":"Step 2: Configure the Webhook","text":"<pre><code>ctx hook notify setup\n# Enter webhook URL: https://maker.ifttt.com/trigger/ctx/json/with/key/YOUR_KEY\n# Webhook configured: https://maker.ifttt.com/***\n# Encrypted at: .context/.notify.enc\n</code></pre> <p>This encrypts the URL with AES-256-GCM using the same key as the scratchpad (<code>~/.ctx/.ctx.key</code>). The encrypted file (<code>.context/.notify.enc</code>) is safe to commit. The key lives outside the project and is never committed.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-3-test-it","level":3,"title":"Step 3: Test It","text":"<pre><code>ctx hook notify test\n# Webhook responded: HTTP 200 OK\n</code></pre> <p>If you see <code>No webhook configured</code>, run <code>ctx hook notify setup</code> first.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-4-configure-events","level":3,"title":"Step 4: Configure Events","text":"<p>Notifications are opt-in: no events are sent unless you configure an event list in <code>.ctxrc</code>:</p> <pre><code># .ctxrc\nnotify:\n  events:\n    - loop       # loop completion or max-iteration hit\n    - nudge      # VERBATIM relay hooks (context checkpoint, persistence, etc.)\n    - relay      # all hook output (verbose, for debugging)\n    - heartbeat  # every-prompt session-alive signal with metadata\n</code></pre> <p>Only listed events fire. Omitting an event silently drops it.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-5-use-in-your-own-skills","level":3,"title":"Step 5: Use in Your Own Skills","text":"<p>Add <code>ctx hook notify</code> calls to any skill or script:</p> <pre><code># In a release skill\nctx hook notify --event release \"v1.2.0 released successfully\" 2>/dev/null || true\n\n# In a backup script\nctx hook notify --event backup \"Nightly backup completed\" 2>/dev/null || true\n</code></pre> <p>The <code>2>/dev/null || true</code> suffix ensures the notification never breaks your script: If there's no webhook or the HTTP call fails, it's a silent noop.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-types","level":2,"title":"Event Types","text":"<p><code>ctx</code> fires these events automatically:</p> Event Source When <code>loop</code> Loop script Loop completes or hits max iterations <code>nudge</code> System hooks VERBATIM relay nudge is emitted (context checkpoint, persistence, ceremonies, journal, resources, knowledge, version) <code>relay</code> System hooks Any hook output (VERBATIM relays, agent directives, block responses) <code>heartbeat</code> System hook Every prompt: session-alive signal with prompt count and context modification status <code>test</code> <code>ctx hook notify test</code> Manual test notification (custom) Your skills You wire <code>ctx hook notify --event <name></code> in your own scripts <p><code>nudge</code> vs <code>relay</code>: The <code>nudge</code> event fires only for VERBATIM relay hooks (the ones the agent is instructed to show verbatim). The <code>relay</code> event fires for all hook output: VERBATIM relays, agent directives, and hard gates. Subscribe to <code>relay</code> for debugging (\"did the agent get the post-commit nudge?\"), <code>nudge</code> for user-facing assurance (\"was the checkpoint emitted?\").</p> <p>Webhooks as a Hook Audit Trail</p> <p>Subscribe to <code>relay</code> events and you get an external record of every hook that fires, independent of the agent. </p> <p>This lets you verify hooks are running and catch cases where the agent  absorbs a nudge instead of surfacing it. </p> <p>See Auditing System Hooks for the full workflow.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#payload-format","level":2,"title":"Payload Format","text":"<p>Every notification sends a JSON POST:</p> <pre><code>{\n  \"event\": \"nudge\",\n  \"message\": \"check-context-size: Context window at 82%\",\n  \"detail\": {\n    \"hook\": \"check-context-size\",\n    \"variant\": \"window\",\n    \"variables\": {\"Percentage\": 82, \"TokenCount\": \"164k\"}\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> <p>The <code>detail</code> field is a structured template reference containing the hook name, variant, and any template variables. This lets receivers filter by hook or variant without parsing rendered text. The field is omitted when no template reference applies (e.g. custom <code>ctx hook notify</code> calls).</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#heartbeat-payload","level":3,"title":"Heartbeat Payload","text":"<p>The <code>heartbeat</code> event fires on every prompt with session metadata and token usage telemetry:</p> <pre><code>{\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc123-...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-28T10:15:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> <p>The <code>tokens</code>, <code>context_window</code>, and <code>usage_pct</code> fields are included when token data is available from the session JSONL file. They are omitted when no usage data has been recorded yet (e.g. first prompt).</p> <p>Unlike other events, <code>heartbeat</code> fires every prompt (not throttled). Use it for observability dashboards or liveness monitoring of long-running sessions.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#security-model","level":2,"title":"Security Model","text":"Component Location Committed? Permissions Encryption key <code>~/.ctx/.ctx.key</code> No (user-level) <code>0600</code> Encrypted URL <code>.context/.notify.enc</code> Yes (safe) <code>0600</code> Webhook URL Never on disk in plaintext N/A N/A <p>The key is shared with the scratchpad. If you rotate the encryption key, re-run <code>ctx hook notify setup</code> to re-encrypt the webhook URL with the new key.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#key-rotation","level":2,"title":"Key Rotation","text":"<p><code>ctx</code> checks the age of the encryption key once per day. If it's older than 90 days (configurable via <code>key_rotation_days</code>), a VERBATIM nudge is emitted suggesting rotation.</p> <pre><code># .ctxrc\nkey_rotation_days: 30   # nudge sooner (default: 90)\n</code></pre>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#worktrees","level":2,"title":"Worktrees","text":"<p>The webhook URL is encrypted with the same encryption key (<code>~/.ctx/.ctx.key</code>). Because the key lives at the user level, it is shared across all worktrees on the same machine - notifications work in worktrees automatically.</p> <p>This means agents running in worktrees cannot send webhook alerts. For autonomous runs where worktree agents are opaque, monitor them from the terminal rather than relying on webhooks. Enrich journals and review results on the main branch after merging.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-log-the-local-complement","level":2,"title":"Event Log: The Local Complement","text":"<p>Don't need a webhook but want diagnostic visibility? Enable <code>event_log: true</code> in <code>.ctxrc</code>. The event log writes the same payload as webhooks to a local JSONL file (<code>.context/state/events.jsonl</code>) that you can query without any external service:</p> <pre><code>ctx hook event --last 20          # recent hook activity\nctx hook event --hook qa-reminder # filter by hook\n</code></pre> <p>Webhooks and event logging are independent: you can use either, both, or neither. Webhooks give you push notifications and an external audit trail. The event log gives you local queryability and <code>ctx doctor</code> integration.</p> <p>See Troubleshooting for how they work together.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tips","level":2,"title":"Tips","text":"<ul> <li>Fire-and-forget: Notifications never block. HTTP errors are silently   ignored. No retry, no response parsing.</li> <li>No webhook = no cost: When no webhook is configured, <code>ctx hook notify</code> exits   immediately. System hooks that call <code>notify.Send()</code> add zero overhead.</li> <li>Multiple projects: Each project has its own <code>.notify.enc</code>. You can point   different projects at different webhooks.</li> <li>Event filter is per-project: Configure <code>notify.events</code> in each project's   <code>.ctxrc</code> independently.</li> </ul>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#next-up","level":2,"title":"Next Up","text":"<p>Auditing System Hooks →: Verify your hooks are running, audit what they do, and get alerted when they go silent.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#see-also","level":2,"title":"See Also","text":"<ul> <li>CLI Reference: <code>ctx</code> hook notify:   full command reference</li> <li>Configuration: <code>.ctxrc</code> settings including   <code>notify</code> options</li> <li>Running an Unattended AI Agent: how loops work   and how notifications fit in</li> <li>Hook Output Patterns: understanding VERBATIM   relays, agent directives, and hard gates</li> <li>Auditing System Hooks: using webhooks as an   external audit trail for hook execution</li> </ul>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/","level":1,"title":"When to Use a Team of Agents","text":"","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-problem","level":2,"title":"The Problem","text":"<p>You have a task, and you are wondering: \"should I throw more agents at it?\"</p> <p>More agents can mean faster results, but they also mean coordination overhead, merge conflicts, divergent mental models, and wasted tokens re-reading context. </p> <p>The wrong setup costs more than it saves.</p> <p>This recipe is a decision framework: It helps you choose between a single agent, parallel worktrees, and a full agent team, and explains what <code>ctx</code> provides at each level.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tldr","level":2,"title":"TL;DR","text":"<ul> <li>Single agent for most work;</li> <li>Parallel worktrees when tasks touch disjoint file sets;</li> <li>Agent teams only when tasks need real-time   coordination. When in doubt, start with one agent.</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-spectrum","level":2,"title":"The Spectrum","text":"<p>There are three modes, ordered by complexity:</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#1-single-agent-default","level":3,"title":"1. Single Agent (Default)","text":"<p>One agent, one session, one branch. This is correct for most work.</p> <p>Use this when:</p> <ul> <li>The task has linear dependencies (step 2 needs step 1's output);</li> <li>Changes touch overlapping files;</li> <li>You need tight feedback loops (review each change before the next);</li> <li>The task requires deep understanding of a single area;</li> <li>Total effort is less than a few hours of agent time.</li> </ul> <p><code>ctx</code> provides: Full <code>.context/</code>: tasks, decisions, learnings, conventions, all in one session. </p> <p>The agent builds a coherent mental model and persists it as it goes.</p> <p>Example tasks: Bug fixes, feature implementation, refactoring a module, writing documentation for one area, debugging.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#2-parallel-worktrees-independent-tracks","level":3,"title":"2. Parallel Worktrees (Independent Tracks)","text":"<p>2-4 agents, each in a separate git worktree on its own branch, working on non-overlapping parts of the codebase.</p> <p>Use this when:</p> <ul> <li>You have 5+ independent tasks in the backlog;</li> <li>Tasks group cleanly by directory or package;</li> <li>File overlap between groups is zero or near-zero;</li> <li>Each track can be completed and merged independently;</li> <li>You want parallelism without coordination complexity.</li> </ul> <p><code>ctx</code> provides: Shared <code>.context/</code> via <code>git</code> (each worktree sees the same tasks, decisions, conventions). <code>/ctx-worktree</code> skill for setup and teardown. <code>TASKS.md</code> as a lightweight work queue.</p> <p>Example tasks: Docs + new package + test coverage (three tracks that don't touch the same files). Parallel recipe writing. Independent module development.</p> <p>See: Parallel Agent Development with Git Worktrees</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#3-agent-team-coordinated-swarm","level":3,"title":"3. Agent Team (Coordinated Swarm)","text":"<p>Multiple agents communicating via messages, sharing a task list, with a lead agent coordinating. Claude Code's team/swarm feature.</p> <p>Use this when:</p> <ul> <li>Tasks have dependencies but can still partially overlap;</li> <li>You need research and implementation happening simultaneously;</li> <li>The work requires different roles (researcher, implementer, tester);</li> <li>A lead agent needs to review and integrate others' work;</li> <li>The task is large enough that coordination cost is justified.</li> </ul> <p><code>ctx</code> provides: <code>.context/</code> as shared state that all agents can read. Task tracking for work assignment. Decisions and learnings as team memory that survives individual agent turnover.</p> <p>Example tasks: Large refactor across modules where a lead reviews merges. Research and implementation where one agent explores options while another builds. Multi-file feature that needs integration testing after parallel implementation.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-decision-framework","level":2,"title":"The Decision Framework","text":"<p>Ask these questions in order:</p> <pre><code>Can one agent do this in a reasonable time?\n  YES → Single agent. Stop here.\n  NO  ↓\n\nCan the work be split into non-overlapping file sets?\n  YES → Parallel worktrees (2-4 tracks)\n  NO  ↓\n\nDo the subtasks need to communicate during execution?\n  YES → Agent team with lead coordination\n  NO  → Parallel worktrees with a merge step\n</code></pre>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-file-overlap-test","level":3,"title":"The File Overlap Test","text":"<p>This is the critical decision point. Before choosing multi-agent, list the files each subtask would touch. If two subtasks modify the same file, they belong in the same track (or the same single-agent session).</p> <pre><code>You: \"I want to parallelize these tasks. Which files would each one touch?\"\n\nAgent: [reads `TASKS.md`, analyzes codebase]\n       \"Task A touches internal/config/ and internal/cli/initialize/\n        Task B touches docs/ and site/\n        Task C touches internal/config/ and internal/cli/status/\n\n        Tasks A and C overlap on internal/config/ # they should be\n        in the same track. Task B is independent.\"\n</code></pre> <p>When in doubt, keep things in one track. A merge conflict in a critical file costs more time than the parallelism saves.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#when-teams-make-things-worse","level":2,"title":"When Teams Make Things Worse","text":"<p>\"More agents\" is not always better. Watch for these patterns:</p> <p>Merge hell: If you are spending more time resolving conflicts than the parallel work saved, you split wrong: Re-group by file overlap.</p> <p>Context divergence: Each agent builds its own mental model. After 30 minutes of independent work, agent A might make assumptions that contradict agent B's approach. Shorter tracks with frequent merges reduce this.</p> <p>Coordination theater: A lead agent spending most of its time assigning tasks, checking status, and sending messages instead of doing work. If the task list is clear enough, worktrees with no communication are cheaper.</p> <p>Re-reading overhead: Every agent reads <code>.context/</code> on startup. A team of 4 agents each reading 4000 tokens of context = 16000 tokens before anyone does any work. For small tasks, that overhead dominates.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#what-ctx-gives-you-at-each-level","level":2,"title":"What <code>ctx</code> Gives You at Each Level","text":"<code>ctx</code> Feature Single Agent Worktrees Team <code>.context/</code> files Full access Shared via git Shared via filesystem <code>TASKS.md</code> Work queue Split by track Assigned by lead Decisions/Learnings Persisted in session Persisted per branch Persisted by any agent <code>/ctx-next</code> Picks next task Picks within track Lead assigns <code>/ctx-worktree</code> N/A Setup + teardown Optional <code>/ctx-commit</code> Normal commits Per-branch commits Per-agent commits","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#team-composition-recipes","level":2,"title":"Team Composition Recipes","text":"<p>Four practical team compositions for common workflows.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#feature-development-3-agents","level":3,"title":"Feature Development (3 Agents)","text":"Role Responsibility Architect Writes spec in <code>specs/</code>, breaks work into TASKS.md phases Implementer Picks tasks from TASKS.md, writes code, marks <code>[x]</code> done Reviewer Runs tests, <code>ctx drift</code>, lint; files issues as new tasks <p>Coordination: TASKS.md checkboxes. Architect writes tasks before implementer starts. Reviewer runs after each implementer commit.</p> <p>Anti-pattern: All three agents editing the same file simultaneously. Sequence the work so only one agent touches a file at a time.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#consolidation-sprint-3-4-agents","level":3,"title":"Consolidation Sprint (3-4 Agents)","text":"Role Responsibility Auditor Runs <code>ctx drift</code>, identifies stale paths and broken refs Code Fixer Updates source code to match context (or vice versa) Doc Writer Updates ARCHITECTURE.md, CONVENTIONS.md, and docs/ Test Fixer (Optional) Fixes tests broken by the fixer's changes <p>Coordination: Auditor's <code>ctx drift</code> output is the shared work queue. Each agent claims a subset of issues by adding <code>#in-progress</code> labels.</p> <p>Anti-pattern: Fixer and doc writer both editing ARCHITECTURE.md. Assign file ownership explicitly.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#release-prep-2-agents","level":3,"title":"Release Prep (2 Agents)","text":"Role Responsibility Release Notes Generates changelog from commits, writes release notes Validation Runs full test suite, lint, build across platforms <p>Coordination: Both read TASKS.md to identify what shipped. Release notes agent works from <code>git log</code>; validation agent works from <code>make audit</code>.</p> <p>Anti-pattern: Release notes agent running tests \"to verify.\" Each agent stays in its lane.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#documentation-sprint-3-agents","level":3,"title":"Documentation Sprint (3 Agents)","text":"Role Responsibility Content Writes new pages, expands existing docs Cross-linker Adds nav entries, cross-references, \"See Also\" sections Verifier Builds site, checks broken links, validates rendering <p>Coordination: Content agent writes files first. Cross-linker updates <code>zensical.toml</code> and index pages after content lands. Verifier builds after each batch.</p> <p>Antipattern: Content and cross-linker both editing <code>zensical.toml</code>. Batch nav updates into the cross-linker's pass.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with one agent: Only add parallelism when you have identified   the bottleneck. \"This would go faster with more agents\" is usually   wrong for tasks under 2 hours.</li> <li>The 3-4 agent ceiling is real: Coordination overhead grows   quadratically. 2 agents = 1 communication pair. 4 agents = 6 pairs.   Beyond 4, you are managing agents more than doing work.</li> <li>Worktrees > teams for most parallelism needs: If agents don't   need to talk to each other during execution, worktrees give you   parallelism with zero coordination overhead.</li> <li>Use <code>ctx</code> as the shared brain: Whether it's one agent or four, the   <code>.context/</code> directory is the single source of truth. Decisions go in   <code>DECISIONS.md</code>, not in chat messages between agents.</li> <li>Merge early, merge often: Long-lived parallel branches diverge.   Merge a track as soon as it's done rather than waiting for all tracks   to finish.</li> <li><code>TASKS.md</code> conflicts are normal: Multiple agents completing different   tasks will conflict on merge. The resolution is always additive: accept   all <code>[x]</code> completions from both sides.</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#next-up","level":2,"title":"Next Up","text":"<p>Parallel Agent Development with Git Worktrees →: Run multiple agents on independent task tracks using git worktrees.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#go-deeper","level":2,"title":"Go Deeper","text":"<ul> <li>CLI Reference: all commands and flags</li> <li>Integrations: setup for Claude Code, Cursor, Aider</li> <li>Session Journal: browse and search session history</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#see-also","level":2,"title":"See Also","text":"<ul> <li>Parallel Agent Development with Git Worktrees:   the mechanical \"how\" for worktree-based parallelism</li> <li>Running an Unattended AI Agent: serial autonomous   loops: a different scaling strategy</li> <li>Tracking Work Across Sessions: managing the task   backlog that feeds into any multi-agent setup</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"reference/","level":1,"title":"Reference","text":"<p>Technical reference for <code>ctx</code> commands, skills, and internals.</p>","path":["Reference"],"tags":[]},{"location":"reference/#the-system-explains-itself","level":3,"title":"The System Explains Itself","text":"<p>The 12 properties that must hold for any valid <code>ctx</code> implementation. Not features: constraints. The system's contract with its users and contributors.</p>","path":["Reference"],"tags":[]},{"location":"reference/#code-conventions","level":3,"title":"Code Conventions","text":"<p>Common patterns and fixes for the AST compliance tests in <code>internal/audit/</code>. When a test fails, find the matching section.</p>","path":["Reference"],"tags":[]},{"location":"reference/#cli","level":3,"title":"CLI","text":"<p>Every command, subcommand, and flag. Now a top-level section: see CLI Reference.</p>","path":["Reference"],"tags":[]},{"location":"reference/#skills","level":3,"title":"Skills","text":"<p>The full skill catalog: what each skill does, when it triggers, and how skills interact with commands.</p>","path":["Reference"],"tags":[]},{"location":"reference/#tool-ecosystem","level":3,"title":"Tool Ecosystem","text":"<p>How <code>ctx</code> compares to Cursor Rules, Aider conventions, CLAUDE.md, and other context approaches.</p>","path":["Reference"],"tags":[]},{"location":"reference/#session-journal","level":3,"title":"Session Journal","text":"<p>Export, browse, and enrich your session history. Covers the journal site, Obsidian export, and the enrichment pipeline.</p>","path":["Reference"],"tags":[]},{"location":"reference/#scratchpad","level":3,"title":"Scratchpad","text":"<p>Encrypted, git-tracked scratch space for short notes and sensitive values that travel with the project.</p>","path":["Reference"],"tags":[]},{"location":"reference/#version-history","level":3,"title":"Version History","text":"<p>Changelog for every <code>ctx</code> release.</p>","path":["Reference"],"tags":[]},{"location":"reference/audit-conventions/","level":1,"title":"Code Conventions","text":"","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#code-conventions-common-patterns-and-fixes","level":1,"title":"Code Conventions: Common Patterns and Fixes","text":"<p>This guide documents the code conventions enforced by <code>internal/audit/</code> AST tests. Each section shows the violation pattern, the fix, and the rationale. When a test fails, find the matching section below.</p> <p>All tests skip <code>_test.go</code> files. The patterns apply only to production code under <code>internal/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#variable-shadowing-bare-err-reuse","level":2,"title":"Variable Shadowing (Bare <code>err :=</code> Reuse)","text":"<p>Test: <code>TestNoVariableShadowing</code></p> <p>When a function has multiple <code>:=</code> assignments to <code>err</code>, each shadows the previous one. This makes it impossible to tell which error a later <code>if err != nil</code> is checking.</p> <p>Before:</p> <pre><code>func Run(cmd *cobra.Command) error {\n    data, err := os.ReadFile(path) \n    if err != nil {\n        return err\n    }\n\n    result, err := json.Unmarshal(data)  // shadows first err\n    if err != nil {\n        return err\n    }\n\n    err = validate(result)  // shadows again\n    return err\n}\n</code></pre> <p>After:</p> <pre><code>func Run(cmd *cobra.Command) error {\n    data, readErr := os.ReadFile(path)\n    if readErr != nil {\n        return readErr\n    }\n\n    result, parseErr := json.Unmarshal(data)\n    if parseErr != nil {\n        return parseErr\n    }\n\n    validateErr := validate(result)\n    return validateErr\n}\n</code></pre> <p>Rule: Use descriptive error names (<code>readErr</code>, <code>writeErr</code>, <code>parseErr</code>, <code>walkErr</code>, <code>absErr</code>, <code>relErr</code>) so each error site is independently identifiable.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#import-name-shadowing","level":2,"title":"Import Name Shadowing","text":"<p>Test: <code>TestNoImportNameShadowing</code></p> <p>When a local variable has the same name as an imported package, the import becomes inaccessible in that scope.</p> <p>Before:</p> <pre><code>import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(session *entity.Session) {  // param shadows import\n    // session package is now unreachable here\n}\n</code></pre> <p>After:</p> <pre><code>import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(sess *entity.Session) {\n    // session package still accessible\n}\n</code></pre> <p>Rule: Parameters, variables, and return values must not reuse imported package names. Common renames: <code>session</code> -> <code>sess</code>, <code>token</code> -> <code>tok</code>, <code>config</code> -> <code>cfg</code>, <code>entry</code> -> <code>ent</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-strings","level":2,"title":"Magic Strings","text":"<p>Test: <code>TestNoMagicStrings</code></p> <p>String literals in function bodies are invisible to refactoring tools and cause silent breakage when the value changes in one place but not another.</p> <p>Before (string literals):</p> <pre><code>func loadContext() {\n    data := filepath.Join(dir, \"TASKS.md\")\n    if strings.HasSuffix(name, \".yaml\") {\n        // ...\n    }\n}\n</code></pre> <p>After:</p> <pre><code>func loadContext() {\n    data := filepath.Join(dir, config.FilenameTask)\n    if strings.HasSuffix(name, config.ExtYAML) {\n        // ...\n    }\n}\n</code></pre> <p>Before (format verbs, also caught):</p> <pre><code>func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return fmt.Sprintf(\"%x\", h[:8])\n}\n</code></pre> <p>After:</p> <pre><code>func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return hex.EncodeToString(h[:cfgFmt.HashPrefixLen])\n}\n</code></pre> <p>Before (URL schemes, also caught):</p> <pre><code>if strings.HasPrefix(target, \"https://\") ||\n    strings.HasPrefix(target, \"http://\") {\n    return target\n}\n</code></pre> <p>After:</p> <pre><code>if strings.HasPrefix(target, cfgHTTP.PrefixHTTPS) ||\n    strings.HasPrefix(target, cfgHTTP.PrefixHTTP) {\n    return target\n}\n</code></pre> <p>Exempt from this check:</p> <ul> <li>Empty string <code>\"\"</code>, single space <code>\" \"</code>, indentation strings</li> <li>Regex capture references (<code>$1</code>, <code>${name}</code>)</li> <li><code>const</code> and <code>var</code> definition sites (that's where constants live)</li> <li>Struct tags</li> <li>Import paths</li> <li>Packages under <code>internal/config/</code>, <code>internal/assets/tpl/</code></li> </ul> <p>Rule: If a string is used for comparison, path construction, or appears in 3+ files, it belongs in <code>internal/config/</code> as a constant. Format strings belong in <code>internal/config/</code> as named constants (e.g., <code>cfgGit.FlagLastN</code>, <code>cfgTrace.RefFormat</code>). User-facing prose belongs in <code>internal/assets/</code> YAML files accessed via <code>desc.Text()</code>.</p> <p>Common fix for <code>fmt.Sprintf</code> with format verbs:</p> Pattern Fix <code>fmt.Sprintf(\"%d\", n)</code> <code>strconv.Itoa(n)</code> <code>fmt.Sprintf(\"%d\", int64Val)</code> <code>strconv.FormatInt(int64Val, 10)</code> <code>fmt.Sprintf(\"%x\", bytes)</code> <code>hex.EncodeToString(bytes)</code> <code>fmt.Sprintf(\"%q\", s)</code> <code>strconv.Quote(s)</code> <code>fmt.Sscanf(s, \"%d\", &n)</code> <code>strconv.Atoi(s)</code> <code>fmt.Sprintf(\"-%d\", n)</code> <code>fmt.Sprintf(cfgGit.FlagLastN, n)</code> <code>\"https://\"</code> <code>cfgHTTP.PrefixHTTPS</code> <code>\"&lt;\"</code> config constant in <code>config/html/</code>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-printf-calls","level":2,"title":"Direct Printf Calls","text":"<p>Test: <code>TestNoPrintfCalls</code></p> <p><code>cmd.Printf</code> and <code>cmd.PrintErrf</code> bypass the write-package formatting pipeline and scatter user-facing text across the codebase.</p> <p>Before:</p> <pre><code>func Run(cmd *cobra.Command, args []string) {\n    cmd.Printf(\"Found %d tasks\\n\", count)\n}\n</code></pre> <p>After:</p> <pre><code>func Run(cmd *cobra.Command, args []string) {\n    write.TaskCount(cmd, count)\n}\n</code></pre> <p>Rule: All formatted output goes through <code>internal/write/</code> which uses <code>cmd.Print</code>/<code>cmd.Println</code> with pre-formatted strings from <code>desc.Text()</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#raw-time-format-strings","level":2,"title":"Raw Time Format Strings","text":"<p>Test: <code>TestNoRawTimeFormats</code></p> <p>Inline time format strings (<code>\"2006-01-02\"</code>, <code>\"15:04:05\"</code>) drift when one call site is updated but others are missed.</p> <p>Before:</p> <pre><code>func formatDate(t time.Time) string {\n    return t.Format(\"2006-01-02\")\n}\n</code></pre> <p>After:</p> <pre><code>func formatDate(t time.Time) string {\n    return t.Format(cfgTime.DateFormat)\n}\n</code></pre> <p>Rule: All time format strings must use constants from <code>internal/config/time/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-flag-registration","level":2,"title":"Direct Flag Registration","text":"<p>Test: <code>TestNoFlagBindOutsideFlagbind</code></p> <p>Direct cobra flag calls (<code>.Flags().StringVar()</code>, etc.) scatter flag wiring across dozens of <code>cmd.go</code> files. Centralizing through <code>internal/flagbind/</code> gives one place to audit flag names, defaults, and description key lookups.</p> <p>Before:</p> <pre><code>func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    c.Flags().StringVarP(&output, \"output\", \"o\", \"\",\n        \"output format\")\n    return c\n}\n</code></pre> <p>After:</p> <pre><code>func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    flagbind.StringFlagShort(c, &output, flag.Output,\n        flag.OutputShort, cmd.DescKeyOutput)\n    return c\n}\n</code></pre> <p>Rule: All flag registration goes through <code>internal/flagbind/</code>. If the helper you need doesn't exist, add it to <code>flagbind/flag.go</code> before using it.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#todo-comments","level":2,"title":"TODO Comments","text":"<p>Test: <code>TestNoTODOComments</code></p> <p>TODO, FIXME, HACK, and XXX comments in production code are invisible to project tracking. They accumulate silently and never get addressed.</p> <p>Before:</p> <pre><code>// TODO: handle pagination\nfunc listEntries() []Entry {\n</code></pre> <p>After:</p> <p>Remove the comment and add a task to <code>.context/TASKS.md</code>:</p> <pre><code>- [ ] Handle pagination in listEntries (internal/task/task.go)\n</code></pre> <p>Rule: Deferred work lives in TASKS.md, not in source comments.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#dead-exports","level":2,"title":"Dead Exports","text":"<p>Test: <code>TestNoDeadExports</code></p> <p>Exported symbols with zero references outside their definition file are dead weight. They increase API surface, confuse contributors, and cost maintenance.</p> <p>Fix: Either delete the export (preferred) or demote it to unexported if it's still used within the file.</p> <p>If the symbol existed for historical reasons and might be needed again, move it to <code>quarantine/deadcode/</code> with a <code>.dead</code> extension. This preserves the code in git without polluting the live codebase:</p> <pre><code>quarantine/deadcode/internal/config/flag/flag.go.dead\n</code></pre> <p>Each <code>.dead</code> file includes a header:</p> <pre><code>// Dead exports quarantined from internal/config/flag/flag.go\n// Quarantined: 2026-04-02\n// Restore from git history if needed.\n</code></pre> <p>Rule: If a test-only allowlist entry is needed (the export exists only for test use), add the fully qualified symbol to <code>testOnlyExports</code> in <code>dead_exports_test.go</code>. Keep this list small; prefer eliminating the export.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#core-package-structure","level":2,"title":"Core Package Structure","text":"<p>Test: <code>TestCoreStructure</code></p> <p><code>core/</code> directories under <code>internal/cli/</code> must contain only <code>doc.go</code> and test files at the top level. All domain logic lives in subpackages. This prevents <code>core/</code> from becoming a god package.</p> <p>Before:</p> <pre><code>internal/cli/dep/core/\n    go.go           # violation: logic at core/ level\n    python.go       # violation\n    node.go         # violation\n    types.go        # violation\n</code></pre> <p>After:</p> <pre><code>internal/cli/dep/core/\n    doc.go          # package doc only\n    golang/\n        golang.go\n        golang_test.go\n        doc.go\n    python/\n        python.go\n        python_test.go\n        doc.go\n    node/\n        node.go\n        node_test.go\n        doc.go\n</code></pre> <p>Rule: Extract each logical unit into its own subpackage under <code>core/</code>. Each subpackage gets a <code>doc.go</code>. The subpackage name should match the domain concept (<code>golang</code>, <code>check</code>, <code>fix</code>, <code>store</code>), not a generic label (<code>util</code>, <code>helper</code>).</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cross-package-types","level":2,"title":"Cross-Package Types","text":"<p>Test: <code>TestCrossPackageTypes</code></p> <p>When a type defined in one package is used from a different module (e.g., <code>cli/doctor</code> importing a type from <code>cli/notify</code>), the type has crossed its module boundary. Cross-cutting types belong in <code>internal/entity/</code> for discoverability.</p> <p>Before:</p> <pre><code>// internal/cli/notify/core/types.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/cli/notify/core\"\nfunc check(p core.NotifyPayload) { ... }\n</code></pre> <p>After:</p> <pre><code>// internal/entity/notify.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/entity\"\nfunc check(p entity.NotifyPayload) { ... }\n</code></pre> <p>Exempt: Types inside <code>entity/</code>, <code>proto/</code>, <code>core/</code> subpackages, and <code>config/</code> packages. Same-module usage (e.g., <code>cli/doctor/cmd/</code> using <code>cli/doctor/core/</code>) is not flagged.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#type-file-convention","level":2,"title":"Type File Convention","text":"<p>Test: <code>TestTypeFileConvention</code>, <code>TestTypeFileConventionReport</code></p> <p>Exported types in <code>core/</code> subpackages should live in <code>types.go</code> (the convention from CONVENTIONS.md), not scattered across implementation files. This makes type definitions discoverable. <code>TestTypeFileConventionReport</code> generates a diagnostic summary of all type placements for triage.</p> <p>Exception: <code>entity/</code> organizes by domain (<code>task.go</code>, <code>session.go</code>), <code>proto/</code> uses <code>schema.go</code>, and <code>err/</code> packages colocate error types with their domain context.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-yaml-linkage","level":2,"title":"DescKey / YAML Linkage","text":"<p>Test: <code>TestDescKeyYAMLLinkage</code></p> <p>Every DescKey constant must have a corresponding key in the YAML asset files, and every YAML key must have a corresponding DescKey constant. Orphans in either direction mean dead text or runtime panics.</p> <p>Fix for orphan YAML key: Delete the YAML entry, or add the corresponding <code>DescKey</code> constant in <code>config/embed/{text,cmd,flag}/</code>.</p> <p>Fix for orphan DescKey: Delete the constant, or add the corresponding entry in the YAML file under <code>internal/assets/commands/text/</code>, <code>cmd/</code>, or <code>flag/</code>.</p> <p>If the orphan YAML entry was once valid but the feature was removed, move the YAML entry to a <code>.dead</code> file in <code>quarantine/deadcode/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#package-doc-quality","level":2,"title":"Package Doc Quality","text":"<p>Test: <code>TestPackageDocQuality</code></p> <p>Every package under <code>internal/</code> must have a <code>doc.go</code> with a meaningful package doc comment (at least 8 lines of real content). One-liners and file-list patterns (<code>// - foo.go</code>, <code>// Source files:</code>) are flagged because they drift as files change.</p> <p>Template:</p> <pre><code>//   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n\n// Package mypackage does X.\n//\n// It handles Y by doing Z. The main entry point is [FunctionName]\n// which accepts A and returns B.\n//\n// Configuration is read from [config.SomeConstant]. Output is\n// written through [write.SomeHelper].\n//\n// This package is used by [parentpackage] during the W lifecycle\n// phase.\npackage mypackage\n</code></pre>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-regex-compilation","level":2,"title":"Inline Regex Compilation","text":"<p>Test: <code>TestNoInlineRegexpCompile</code></p> <p><code>regexp.MustCompile</code> and <code>regexp.Compile</code> inside function bodies recompile the pattern on every call. Compiled patterns belong at package level.</p> <p>Before:</p> <pre><code>func parse(s string) bool {\n    re := regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n    return re.MatchString(s)\n}\n</code></pre> <p>After:</p> <pre><code>// In internal/config/regex/regex.go:\n// DatePattern matches ISO date format (YYYY-MM-DD).\nvar DatePattern = regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n\n// In calling package:\nfunc parse(s string) bool {\n    return regex.DatePattern.MatchString(s)\n}\n</code></pre> <p>Rule: All compiled regexes live in <code>internal/config/regex/</code> as package-level <code>var</code> declarations. Two tests enforce this: <code>TestNoInlineRegexpCompile</code> catches function-body compilation, and <code>TestNoRegexpOutsideRegexPkg</code> catches package-level compilation outside <code>config/regex/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#doc-comments","level":2,"title":"Doc Comments","text":"<p>Test: <code>TestDocComments</code></p> <p>All functions (exported and unexported), structs, and package-level variables must have a doc comment. Config packages allow group doc comments for <code>const</code> blocks.</p> <p>Before:</p> <pre><code>func buildIndex(entries []Entry) map[string]int {\n</code></pre> <p>After:</p> <pre><code>// buildIndex maps entry names to their position in the\n// ordered slice for O(1) lookup during reconciliation.\n//\n// Parameters:\n//   - entries: ordered slice of entries to index\n//\n// Returns:\n//   - map[string]int: name-to-position mapping\nfunc buildIndex(entries []Entry) map[string]int {\n</code></pre> <p>Rule: Every function, struct, and package-level <code>var</code> gets a doc comment in godoc format. Functions include <code>Parameters:</code> and <code>Returns:</code> sections. Structs with 2+ fields document every field. See CONVENTIONS.md for the full template.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#line-length","level":2,"title":"Line Length","text":"<p>Test: <code>TestLineLength</code></p> <p>Lines in non-test Go files must not exceed 80 characters. This is a hard check, not a suggestion.</p> <p>Before:</p> <pre><code>_ = trace.Record(fmt.Sprintf(cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum), state.Dir())\n</code></pre> <p>After:</p> <pre><code>ref := fmt.Sprintf(\n    cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum,\n)\n_ = trace.Record(ref, state.Dir())\n</code></pre> <p>Rule: Break at natural points: function arguments, struct fields, chained calls. Long strings (URLs, struct tags) are the rare acceptable exception.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#literal-whitespace","level":2,"title":"Literal Whitespace","text":"<p>Test: <code>TestNoLiteralWhitespace</code></p> <p>Bare whitespace string and byte literals (<code>\"\\n\"</code>, <code>\"\\r\\n\"</code>, <code>\"\\t\"</code>) must not appear outside <code>internal/config/token/</code>. All other packages use the token constants.</p> <p>Before:</p> <pre><code>output := strings.Join(lines, \"\\n\")\n</code></pre> <p>After:</p> <pre><code>output := strings.Join(lines, token.Newline)\n</code></pre> <p>Rule: Whitespace literals are defined once in <code>internal/config/token/</code>. Use <code>token.Newline</code>, <code>token.Tab</code>, <code>token.CRLF</code>, etc.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-numeric-values","level":2,"title":"Magic Numeric Values","text":"<p>Test: <code>TestNoMagicValues</code></p> <p>Numeric literals in function bodies need constants, with narrow exceptions.</p> <p>Before:</p> <pre><code>if len(entries) > 100 {\n    entries = entries[:100]\n}\n</code></pre> <p>After:</p> <pre><code>if len(entries) > config.MaxEntries {\n    entries = entries[:config.MaxEntries]\n}\n</code></pre> <p>Exempt: <code>0</code>, <code>1</code>, <code>-1</code>, <code>2</code>-<code>10</code>, strconv radix/bitsize args (<code>10</code>, <code>32</code>, <code>64</code> in <code>strconv.Parse*</code>/<code>Format*</code>), octal permissions (caught separately by <code>TestNoRawPermissions</code>), and <code>const</code>/<code>var</code> definition sites.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-separators","level":2,"title":"Inline Separators","text":"<p>Test: <code>TestNoInlineSeparators</code></p> <p><code>strings.Join</code> calls must use token constants for their separator argument, not string literals.</p> <p>Before:</p> <pre><code>result := strings.Join(parts, \", \")\n</code></pre> <p>After:</p> <pre><code>result := strings.Join(parts, token.CommaSep)\n</code></pre> <p>Rule: Separator strings live in <code>internal/config/token/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stuttery-function-names","level":2,"title":"Stuttery Function Names","text":"<p>Test: <code>TestNoStutteryFunctions</code></p> <p>Function names must not redundantly include their package name as a PascalCase word boundary. Go callers already write <code>pkg.Function</code>, so <code>pkg.PkgFunction</code> stutters.</p> <p>Before:</p> <pre><code>// In package write\nfunc WriteJournal(cmd *cobra.Command, ...) {\n</code></pre> <p>After:</p> <pre><code>// In package write\nfunc Journal(cmd *cobra.Command, ...) {\n</code></pre> <p>Exempt: Identity functions like <code>write.Write</code> / <code>write.write</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#predicate-naming-no-ishascan-prefix","level":2,"title":"Predicate Naming (No <code>Is</code>/<code>Has</code>/<code>Can</code> Prefix)","text":"<p>Test: None (manual review convention)</p> <p>Exported methods that return <code>bool</code> must not use <code>Is</code>, <code>Has</code>, or <code>Can</code> prefixes. The predicate reads more naturally without them, especially at call sites where the package name provides context.</p> <p>Before:</p> <pre><code>func IsCompleted(t *Task) bool { ... }\nfunc HasChildren(n *Node) bool { ... }\nfunc IsExemptPackage(path string) bool { ... }\n</code></pre> <p>After:</p> <pre><code>func Completed(t *Task) bool { ... }\nfunc Children(n *Node) bool { ... }  // or: ChildCount > 0\nfunc ExemptPackage(path string) bool { ... }\n</code></pre> <p>Rule: Drop the prefix. Private helpers may use prefixes when it reads more naturally (<code>isValid</code> in a local context is fine). This convention applies to exported methods and package-level functions. See CONVENTIONS.md \"Predicates\" section.</p> <p>This is not yet enforced by an AST test; it requires semantic understanding of return types and naming intent that makes automated detection fragile. Apply during code review.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#mixed-visibility","level":2,"title":"Mixed Visibility","text":"<p>Test: <code>TestNoMixedVisibility</code></p> <p>Files with exported functions must not also contain unexported functions. Public API and private helpers live in separate files.</p> <p>Before:</p> <pre><code>load.go\n    func Load() { ... }        // exported\n    func parseHeader() { ... } // unexported, violation\n</code></pre> <p>After:</p> <pre><code>load.go\n    func Load() { ... }        // exported only\nparse.go\n    func parseHeader() { ... } // private helper\n</code></pre> <p>Exempt: Files with exactly one function, <code>doc.go</code>, test files.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stray-errgo-files","level":2,"title":"Stray Err.Go Files","text":"<p>Test: <code>TestNoStrayErrFiles</code></p> <p><code>err.go</code> files must only exist under <code>internal/err/</code>. Error constructors anywhere else create a broken-window pattern where contributors add local error definitions when they see a local <code>err.go</code>.</p> <p>Fix: Move the error constructor to <code>internal/err/<domain>/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cli-cmd-structure","level":2,"title":"CLI Cmd Structure","text":"<p>Test: <code>TestCLICmdStructure</code></p> <p>Each <code>cmd/$sub/</code> directory under <code>internal/cli/</code> may contain only <code>cmd.go</code>, <code>run.go</code>, <code>doc.go</code>, and test files. Extra <code>.go</code> files (helpers, output formatters, types) belong in the corresponding <code>core/</code> subpackage.</p> <p>Before:</p> <pre><code>internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\n    format.go   # violation: helper in cmd dir\n</code></pre> <p>After:</p> <pre><code>internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\ninternal/cli/doctor/core/format/\n    format.go\n    doc.go\n</code></pre>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-namespace","level":2,"title":"DescKey Namespace","text":"<p>Test: <code>TestUseConstantsOnlyInCobraUse</code>, <code>TestDescKeyOnlyInLookupCalls</code>, <code>TestNoWrongNamespaceLookup</code></p> <p>Three tests enforce DescKey/Use constant discipline:</p> <ol> <li><code>Use*</code> constants appear only in cobra <code>Use:</code> struct field    assignments, never as arguments to <code>desc.Text()</code> or elsewhere.</li> <li><code>DescKey*</code> constants are passed only to <code>assets.CommandDesc()</code>,    <code>assets.FlagDesc()</code>, or <code>desc.Text()</code>, never to cobra <code>Use:</code>.</li> <li>No cross-namespace lookups: <code>TextDescKey</code> must not be passed to    <code>CommandDesc()</code>, <code>FlagDescKey</code> must not be passed to <code>Text()</code>, etc.</li> </ol>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#yaml-examples-registry-linkage","level":2,"title":"YAML Examples / Registry Linkage","text":"<p>Test: <code>TestExamplesYAMLLinkage</code>, <code>TestRegistryYAMLLinkage</code></p> <p>Every key in <code>examples.yaml</code> and <code>registry.yaml</code> must match a known entry type constant. Prevents orphan entries that are never rendered.</p> <p>Fix: Delete the orphan YAML entry, or add the corresponding constant in <code>config/entry/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#other-enforced-patterns","level":2,"title":"Other Enforced Patterns","text":"<p>These tests follow the same fix approach: extract the operation to its designated package:</p> Test Violation Fix <code>TestNoNakedErrors</code> <code>fmt.Errorf</code>/<code>errors.New</code> outside <code>internal/err/</code> Add error constructor to <code>internal/err/<domain>/</code> <code>TestNoRawFileIO</code> Direct <code>os.ReadFile</code>, <code>os.Create</code>, etc. Use <code>io.SafeReadFile</code>, <code>io.SafeWriteFile</code>, etc. <code>TestNoRawLogging</code> Direct <code>fmt.Fprintf(os.Stderr, ...)</code> Use <code>log/warn.Warn()</code> or <code>log/event.Append()</code> <code>TestNoExecOutsideExecPkg</code> <code>exec.Command</code> outside <code>internal/exec/</code> Add command to <code>internal/exec/<domain>/</code> <code>TestNoCmdPrintOutsideWrite</code> <code>cmd.Print*</code> outside <code>internal/write/</code> Add output helper to <code>internal/write/<domain>/</code> <code>TestNoRawPermissions</code> Octal literals (<code>0644</code>, <code>0755</code>) Use <code>config/fs.PermFile</code>, <code>config/fs.PermExec</code>, etc. <code>TestNoErrorsAs</code> <code>errors.As()</code> Use <code>errors.AsType()</code> (generic, Go 1.23+) <code>TestNoStringConcatPaths</code> <code>dir + \"/\" + file</code> Use <code>filepath.Join(dir, file)</code>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#general-fix-workflow","level":2,"title":"General Fix Workflow","text":"<p>When an audit test fails:</p> <ol> <li>Read the error message. It includes <code>file:line</code> and a    description of the violation.</li> <li>Find the matching section above. The test name maps directly    to a section.</li> <li>Apply the pattern. Most fixes are mechanical: extract to the    right package, rename a variable, or replace a literal with a    constant.</li> <li>Run <code>make test</code> before committing. Audit tests run as part of    <code>go test ./internal/audit/</code>.</li> <li>Don't add allowlist entries as a first resort. Fix the code.    Allowlists exist only for genuinely unfixable cases (test-only    exports, config packages that are definitionally exempt).</li> </ol>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/comparison/","level":1,"title":"Tool Ecosystem","text":"","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"<p>Many tools help AI think.</p> <p><code>ctx</code> helps AI remember.</p> <ul> <li>Not by storing thoughts,</li> <li>but by preserving intent.</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How <code>ctx</code> Differs from Similar Tools","text":"<p>There are many tools in the AI ecosystem that touch parts of the context problem:</p> <ul> <li>Some manage prompts.  </li> <li>Some retrieve data.  </li> <li>Some provide runtime context objects.  </li> <li>Some offer enterprise platforms.</li> </ul> <p><code>ctx</code> focuses on a different layer entirely.</p> <p>This page explains where <code>ctx</code> fits, and where it intentionally does not.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"<p>Most tools treat context as input.</p> <p><code>ctx</code> treats context as infrastructure.</p> <p>That single difference explains nearly all of <code>ctx</code>'s design choices.</p> Question Most tools <code>ctx</code> Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"<p>Examples include:</p> <ul> <li>prompt templates;</li> <li>reusable system prompts;</li> <li>prompt libraries;</li> <li>prompt versioning tools.</li> </ul> <p>These tools help you start a session.</p> <p>They do not help you continue one.</p> <p>Prompt tools:</p> <ul> <li>inject text at session start;</li> <li>are ephemeral by design;</li> <li>do not evolve with the project.</li> </ul> <p><code>ctx</code>:</p> <ul> <li>persists knowledge over time;</li> <li>accumulates decisions and learnings;</li> <li>makes the context part of the repository itself.</li> </ul> <p>Prompt tooling and <code>ctx</code> are complementary; not competing.  Yet, they operate in different layers.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"<p>RAG systems typically:</p> <ul> <li>index documents</li> <li>embed text</li> <li>retrieve chunks dynamically at runtime</li> </ul> <p>They are excellent for:</p> <ul> <li>large knowledge bases</li> <li>static documentation</li> <li>reference material</li> </ul> <p>RAG answers questions like:</p> <p>\"What information might be relevant right now?\"</p> <p><code>ctx</code> answers a different question:</p> <p>\"What have we already decided, learned, or committed to?\"</p> <p>Here are some key differences:</p> RAG <code>ctx</code> Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory <p><code>ctx</code> does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.</p> <p>RAG belongs to the data plane; <code>ctx</code> defines the context control plane.</p> <p>It focuses on project memory, not knowledge search.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"<p>Agent frameworks often provide:</p> <ul> <li>task loops</li> <li>tool orchestration</li> <li>planner/executor patterns</li> <li>autonomous iteration</li> </ul> <p>These systems are powerful, but they typically assume that:</p> <ul> <li>memory is external</li> <li>context is injected</li> <li>state is transient</li> </ul> <p>Agent frameworks answer:</p> <p>\"How should the agent act?\"</p> <p><code>ctx</code> answers:</p> <p>\"What should the agent remember?\"</p> <p>Without persistent context, agents tend to:</p> <ul> <li>rediscover decisions</li> <li>repeat mistakes</li> <li>lose architectural intent</li> </ul> <p>This is why <code>ctx</code> pairs well with autonomous loop workflows:</p> <ul> <li>The loop provides iteration</li> <li><code>ctx</code> provides continuity</li> </ul> <p>Together, loops become cumulative instead of forgetful.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"<p>Some SDKs expose \"context\" objects that exist:</p> <ul> <li>inside a process</li> <li>during a request</li> <li>for the lifetime of a call chain</li> </ul> <p>These are extremely useful and completely different.</p> <p>SDK context objects:</p> <ul> <li>are in-memory</li> <li>disappear when the process ends</li> <li>are not shared across sessions</li> </ul> <p><code>ctx</code>:</p> <ul> <li>survives process restarts</li> <li>survives new chats</li> <li>survives new days</li> </ul> <p>They share a name, not a purpose.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"<p>Enterprise platforms often provide:</p> <ul> <li>centralized context services</li> <li>dashboards</li> <li>access control</li> <li>organizational knowledge layers</li> </ul> <p>These tools are designed for:</p> <ul> <li>teams</li> <li>governance</li> <li>compliance</li> <li>managed environments</li> </ul> <p><code>ctx</code> is intentionally:</p> <ul> <li>local-first: context lives next to your code, not   behind a service boundary.</li> <li>file-based: everything important is a Markdown   file you can read, diff, grep, and version-control.</li> <li>single-binary core: the context persistence path   (<code>init</code>, <code>add</code>, <code>agent</code>, <code>status</code>, <code>drift</code>, <code>load</code>,   <code>sync</code>, <code>compact</code>, <code>task</code>, <code>decision</code>, <code>learning</code>, and   their siblings) is a single Go binary with no required   runtime dependencies. Optional integrations (<code>ctx   trace</code> (needs <code>git</code>), <code>ctx serve</code> (needs <code>zensical</code>),   the <code>ctx</code> Hub (needs a running hub), Claude Code   plugin (needs <code>claude</code>)) are opt-in and each declares   its dependency explicitly.</li> <li>CLI-driven: every feature is reachable from the   command line and scriptable.</li> <li>developer-controlled: no auto-updating cloud   service, no telemetry, no account to sign up for.</li> </ul> <p>The core <code>ctx</code> binary does not require:</p> <ul> <li>a server</li> <li>a database</li> <li>an account</li> <li>a SaaS backend</li> <li>network connectivity (for core operations)</li> </ul> <p><code>ctx</code> optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#specific-tool-comparisons","level":2,"title":"Specific Tool Comparisons","text":"<p>Users often evaluate <code>ctx</code> against specific tools they already use. These comparisons clarify where responsibilities overlap, where they diverge, and where the tools are genuinely complementary.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#claude-code-memory-anthropic-auto-memory","level":3,"title":"Claude Code Memory / Anthropic Auto-Memory","text":"<p>Anthropic's auto-memory is tool-managed memory (L2): the model decides what to remember, stores it automatically, and retrieves it implicitly. <code>ctx</code> is system memory (L3): humans and agents explicitly curate decisions, learnings, and tasks in inspectable files.</p> <p>Auto-memory is convenient - you do not configure anything. But it is also opaque: you cannot see what was stored, edit it precisely, or share it across tools. <code>ctx</code> files are plain Markdown in your repository, visible in diffs and code review.</p> <p>The two are complementary. <code>ctx</code> can absorb auto-memory as an input source (importing what the model remembered into structured context files) while providing the durable, inspectable layer that auto-memory lacks.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cursorrules-clauderules","level":3,"title":".Cursorrules / .Claude/rules","text":"<p>Static rule files (<code>.cursorrules</code>, <code>.claude/rules/</code>) declare conventions: coding style, forbidden patterns, preferred libraries. They are effective for what to do and load automatically at session start.</p> <p><code>ctx</code> adds dimensions that rule files do not cover: architectural decisions with rationale, learnings discovered during development, active tasks, and a constitution that governs agent behavior. Critically, <code>ctx</code> context accumulates - each session can add to it, and token budgeting ensures only the most relevant context is injected.</p> <p>Use rule files for static conventions. Use <code>ctx</code> for evolving project memory.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#aider-read-watch","level":3,"title":"Aider <code>--read</code> / <code>--watch</code>","text":"<p>Aider's <code>--read</code> flag injects file contents at session start; <code>--watch</code> reloads them on change. The concept is similar to <code>ctx</code>'s \"load\" step: make the agent aware of specific files.</p> <p>The differences emerge beyond loading. Aider has no persistence model -- nothing the agent learns during a session is written back. There is no token budgeting (large files consume the full context window), no priority ordering across file types, and no structured format for decisions or learnings. <code>ctx</code> provides the full lifecycle: load, accumulate, persist, and budget.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#copilot-workspace","level":3,"title":"Copilot @Workspace","text":"<p>GitHub Copilot's <code>@workspace</code> performs workspace-wide code search. It answers \"what code exists?\" - finding function definitions, usages, and file structure across the repository.</p> <p><code>ctx</code> answers a different question: \"what did we decide?\" It stores architectural intent, not code indices. Copilot's workspace search and <code>ctx</code>'s project memory are orthogonal; one finds code, the other preserves the reasoning behind it.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cline-memory","level":3,"title":"Cline Memory","text":"<p>Cline's memory bank stores session context within the Cline extension. The motivation is similar to <code>ctx</code>: help the agent remember across sessions.</p> <p>The key difference is portability. Cline memory is tied to Cline - it does not transfer to Claude Code, Cursor, Aider, or any other tool. <code>ctx</code> is tool-agnostic: context lives in plain files that any editor, agent, or script can read. Switching tools does not mean losing memory.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-a-good-fit","level":2,"title":"When <code>ctx</code> Is a Good Fit","text":"<p><code>ctx</code> works best when:</p> <ul> <li>you want AI work to compound over time;</li> <li>architectural decisions matter;</li> <li>context must be inspectable;</li> <li>humans and AI must share the same source of truth;</li> <li>Git history should include why, not just what.</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When <code>ctx</code> Is Not the Right Tool","text":"<p><code>ctx</code> is probably not what you want if:</p> <ul> <li>you only need one-off prompts;</li> <li>you rely exclusively on RAG;</li> <li>you want autonomous agents without a human-readable state;</li> <li>you require centralized enterprise control;</li> <li>you want black-box memory systems,</li> </ul> <p>These are valid goals; just different ones.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>You Can't Import Expertise:    why project-specific context matters more than generic best practices</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/design-invariants/","level":1,"title":"Invariants","text":"","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-system-explains-itself","level":1,"title":"The System Explains Itself","text":"<p>These are the properties that must hold for any valid <code>ctx</code> implementation.</p> <ul> <li>These are not features.</li> <li>These are constraints.</li> </ul> <p>A change that violates an invariant is a category error,  not an improvement.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#cognitive-state-tiers","level":2,"title":"Cognitive State Tiers","text":"<p><code>ctx</code> distinguishes between three forms of state:</p> <ul> <li>Authoritative state: Versioned, inspectable artifacts that define intent    and survive time.</li> <li>Delivery views: Deterministic assemblies of the authoritative state for a    specific budget or workflow.</li> <li>Ephemeral working state: Local, transient, or sensitive data that    assists interaction but does not define system truth.</li> </ul> <p>The invariants below apply primarily to the authoritative cognitive state.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#1-cognitive-state-is-explicit","level":2,"title":"1. Cognitive State Is Explicit","text":"<p>All authoritative context lives in artifacts that can be inspected, reviewed, and versioned.</p> <p>If something is important, it must exist as a file: Not only in a prompt,  a chat, or a model's hidden memory.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#2-assembly-is-reproducible","level":2,"title":"2. Assembly Is Reproducible","text":"<p>Given the same:</p> <ul> <li>repository state,</li> <li>configuration,</li> <li>and inputs,</li> </ul> <p>context assembly produces the same result.</p> <p>Heuristics may rank or filter for delivery under constraints.</p> <p>They do not alter the authoritative state.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#3-the-authoritative-state-is-human-readable","level":2,"title":"3. The Authoritative State Is Human-Readable","text":"<p>The authoritative cognitive state must be stored in formats that a human can:</p> <ul> <li>read,</li> <li>diff,</li> <li>review,</li> <li>and edit directly.</li> </ul> <p>Sensitive working memory may be encrypted at rest. However, encryption must not become the only representation of  authoritative knowledge.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#4-artifacts-outlive-sessions","level":2,"title":"4. Artifacts Outlive Sessions","text":"<p>Sessions are transient.</p> <p>Knowledge persists.</p> <p>Reasoning, decisions, and outcomes must remain available after the  interaction that produced them has ended.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#5-authority-is-user-defined","level":2,"title":"5. Authority Is User-Defined","text":"<p>What enters the authoritative context is an explicit human decision.</p> <p>Models may suggest.</p> <p>Automation may assist.</p> <p>Selection is never implicit.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#6-operation-is-local-first","level":2,"title":"6. Operation Is Local-First","text":"<p>The core system must function without requiring network access or a  remote service.</p> <p>External systems may extend <code>ctx</code>.</p> <p>They must not be required for its operation.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#7-versioning-is-the-memory-model","level":2,"title":"7. Versioning Is the Memory Model","text":"<p>The evolution of the authoritative cognitive state must be:</p> <ul> <li>preserved,</li> <li>inspectable,</li> <li>and branchable.</li> </ul> <p>Ephemeral and sensitive working state may use different retention and diff  strategies by design.</p> <p>Understanding includes understanding how we arrived here.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#8-structure-enables-scale","level":2,"title":"8. Structure Enables Scale","text":"<p>Unstructured accumulation is not memory.</p> <p>Authoritative cognitive state must have a defined layout that:</p> <ul> <li>communicates intent,</li> <li>supports navigation,</li> <li>and prevents drift.</li> </ul>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#9-verification-is-the-scoreboard","level":2,"title":"9. Verification Is the Scoreboard","text":"<p>Claims without recorded outcomes are noise.</p> <p>Reality (observed and captured) is the only signal that compounds.</p> <p>This invariant defines a required direction:</p> <p>The authoritative state must be able to record expectation and result.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#10-capture-once-reuse-indefinitely","level":2,"title":"10. Capture Once, Reuse Indefinitely","text":"<p>Work that has already produced understanding must not be re-derived from  scratch.</p> <p>Explored paths, rejected options, and validated conclusions are  permanent assets.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#11-policies-are-encoded-not-remembered","level":2,"title":"11. Policies Are Encoded, Not Remembered","text":"<p>Alignment must not depend on recall or goodwill.</p> <p>Constraints that matter must exist in machine-readable form and participate in context assembly.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#12-the-system-explains-itself","level":2,"title":"12. The System Explains Itself","text":"<p>From the repository state alone it must be possible to determine:</p> <ul> <li>what was authoritative,</li> <li>what constraints applied.</li> </ul> <p>Delivery views may be optimized.</p> <p>They must not become the only explanation.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#non-goals","level":1,"title":"Non-Goals","text":"<p>To avoid category errors, <code>ctx</code> does not attempt to be:</p> <ul> <li>a skill,</li> <li>a prompt management tool,</li> <li>a chat history viewer,</li> <li>an autonomous agent runtime,</li> <li>a vector database,</li> <li>a hosted memory service.</li> </ul> <p>Such systems may integrate with <code>ctx</code>.</p> <p>They do not define it.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#implications-for-contributions","level":1,"title":"Implications for Contributions","text":"<p>Valid contributions:</p> <ul> <li>strengthen an invariant,</li> <li>reduce the cost of maintaining an invariant,</li> <li>or extend the system without violating invariants.</li> </ul> <p>Invalid contributions:</p> <ul> <li>introduce hidden authoritative state,</li> <li>replace reproducible assembly with non-reproducible behavior,</li> <li>make core operation depend on external services,</li> <li>reduce human inspectability of authoritative state,</li> <li>or bypass explicit user authority over what becomes authoritative.</li> </ul>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-contract","level":1,"title":"The Contract","text":"<p>Everything else (commands, skills, layouts, integrations, optimizations)  is an implementation detail.</p> <p>These invariants are the system.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/scratchpad/","level":1,"title":"Scratchpad","text":"","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#what-is-ctx-scratchpad","level":2,"title":"What Is <code>ctx</code> Scratchpad?","text":"<p>A one-liner scratchpad, encrypted at rest, synced via <code>git</code>.</p> <p>Quick notes that don't fit decisions, learnings, or tasks: reminders, intermediate values, sensitive tokens, working memory during debugging. Entries are numbered, reorderable, and persist across sessions.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#encrypted-by-default","level":2,"title":"Encrypted by Default","text":"<p>Scratchpad entries are encrypted with <code>AES-256-GCM</code> before touching the disk.</p> Component Path Git status Encryption key <code>~/.ctx/.ctx.key</code> User-level, <code>0600</code> permissions Encrypted data <code>.context/scratchpad.enc</code> Committed <p>The key is generated automatically during <code>ctx init</code> (256-bit via <code>crypto/rand</code>) and stored at <code>~/.ctx/.ctx.key</code>. One key per machine, shared across all projects.</p> <p>The ciphertext format is <code>[12-byte nonce][ciphertext+tag]</code>. No external dependencies: Go stdlib only.</p> <p>Because the key is <code>.gitignore</code>d and the data is committed, you get:</p> <ul> <li>At-rest encryption: the <code>.enc</code> file is opaque without the key</li> <li>Git sync: push/pull the encrypted file like any other tracked file</li> <li>Key separation: the key never leaves the machine unless you copy it</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#commands","level":2,"title":"Commands","text":"Command Purpose <code>ctx pad</code> List all entries (numbered 1-based) <code>ctx pad show N</code> Output raw text of entry N (no prefix, pipe-friendly) <code>ctx pad add \"text\"</code> Append a new entry <code>ctx pad rm ID [ID...]</code> Remove entries by stable ID (supports ranges: <code>3-5</code>) <code>ctx pad edit N \"text\"</code> Replace entry N with new text <code>ctx pad edit N --append \"text\"</code> Append text to the end of entry N <code>ctx pad edit N --prepend \"text\"</code> Prepend text to the beginning of entry N <code>ctx pad edit N --tag tagname</code> Add a tag to entry N <code>ctx pad add TEXT --file PATH</code> Ingest a file as a blob entry (TEXT is the label) <code>ctx pad show N --out PATH</code> Write decoded blob content to a file <code>ctx pad normalize</code> Reassign entry IDs as 1..N <code>ctx pad mv N M</code> Move entry from position N to position M <code>ctx pad resolve</code> Show both sides of a merge conflict for resolution <code>ctx pad import FILE</code> Bulk-import lines from a file (or stdin with <code>-</code>) <code>ctx pad import --blob DIR</code> Import directory files as blob entries <code>ctx pad export [DIR]</code> Export all blob entries to a directory as files <code>ctx pad merge FILE...</code> Merge entries from other scratchpad files into current <code>ctx pad --tag TAG</code> List entries filtered by tag (prefix with <code>~</code> to exclude) <code>ctx pad tags</code> List all tags with counts <code>ctx pad tags --json</code> List all tags with counts as JSON <p>All commands decrypt on read, operate on plaintext in memory, and re-encrypt on write. The key file is never printed to stdout.</p> <p>For blob entries, <code>--append</code>, <code>--prepend</code>, and <code>--tag</code> modify the label while preserving the blob data.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#examples","level":3,"title":"Examples","text":"<pre><code># Add a note\nctx pad add \"check DNS propagation after deploy\"\n\n# List everything\nctx pad\n#   1. check DNS propagation after deploy\n#   2. staging API key: sk-test-abc123\n\n# Show raw text (for piping)\nctx pad show 2\n# sk-test-abc123\n\n# Compose entries\nctx pad edit 1 --append \"$(ctx pad show 2)\"\n\n# Reorder\nctx pad mv 2 1\n\n# Clean up (IDs are stable; they don't shift when entries are deleted)\nctx pad rm 2\n</code></pre>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#tags","level":2,"title":"Tags","text":"<p>Entries can contain <code>#word</code> tags for lightweight categorization. Tags are convention-based: any <code>#word</code> token in an entry's text is a tag. No special syntax to add or remove them; use the existing <code>add</code> and <code>edit</code> commands.</p> <pre><code># Add tagged entries\nctx pad add \"check DNS propagation #later\"\nctx pad add \"deploy hotfix #urgent\"\nctx pad add \"review PR #later #ci\"\n\n# Filter by tag\nctx pad --tag later\n#   1. check DNS propagation #later\n#   3. review PR #later #ci\n\n# Exclude a tag\nctx pad --tag ~later\n#   2. deploy hotfix #urgent\n\n# Multiple filters (AND logic)\nctx pad --tag later --tag ci\n#   3. review PR #later #ci\n\n# List all tags with counts\nctx pad tags\n# ci       1\n# later    2\n# urgent   1\n\n# JSON output\nctx pad tags --json\n# [{\"tag\":\"ci\",\"count\":1},{\"tag\":\"later\",\"count\":2},{\"tag\":\"urgent\",\"count\":1}]\n\n# Add a tag to an existing entry\nctx pad edit 1 --tag done\n\n# Combine with other operations\nctx pad edit 1 --append \"checked\" --tag done\n\n# Remove a tag (replace entry text without the tag)\nctx pad edit 1 \"check DNS propagation\"\n</code></pre> <p>Entry IDs are stable; they don't shift when other entries are deleted, so <code>ctx pad rm 3</code> always targets the same entry. Use <code>ctx pad normalize</code> to reassign IDs as 1..N if gaps bother you. Tags are case-sensitive and support letters, digits, hyphens, and underscores (<code>#high-priority</code>, <code>#v2</code>, <code>#my_tag</code>).</p> <p>For blob entries, tags are extracted from the label only.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#bulk-import-and-export","level":2,"title":"Bulk Import and Export","text":"<p>Import lines from a file in bulk (each non-empty line becomes an entry):</p> <pre><code># Import from a file\nctx pad import notes.txt\n\n# Import from stdin\ngrep TODO *.go | ctx pad import -\n</code></pre> <p>Export all blob entries to a directory as files:</p> <pre><code># Export to a directory\nctx pad export ./ideas\n\n# Preview without writing\nctx pad export --dry-run\n\n# Overwrite existing files\nctx pad export --force ./backup\n</code></pre>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#merging-scratchpads","level":2,"title":"Merging Scratchpads","text":"<p>Combine entries from other scratchpad files into your current pad. Useful when merging work from parallel worktrees, other machines, or teammates:</p> <pre><code># Merge from a worktree's encrypted scratchpad\nctx pad merge worktree/.context/scratchpad.enc\n\n# Merge from multiple sources (encrypted and plaintext)\nctx pad merge pad-a.enc notes.md\n\n# Merge a foreign encrypted pad using its key\nctx pad merge --key /other/.ctx.key foreign.enc\n\n# Preview without writing\nctx pad merge --dry-run pad-a.enc pad-b.md\n</code></pre> <p>Each input file is auto-detected as encrypted or plaintext: decryption is attempted first, and on failure the file is parsed as plain text. Entries are deduplicated by exact content, so running merge twice with the same file is safe.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#file-blobs","level":2,"title":"File Blobs","text":"<p>The scratchpad can store small files (up to 64 KB) as blob entries. Files are base64-encoded and stored with a human-readable label.</p> <pre><code># Ingest a file: first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# Listing shows label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n\n# Extract to a file\nctx pad show 2 --out ./recovered.yaml\n\n# Or print decoded content to stdout\nctx pad show 2\n</code></pre> <p>Blob entries are encrypted identically to text entries. The internal format is <code>label:::base64data</code>: You never need to construct this manually.</p> Constraint Value Max file size (pre-encoding) 64 KB Storage format <code>label:::base64(content)</code> Display <code>label [BLOB]</code> in listings <p>When Should You Use Blobs</p> <p>Blobs are for small files you want encrypted and portable: config snippets, key fragments, deployment manifests, test fixtures. For anything larger than 64 KB, use the filesystem directly.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#using-with-ai","level":2,"title":"Using with AI","text":"<p>Use Natural Language</p> <p>As in many <code>ctx</code> features, the <code>ctx</code> scratchpad can also be used with natural langauge. You don't have to memorize the CLI commands.</p> <p>CLI gives you \"precision\", whereas natural language gives you flow.</p> <p>The <code>/ctx-pad</code> skill maps natural language to <code>ctx pad</code> commands. You don't need to remember the syntax:</p> You say What happens \"jot down: check DNS after deploy\" <code>ctx pad add \"check DNS after deploy\"</code> \"show my scratchpad\" <code>ctx pad</code> \"delete the third entry\" <code>ctx pad rm 3</code> \"update entry 2 to include the new endpoint\" <code>ctx pad edit 2 \"...\"</code> \"move entry 4 to the top\" <code>ctx pad mv 4 1</code> \"import my notes from notes.txt\" <code>ctx pad import notes.txt</code> \"export all blobs to ./backup\" <code>ctx pad export ./backup</code> \"merge the scratchpad from the worktree\" <code>ctx pad merge worktree/.context/scratchpad.enc</code> <p>The skill handles the translation. You describe what you want in plain English; the agent picks the right command.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#worktrees","level":2,"title":"Worktrees","text":"<p>The encryption key lives at <code>~/.ctx/.ctx.key</code> (outside the project directory). Because all worktrees on the same machine share this path, <code>ctx pad</code> works in worktrees automatically - no special setup needed.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#key-distribution","level":2,"title":"Key Distribution","text":"<p>The encryption key (<code>~/.ctx/.ctx.key</code>) stays on the machine where it was generated. <code>ctx</code> never transmits it.</p> <p>To share the scratchpad across machines:</p> <ol> <li>Copy the key manually: <code>scp</code>, USB drive, password manager.</li> <li>Push/pull the <code>.enc</code> file via git as usual.</li> <li>Both machines can now read and write the same scratchpad.</li> </ol> <p>Never Commit the Key</p> <p>The key is <code>.gitignore</code>d by default. If you override this, anyone with repo access can decrypt your scratchpad. </p> <p>Treat the key like an SSH private key.</p> <p>See the Syncing Scratchpad Notes Across Machines recipe for a step-by-step walkthrough.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#plaintext-override","level":2,"title":"Plaintext Override","text":"<p>For projects where encryption is unnecessary, disable it in <code>.ctxrc</code>:</p> <pre><code>scratchpad_encrypt: false\n</code></pre> <p>In plaintext mode:</p> <ul> <li>Entries are stored in <code>.context/scratchpad.md</code> instead of <code>.enc</code>.</li> <li>No key is generated or required.</li> <li>All <code>ctx pad</code> commands work identically.</li> <li>The file is human-readable and diffable.</li> </ul> <p>When Should You Use Plaintext</p> <p>Plaintext mode is useful for non-sensitive projects, solo work where encryption adds friction, or when you want scratchpad entries visible in <code>git diff</code>.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#when-should-you-use-scratchpad-versus-context-files","level":2,"title":"When Should You Use Scratchpad versus Context Files","text":"Use case Where it goes Temporary reminders (\"check X after deploy\") Scratchpad Working values during debugging Scratchpad Sensitive tokens or API keys (short-term) Scratchpad Quick notes that don't fit anywhere else Scratchpad Items that are not directly relevant to the project Scratchpad Things that you want to keep near, but also hidden Scratchpad Work items with completion tracking <code>TASKS.md</code> Trade-offs with rationale <code>DECISIONS.md</code> Reusable lessons with context/lesson/application <code>LEARNINGS.md</code> Codified patterns and standards <code>CONVENTIONS.md</code> <p>Rule of thumb: </p> <ul> <li>If it needs structure or will be referenced months later, use   a context file (i.e. <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>TASKS.md</code>). </li> <li>If it is working memory for the current session or week, use    the scratchpad.</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#see-also","level":2,"title":"See Also","text":"<ul> <li>Syncing Scratchpad Notes Across Machines:   Key distribution, push/pull workflow, merge conflict resolution</li> <li>Using the Scratchpad:   Natural language examples, blob workflow, when to use scratchpad vs context files</li> <li>Context Files: Format and conventions for all   <code>.context/</code> files</li> <li>Security: Trust model and permission hygiene</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/session-journal/","level":1,"title":"Session Journal","text":"<p>Important Security Note</p> <p>Session journals contain sensitive data such as file contents, commands, API keys, internal discussions,  error messages with stack traces, and more. </p> <p>The <code>.context/journal-site/</code> and <code>.context/journal-obsidian/</code> directories MUST be <code>.gitignore</code>d.</p> <ul> <li>DO NOT host your journal publicly.</li> <li>DO NOT commit your journal files to version control.</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#browse-your-session-history","level":2,"title":"Browse Your Session History","text":"<p><code>ctx</code>'s Session Journal turns your AI coding sessions into a browsable,  searchable, and editable archive.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#quick-start","level":2,"title":"Quick Start","text":"<p>After using <code>ctx</code> for a couple of sessions, you can generate a  journal site with:</p> <pre><code># Import all sessions to markdown\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n</code></pre> <p>Then open http://localhost:8000 to browse your sessions.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#what-you-get","level":2,"title":"What You Get","text":"<p>The Session Journal gives you:</p> <ul> <li>Browsable history: Navigate through all your AI sessions by date</li> <li>Full conversations: See every message, tool use, and result</li> <li>Token usage: Track how many tokens each session consumed</li> <li>Search: Find sessions by content, project, or date</li> <li>Dark mode: Easy on the eyes for late-night archaeology</li> </ul> <p>Each session page includes the following sections:</p> Section Content Metadata Date, time, duration, model, project, git branch Summary Space for your notes (editable) Tool Usage Which tools were used and how often Conversation Full transcript with timestamps","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#1-import-sessions","level":3,"title":"1. Import Sessions","text":"<pre><code># Import all sessions from current project (only new files)\nctx journal import --all\n\n# Import sessions from all projects\nctx journal import --all --all-projects\n\n# Import a specific session by ID (always writes)\nctx journal import abc123\n\n# Preview what would be imported\nctx journal import --all --dry-run\n\n# Re-import existing (regenerates conversation, preserves YAML frontmatter)\nctx journal import --all --regenerate\n\n# Discard frontmatter during regeneration\nctx journal import --all --regenerate --keep-frontmatter=false -y\n</code></pre> <p>Imported sessions go to <code>.context/journal/</code> as editable Markdown files.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#2-generate-the-site","level":3,"title":"2. Generate the Site","text":"<pre><code># Generate site structure\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n</code></pre> <p>The site is generated in <code>.context/journal-site/</code> by default.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#3-browse-and-search","level":3,"title":"3. Browse and Search","text":"<p>Open http://localhost:8000 after running <code>--serve</code>.</p> <ul> <li>Use the sidebar to navigate by date</li> <li>Use search (<code>/</code> key) to find specific content</li> <li>Click any session to see the full conversation</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#editing-sessions","level":2,"title":"Editing Sessions","text":"<p>Imported sessions are plain Markdown in <code>.context/journal/</code>. You can:</p> <ul> <li>Add summaries: Fill in the <code>## Summary</code> section</li> <li>Add notes: Insert your own commentary anywhere</li> <li>Highlight key moments: Use Markdown formatting</li> <li>Delete noise: Remove irrelevant tool outputs</li> </ul> <p>After editing, regenerate the site:</p> <pre><code>ctx journal site --serve\n</code></pre> Safe by Default <p>Running <code>ctx journal import --all</code> only imports new sessions. Existing files are skipped entirely (your edits and enrichments are never touched).</p> <p>Use <code>--regenerate</code> to re-import existing files. Conversation content is regenerated, but YAML frontmatter (topics, type, outcome, etc.) is preserved. You'll be prompted before any existing files are overwritten; add <code>-y</code> to skip the prompt.</p> <p>Use <code>--keep-frontmatter=false</code> to discard enriched frontmatter during regeneration.</p> <p>Locked entries (via <code>ctx journal lock</code>) are always skipped, regardless of flags. If you prefer to add <code>locked: true</code> to frontmatter during enrichment, run <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#large-sessions","level":2,"title":"Large Sessions","text":"<p>Sessions with many messages (200+) are automatically split into multiple parts  for better browser performance. Navigation links connect the parts:</p> <pre><code>session-abc123.md      (Part 1 of 3)\nsession-abc123-p2.md   (Part 2 of 3)\nsession-abc123-p3.md   (Part 3 of 3)\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#suggestion-sessions","level":2,"title":"Suggestion Sessions","text":"<p>Claude Code generates \"suggestion\" sessions for auto-complete prompts. These are separated in the index under a \"Suggestions\" section to keep your main  session list focused.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enriching-journal-entries","level":2,"title":"Enriching Journal Entries","text":"<p>Raw imported sessions contain basic metadata (date, time, project) but lack the structured information needed for effective search, filtering, and analysis. Journal enrichment adds semantic metadata that transforms a flat archive into a searchable knowledge base.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#why-enrich","level":3,"title":"Why Enrich?","text":"<p>Without enrichment, you have timestamps and raw conversations. With enrichment:</p> <ul> <li>Find sessions by topic: \"Show me all auth-related sessions\"</li> <li>Filter by outcome: \"What did I abandon vs complete?\"</li> <li>Track technology usage: \"When did I last work with PostgreSQL?\"</li> <li>Identify key files: Jump directly to the files discussed</li> <li>Get summaries: Understand what happened without reading transcripts</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-frontmatter-schema","level":3,"title":"The Frontmatter Schema","text":"<p>Enriched entries begin with YAML frontmatter:</p> <pre><code>---\ntitle: \"Implement caching layer\"\ndate: 2026-01-27\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/cache/memory.go\n---\n</code></pre> Field Required Description <code>title</code> Yes Descriptive title (not the session slug) <code>date</code> Yes Session date (YYYY-MM-DD) <code>type</code> Yes Session type (see below) <code>outcome</code> Yes How the session ended (see below) <code>topics</code> No Subject areas discussed <code>technologies</code> No Languages, databases, frameworks <code>libraries</code> No Specific packages or libraries used <code>key_files</code> No Important files created or modified <p>Type values:</p> Type When to use <code>feature</code> Building new functionality <code>bugfix</code> Fixing broken behavior <code>refactor</code> Restructuring without behavior change <code>exploration</code> Research, learning, experimentation <code>debugging</code> Investigating issues <code>documentation</code> Writing docs, comments, README <p>Outcome values:</p> Outcome Meaning <code>completed</code> Goal achieved <code>partial</code> Some progress, work continues <code>abandoned</code> Stopped pursuing this approach <code>blocked</code> Waiting on external dependency","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-ctx-journal-enrich","level":3,"title":"Using <code>/ctx-journal-enrich</code>","text":"<p>The <code>/ctx-journal-enrich</code> skill automates enrichment by analyzing conversation content and proposing metadata.</p> <p>Invoke by session identifier:</p> <pre><code>/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-01-24\n/ctx-journal-enrich 76fe2ab9\n</code></pre> <p>The skill will:</p> <ol> <li>Check if locked - locked entries are skipped (same as export);</li> <li>Find the matching journal file;</li> <li>Read and analyze the conversation;</li> <li>Propose frontmatter (type, topics, outcome, technologies);</li> <li>Generate a 2-3 sentence summary;</li> <li>Extract decisions, learnings, and tasks mentioned;</li> <li>Show a diff and ask for confirmation before writing.</li> </ol>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#before-and-after","level":3,"title":"Before and After","text":"<p>Before enrichment:</p> <pre><code># twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\n[Add your summary of this session]\n\n## Conversation\n...\n</code></pre> <p>After enrichment:</p> <pre><code>---\ntitle: \"Add Redis caching to API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n\n# twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\nImplemented Redis-based caching middleware for frequently accessed API endpoints.\nAdded cache invalidation on writes and configurable TTL per route. Reduced\n the average response time from 200ms to 15ms for cached routes.\n\n## Decisions\n\n* Used Redis over in-memory cache for horizontal scaling\n* Chose per-route TTL configuration over global setting\n\n## Learnings\n\n* Redis WATCH command prevents race conditions during cache invalidation\n\n## Conversation\n...\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enrichment-and-site-generation","level":3,"title":"Enrichment and Site Generation","text":"<p>The journal site generator uses enriched metadata for better organization:</p> <ul> <li>Titles appear in navigation instead of slugs</li> <li>Summaries provide context in the index</li> <li>Topics enable filtering (when using search)</li> <li>Types allow grouping by work category</li> </ul> <p>Future improvements will add topic-based navigation and outcome filtering to the generated site.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#batch-enrichment","level":3,"title":"Batch Enrichment","text":"<p>To enrich multiple sessions, process them one at a time:</p> <pre><code># List unenriched sessions (those without frontmatter)\ngrep -L \"^---$\" .context/journal/*.md | head -10\n</code></pre> <p>Then run <code>/ctx-journal-enrich</code> on each. Enrichment is intentionally interactive to ensure accuracy.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#obsidian-vault-export","level":2,"title":"Obsidian Vault Export","text":"<p>If you use Obsidian for knowledge management, you can export your journal as an Obsidian vault instead of (or alongside) the static site:</p> <pre><code>ctx journal obsidian\n</code></pre> <p>This generates a vault in <code>.context/journal-obsidian/</code> with:</p> <ul> <li>Wikilinks (<code>[[target|display]]</code>) instead of Markdown links</li> <li>MOC pages (Map of Content) for topics, key files, and session types</li> <li>Related sessions footer per entry: links to entries sharing the same topics</li> <li>Transformed frontmatter: <code>topics</code> renamed to <code>tags</code> (Obsidian-recognized),   <code>aliases</code> added from title for search</li> <li>Graph-optimized structure: MOC hubs and cross-linked entries create dense   graph connectivity</li> </ul> <p>To use: open the output directory in Obsidian (\"Open folder as vault\").</p> <pre><code># Custom output directory\nctx journal obsidian --output ~/vaults/ctx-journal\n</code></pre> <p>Static Site vs Obsidian Vault</p> <p>Use <code>ctx journal site</code> when you want a web-browsable archive with search and dark mode. Use <code>ctx journal obsidian</code> when you want graph view, backlinks, and tag-based navigation inside Obsidian. Both use the same enriched source entries: you can generate both.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#full-pipeline","level":2,"title":"Full Pipeline","text":"<p>The complete journal workflow has four stages. Each is idempotent: safe to re-run, and stages skip already-processed entries.</p> <pre><code>import → enrich → rebuild\n</code></pre> Stage Command / Skill What it does Skips if Import <code>ctx journal import --all</code> Converts session JSONL to Markdown File already exists (safe default) Enrich <code>/ctx-journal-enrich</code> Adds frontmatter, summaries, topics Frontmatter already present Rebuild <code>ctx journal site --build</code> Generates static HTML site (never) Obsidian <code>ctx journal obsidian</code> Generates Obsidian vault with wikilinks (never) <p>One-Command Pipeline</p> <p><code>/ctx-journal-enrich-all</code> handles import automatically - it detects unimported sessions and imports them before enriching. You only need to run <code>ctx journal site --build</code> afterward.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-make-journal","level":3,"title":"Using <code>make journal</code>","text":"<p>If your project includes <code>Makefile.ctx</code> (deployed by <code>ctx init</code>), the first and last stages are combined:</p> <pre><code>make journal           # import + rebuild\n</code></pre> <p>After it runs, it reminds you to enrich in Claude Code:</p> <pre><code>Next steps (in Claude Code):\n  /ctx-journal-enrich-all # imports if needed + adds metadata per entry\n\nThen re-run: make journal\n</code></pre> <p>Rendering Issues?</p> <p>If individual entries have rendering problems (broken fences, malformed lists), check the programmatic normalization in the import pipeline. Most cases are handled automatically during <code>ctx journal import</code>.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#tips","level":2,"title":"Tips","text":"<p>Daily workflow: <pre><code># Import, browse, then enrich in Claude Code\nmake journal && make journal-serve\n# Then in Claude Code: /ctx-journal-enrich <session>\n</code></pre></p> <p>After a productive session: <pre><code># Import just that session and add notes\nctx journal import <session-id>\n# Edit .context/journal/<session>.md\n# Regenerate: ctx journal site\n</code></pre></p> <p>Searching across all sessions: <pre><code># Use grep on the journal directory\ngrep -r \"authentication\" .context/journal/\n</code></pre></p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#requirements","level":2,"title":"Requirements","text":"Use <code>pipx</code> for <code>zensical</code> <p><code>pip install zensical</code> may install a non-functional stub on system Python. Using <code>venv</code> has other issues too.</p> <p>These issues especially happen on Mac OSX.</p> <p>Use <code>pipx install zensical</code>, which creates an isolated environment and handles Python version management automatically.</p> <p>The journal site uses zensical for static site generation:</p> <pre><code>pipx install zensical\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx journal</code>: Session discovery and listing</li> <li><code>ctx journal site</code>: Static site generation</li> <li><code>ctx journal obsidian</code>: Obsidian vault export</li> <li>Context Files: The <code>.context/</code> directory structure</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/skills/","level":1,"title":"Skills","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skills","level":2,"title":"Skills","text":"<p>Skills are slash commands that run inside your AI assistant (e.g., <code>/ctx-next</code>), as opposed to CLI commands that run in your terminal (e.g., <code>ctx status</code>). </p> <p>Skills give your agent structured workflows: It knows what to read, what to  run, and when to ask. Most wrap one or more <code>ctx</code> CLI commands with  opinionated behavior on top. </p> <p>Skills Are Best Used Conversationally</p> <p>The beauty of <code>ctx</code> is that it's designed to be intuitive and  conversational, allowing you to interact with your AI assistant  naturally. That's why you don't have to memorize many of these skills.</p> <p>See the Prompting Guide for natural-language  triggers that invoke these skills conversationally.</p> <p>However, when you need a more precise control, you have the option to invoke the relevant skills directly.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#all-skills","level":2,"title":"All Skills","text":"Skill Description Type <code>/ctx-remember</code> Recall project context and present structured readback user-invocable <code>/ctx-wrap-up</code> End-of-session context persistence ceremony user-invocable <code>/ctx-status</code> Show context summary with interpretation user-invocable <code>/ctx-agent</code> Load full context packet for AI consumption user-invocable <code>/ctx-next</code> Suggest 1-3 concrete next actions with rationale user-invocable <code>/ctx-commit</code> Commit with integrated context persistence user-invocable <code>/ctx-reflect</code> Pause and reflect on session progress user-invocable <code>/ctx-task-add</code> Add actionable task to TASKS.md user-invocable <code>/ctx-decision-add</code> Record architectural decision with rationale user-invocable <code>/ctx-learning-add</code> Record gotchas and lessons learned user-invocable <code>/ctx-convention-add</code> Record coding convention for consistency user-invocable <code>/ctx-archive</code> Archive completed tasks from TASKS.md user-invocable <code>/ctx-pad</code> Manage encrypted scratchpad entries user-invocable <code>/ctx-history</code> Browse and import AI session history user-invocable <code>/ctx-journal-enrich</code> Enrich single journal entry with metadata user-invocable <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich user-invocable <code>/ctx-blog</code> Generate blog post draft from project activity user-invocable <code>/ctx-blog-changelog</code> Generate themed blog post from a commit range user-invocable <code>/ctx-consolidate</code> Consolidate redundant learnings or decisions user-invocable <code>/ctx-drift</code> Detect and fix context drift user-invocable <code>/ctx-prompt</code> Apply, list, and manage saved prompt templates user-invocable <code>/ctx-prompt-audit</code> Analyze prompting patterns for improvement user-invocable <code>/ctx-link-check</code> Audit docs for dead internal and external links user-invocable <code>/ctx-permission-sanitize</code> Audit Claude Code permissions for security risks user-invocable <code>/ctx-brainstorm</code> Structured design dialogue before implementation user-invocable <code>/ctx-spec</code> Scaffold a feature spec from a project template user-invocable <code>/ctx-plan-import</code> Import Claude Code plan files into project specs user-invocable <code>/ctx-implement</code> Execute a plan step-by-step with verification user-invocable <code>/ctx-loop</code> Generate autonomous loop script user-invocable <code>/ctx-worktree</code> Manage git worktrees for parallel agents user-invocable <code>/ctx-architecture</code> Build and maintain architecture maps user-invocable <code>/ctx-architecture-failure-analysis</code> Adversarial failure analysis for correctness bugs user-invocable <code>/ctx-remind</code> Manage session-scoped reminders user-invocable <code>/ctx-doctor</code> Troubleshoot <code>ctx</code> behavior with health checks and event analysis user-invocable <code>/ctx-skill-audit</code> Audit skills against Anthropic prompting best practices user-invocable <code>/ctx-skill-create</code> Create, improve, and test skills user-invocable <code>/ctx-pause</code> Pause context hooks for this session user-invocable <code>/ctx-resume</code> Resume context hooks after a pause user-invocable <code>/ctx-kb-ingest</code> Editorial KB pass (topic-page / triage / evidence-only) user-invocable <code>/ctx-kb-ask</code> Q&A grounded in the KB; refuses to web-jump user-invocable <code>/ctx-kb-site-review</code> Mechanical KB structural audit user-invocable <code>/ctx-kb-ground</code> Re-ground the KB against listed external sources user-invocable <code>/ctx-kb-note</code> Park a finding in <code>ingest/findings.md</code> user-invocable <code>/ctx-handover</code> Handover step delegated by <code>/ctx-wrap-up</code>; folds postdated closeouts sub-mechanism","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-lifecycle","level":2,"title":"Session Lifecycle","text":"<p>Skills for starting, running, and ending a productive session.</p> <p>Session Ceremonies</p> <p>Two skills in this group are ceremony skills: <code>/ctx-remember</code> (session start) and <code>/ctx-wrap-up</code> (session end). Unlike other skills that work conversationally, these should be invoked as explicit slash commands for completeness. See Session Ceremonies.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remember","level":3,"title":"<code>/ctx-remember</code>","text":"<p>Recall project context and present a structured readback. Ceremony skill: invoke explicitly at session start.</p> <p>Wraps: <code>ctx agent --budget 4000</code>, <code>ctx journal source --limit 3</code>, reads TASKS.md, DECISIONS.md, LEARNINGS.md</p> <p>See also: Session Ceremonies, The Complete Session</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-status","level":3,"title":"<code>/ctx-status</code>","text":"<p>Show context summary (files, token budget, tasks, recent activity) with interpreted suggestions.</p> <p>Wraps: <code>ctx status [--verbose] [--json]</code></p> <p>See also: The Complete Session, <code>ctx status</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-agent","level":3,"title":"<code>/ctx-agent</code>","text":"<p>Load the full context packet optimized for AI consumption. Also runs automatically via the PreToolUse hook with cooldown.</p> <p>Wraps: <code>ctx agent [--budget] [--format] [--cooldown] [--session]</code></p> <p>See also: The Complete Session, <code>ctx agent</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-next","level":3,"title":"<code>/ctx-next</code>","text":"<p>Suggest 1-3 concrete next actions ranked by priority, momentum, and unblocked status.</p> <p>Wraps: reads TASKS.md, <code>ctx journal source --limit 3</code></p> <p>See also: The Complete Session, Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-commit","level":3,"title":"<code>/ctx-commit</code>","text":"<p>Commit code with integrated context persistence: pre-commit checks, staged files, Co-Authored-By trailer, and a post-commit prompt to capture decisions and learnings.</p> <p>Wraps: <code>git add</code>, <code>git commit</code>, optionally chains to <code>/ctx-decision-add</code> and <code>/ctx-learning-add</code></p> <p>See also: The Complete Session</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-reflect","level":3,"title":"<code>/ctx-reflect</code>","text":"<p>Pause and reflect on session progress. Walks through a checklist of learnings, decisions, task completions, and session notes to persist.</p> <p>Wraps: chains to <code>ctx learning add</code>, <code>ctx decision add</code>, manual TASKS.md updates</p> <p>See also: The Complete Session, Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-wrap-up","level":3,"title":"<code>/ctx-wrap-up</code>","text":"<p>End-of-session context persistence ceremony. Gathers signal from git diff, recent commits, and conversation themes. Proposes candidates (learnings, decisions, conventions, tasks) with complete structured fields for user approval, then persists via <code>ctx add</code>. Offers <code>/ctx-commit</code> if uncommitted changes remain. Always delegates to <code>/ctx-handover</code> as its final step, regardless of whether <code>.context/kb/</code> exists: KB presence only affects what gets folded into the handover, not whether it is written. Ceremony skill: invoke explicitly at session end.</p> <p>Trigger phrases: \"let's wrap up\", \"save context\", \"save state\", \"leave a handover\", \"before I go\", \"stepping away\", \"end of session\"</p> <p>Wraps: <code>git diff --stat</code>, <code>git log</code>, <code>ctx learning add</code>, <code>ctx decision add</code>, <code>ctx convention add</code>, <code>ctx task add</code>, chains to <code>/ctx-commit</code>, delegates to <code>/ctx-handover</code></p> <p>See also: Session Ceremonies, The Complete Session, <code>/ctx-handover</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#context-persistence","level":2,"title":"Context Persistence","text":"<p>Skills for recording work artifacts: tasks, decisions, learnings, conventions: into <code>.context/</code> files.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-task-add","level":3,"title":"<code>/ctx-task-add</code>","text":"<p>Add an actionable task with optional priority and phase section.</p> <p>Wraps: <code>ctx task add \"description\" [--priority high|medium|low] --session-id ID --branch BR --commit HASH</code></p> <p>See also: Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-decision-add","level":3,"title":"<code>/ctx-decision-add</code>","text":"<p>Record an architectural decision with context, rationale, and consequence. Supports Y-statement (lightweight) and full ADR formats.</p> <p>Wraps: <code>ctx decision add \"title\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id ID --branch BR --commit HASH</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-learning-add","level":3,"title":"<code>/ctx-learning-add</code>","text":"<p>Record a project-specific gotcha, bug, or unexpected behavior. Filters for insights that are searchable, project-specific, and required real effort to discover.</p> <p>Wraps: <code>ctx learning add \"title\" --context \"...\" --lesson \"...\" --application \"...\" --session-id ID --branch BR --commit HASH</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-convention-add","level":3,"title":"<code>/ctx-convention-add</code>","text":"<p>Record a coding convention that should be standardized across sessions. Targets patterns seen 2-3+ times.</p> <p>Wraps: <code>ctx convention add \"rule\" --section \"Name\"</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-archive","level":3,"title":"<code>/ctx-archive</code>","text":"<p>Archive completed tasks from TASKS.md to a timestamped file in <code>.context/archive/</code>. Preserves phase headers for traceability.</p> <p>Wraps: <code>ctx task archive [--dry-run]</code></p> <p>See also: Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#scratchpad","level":2,"title":"Scratchpad","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pad","level":3,"title":"<code>/ctx-pad</code>","text":"<p>Manage the encrypted scratchpad: add, remove, edit, and reorder one-liner notes. Encrypted at rest with AES-256-GCM.</p> <p>Wraps: <code>ctx pad</code>, <code>ctx pad add</code>, <code>ctx pad rm</code>, <code>ctx pad edit</code>, <code>ctx pad mv</code>, <code>ctx pad import</code>, <code>ctx pad export</code>, <code>ctx pad merge</code></p> <p>See also: Scratchpad, Using the Scratchpad</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#journal-history","level":2,"title":"Journal & History","text":"<p>Skills for browsing, exporting, and enriching your AI session history into a structured journal.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-history","level":3,"title":"<code>/ctx-history</code>","text":"<p>Browse, inspect, and import AI session history. List recent sessions, show details by slug or ID, and import to <code>.context/journal/</code>.</p> <p>Wraps: <code>ctx journal source</code>, <code>ctx journal source --show</code>, <code>ctx journal import</code></p> <p>See also: Browsing and Enriching Past Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich","level":3,"title":"<code>/ctx-journal-enrich</code>","text":"<p>Enrich a single journal entry with YAML frontmatter: title, type, outcome, topics, technologies, and summary. Shows diff before writing.</p> <p>Wraps: reads and edits <code>.context/journal/*.md</code> files</p> <p>See also: Browsing and Enriching Past Sessions, Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich-all","level":3,"title":"<code>/ctx-journal-enrich-all</code>","text":"<p>Full journal pipeline: imports unimported sessions first, then batch-enriches all unenriched entries. Filters out short sessions and continuations. Can spawn subagents for large backlogs.</p> <p>Wraps: <code>ctx journal import --all</code> + iterates <code>/ctx-journal-enrich</code></p> <p>See also: Browsing and Enriching Past Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#content-creation","level":2,"title":"Content Creation","text":"<p>Skills for turning project activity into publishable content.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog","level":3,"title":"<code>/ctx-blog</code>","text":"<p>Generate a blog post draft from recent project activity: git history, decisions, learnings, tasks, and journal entries. Requires a narrative arc (problem, approach, outcome).</p> <p>Wraps: reads <code>git log</code>, DECISIONS.md, LEARNINGS.md, TASKS.md, journal entries; writes to <code>docs/blog/</code></p> <p>See also: Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog-changelog","level":3,"title":"<code>/ctx-blog-changelog</code>","text":"<p>Generate a themed blog post from a commit range. Takes a starting commit and unifying theme, analyzes diffs and journal entries from that period.</p> <p>Wraps: <code>git log</code>, <code>git diff --stat</code>; writes to <code>docs/blog/</code></p> <p>See also: Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#auditing-health","level":2,"title":"Auditing & Health","text":"<p>Skills for detecting drift, auditing alignment, and improving prompt quality.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-consolidate","level":3,"title":"<code>/ctx-consolidate</code>","text":"<p>Consolidate redundant entries in LEARNINGS.md or DECISIONS.md. Groups overlapping entries by keyword similarity, presents candidates, and (with user approval) merges groups into denser combined entries. Originals are archived, not deleted.</p> <p>Wraps: reads LEARNINGS.md and DECISIONS.md, writes consolidated entries, archives originals, runs <code>ctx reindex</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-drift","level":3,"title":"<code>/ctx-drift</code>","text":"<p>Detect and fix context drift: stale paths, missing files, file age staleness, task accumulation, entry count warnings, and constitution violations via <code>ctx drift</code>. Also detects skill drift against canonical templates.</p> <p>Wraps: <code>ctx drift [--fix]</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-prompt-audit","level":3,"title":"<code>/ctx-prompt-audit</code>","text":"<p>Analyze recent prompting patterns to identify vague or ineffective prompts. Reviews 3-5 journal entries and suggests rewrites with positive observations.</p> <p>Wraps: reads <code>.context/journal/</code> entries</p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-doctor","level":3,"title":"<code>/ctx-doctor</code>","text":"<p>Troubleshoot <code>ctx</code> behavior. Runs structural health checks via <code>ctx doctor</code>, analyzes event log patterns via <code>ctx hook event</code>, and presents findings with suggested actions. The CLI provides the structural baseline; the agent adds semantic analysis of event patterns and correlations.</p> <p>Wraps: <code>ctx doctor --json</code>, <code>ctx hook event --json --last 100</code>, <code>ctx remind list</code>, <code>ctx hook message list</code>, reads <code>.ctxrc</code></p> <p>Trigger phrases: \"diagnose\", \"troubleshoot\", \"doctor\", \"health check\", \"why didn't my hook fire?\", \"hooks seem broken\", \"something seems off\"</p> <p>Graceful degradation: If <code>event_log</code> is not enabled, the skill still works but with reduced capability. It runs structural checks and notes: \"Enable <code>event_log: true</code> in <code>.ctxrc</code> for hook-level diagnostics.\"</p> <p>See also: Troubleshooting, <code>ctx doctor</code> CLI, <code>ctx hook event</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-link-check","level":3,"title":"<code>/ctx-link-check</code>","text":"<p>Scan all Markdown files under <code>docs/</code> for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, and skips localhost/example URLs.</p> <p>Wraps: Glob + Grep to scan, <code>curl</code> for external checks</p> <p>Trigger phrases: \"check links\", \"audit links\", \"any broken links?\", \"dead links\"</p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-permission-sanitize","level":3,"title":"<code>/ctx-permission-sanitize</code>","text":"<p>Audit <code>.claude/settings.local.json</code> for dangerous permissions across four risk categories: hook bypass (Critical), destructive commands (High), config injection vectors (High), and overly broad patterns (Medium). Reports findings by severity and offers specific fix actions with user confirmation.</p> <p>Wraps: reads <code>.claude/settings.local.json</code>, edits with confirmation</p> <p>Trigger phrases: \"audit permissions\", \"are my permissions safe?\", \"sanitize permissions\", \"check settings\"</p> <p>See also: Claude Code Permission Hygiene</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#planning-execution","level":2,"title":"Planning & Execution","text":"<p>Skills for structured design, implementation, and parallel agent workflows.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-brainstorm","level":3,"title":"<code>/ctx-brainstorm</code>","text":"<p>Transform raw ideas into clear, validated designs through structured dialogue before any implementation begins. Follows a gated process: understand context, clarify the idea (one question at a time), surface non-functional requirements, lock understanding with user confirmation, explore 2-3 design approaches with trade-offs, stress-test the chosen approach, and present the detailed design.</p> <p>Wraps: reads DECISIONS.md, relevant source files; chains to <code>/ctx-decision-add</code> for recording design choices</p> <p>Trigger phrases: \"let's brainstorm\", \"design this\", \"think through\", \"before we build\", \"what approach should we take?\"</p> <p>See also: <code>/ctx-spec</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-spec","level":3,"title":"<code>/ctx-spec</code>","text":"<p>Scaffold a feature spec from the project template and walk through each section with the user. Covers: problem, approach, happy path, edge cases, validation rules, error handling, interface, implementation, configuration, testing, and non-goals. Spends extra time on edge cases and error handling.</p> <p>Wraps: reads <code>specs/tpl/spec-template.md</code>, writes to <code>specs/</code>, optionally chains to <code>/ctx-task-add</code></p> <p>Trigger phrases: \"spec this out\", \"write a spec\", \"create a spec\", \"design document\"</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#-brief-path-flag","level":4,"title":"<code>--brief <path></code> flag","text":"<p>When invoked as <code>/ctx-spec --brief <path></code>, the skill treats the file at <code><path></code> as the authoritative source and skips the interactive Q&A. Use this when a prior <code>/ctx-plan</code> session produced a debated brief that already covers the design.</p> <p>The skill enforces this authority order when sources disagree:</p> <ol> <li>Frozen contracts in <code>docs/</code> (release notes, public CLI docs)</li> <li>Recorded decisions in <code>.context/DECISIONS.md</code></li> <li>The brief at <code><path></code></li> <li>Agent inference, only when 1 through 3 are silent, and    labeled <code>TBD</code> in the resulting spec so it stands out for    review.</li> </ol> <p>Light compression for clarity is allowed; new facts are not. Where the brief is silent, the spec writes <code>TBD</code> rather than filling the gap from inference. If the brief contradicts a frozen contract, the contradiction is surfaced to the user rather than silently followed.</p> <p>See also: <code>/ctx-brainstorm</code>, <code>/ctx-plan</code>, <code>/ctx-plan-import</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-plan-import","level":3,"title":"<code>/ctx-plan-import</code>","text":"<p>Import Claude Code plan files (<code>~/.claude/plans/*.md</code>) into the project's <code>specs/</code> directory. Lists plans with dates and H1 titles, supports filtering (<code>--today</code>, <code>--since</code>, <code>--all</code>), slugifies headings for filenames, and optionally creates tasks referencing each imported spec.</p> <p>Wraps: reads <code>~/.claude/plans/*.md</code>, writes to <code>specs/</code>, optionally chains to <code>/ctx-task-add</code></p> <p>See also: Importing Claude Code Plans, Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-implement","level":3,"title":"<code>/ctx-implement</code>","text":"<p>Execute a multi-step plan with build and test verification at each step. Loads a plan from a file or conversation context, breaks it into atomic steps, and checkpoints after every 3-5 steps.</p> <p>Wraps: reads plan file, runs verification commands (<code>go build</code>, <code>go test</code>, etc.)</p> <p>See also: Running an Unattended AI Agent</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-loop","level":3,"title":"<code>/ctx-loop</code>","text":"<p>Generate a ready-to-run shell script for autonomous AI iteration. Supports Claude Code, Aider, and generic tool templates with configurable completion signals.</p> <p>Wraps: <code>ctx loop [--tool] [--prompt] [--max-iterations] [--completion] [--output]</code></p> <p>See also: Autonomous Loops, Running an Unattended AI Agent</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-worktree","level":3,"title":"<code>/ctx-worktree</code>","text":"<p>Manage git worktrees for parallel agent development. Create sibling worktrees on dedicated branches, analyze task blast radius for grouping, and tear down with merge.</p> <p>Wraps: <code>git worktree add</code>, <code>git worktree list</code>, <code>git worktree remove</code>, <code>git merge</code></p> <p>See also: Parallel Agent Development with Git Worktrees</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture","level":3,"title":"<code>/ctx-architecture</code>","text":"<p>Build and maintain architecture maps incrementally. Creates or refreshes <code>ARCHITECTURE.md</code> (succinct project map, loaded at session start) and <code>DETAILED_DESIGN.md</code> (deep per-module reference, consulted on-demand). Coverage is tracked in <code>map-tracking.json</code> so each run extends the map rather than re-analyzing everything.</p> <p>Wraps: <code>ctx status</code>, <code>git log</code>, reads source files; writes <code>ARCHITECTURE.md</code>, <code>DETAILED_DESIGN.md</code>, <code>map-tracking.json</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture-failure-analysis","level":3,"title":"<code>/ctx-architecture-failure-analysis</code>","text":"<p>Adversarial failure analysis that generates falsifiable incident hypotheses against architecture artifacts. Hunts for correctness bugs that survive code review and tests: race conditions, ordering assumptions, cache staleness, error swallowing, ownership gaps, idempotency failures, state machine drift, and scaling cliffs.</p> <p>Requires <code>/ctx-architecture</code> artifacts as input. Reads <code>ARCHITECTURE.md</code>, <code>DETAILED_DESIGN*.md</code>, and <code>map-tracking.json</code>, then systematically applies 9 failure categories to every mutation point. Each finding carries an evidence standard (code path, trigger, failure path, silence reason, code evidence), a confidence level, and an explicit risk score. A mandatory challenge phase attempts to disprove each finding before it is accepted.</p> <p>Produces <code>.context/DANGER-ZONES.md</code> with ranked findings split into Critical (risk >= 7, silent/cascading) and Elevated tiers.</p> <p>Wraps: reads architecture artifacts, source code; writes <code>DANGER-ZONES.md</code>. Optionally uses GitNexus for blast radius and Gemini Search for cross-referencing known failure patterns.</p> <p>Relationship:</p> Skill Mode <code>/ctx-architecture</code> Map what exists <code>/ctx-architecture-enrich</code> Improve map fidelity <code>/ctx-architecture-failure-analysis</code> Generate falsifiable incident hypotheses","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remind","level":3,"title":"<code>/ctx-remind</code>","text":"<p>Manage session-scoped reminders via natural language. Translates user intent (\"remind me to refactor swagger\") into the corresponding <code>ctx remind</code> command. Handles date conversion for <code>--after</code> flags.</p> <p>Wraps: <code>ctx remind</code>, <code>ctx remind list</code>, <code>ctx remind dismiss</code></p> <p>See also: Session Reminders</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skill-authoring","level":2,"title":"Skill Authoring","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-audit","level":3,"title":"<code>/ctx-skill-audit</code>","text":"<p>Audit one or more skills against Anthropic prompting best practices. Checks audit dimensions: positive framing, motivation, phantom references, examples, subagent guards, scope, and descriptions. Reports findings by severity with concrete fix suggestions.</p> <p>Wraps: reads <code>internal/assets/claude/skills/*/SKILL.md</code> or <code>.claude/skills/*/SKILL.md</code>, references <code>anthropic-best-practices.md</code></p> <p>Trigger phrases: \"audit this skill\", \"check skill quality\", \"review the skills\", \"are our skills any good?\"</p> <p>See also: <code>/ctx-skill-create</code>, Contributing</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-create","level":3,"title":"<code>/ctx-skill-create</code>","text":"<p>Create, improve, and test skills. Guides the full lifecycle: capture intent, interview for edge cases, draft the SKILL.md, test with realistic prompts, review results with the user, and iterate. Applies core principles: the agent is already smart (only add what it does not know), the description is the trigger (make it specific and \"pushy\"), and explain the why instead of rigid directives.</p> <p>Wraps: reads/writes <code>.claude/skills/</code> and <code>internal/assets/claude/skills/</code></p> <p>Trigger phrases: \"create a skill\", \"turn this into a skill\", \"make a slash command\", \"this should be a skill\", \"improve this skill\", \"the skill isn't triggering\"</p> <p>See also: Contributing</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-control","level":2,"title":"Session Control","text":"<p>Skills for controlling hook behavior during a session.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pause","level":3,"title":"<code>/ctx-pause</code>","text":"<p>Pause all context nudge and reminder hooks for the current session. Security hooks still fire. Use for quick investigations or tasks that don't need ceremony overhead.</p> <p>Wraps: <code>ctx hook pause</code></p> <p>Trigger phrases: \"pause <code>ctx</code>\", \"pause context\", \"stop the nudges\", \"quiet mode\"</p> <p>See also: Pausing Context Hooks</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-resume","level":3,"title":"<code>/ctx-resume</code>","text":"<p>Resume context hooks after a pause. Restores normal nudge, reminder, and ceremony behavior. Silent no-op if not paused.</p> <p>Wraps: <code>ctx hook resume</code></p> <p>Trigger phrases: \"resume <code>ctx</code>\", \"resume context\", \"turn nudges back on\", \"unpause\"</p> <p>See also: Pausing Context Hooks</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"<p>Skills for the editorial knowledge-ingestion pipeline. Active when <code>.context/kb/</code> exists (laid down by <code>ctx init</code>). The pipeline gives you evidence-tracked knowledge with confidence bands, folder-shaped topic pages, a source-coverage state machine, and per-session handovers that fold postdated closeouts.</p> <p>See the Build a Knowledge Base recipe for the full workflow. The editorial constitution lives at <code>.context/ingest/KB-RULES.md</code>.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ingest","level":3,"title":"<code>/ctx-kb-ingest</code>","text":"<p>Mode-aware editorial pass. Declares its pass-mode (<code>topic-page</code> / <code>triage</code> / <code>evidence-only</code>) up front, scans the source-coverage ledger for adjacent incomplete topics, synthesizes prose into <code>.context/kb/topics/<slug>/index.md</code>, mints <code>EV-###</code> rows in <code>evidence-index.md</code>, runs a four-invariant completion circuit breaker, and writes a closeout under <code>.context/ingest/closeouts/</code>. Refuses on empty input.</p> <p>Wraps: <code>ctx kb ingest</code>, <code>ctx kb topic new</code>, the writer packages under <code>internal/write/kb/</code>.</p> <p>Trigger phrases: \"ingest the transcripts\", \"pull this into the kb\", \"add evidence from\"</p> <p>See also: Build a Knowledge Base, Typical KB Session, <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ask","level":3,"title":"<code>/ctx-kb-ask</code>","text":"<p>Q&A grounded in the KB. Cites <code>EV-###</code> rows; refuses to web-jump. When the KB cannot answer, opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> rather than inventing. Refuses on empty question.</p> <p>Wraps: <code>ctx kb ask</code>, reads <code>.context/kb/*.md</code></p> <p>Trigger phrases: \"does the kb say\", \"according to evidence\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-site-review","level":3,"title":"<code>/ctx-kb-site-review</code>","text":"<p>Mechanical structural audit. Coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, refuses judgment calls that require evidence (those go through ingest).</p> <p>Wraps: <code>ctx kb site-review</code></p> <p>Trigger phrases: \"audit the kb\", \"check kb for rot\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ground","level":3,"title":"<code>/ctx-kb-ground</code>","text":"<p>External re-grounding pass. Reads <code>.context/ingest/grounding-sources.md</code> and refreshes each listed source. Refuses cleanly when the file is absent or empty.</p> <p>Wraps: <code>ctx kb ground</code></p> <p>Trigger phrases: \"re-ground the kb\", \"check upstream\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-note","level":3,"title":"<code>/ctx-kb-note</code>","text":"<p>Lightweight capture into <code>.context/ingest/findings.md</code>. Never writes to a topic page or <code>evidence-index.md</code>. Use for parking findings the next ingest pass should absorb.</p> <p>Wraps: <code>ctx kb note \"<text>\"</code></p> <p>Trigger phrases: \"drop a note\", \"park this finding\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-handover","level":3,"title":"<code>/ctx-handover</code>","text":"<p>Per-session handover artifact writer; the sub-mechanism that <code>/ctx-wrap-up</code> delegates to as its final step. Collects <code>--summary</code> (past tense) and <code>--next</code> (future tense, specific) and calls <code>ctx handover write</code>. Writes the handover to <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). Folds postdated closeouts into a <code>## Folded closeouts</code> section and physically archives the source closeouts under <code>.context/archive/closeouts/</code> (closeouts are append-never-rewrite; archival moves bytes but does not modify them). <code>--no-fold</code> skips the fold for mid-session checkpoints.</p> <p>Mandatory tail of <code>/ctx-wrap-up</code>. Direct invocation is reserved for <code>--no-fold</code> mid-session checkpoints and recovery after an aborted session.</p> <p>Wraps: <code>ctx handover write <title> --summary X --next Y</code></p> <p>See also: <code>/ctx-wrap-up</code>, Typical KB Session, Recover an Aborted KB Session, <code>ctx handover</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#project-specific-skills","level":2,"title":"Project-Specific Skills","text":"<p>The <code>ctx</code> plugin ships the skills listed above. Teams can add their own project-specific skills to <code>.claude/skills/</code> in the project root: These are separate from plugin-shipped skills and are scoped to the project.</p> <p>Project-specific skills follow the same format and are invoked the same way.</p> <p>Custom skills are not covered in this reference.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/versions/","level":1,"title":"Version History","text":"","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#version-history","level":2,"title":"Version History","text":"<p>Documentation snapshots for each release. </p> <p>Tap the corresponding view docs to view the docs as they were at that release.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.8.0 2026-03-23 view docs v0.6.0 2026-02-16 view docs v0.3.0 2026-02-07 view docs v0.2.0 2026-02-01 view docs v0.1.2 2026-01-27 view docs v0.1.1 2026-01-26 view docs v0.1.0 2026-01-25 view docs","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v080-the-architecture-release","level":3,"title":"<code>v0.8.0</code>: The Architecture Release","text":"<p>MCP server for tool-agnostic AI integration. Memory bridge connecting Claude Code auto-memory to <code>.context/</code>. Complete CLI restructuring into <code>cmd/ + core/</code> taxonomy. All user-facing strings externalized to YAML. <code>fatih/color</code> removed; two direct dependencies remain.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v060-the-integration-release","level":3,"title":"<code>v0.6.0</code>: The Integration Release","text":"<p>Plugin architecture: hooks and skills converted from shell scripts to Go subcommands, shipped as a Claude Code marketplace plugin. Multi-tool hook generation for Cursor, Aider, Copilot, and Windsurf. Webhook notifications with encrypted URL storage.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v030-the-discipline-release","level":3,"title":"<code>v0.3.0</code>: The Discipline Release","text":"<p>Journal static site generation via zensical. 49-skill audit and fix pass (positive framing, phantom reference removal, scope tightening). Context consolidation skill. <code>golangci-lint</code> v2 migration.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v020-the-archaeology-release","level":3,"title":"<code>v0.2.0</code>: The Archaeology Release","text":"<p>Session journal system: <code>ctx journal import</code> converts Claude Code JSONL transcripts to browsable Markdown. Constants refactor with semantic prefixes (<code>Dir*</code>, <code>File*</code>, <code>Filename*</code>). CRLF handling for Windows compatibility.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v012","level":3,"title":"<code>v0.1.2</code>","text":"<p>Default Claude Code permissions deployed on <code>ctx init</code>. Prompting guide published as a standalone documentation page.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v011","level":3,"title":"<code>v0.1.1</code>","text":"<p>Bug fixes: hook schema key format corrected, JSON unicode escaping fixed in context file output.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v010-initial-release","level":3,"title":"<code>v0.1.0</code>: Initial Release","text":"<p>CLI with 15 subcommands, 6 context file types (CONSTITUTION, TASKS, CONVENTIONS, ARCHITECTURE, DECISIONS, LEARNINGS), Makefile build system, and Claude Code hook integration.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#latest","level":2,"title":"Latest","text":"<p>The main documentation always reflects the latest development version.</p> <p>For the most recent stable release, see v0.8.0.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#changelog","level":2,"title":"Changelog","text":"<p>For detailed changes between versions, see the  GitHub Releases page.</p>","path":["Reference","Version History"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"<p>Security model, agent hardening, and vulnerability reporting.</p>","path":["Security"],"tags":[]},{"location":"security/#security-design","level":3,"title":"Security Design","text":"<p>Trust model, what <code>ctx</code> does for security, permission hygiene, state file management, and the log-first audit trail principle. Read first to understand the security boundaries.</p>","path":["Security"],"tags":[]},{"location":"security/#securing-ai-agents","level":3,"title":"Securing AI Agents","text":"<p>Defense in depth for unattended AI agents: five layers of protection, each with a known bypass, strength in combination.</p>","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":3,"title":"Reporting Vulnerabilities","text":"<p>How to report a security issue: email, GitHub private reporting, PGP-encrypted submissions, what to include, and the response timeline.</p>","path":["Security"],"tags":[]},{"location":"security/agent-security/","level":1,"title":"Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#defense-in-depth-securing-ai-agents","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-problem","level":2,"title":"The Problem","text":"<p>An unattended AI agent with unrestricted access to your machine is an unattended shell with unrestricted access to your machine.</p> <p>This is not a theoretical concern. AI coding agents execute shell commands, write files, make network requests, and modify project configuration. When running autonomously (overnight, in a loop, without a human watching), the attack surface is the full capability set of the operating system user account.</p> <p>The risk is not that the AI is malicious. The risk is that the AI is controllable: it follows instructions from context, and context can be poisoned.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#threat-model","level":2,"title":"Threat Model","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#how-agents-get-compromised","level":3,"title":"How Agents Get Compromised","text":"<p>AI agents follow instructions from multiple sources: system prompts, project files, conversation history, and tool outputs. An attacker who can inject content into any of these sources can redirect the agent's behavior.</p> Vector How it works Prompt injection via dependencies A malicious package includes instructions in its README, changelog, or error output. The agent reads these during installation or debugging and follows them. Prompt injection via fetched content The agent fetches a URL (documentation, API response, Stack Overflow answer) containing embedded instructions. Poisoned project files A contributor adds adversarial instructions to <code>CLAUDE.md</code>, <code>.cursorrules</code>, or <code>.context/</code> files. The agent loads these at session start. Self-modification between iterations In an autonomous loop, the agent modifies its own configuration files. The next iteration loads the modified config with no human review. Tool output injection A command's output (error messages, log lines, file contents) contains instructions the agent interprets and follows.","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#what-can-a-compromised-agent-do","level":3,"title":"What Can a Compromised Agent Do","text":"<p>Depends entirely on what permissions and access the agent has:</p> Access level Potential impact Unrestricted shell Execute any command, install software, modify system files Network access Exfiltrate source code, credentials, or context files to external servers Docker socket Escape container isolation by spawning privileged sibling containers SSH keys Pivot to other machines, push to remote repositories, access production systems Write access to own config Disable its own guardrails for the next iteration","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-defense-layers","level":2,"title":"The Defense Layers","text":"<p>No single layer is sufficient. Each layer catches what the others miss.</p> <pre><code>Layer 1: Soft instructions     (CONSTITUTION.md, playbook)\nLayer 2: Application controls  (permission allowlist, tool restrictions)\nLayer 3: OS-level isolation    (user accounts, filesystem, containers)\nLayer 4: Network controls      (firewall rules, airgap)\nLayer 5: Infrastructure        (VM isolation, resource limits)\n</code></pre>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"<p>Markdown files like <code>CONSTITUTION.md</code> and the Agent Playbook tell the agent what to do and what not to do. These are probabilistic: the agent usually follows them, but there is no enforcement mechanism.</p> <p>What it catches: Most common mistakes. An agent that has been told \"never delete production data\" will usually not delete production data.</p> <p>What it misses: Prompt injection. A sufficiently crafted injection can override soft instructions. Long context windows dilute attention on rules stated early. Edge cases where instructions are ambiguous.</p> <p>Verdict: Necessary but not sufficient. Good for the common case. Do not rely on it for security boundaries.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"<p>AI tool runtimes (Claude Code, Cursor, etc.) provide permission systems: tool allowlists, command restrictions, confirmation prompts.</p> <p>For Claude Code, <code>ctx init</code> writes both an allowlist and an explicit deny list into <code>.claude/settings.local.json</code>. The golden images live in <code>internal/assets/permissions/</code>:</p> <p>Allowlist (<code>allow.txt</code>): only these tools run without confirmation:</p> <pre><code>Bash(ctx:*)\nSkill(ctx-convention-add)\nSkill(ctx-decision-add)\n... # all bundled ctx-* skills\n</code></pre> <p>Deny list (<code>deny.txt</code>): these are blocked even if the agent requests them:</p> <pre><code># Dangerous operations\nBash(sudo *)\nBash(git push *)\nBash(git push)\nBash(rm -rf /*)\nBash(rm -rf ~*)\nBash(curl *)\nBash(wget *)\nBash(chmod 777 *)\n\n# Sensitive file reads\nRead(**/.env)\nRead(**/.env.*)\nRead(**/*credentials*)\nRead(**/*secret*)\nRead(**/*.pem)\nRead(**/*.key)\n\n# Sensitive file edits\nEdit(**/.env)\nEdit(**/.env.*)\n</code></pre> <p>What it catches: The agent cannot run commands outside the allowlist, and the deny list blocks dangerous operations even if a future allowlist change were to widen access. If <code>rm</code>, <code>curl</code>, <code>sudo</code>, or <code>docker</code> are not allowed and <code>sudo</code>/<code>curl</code>/<code>wget</code> are explicitly denied, the agent cannot invoke them regardless of what any prompt says.</p> <p>What it misses: The agent can modify the allowlist itself. In an autonomous loop, if the agent writes to <code>.claude/settings.local.json</code>, and the next iteration loads the modified config, then the protection is effectively lost. The application enforces the rules, but the application  reads the rules from files the agent can write.</p> <p>Verdict: Strong first layer. Must be combined with self-modification prevention (Layer 3).</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-3-os-level-isolation-deterministic-and-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Deterministic and Unbypassable)","text":"<p>The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without <code>CAP_NET_RAW</code> cannot open raw sockets. These are kernel boundaries.</p> Control Purpose Dedicated user account No <code>sudo</code>, no privileged group membership (<code>docker</code>, <code>wheel</code>, <code>adm</code>). The agent cannot escalate privileges. Filesystem permissions Project directory writable; everything else read-only or inaccessible. Agent cannot reach other projects, home directories, or system config. Immutable config files <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, and <code>.context/CONSTITUTION.md</code> owned by a different user or marked immutable (<code>chattr +i</code> on Linux). The agent cannot modify its own guardrails. <p>What it catches: Privilege escalation, self-modification, lateral movement to other projects or users.</p> <p>What it misses: Actions within the agent's legitimate scope. If the agent has write access to source code (which it needs to do its job), it can introduce vulnerabilities in the code itself.</p> <p>Verdict: Essential. This is the layer that makes the other layers trustworthy.</p> <p>OS-level isolation does not make the agent safe; it makes the other layers meaningful.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"<p>An agent that cannot reach the internet cannot exfiltrate data. It also cannot ingest new instructions mid-loop from external documents, API responses, or hostile content.</p> Scenario Recommended control Agent does not need the internet <code>--network=none</code> (container) or outbound firewall drop-all Agent needs to fetch dependencies Allow specific registries (npmjs.com, proxy.golang.org, pypi.org) via firewall rules. Block everything else. Agent needs API access Allow specific API endpoints only. Use an HTTP proxy with allowlisting. <p>What it catches: Data exfiltration, phone-home payloads, downloading additional tools, and instruction injection via fetched content.</p> <p>What it misses: Nothing, if the agent genuinely does not need the network. The tradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"<p>The strongest boundary is a separate machine (or something that behaves like one).</p> <p>The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.</p> <p>Containers (Docker, Podman):</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n</code></pre> <p>Docker Socket Is Sudo Access</p> <p>Critical: never mount the Docker socket (<code>/var/run/docker.sock</code>).</p> <p>An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox. </p> <p>Use rootless Docker or Podman to eliminate this escalation path.</p> <p>Virtual machines: The strongest isolation. The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.</p> <p>Resource limits: CPU, memory, and disk quotas prevent a runaway agent from consuming all resources. Use <code>ulimit</code>, cgroup limits, or container resource constraints.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A defense-in-depth setup for overnight autonomous runs:</p> Layer Implementation Stops Soft instructions <code>CONSTITUTION.md</code> with \"never delete tests\", \"always run tests before committing\" Common mistakes (probabilistic) Application allowlist <code>.claude/settings.local.json</code> with explicit tool permissions Unauthorized commands (deterministic within runtime) Immutable config <code>chattr +i</code> on <code>CLAUDE.md</code>, <code>.claude/</code>, <code>CONSTITUTION.md</code> Self-modification between iterations Unprivileged user Dedicated user, no sudo, no docker group Privilege escalation Container <code>--cap-drop=ALL --network=none</code>, rootless, no socket mount Host escape, network exfiltration Resource limits <code>--memory=4g --cpus=2</code>, disk quotas Resource exhaustion <p>Each layer is straightforward: The strength is in the combination.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>\"I'll just use <code>--dangerously-skip-permissions</code>\": This disables Layer 2 entirely. Without Layers 3-5, you have no protection at all. Only use this flag inside a properly isolated container or VM.</p> <p>\"The agent is sandboxed in Docker\": A Docker container with the Docker socket mounted, running as root, with <code>--privileged</code>, and full network access is not sandboxed. It is a root shell with extra steps.</p> <p>\"<code>CONSTITUTION.md</code> says not to do that\": Markdown is a suggestion. It works most of the time. It is not a security boundary. Do not use it as one.</p> <p>\"I reviewed the <code>CLAUDE.md</code>, it's fine\": The agent can modify <code>CLAUDE.md</code> during iteration N. Iteration N+1 loads the modified version. Unless the file is immutable, your review is stale.</p> <p>\"The agent only has access to this one project\": Does the project directory contain <code>.env</code> files, SSH keys, API tokens, or credentials? Does it have a <code>.git/config</code> with push access to a remote? Filesystem isolation means isolating what is in the directory too.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-security-considerations","level":2,"title":"Team Security Considerations","text":"<p>When multiple developers share a <code>.context/</code> directory, security considerations extend beyond single-agent hardening.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#code-review-for-context-files","level":3,"title":"Code Review for Context Files","text":"<p>Treat <code>.context/</code> changes like code changes. Context files influence agent behavior (a modified <code>CONSTITUTION.md</code> or <code>CONVENTIONS.md</code> changes what every agent on the team will do next session). Review them in PRs with the same scrutiny you apply to production code.</p> <p>Watch for:</p> <ul> <li>Weakened constitutional rules (removed constraints, softened language)</li> <li>New decisions that contradict existing ones without acknowledging it</li> <li>Learnings that encode incorrect assumptions</li> <li>Task additions that bypass the team's prioritization process</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#gitignore-patterns","level":3,"title":"Gitignore Patterns","text":"<p><code>ctx init</code> configures <code>.gitignore</code> automatically, but verify these patterns are in place:</p> <ul> <li>Always gitignored: <code>.ctx.key</code> (encryption key),    <code>.context/logs/</code>, <code>.context/journal/</code></li> <li>Team decision: <code>scratchpad.enc</code> (encrypted, safe to commit for   shared scratchpad state); <code>.gitignore</code> if scratchpads are personal</li> <li>Never committed: <code>.env</code>, credentials, API keys (enforced by   drift secret detection)</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#multi-developer-context-sharing","level":3,"title":"Multi-Developer Context Sharing","text":"<p><code>CONSTITUTION.md</code> is the shared contract. All team members and their agents inherit it. Changes require team consensus, not unilateral edits.</p> <p>When multiple agents write to the same context files concurrently (e.g., two developers adding learnings simultaneously), git merge conflicts are expected. Resolution is typically additive: accept both additions. Destructive resolution (dropping one side) loses context.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-conventions-for-context-management","level":3,"title":"Team Conventions for Context Management","text":"<p>Establish and document:</p> <ul> <li>Who reviews context changes: Same reviewers as code, or a   designated context owner?</li> <li>How to resolve conflicting decisions: If two sessions record   contradictory decisions, which wins? Default: the later one must   explicitly supersede the earlier one with rationale.</li> <li>Frequency of context maintenance: Weekly <code>ctx drift</code> checks,   monthly consolidation passes, archival after each milestone.</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#checklist","level":2,"title":"Checklist","text":"<p>Before running an unattended AI agent:</p> <ul> <li> Agent runs as a dedicated unprivileged user (no sudo, no docker group)</li> <li> Agent's config files are immutable or owned by a different user</li> <li> Permission allowlist restricts tools to the project's toolchain</li> <li> Container drops all capabilities (<code>--cap-drop=ALL</code>)</li> <li> Docker socket is NOT mounted</li> <li> Network is disabled or restricted to specific domains</li> <li> Resource limits are set (memory, CPU, disk)</li> <li> No SSH keys, API tokens, or credentials are accessible to the agent</li> <li> Project directory does not contain <code>.env</code> or secrets files</li> <li> Iteration cap is set (<code>--max-iterations</code>)</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Running an Unattended AI Agent: the   <code>ctx</code> recipe for autonomous loops, including step-by-step permissions   and isolation setup</li> <li>Security: <code>ctx</code>'s own trust model and vulnerability   reporting</li> <li>Autonomous Loops: full documentation of the   loop pattern, prompt templates, and troubleshooting</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/design/","level":1,"title":"Security Design","text":"<p>How <code>ctx</code> thinks about security: trust boundaries, what the system does and does not do for you, the engineering principle behind the audit trail, and the permission hygiene workflow.</p> <p>For vulnerability disclosure, see Reporting Vulnerabilities.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#trust-model","level":2,"title":"Trust Model","text":"<p><code>ctx</code> operates within a single trust boundary: the local filesystem.</p> <p>The person who authors <code>.context/</code> files is the same person who runs the agent that reads them. There is no remote input, no shared state, and no server component.</p> <p>This means:</p> <ul> <li><code>ctx</code> does not sanitize context files for prompt injection. This   is a deliberate design choice, not an oversight. The files are   authored by the developer who owns the machine: sanitizing their   own instructions back to them would be counterproductive.</li> <li>If you place adversarial instructions in your own <code>.context/</code>   files, your agent will follow them. This is expected behavior.   You control the context; the agent trusts it.</li> </ul> <p>Shared Repositories</p> <p>In shared repositories, <code>.context/</code> files should be reviewed in code review (the same way you would review CI/CD config or Makefiles). A malicious contributor could add harmful instructions to <code>CONSTITUTION.md</code> or <code>TASKS.md</code>.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#what-ctx-does-for-security","level":2,"title":"What <code>ctx</code> Does for Security","text":"<p><code>ctx</code> is designed with security in mind:</p> <ul> <li>No secrets in context: The constitution explicitly forbids   storing secrets, tokens, API keys, or credentials in <code>.context/</code>   files.</li> <li>Local only: <code>ctx</code> runs entirely locally with no external   network calls.</li> <li>No code execution: <code>ctx</code> reads and writes Markdown files only;   it does not execute arbitrary code.</li> <li>Git-tracked: Core context files are meant to be committed, so   they should never contain sensitive data. Exception: <code>sessions/</code>   and <code>journal/</code> contain raw conversation data and should be   gitignored.</li> </ul>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#permission-hygiene","level":2,"title":"Permission Hygiene","text":"<p>Claude Code evaluates permissions in deny → ask → allow order. <code>ctx init</code> automatically populates <code>permissions.deny</code> with rules that block dangerous operations before the allow list is ever consulted.</p> <p>Default deny rules block:</p> <ul> <li><code>sudo</code>, <code>git push</code>, <code>rm -rf /</code>, <code>rm -rf ~</code>, <code>curl</code>, <code>wget</code>,   <code>chmod 777</code></li> <li><code>Read</code> / <code>Edit</code> of <code>.env</code>, credentials, secrets, <code>.pem</code>, <code>.key</code>   files</li> </ul> <p>Even with deny rules in place, the allow list accumulates one-off permissions over time. Periodically review for:</p> <ul> <li>Destructive commands: <code>git reset --hard</code>, <code>git clean -f</code>, etc.</li> <li>Config injection vectors: permissions that allow modifying   files controlling agent behavior (<code>CLAUDE.md</code>,   <code>settings.local.json</code>).</li> <li>Broad wildcards: overly permissive patterns that pre-approve   more than intended.</li> </ul> <p>For the full hygiene workflow, see the Claude Code Permission Hygiene recipe.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#state-file-management","level":2,"title":"State File Management","text":"<p>Hook state files (throttle markers, prompt counters, pause markers) are stored in <code>.context/state/</code>, which is project-scoped and gitignored. State files are automatically managed by the hooks that create them; no manual cleanup is needed.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#log-first-audit-trail","level":2,"title":"Log-First Audit Trail","text":"<p>The event log (<code>.context/state/events.jsonl</code>) is the authoritative record of what <code>ctx</code> hooks did during a session. Several audit-adjacent features depend on that log being trustworthy, not merely best-effort:</p> <ul> <li><code>ctx event</code> / <code>ctx system view-events</code> replays session history   from the log.</li> <li>Webhook notifications give operators a real-time signal that   assumes every notification corresponds to a logged event.</li> <li>Drift, freshness, and map-staleness checks count events over   time and surface regressions.</li> </ul> <p>A log that silently drops entries while the rest of the system claims success is worse than no log at all: operators see a green TUI and a webhook notification and conclude \"it happened,\" even when the audit trail never landed. The codebase treats this as a correctness problem, not a UX polish problem.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#the-rule","level":3,"title":"The Rule","text":"<p>Any code path that emits an observable side effect (webhook, stdout marker, throttle-file touch, state mutation) must append the corresponding event-log entry first and gate the side effect on the append succeeding. If the log write fails, the side effect must not fire.</p> <p>In code, this shape:</p> <pre><code>if appendErr := event.Append(channel, msg, sessionID, ref); appendErr != nil {\n    return appendErr // do NOT send the webhook or touch the marker\n}\nif sendErr := notify.Send(channel, msg, sessionID, ref); sendErr != nil {\n    return sendErr\n}\n// downstream side effects (marker touch, stdout, etc.)\n</code></pre> <p>The <code>nudge.Relay</code> helper in <code>internal/cli/system/core/nudge</code> enforces this for the common \"log + webhook\" pair. Hook <code>Run</code> functions that compose their own sequence (<code>session_event</code>, <code>heartbeat</code>, several <code>check_*</code> hooks) follow the same ordering explicitly.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#known-gaps","level":3,"title":"Known Gaps","text":"<ul> <li>Nudge webhooks have no log channel. <code>nudge.EmitAndRelay</code>   sends a \"nudge\" notification before the \"relay\" event is logged.   The nudge leg is fire-and-forget because no event-log channel   records nudges today. A future refactor may add one; until then   this is the one documented exception.</li> <li><code>ctx agent --cooldown</code> and <code>ctx doctor</code> propagate rather than   gate. They surface real errors to the caller (usually Cobra)   rather than deciding what to do with them locally. Editors that   invoke these commands may display errors in an ugly way; the   ugliness is the correct signal (something persisted is broken),   not a defect to smooth over.</li> <li>Verbose hook logs in <code>core/log.Message</code> stay best-effort.   That logger captures per-hook activity (how many prompts, which   percent, etc.) for debugging; it is NOT the event audit trail.   Its failures go to stderr via <code>log/warn.Warn</code> rather than   propagating, because losing an operational log line is not a   correctness problem.</li> </ul>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#background","level":3,"title":"Background","text":"<p>The <code>error</code> returns on <code>event.Append</code>, <code>io.AppendBytes</code>, <code>nudge.Relay</code>, and <code>cooldown.Active</code> / <code>cooldown.TouchTombstone</code> were introduced as part of the resolver-tightening refactor. Before that change, most hook paths called these helpers and silently discarded their errors. The principle above was extracted from the observation that every user-visible correctness problem hit during the refactor traced back to some function saying \"this succeeded\" when the underlying write never landed.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#best-practices","level":2,"title":"Best Practices","text":"<ol> <li>Review before committing: Always review <code>.context/</code> files    before committing.</li> <li>Use <code>.gitignore</code>: If you must store sensitive notes locally,    add them to <code>.gitignore</code>.</li> <li>Drift detection: Run <code>ctx drift</code> to check for potential    issues.</li> <li>Permission audit: Review <code>.claude/settings.local.json</code> after    busy sessions.</li> </ol>","path":["Security","Security Design"],"tags":[]},{"location":"security/hub/","level":1,"title":"Hub Security Model","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#ctx-hub-security-model","level":1,"title":"<code>ctx</code> Hub: Security Model","text":"<p>What the hub defends against, what it does not defend against, and the concrete mechanisms in play.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#threat-model","level":2,"title":"Threat Model","text":"<p>The hub is designed for trusted cross-project knowledge sharing within a team or homelab. It assumes:</p> <ul> <li>The hub host is trusted. Anyone with root on that box can read   every entry ever published.</li> <li>Network is semi-trusted. Hub traffic is gRPC over TCP; TLS is   strongly recommended but not mandatory.</li> <li>Client machines are trusted enough to hold a per-project client   token. Losing a client token is roughly equivalent to losing an   API key: scoped damage, not total compromise.</li> <li>Entry content is not secret. Decisions, learnings, and   conventions may be indexed by AI agents, rendered in docs,   shared across projects. Do not push credentials or PII into   the hub.</li> </ul> <p>The hub is not a secure messaging system, a secrets store, or a compliance-grade audit log. If your threat model needs those, use a dedicated tool and keep the hub for knowledge sharing.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#mechanisms","level":2,"title":"Mechanisms","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#bearer-tokens","level":3,"title":"Bearer Tokens","text":"<p>All RPCs except <code>Register</code> require a bearer token in gRPC metadata. Two kinds of tokens exist:</p> Kind Format Scope Lifetime Admin token <code>ctx_adm_...</code> Register new projects Manual rotate Client token <code>ctx_cli_...</code> Publish, Sync, Listen, Status Project lifetime <p>Tokens are compared in constant time (<code>crypto/subtle</code>) to prevent timing oracles, and looked up via an <code>O(1)</code> hash map so the comparison cost does not depend on the total number of registered clients.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#client-side-encryption-at-rest","level":3,"title":"Client-Side Encryption at Rest","text":"<p><code>.context/.connect.enc</code> stores the client token and hub address, encrypted with AES-256-GCM using the same scheme the notification subsystem uses. The key is derived from <code>ctx</code>'s local keyring (see <code>internal/crypto</code>).</p> <p>An attacker with read access to the project directory cannot learn the client token without also breaking <code>ctx</code>'s local keyring.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#hub-side-token-storage","level":3,"title":"Hub-Side Token Storage","text":"<p>Tokens Are Stored in Plaintext on the Hub Host</p> <p><code><data-dir>/clients.json</code> currently stores client tokens verbatim, not hashed. Anyone with read access to the hub's data directory sees every registered client's token and can impersonate any project that has ever registered.</p> <p>Mitigations today:</p> <ul> <li>Run the hub as an unprivileged user and lock the data   directory with <code>chmod 700 <data-dir></code>.</li> <li>Use the systemd unit in   Operations,   which enables <code>ProtectSystem=strict</code>,   <code>NoNewPrivileges=true</code>, and a dedicated user.</li> <li>Never expose <code><data-dir></code> over NFS, SMB, or shared   filesystems.</li> <li>Treat <code><data-dir></code> the same way you'd treat   <code>/etc/shadow</code>: back it up encrypted, never check it   into version control.</li> </ul> <p>Hashing <code>clients.json</code> and moving to keyring-backed storage is tracked as a follow-up in the PR #60 task group. Until that lands, assume a hub host compromise equals total hub compromise.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#input-validation","level":3,"title":"Input Validation","text":"<p>Every published entry is validated before it touches the log:</p> <ul> <li>Type must be one of: <code>decision</code>, <code>learning</code>, <code>convention</code>,   <code>task</code>. Unknown types are rejected.</li> <li>ID and Origin are required and non-empty.</li> <li>Content size is capped at 1 MB. Reasonable for text,   hostile for attempts to fill the disk.</li> <li>Duplicate project registration is rejected; a client that   replays an old <code>Register</code> call gets an error, not a second   token.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#no-script-execution","level":3,"title":"No Script Execution","text":"<p>The hub never interprets entry content. There is no expression language, no template evaluation, no Markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#audit-trail","level":3,"title":"Audit Trail","text":"<p><code>entries.jsonl</code> is append-only. Every accepted publish is recorded with the publishing project's origin tag and sequence number. Nothing is ever deleted by the hub; retention is managed manually by the operator (see log rotation).</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#what-the-hub-does-not-defend-against","level":2,"title":"What the Hub Does Not Defend Against","text":"<ul> <li>Untrusted entry senders. A client with a valid token can   publish anything (within the 1 MB cap). There is no content   validation beyond shape.</li> <li>Denial of service from a registered client. A misbehaving   client can publish until disk is full. Monitor   <code>entries.jsonl</code> growth.</li> <li>Network eavesdropping without TLS. Plain gRPC leaks entry   content and tokens. Use a TLS-terminating reverse proxy   (see Multi-machine recipe).</li> <li>Host compromise. Root on the hub host = access to every   entry and every token. Harden the host.</li> <li>Accidental secret upload. The hub will happily fan out a   decision containing an API key. Sanitize content before   publishing.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#operational-hardening-checklist","level":2,"title":"Operational Hardening Checklist","text":"<ul> <li> Run the hub as an unprivileged user with       <code>NoNewPrivileges=true</code> and <code>ProtectSystem=strict</code> (see       the systemd unit in       Operations).</li> <li> Terminate TLS in front of the hub for anything beyond       a trusted LAN.</li> <li> Restrict the listen port with firewall rules to the       client subnet only.</li> <li> Back up <code><data-dir>/admin.token</code> to a secrets manager; do       not leave it in shell history.</li> <li> Rotate the admin token when a team member with access       leaves. Client tokens keep working across rotations.</li> <li> Monitor <code>entries.jsonl</code> growth; alert on sudden spikes.</li> <li> Run NTP on all clients to prevent entry-timestamp skew.</li> <li> Do not publish from machines you do not trust.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#responsible-disclosure","level":2,"title":"Responsible Disclosure","text":"<p>Security issues in the hub follow the same process as the rest of <code>ctx</code>; see Reporting.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub Operations</li> <li><code>ctx</code> Hub failure modes</li> <li>HA cluster recipe</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/reporting/","level":1,"title":"Reporting Vulnerabilities","text":"<p>Disclosure process for security issues in <code>ctx</code>. For the broader security model (trust boundaries, audit trail, permission hygiene), see Security Design.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"<p>At <code>ctx</code> we take security very seriously.</p> <p>If you discover a security vulnerability in <code>ctx</code>, please report it responsibly.</p> <p>Do NOT open a public issue for security vulnerabilities.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#email","level":3,"title":"Email","text":"<p>Send details to security@ctx.ist.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"<ol> <li>Go to the Security tab;</li> <li>Click \"Report a Vulnerability\";</li> <li>Provide a detailed description.</li> </ol>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#encrypted-reports-optional","level":3,"title":"Encrypted Reports (Optional)","text":"<p>If your report contains sensitive details (proof-of-concept exploits, credentials, or internal system information), you can encrypt your message with our PGP key:</p> <ul> <li>In-repo: <code>SECURITY_KEY.asc</code></li> <li>Keybase: keybase.io/alekhinejose</li> </ul> <pre><code># Import the key\ngpg --import SECURITY_KEY.asc\n\n# Encrypt your report\ngpg --armor --encrypt --recipient security@ctx.ist report.txt\n</code></pre> <p>Encryption is optional. Unencrypted reports to security@ctx.ist or via GitHub Private Reporting are perfectly fine.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#what-to-include","level":3,"title":"What to Include","text":"<ul> <li>Description of the vulnerability,</li> <li>Steps to reproduce,</li> <li>Potential impact,</li> <li>Suggested fix (if any).</li> </ul>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#attribution","level":2,"title":"Attribution","text":"<p>We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#response-timeline","level":2,"title":"Response Timeline","text":"<p>Open Source, Best-Effort Timelines</p> <p><code>ctx</code> is a volunteer-maintained open source project.</p> <p>The timelines below are guidelines, not guarantees, and depend on contributor availability.</p> <p>We will address security reports on a best-effort basis and prioritize them by severity.</p> Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"thesis/","level":1,"title":"Context as State","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#a-persistence-layer-for-human-ai-cognition","level":2,"title":"A Persistence Layer for Human-AI Cognition","text":"<p>Volkan Özçelik - me@volkan.io</p> <p>February 2026</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#abstract","level":3,"title":"Abstract","text":"<p>As AI tools evolve from code-completion utilities into reasoning collaborators,  the knowledge that governs their behavior becomes as important as the code they  produce; yet, that knowledge is routinely discarded at the end of every session.</p> <p>AI-assisted development systems assemble context at prompt time using heuristic  retrieval from mutable sources: recent files, semantic search results,  session history. These approaches optimize relevance at the moment of generation  but do not persist the cognitive state that produced decisions. Reasoning is  not reproducible, intent is lost across sessions, and teams cannot audit the  knowledge that constrains automated behavior.</p> <p>This paper argues that context should be treated as deterministic, version-controlled state rather than as a transient query result. We ground this  argument in three sources of evidence: a landscape analysis of 17 systems  spanning AI coding assistants, agent frameworks, and knowledge stores;  a taxonomy of five primitive categories that reveals irrecoverable architectural  trade-offs; and an experience report from <code>ctx</code>,  a persistence layer for AI-assisted development, which developed itself using its  own persistence model across 389 sessions over 33 days. We define a three-tier  model for cognitive state: authoritative knowledge, delivery views, and  ephemeral state. Then we present six design invariants empirically validated  by 56 independent rejection decisions observed across the analyzed landscape.  We show that context determinism applies to assembly, not to model output,  and that the curation cost this model requires is offset by compounding returns  in reproducibility, auditability, and team cognition.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#1-introduction","level":2,"title":"1. Introduction","text":"<p>The introduction of large language models into software development has shifted  the primary interface from code execution to interactive reasoning. In this  environment, the correctness of an output depends not only on source code but  on the context supplied to the model: the conventions, decisions, architectural  constraints, and domain knowledge that bound the space of acceptable responses.</p> <p>Current systems treat context as a query result assembled at the moment of  interaction. A developer begins a session; the tool retrieves what it estimates  to be relevant from chat history, recent files, and vector stores; the model  generates output conditioned on this transient assembly; the session ends, and  the context evaporates. The next session begins the cycle again.</p> <p>This model has improved substantially over the past year. <code>CLAUDE.md</code> files,  Cursor rules, Copilot's memory system, and tools such as Mem0, Letta, and Kindex  each address aspects of the persistence problem. Yet across 17 systems we  analyzed spanning AI coding assistants, agent frameworks, autonomous coding  agents, and purpose-built knowledge stores, no system provides all five of the  following properties simultaneously: deterministic context assembly,  human-readable file-based persistence, token-budgeted delivery,  a single-binary core with zero required runtime dependencies for the  persistence path, and local-first operation.</p> <p>This paper does not propose a universal replacement for retrieval-centric  workflows. It defines a persistence layer (embodied in <code>ctx</code> (https://ctx.ist))  whose advantages emerge under specific operational conditions: when  reproducibility is a requirement, when knowledge must outlive sessions and  individuals, when teams require shared cognitive authority, or when offline  operation is necessary. </p> <p>The trade-offs (manual curation cost, reduced automatic recall, coarser  granularity) are intentional and mirror the trade-offs accepted by systems that  favor reproducibility over convenience, such as reproducible builds and  immutable infrastructure <sup>1</sup> <sup>6</sup>.</p> <p>The contribution is threefold: a three-tier model for cognitive state that  resolves the ambiguity between authoritative knowledge and ephemeral session  artifacts; six design invariants empirically grounded in a cross-system  landscape analysis; and an experience report demonstrating that the model  produces compounding returns when applied to its own development.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#2-the-limits-of-prompt-time-context","level":2,"title":"2. The Limits of Prompt-Time Context","text":"<p>Prompt-time assembly pipelines typically consist of corpus selection, retrieval, ranking, and truncation. These pipelines are probabilistic and time-dependent, producing three failure modes that compound over the lifetime of a project.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#21-non-reproducibility","level":3,"title":"2.1 Non-Reproducibility","text":"<p>If context is derived from mutable sources using heuristic ranking, identical requests at different times receive different inputs. A developer who asks  \"What is our authentication strategy?\" on Tuesday may receive a different  context window than the same question on Thursday: Not because the strategy  changed, but because the retrieval heuristic surfaced different fragments.</p> <p>Reproducibility (the ability to reconstruct the exact inputs that produced a  given output) is a foundational property of reliable systems. Its loss in  AI-assisted development mirrors the historical evolution from ad-hoc builds to  deterministic build systems <sup>1</sup> <sup>2</sup>. The build community learned that when  outputs depend on implicit state (environment variables, system clocks,  network-fetched dependencies), debugging becomes archaeology. The same principle  applies when AI outputs depend on non-deterministic context retrieval.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#22-opaque-knowledge","level":3,"title":"2.2 Opaque Knowledge","text":"<p>Embedding-based memory increases recall but reduces inspectability. When a  vector store determines that a code snippet is \"similar\" to the current query,  the ranking function is opaque: the developer cannot inspect why that snippet  was chosen, whether a more relevant artifact was excluded, or whether the  ranking will remain stable. This prevents deterministic debugging, policy  auditing, and causal attribution (properties that information retrieval theory  identifies as fundamental trade-offs of probabilistic ranking) <sup>3</sup>.</p> <p>In practice, this opacity manifests as a compliance ceiling. In our experience  developing a context management system (detailed in Section 7), soft instructions  (directives that ask an AI agent to read specific files or follow specific  procedures) achieve approximately 75-85% compliance. The remaining 15-25%  represents cases where the agent exercises judgment about whether the  instruction applies, effectively applying a second ranking function on top of  the explicit directive. When 100% compliance is required, instruction is  insufficient; the content must be injected directly, removing the agent's option to skip it.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#23-loss-of-intent","level":3,"title":"2.3 Loss of Intent","text":"<p>Session transcripts record interaction but not cognition. A transcript captures what was said but not which assumptions were accepted, which alternatives were  rejected, or which constraints governed the decision. The distinction matters:  a decision to use PostgreSQL recorded as a one-line note (\"Use PostgreSQL\")  teaches a model what was decided; a structured record with context, rationale,  and consequences teaches it why (and why is what prevents the model from  unknowingly reversing the decision in a future session) <sup>4</sup>.</p> <p>Session transcripts provide history. Cognitive state requires something more:  the persistent, structured representation of the knowledge required for correct decision-making.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#3-cognitive-state-a-three-tier-model","level":2,"title":"3. Cognitive State: A Three-Tier Model","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#31-definitions","level":3,"title":"3.1 Definitions","text":"<p>We define cognitive state as the authoritative, persistent representation of the knowledge required for correct decision-making within a project. It is human-authored or human-ratified, versioned, inspectable, and reproducible. It  is distinct from logs, transcripts, retrieval results, and model-generated  summaries.</p> <p>Previous formulations of this idea have treated cognitive state as a monolithic  concept. In practice, a three-tier model better captures the operational reality:</p> <p>Tier 1: Authoritative State: The canonical knowledge that the system treats  as ground truth. In a concrete implementation, this corresponds to a set of  human-curated files with defined schemas: a constitution (inviolable rules),  conventions (code patterns), an architecture document (system structure),  decision records (choices with rationale), learnings (captured experience), a  task list (current work), a glossary (domain terminology), and an agent  playbook (operating instructions). Each file has a single purpose, a defined  lifecycle, and a distinct update frequency. Authoritative state is  version-controlled alongside code and reviewed through the same mechanisms  (diffs, pull requests, blame annotations).</p> <p>Tier 2: Delivery Views: Derived representations of authoritative state,  assembled for consumption by a model. A delivery view is produced by a  deterministic assembly function that takes the authoritative state, a token  budget, and an inclusion policy as inputs and produces a context window as  output. The same authoritative state, budget, and policy must always produce the  same delivery view. Delivery views are ephemeral (they exist only for the  duration of a session), but their construction is reproducible.</p> <p>Tier 3: Ephemeral State: Session transcripts, scratchpad notes, draft  journal entries, and other artifacts that exist during or immediately after a  session but are not authoritative. Ephemeral state is the raw material from  which authoritative state may be extracted through human review, but it is  never consumed directly by the assembly function.</p> <p>This three-tier model resolves confusion present in earlier formulations: the claim that AI output is a deterministic function of the repository state.  The corrected claim is that context selection is deterministic (the delivery  view is a function of authoritative state), but model output remains  stochastic, conditioned on the deterministic context. Formally:</p> <pre><code>delivery_view = assemble(authoritative_state, budget, policy)\noutput = model(delivery_view)   # stochastic\n</code></pre> <p>The persistence layer's contribution is making <code>assemble</code> reproducible, not making <code>model</code> deterministic.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#32-separation-of-concerns","level":3,"title":"3.2 Separation of Concerns","text":"<p>The decision to separate authoritative state into distinct files with distinct purposes is not cosmetic. Different types of knowledge have different lifecycles:</p> Knowledge Type Update Frequency Read Frequency Load Priority Example Constitution Rarely Every session Always \"Never commit secrets to git\" Tasks Every session Session start Always \"Implement token budget CLI flag\" Conventions Weekly Before coding High \"All errors use structured logging with severity levels\" Decisions When decided When questioning Medium \"Use PostgreSQL over MySQL (see ADR-003)\" Learnings When learned When stuck Medium \"Hook scripts >50ms degrade interactive UX\" Architecture When changed When designing On demand \"Three-layer pipeline: ingest → enrich → assemble\" Journal Every session Rarely Never auto \"Session 247: Removed dead-end session copy layer\" <p>A monolithic context file would force the assembly function to load everything  or nothing. Separation enables progressive disclosure: the minimum context  that matters for the current moment, with the option to load more when needed.  A normal session loads the constitution, tasks, and conventions; a deep  investigation loads decision history and journal entries from specific dates.</p> <p>The budget mechanism is the constraint that makes separation valuable. Without a  budget, the default behavior is to load everything, which destroys the attention  density that makes loaded context useful. With a budget, the assembly function  must prioritize ruthlessly: constitution first (always full), then tasks and  conventions (budget-capped), then decisions and learnings (scored by recency).  Entries that do not fit receive title-only summaries rather than being silently  dropped (an application of the \"tell me what you don't know\" pattern identified  independently by four systems in our landscape analysis).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#4-design-invariants","level":2,"title":"4. Design Invariants","text":"<p>The following six invariants define the constraints that a cognitive state  persistence layer must satisfy. They are not axioms chosen a priori; they are  empirically grounded properties whose violation was independently identified as  producing complexity costs across the 17 systems we analyzed.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-1-markdown-on-filesystem-persistence","level":3,"title":"Invariant 1: Markdown-on-Filesystem Persistence","text":"<p>Context files must be human-readable, git-diffable, and editable with any text editor. No database. No binary storage.</p> <p>Validation: 11 independent rejection decisions across the analyzed landscape  protected this property. Systems that adopted embedded records, binary  serialization, or knowledge graphs as their core primitive consistently traded  away the ability for a developer to run <code>cat DECISIONS.md</code> and understand the  system's knowledge. The inspection cost of opaque storage compounds over the  lifetime of a project: every debugging session, every audit, every onboarding  conversation requires specialized tooling to access knowledge that could have  been a text file.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-2-zero-runtime-dependencies","level":3,"title":"Invariant 2: Zero Runtime Dependencies","text":"<p>The tool must work with no installed runtimes, no running services, and no API keys for core functionality.</p> <p>Validation: 13 independent rejection decisions protected this property (the most frequently defended invariant). Systems that required databases  (PostgreSQL, SQLite, Redis), embedding models, server daemons, container  runtimes, or cloud APIs for core operation introduced failure modes proportional  to their dependency count. A persistence layer that depends on infrastructure is  not a persistence layer; it is a service. Services have uptime requirements,  version compatibility matrices, and operational costs that simple file  operations do not.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-3-deterministic-context-assembly","level":3,"title":"Invariant 3: Deterministic Context Assembly","text":"<p>The same files plus the same budget must produce the same output. No  embedding-based retrieval, no LLM-driven selection, no wall-clock-dependent  scoring in the assembly path.</p> <p>Validation: 6 independent rejection decisions protected this property.  Non-deterministic assembly (whether from embedding variance, LLM-based selection,  or time-dependent scoring) destroys the ability to reproduce a context window  and therefore to diagnose why a model produced a given output. Determinism in  the assembly path is what makes the persistence layer auditable.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-4-human-authority-over-persistent-state","level":3,"title":"Invariant 4: Human Authority over Persistent State","text":"<p>The agent may propose changes to context files but must not unilaterally modify  them. All persistent changes go through human-reviewable git commits.</p> <p>Validation: 6 independent rejection decisions protected this property. Systems  that allowed agents to self-modify their memory (writing freeform notes,  auto-pruning old entries, generating summaries as ground truth) consistently  produced lower-quality persistent context than systems that enforced human  review. Structure is a feature, not a limitation: across the landscape, the  pattern \"structured beats freeform\" was independently discovered by four  systems that evolved from freeform LLM summaries to typed schemas with required  fields.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-5-local-first-air-gap-capable","level":3,"title":"Invariant 5: Local-First, Air-Gap Capable","text":"<p>Core functionality must work offline with no network access. Cloud services may be used for optional features but never for core context management.</p> <p>Validation: 7 independent rejection decisions protected this property.  Infrastructure-dependent memory systems cannot operate in classified  environments, isolated networks, or constrained-environment scenarios. A  filesystem-native model continues to function under all conditions where the  repository is accessible.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-6-no-default-telemetry","level":3,"title":"Invariant 6: No Default Telemetry","text":"<p>Any analytics, if ever added, must be strictly opt-in.</p> <p>Validation: 4 independent rejection decisions protected this property. Default  telemetry erodes the trust model that a persistence layer depends on. If  developers must trust the system with their architectural decisions, operational  learnings, and project constraints, the system cannot simultaneously be reporting  usage data to external services.</p> <p>These six invariants collectively define a design space. Each feature proposal  can be evaluated against them: a feature that violates any invariant is rejected  regardless of how many other systems implement it. The discipline of constraint  (refusing to add capabilities that compromise foundational properties) is  itself an architectural contribution. Across the 17 analyzed systems, 56 patterns were explicitly rejected for violating these invariants. The rejection  count per invariant (11, 13, 6, 6, 7, 4) provides a rough measure of each  property's vulnerability to architectural erosion. A representative sample of  these rejections is provided in Appendix A.<sup>1</sup></p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#5-landscape-analysis","level":2,"title":"5. Landscape Analysis","text":"<p>The 17 systems were selected to cover the architectural design space rather than  to achieve completeness. Each included system satisfies three criteria: it  represents a distinct architectural primitive for AI-assisted development, it is  actively maintained or widely referenced, and it provides sufficient public  documentation or source code for architectural inspection. The goal was to  ensure that every major category of primitive (document, embedded record, state  snapshot, event/message, construction/derivation) was represented by multiple  systems, enabling cross-system pattern detection.</p> <p>The resulting set spans six categories: AI coding assistants (Continue,  Sourcegraph/Cody, Aider, Claude Code), AI agent frameworks (CrewAI, AutoGen,  LangGraph, LlamaIndex, Letta/MemGPT), autonomous coding agents (OpenHands,  Sweep), session provenance tools (Entire), data versioning systems (Dolt,  Pachyderm), pipeline/build systems (Dagger), and purpose-built knowledge  stores (QubicDB, Kindex). Each system was analyzed from its source code and  documentation, producing 34 individual analysis artifacts (an architectural  profile and a set of insights per system) that yielded 87 adopt/adapt  recommendations, 56 explicit rejection decisions, and 52 watch items.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#51-primitive-taxonomy","level":3,"title":"5.1 Primitive Taxonomy","text":"<p>Every system in the AI-assisted development landscape operates on a core  primitive: an atomic unit around which the entire architecture revolves. Our  analysis of 17 systems reveals five categories of primitives, each making  irrecoverable trade-offs:</p> <p>Group A: Document/File Primitives: Human-readable documents as the primary  unit. Documents are authored by humans, version-controlled in git, and consumed  by AI tools. The invariant of this group is that the primitive is always  human-readable and version-controllable with standard tools. Three systems  participate in this pattern: the system described in this paper as a pure  expression, and Continue (via its rules directory) and Claude Code  (via <code>CLAUDE.md</code> files) as partial participants: both use document-based  context as an input but organize around different core primitives.</p> <p>Group B: Embedded Record Primitives: Vector-embedded records stored with  numerical embeddings for similarity search, metadata for filtering, and scoring  mechanisms for ranking. Five systems use this approach  (LlamaIndex, CrewAI, Letta/MemGPT, QubicDB, Kindex). The invariant is that the  primitive requires an embedding model or vector database for core operations:  a dependency that precludes offline and air-gapped use.</p> <p>Group C: State Snapshot Primitives: Point-in-time captures of the complete  system state. The invariant is that any past state can be reconstructed at any  historical point. Three systems use this approach (LangGraph, Entire, Dolt).</p> <p>Group D: Event/Message Primitives: Sequential events or messages forming an  append-only log with causal relationships. Four systems use this approach  (OpenHands, AutoGen, Claude Code, Sweep). The invariant is temporal ordering  and append-only semantics.</p> <p>Group E: Construction/Derivation Primitives: Derived or constructed values  that encode how they were produced. The invariant is that the primitive is a  function of its inputs; re-executing the same inputs produces the same  primitive. Three systems use this approach (Dagger, Pachyderm, Aider).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#52-comparison-matrix","level":3,"title":"5.2 Comparison Matrix","text":"<p>The five primitive categories differ along seven dimensions:</p> Property Document Embedded Record State Snapshot Event/Message Construction Human-readable Yes No Varies Partially No Version-controllable Yes No Varies Yes Yes Queryable by meaning No Yes No No No Rewindable Via git No Yes Yes (replay) Yes Deterministic Yes No Yes Yes Yes Zero-dependency Yes No Varies Varies Varies Offline-capable Yes No Varies Varies Yes <p>The document primitive is the only one that simultaneously satisfies  human-readability, version-controllability, determinism, zero dependencies, and  offline capability. This is not because documents are superior in general (embedded records provide semantic queryability that documents lack) but because  the combination of all five properties is what the persistence layer requires.  The choice between primitive categories is not a matter of capability but of  which properties are considered invariant.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#53-convergent-patterns","level":3,"title":"5.3 Convergent Patterns","text":"<p>Across the 17 analyzed systems, six design patterns were independently  discovered. These convergent patterns carry extra validation weight because  they emerged from different problem spaces:</p> <p>Pattern 1: \"Tell me what you don't know\": When context is incomplete,  explicitly communicate to the model what information is missing and what  confidence level the provided context represents. Four systems independently  converged on this pattern: inserting skip markers, tracking evidence gaps,  annotating provenance, or naming output quality tiers.</p> <p>Pattern 2: \"Freshness matters\": Information relevance decreases over time.  Three systems independently chose exponential decay with different half-lives  (30 days, 90 days, and LRU ordering). Static priority ordering with no time  dimension leaves relevant recent knowledge at the same priority as stale  entries. This pattern is in productive tension with the persistence model's  emphasis on determinism: the claim is not that time-dependence is irrelevant,  but that it belongs in the curation step (a human deciding to consolidate or  archive stale entries) rather than in the assembly function (an algorithm  silently down-ranking entries based on age).</p> <p>Pattern 3: \"Content-address everything\": Compute a hash of content at  creation time for deduplication, cache invalidation, integrity verification,  and change detection. Five systems independently implement content hashing,  each discovering it solves different problems <sup>5</sup>.</p> <p>Pattern 4: \"Structured beats freeform\": When capturing knowledge or session  state, a structured schema with required fields produces more useful data than  freeform text. Four systems evolved from freeform summaries to typed schemas: one moving from LLM-generated prose to a structured condenser with explicit  fields for completed tasks, pending tasks, and files modified.</p> <p>Pattern 5: \"Protocol convergence\": The Model Context Protocol (MCP) is  emerging as a standard tool integration layer. Nine of 17 systems support it,  spanning every category in the analysis. MCP's significance for the persistence  model is that it provides a transport mechanism for context delivery without  dictating how context is stored or assembled. This makes the approach compatible  with both retrieval-centric and persistence-centric architectures.</p> <p>Pattern 6: \"Human-in-the-loop for memory\": Critical memory decisions should  involve human judgment. Fully automated memory management produces lower-quality  persistent context than human-reviewed systems. Four systems independently  converged on variants of this pattern: ceremony-based consolidation,  interrupt/resume for human input, confirmation mode for high-risk actions, and  separated \"think fast\" vs. \"think slow\" processing paths.</p> <p>Pattern 6 directly validates the ceremony model described in this paper. The  persistence layer requires human curation not because automation is impossible,  but because the quality of persistent knowledge degrades when the curation step  is removed. The improvement opportunity is to make curation easier, not to  automate it away.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#6-worked-example-architectural-decision-under-two-models","level":2,"title":"6. Worked Example: Architectural Decision under Two Models","text":"<p>We now instantiate the three-tier model in a concrete system  (<code>ctx</code>) and  illustrate the difference between prompt-time retrieval and cognitive state  persistence using a real scenario from its development.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#61-the-problem","level":3,"title":"6.1 The Problem","text":"<p>During development, the system accumulated three overlapping storage layers for  session data: raw transcripts (owned by the AI tool), session copies  (JSONL copies plus context snapshots), and enriched journal entries  (Markdown summaries). The middle layer (session copies) was a dead-end write  sink. An auto-save hook copied transcripts to a directory that nothing read  from, because the journal pipeline already read directly from the raw  transcripts. Approximately 15 source files, a shell hook, 20 configuration  constants, and 30 documentation references supported infrastructure with  no consumers.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#62-prompt-time-retrieval-model","level":3,"title":"6.2 Prompt-Time Retrieval Model","text":"<p>In a retrieval-based system, the decision to remove the middle layer depends on  whether the retrieval function surfaces the relevant context:</p> <p>The developer asks: \"Should we simplify the session storage?\" The retrieval  system must find and rank the original discussion thread where the three layers  were designed, the usage statistics showing zero reads from the middle layer,  the journal pipeline documentation showing it reads from raw transcripts  directly, and the dependency analysis showing 15 files, a hook, and 30 doc  references. If any of these fragments are not retrieved (because they are in old  chat history, because the embedding similarity score is low, or because the  token budget was consumed by more recent but less relevant context), the model  may recommend preserving the middle layer, or may not realize it exists.</p> <p>Six months later, a new team member asks the same question. The retrieval results  will differ: the original discussion has aged out of recency scoring, the usage  statistics are no longer in recent history, and the model may re-derive the  answer or arrive at a different conclusion.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#63-cognitive-state-model","level":3,"title":"6.3 Cognitive State Model","text":"<p>In the persistence model, the decision is recorded as a structured artifact at  write time:</p> <pre><code>## [2026-02-11] Remove .context/sessions/ storage layer\n\n**Status**: Accepted\n\n**Context**: The session/recall/journal system had three overlapping\nstorage layers. The recall pipeline reads directly from raw transcripts,\nmaking .context/sessions/ a dead-end write sink that nothing reads from.\n\n**Decision**: Remove .context/sessions/ entirely. Two stores remain:\nraw transcripts (global, tool-owned) and enriched journal\n(project-local).\n\n**Rationale**: Dead-end write sinks waste code surface, maintenance\neffort, and user attention. The recall pipeline already proved that\nreading directly from raw transcripts is sufficient. Context snapshots\nare redundant with git history.\n\n**Consequence**: Deleted internal/cli/session/ (15 files), removed\nauto-save hook, removed --auto-save from watch, removed pre-compact\nauto-save, removed /ctx-save skill, updated ~45 documentation files.\nFour earlier decisions superseded.\n</code></pre> <p>This artifact is:</p> <ul> <li>Deterministically included in every subsequent session's delivery view   (budget permitting, with title-only fallback if budget is exceeded)</li> <li>Human-readable and reviewable as a diff in the commit that introduced it</li> <li>Permanent: it persists in version control regardless of retrieval heuristics</li> <li>Causally linked: it explicitly supersedes four earlier decisions,    creating an auditable chain</li> </ul> <p>When the new team member asks \"Why don't we store session copies?\" six months  later, the answer is the same artifact, at the same revision, with the same  rationale. The reasoning is reconstructible because it was persisted at write  time, not discovered at query time.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#64-the-diff-when-policy-changes","level":3,"title":"6.4 The Diff When Policy Changes","text":"<p>If a future requirement re-introduces session storage (for example, to  support multi-agent session correlation), the change appears as a diff to the  decision record:</p> <pre><code>- **Status**: Accepted\n+ **Status**: Superseded by [2026-08-15] Reintroduce session storage\n+ for multi-agent correlation\n</code></pre> <p>The new decision record references the old one, creating a chain of reasoning visible in <code>git log</code>. In the retrieval model, the old decision would simply be  ranked lower over time and eventually forgotten.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#7-experience-report-a-system-that-designed-itself","level":2,"title":"7. Experience Report: A System That Designed Itself","text":"<p>The persistence model described in this paper was developed and tested by using  it on its own development. Over 33 days and 389 sessions, the system's context  files accumulated a detailed record of decisions made, reversed, and  consolidated: providing quantitative and qualitative evidence for the model's  properties.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#71-scale-and-structure","level":3,"title":"7.1 Scale and Structure","text":"<p>The development produced the following authoritative state artifacts:</p> <ul> <li>8 consolidated decision records covering 24 original decisions spanning    context injection architecture, hook design, task management, security,    agent autonomy, and webhook systems</li> <li>18 consolidated learning records covering 75 original observations spanning    agent compliance, hook behavior, testing patterns, documentation drift, and    tool integration</li> <li>A constitution with 13 inviolable rules across 4 categories    (security, quality, process, context preservation)</li> <li>389 enriched journal entries providing a complete session-level audit trail</li> </ul> <p>The consolidation ratio (24 decisions compressed to 8 records, 75 learnings  compressed to 18) illustrates the curation cost and its return: authoritative  state becomes denser and more useful over time as related entries are merged,  contradictions are resolved, and superseded decisions are marked.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#72-architectural-reversals","level":3,"title":"7.2 Architectural Reversals","text":"<p>Three architectural reversals during development provide evidence that the  persistence model captures and communicates reasoning effectively:</p> <p>Reversal 1: The two-tier persistence model: The original design included a  middle storage tier for session copies. After 21 days of development, the middle  tier was identified as a dead-end write sink (described in Section 6). The  decision record captured the full context, and the removal was executed cleanly:  15 source files, a shell hook, and 45 documentation references. The pattern of  a \"dead-end write sink\" was subsequently observed in 7 of 17 systems in our  landscape analysis that store raw transcripts alongside structured context.</p> <p>Reversal 2: The prompt-coach hook: An early design included a hook that  analyzed user prompts and offered improvement suggestions. After deployment,  the hook produced zero useful tips, its output channel was invisible to users,  and it accumulated orphan temporary files. The hook was removed, and the  decision record captured the failure mode for future reference.</p> <p>Reversal 3: The soft-instruction compliance model: The original context  injection strategy relied on soft instructions: directives asking the AI agent  to read specific files. After measuring compliance across multiple sessions,  we found a consistent 75-85% compliance ceiling. The revised strategy  injects content directly, bypassing the agent's judgment about whether to  comply. The learning record captures the ceiling measurement and the rationale  for the architectural change.</p> <p>Each reversal was captured as a structured decision record with context,  rationale, and consequences. In a retrieval-based system, these reversals would  exist only in chat history, discoverable only if the retrieval function happens  to surface them. In the persistence model, they are permanent, indexable  artifacts that inform future decisions.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#73-compliance-ceiling","level":3,"title":"7.3 Compliance Ceiling","text":"<p>The 75-85% compliance ceiling for soft instructions is the most operationally  significant finding from the experience report. It means that any context  management strategy relying on agent compliance with instructions (\"read this  file,\" \"follow this convention,\" \"check this list\") has a hard ceiling on  reliability.</p> <p>The root cause is structural: the instruction \"don't apply judgment\" is itself  evaluated by judgment. When an agent receives a directive to read a file, it  first assesses whether the directive is relevant to the current task (and that  assessment is the judgment the directive was trying to prevent).</p> <p>The architectural response maps directly to the formal model defined in  Section 3.1. Content requiring 100% compliance is included in  <code>authoritative_state</code> and injected by the deterministic <code>assemble</code> function,  bypassing the agent entirely. Content where 80% compliance is acceptable is  delivered as instructions within the delivery view. The three-tier architecture  makes this distinction explicit: authoritative state is injected; delivery  views are assembled deterministically; ephemeral state is available but  not pushed.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#74-compounding-returns","level":3,"title":"7.4 Compounding Returns","text":"<p>Over 33 days, we observed a qualitative shift in the development experience.  Early sessions (days 1-7) spent significant time re-establishing context: explaining conventions, re-stating constraints, re-deriving past decisions.  Later sessions (days 25-33) began with the agent loading curated context and  immediately operating within established constraints, because the constraints  were in files rather than in chat history.</p> <p>This compounding effect (where each session's context curation improves all  subsequent sessions) is the primary return on the curation investment. The cost  is borne once (writing a decision record, capturing a learning, updating the  task list); the benefit is collected on every subsequent session load.</p> <p>The effect is analogous to compound interest in financial systems: the  knowledge base grows not linearly with effort but with increasing marginal  returns as new knowledge interacts with existing context. A learning captured  on day 5 prevents a mistake on day 12, which avoids a debugging session that  would have consumed a day 12 session, freeing that session for productive work  that generates new learnings. The growth is not literally exponential (it is  bounded by project scope and subject to diminishing returns as the knowledge  base matures), but within the observed 33-day window, the returns were  consistently accelerating.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#75-scope-and-generalizability","level":3,"title":"7.5 Scope and Generalizability","text":"<p>This experience report is self-referential by design: the system was developed  using its own persistence model. This circularity strengthens the internal  validity of the findings (the model was stress-tested under authentic  conditions) but limits external generalizability. The two-week crossover point  was observed on a single project of moderate complexity with a small team  already familiar with the model's assumptions. Whether the same crossover holds  for larger teams, for codebases with different characteristics, or for teams  adopting the model without having designed it remains an open empirical  question. The quantitative claims in this section should be read as  existence proofs (demonstrating that the model can produce compounding  returns) rather than as predictions about specific adoption scenarios.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#8-situating-the-persistence-layer","level":2,"title":"8. Situating the Persistence Layer","text":"<p>The persistence layer occupies a specific position in the stack of AI-assisted  development:</p> <pre><code>Application Logic\nAI Interaction / Agents\nContext Retrieval Systems\nCognitive State Persistence Layer\nVersion Control / Storage\n</code></pre> <p>Current systems innovate primarily in the retrieval layer (improving how  context is discovered, ranked, and delivered at query time). The persistence  layer sits beneath retrieval and above version control. Its role is to maintain  the authoritative state that retrieval systems may query but do not own. The  relationship is complementary: retrieval answers \"What in the corpus might be  relevant?\"; cognitive state answers \"What must be true for this system to  operate correctly?\" A mature system uses both: retrieval for discovery,  persistence for authority.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#9-applicability-and-trade-offs","level":2,"title":"9. Applicability and Trade-Offs","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#91-when-to-use-this-model","level":3,"title":"9.1 When to Use This Model","text":"<p>A cognitive state persistence layer is most appropriate when:</p> <p>Reproducibility is a requirement: If a system must be able to answer \"Why  did this output occur, and can it be produced again?\" then deterministic,  version-controlled context becomes necessary. This is relevant in regulated  environments, safety-critical systems, long-lived infrastructure, and  security-sensitive deployments.</p> <p>Knowledge must outlive sessions and individuals: Projects with multi-year  lifetimes accumulate architectural decisions, domain interpretations, and  operational policy. If this knowledge is stored only in chat history, issue  trackers, and institutional memory, it decays. The persistence model converts  implicit knowledge into branchable, reviewable artifacts.</p> <p>Teams require shared cognitive authority: In collaborative environments,  correctness depends on a stable answer to \"What does the system believe to be  true?\" When this answer is derived from retrieval heuristics, authority shifts  to ranking algorithms. When it is versioned and human-readable, authority  remains with the team.</p> <p>Offline or air-gapped operation is required: Infrastructure-dependent  memory systems cannot operate in classified environments, isolated networks,  or constrained-environment scenarios.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#92-when-not-to-use-this-model","level":3,"title":"9.2 When Not to Use This Model","text":"<p>Zero-configuration personal workflows: For short-lived or exploratory tasks,  the cost of explicit knowledge curation outweighs its benefits. Heuristic  retrieval is sufficient when correctness is non-critical, outputs are  disposable, and historical reconstruction is unnecessary.</p> <p>Maximum automatic recall from large corpora: Vector retrieval systems  provide superior performance when the primary task is searching vast, weakly  structured information spaces. The persistence model assumes that what matters  can be decided and that this decision is valuable to record.</p> <p>Fully autonomous agent architectures: Agent runtimes that generate and  discard state continuously, optimizing for local goal completion, do not benefit  from a model that centers human ratification of knowledge.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#93-incremental-adoption","level":3,"title":"9.3 Incremental Adoption","text":"<p>The transition does not require full system replacement. An incremental path:</p> <p>Step 1: Record decisions as versioned artifacts: Instead of allowing  conclusions to remain in discussion threads, persist them in reviewable form  with context, rationale, and consequences <sup>4</sup>. This alone converts ephemeral  reasoning into the cognitive state.</p> <p>Step 2: Make inclusion deterministic: Define explicit assembly rules.  Retrieval may still exist, but it is no longer authoritative.</p> <p>Step 3: Move policy into cognitive state: When system behavior depends on  stable constraints, encode those constraints as versioned knowledge. Behavior  becomes reproducible.</p> <p>Step 4: Optimize assembly, not retrieval: Once the authoritative layer  exists, performance improvements come from budgeting, caching, and structural  refinement rather than from improving ranking heuristics.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#94-the-curation-cost","level":3,"title":"9.4 The Curation Cost","text":"<p>The primary objection to this model is the cost of explicit knowledge curation.  This cost is real. Writing a structured decision record takes longer than  letting a chatbot auto-summarize a conversation. Maintaining a glossary requires  discipline. Consolidating 75 learnings into 18 records requires judgment.</p> <p>The response is not that the cost is negligible but that it is amortized.  A decision record written once is loaded hundreds of times. A learning captured  today prevents repeated mistakes across all future sessions. The curation cost  is paid once; the benefit compounds.</p> <p>The experience report provides rough order-of-magnitude numbers. Across 389  sessions over 33 days, curation activities (writing decision records,  capturing learnings, updating the task list, consolidating entries) averaged  approximately 3-5 minutes per session. In early sessions (days 1-7), before  curated context existed, re-establishing context consumed approximately 10-15  minutes per session: re-explaining conventions, re-stating architectural  constraints, re-deriving decisions that had been made but not persisted. By the  final week (days 25-33), the re-explanation overhead had dropped to near zero:  the agent loaded curated context and began productive work immediately.</p> <p>At ~12 sessions per day, the curation cost was roughly 35-60 minutes daily.  The re-explanation cost in the first week was roughly 120-180 minutes daily.  By the third week, that cost had fallen to under 15 minutes daily while the  curation cost remained stable. The crossover (where cumulative curation cost  was exceeded by cumulative time saved) occurred around day 10. These figures are  approximate and derived from a single project with a small team already familiar  with the model; the crossover point will vary with project complexity,  team size, and curation discipline.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#10-future-work","level":2,"title":"10. Future Work","text":"<p>Several directions are compatible with the model described here:</p> <p>Section-level deterministic budgeting: Current assembly operates at file  granularity. Section-level budgeting would allow finer-grained control (including  specific decision records while excluding others within the same file) without  sacrificing determinism.</p> <p>Causal links between decisions: The experience report shows that decisions  frequently reference earlier decisions (superseding, extending, or qualifying  them). Formal causal links would enable traversal of the decision graph and  automatic detection of orphaned or contradictory constraints.</p> <p>Content-addressed context caches: Five systems in our landscape analysis  independently discovered that content hashing provides cache invalidation,  integrity verification, and change detection. Applying content addressing to the  assembly output would enable efficient cache reuse when the authoritative state  has not changed.</p> <p>Conditional context inclusion: Five systems independently suggest that  context entries could carry activation conditions (file patterns, task  keywords, or explicit triggers) that control whether they are included in a  given assembly. This would reduce the per-session budget cost of large  knowledge bases without sacrificing determinism.</p> <p>Provenance metadata: Linking context entries to the sessions, decisions, or  learnings that motivated them would strengthen the audit trail. Optional  provenance fields on Markdown entries (session identifier, cause reference,  motivation) would be lightweight and compatible with the existing file-based  model.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#11-conclusion","level":2,"title":"11. Conclusion","text":"<p>AI-assisted development has treated context as a \"query result\" assembled at the  moment of interaction, discarded at the session end. This paper identifies a  complementary layer: the persistence of authoritative cognitive state as  deterministic, version-controlled artifacts.</p> <p>The contribution is grounded in three sources of evidence. A landscape analysis  of 17 systems reveals five categories of primitives and shows that no existing  system provides the combination of human-readability, determinism, zero  dependencies, and offline capability that the persistence layer requires. Six  design invariants, validated by 56 independent rejection decisions, define the  constraints of the design space. An experience report over 389 sessions and  33 days demonstrates compounding returns: later sessions start faster,  decisions are not re-derived, and architectural reversals are captured with  full context.</p> <p>The core claim is this: persistent cognitive state enables causal reasoning  across time. A system built on this model can explain not only what is true,  but why it became true and when it changed.</p> <p>When context is the state:</p> <ul> <li>Reasoning is reproducible: the same authoritative state, budget, and policy    produce the same delivery view.</li> <li>Knowledge is auditable: decisions are traceable to explicit artifacts with    context, rationale, and consequences.</li> <li>Understanding compounds: each session's curation improves all subsequent    sessions.</li> </ul> <p>The choice between retrieval-centric workflows and a persistence layer is not a  matter of capability but of time horizon. Retrieval optimizes for relevance at  the moment of interaction. Persistence optimizes for the durability of  understanding across the lifetime of a project.</p> <p>🐸🖤 \"Gooood... let the deterministic context flow through the repository...\" - Kermit the Sidious, probably</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#appendix-a-representative-rejection-decisions","level":2,"title":"Appendix A: Representative Rejection Decisions","text":"<p>The 56 rejection decisions referenced in Section 4 were cataloged across all 17  system analyses, grouped by the invariant they would violate. This appendix  provides a representative sample (two per invariant) to illustrate the  methodology.</p> <p>Invariant 1: Markdown-on-Filesystem (11 rejections): CrewAI's vector  embedding storage was rejected because embeddings are not human-readable, not  git-diff-friendly, and require external services. Kindex's knowledge graph as  core primitive was rejected because it requires specialized commands to inspect  content that could be a text file (<code>kin show <id></code> vs. <code>cat DECISIONS.md</code>).</p> <p>Invariant 2: Zero Runtime Dependencies (13 rejections): Letta/MemGPT's  PostgreSQL-backed architecture was rejected because it conflicts with  local-first, no-database, single-binary operation. Pachyderm's Kubernetes-based  distributed architecture was rejected as the antithesis of a single-binary  design for a tool that manages text files.</p> <p>Invariant 3: Deterministic Assembly (6 rejections): LlamaIndex's  embedding-based retrieval as the primary selection mechanism was rejected because  it destroys determinism, requires an embedding model, and removes human  judgment from the selection process. QubicDB's wall-clock-dependent scoring was  rejected because it directly conflicts with the \"same inputs produce same  output\" property.</p> <p>Invariant 4: Human Authority (6 rejections): Letta/MemGPT's agent  self-modification of memory was rejected as fundamentally opposed to  human-curated persistence. Claude Code's unstructured auto-memory (where the  agent writes freeform notes) was rejected because structured files with defined  schemas produce higher-quality persistent context than unconstrained agent  output.</p> <p>Invariant 5: Local-First / Air-Gap Capable (7 rejections): Sweep's  cloud-dependent architecture was rejected as fundamentally incompatible with  the local-first, offline-capable model. LangGraph's managed cloud deployment  was rejected because cloud dependencies for core functionality violate  air-gap capability.</p> <p>Invariant 6: No Default Telemetry (4 rejections): Continue's  telemetry-by-default (PostHog) was rejected because it contradicts the  local-first, privacy-respecting trust model. CrewAI's global telemetry on  import (Scarf tracking pixel) was rejected because it violates user trust and  breaks air-gap capability.</p> <p>The remaining 9 rejections did not map to a specific invariant but were  rejected on other architectural grounds: for example, Aider's  full-file-content-in-context approach (which defeats token budgeting),  AutoGen's multi-agent orchestration as core primitive (scope creep),  and Claude Code's 30-day transcript retention limit  (institutional knowledge should have no automatic expiration).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#references","level":2,"title":"References","text":"<ol> <li> <p>Reproducible Builds Project, \"Reproducible Builds: Increasing the Integrity of Software Supply Chains\", 2017. https://reproducible-builds.org/docs/definition/ ↩↩↩</p> </li> <li> <p>S. McIntosh et al., \"The Impact of Build System Evolution on Software Quality\", ICSE, 2015. https://doi.org/10.1109/ICSE.2015.70 ↩</p> </li> <li> <p>C. Manning, P. Raghavan, H. Schütze, Introduction to Information Retrieval, Cambridge University Press, 2008. https://nlp.stanford.edu/IR-book/ ↩</p> </li> <li> <p>M. Nygard, \"Documenting Architecture Decisions\", Cognitect Blog, 2011. https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions ↩↩</p> </li> <li> <p>L. Torvalds et al., Git Internals - Git Objects (content-addressed storage concepts). https://git-scm.com/book/en/v2/Git-Internals-Git-Objects ↩</p> </li> <li> <p>Kief Morris, Infrastructure as Code, O'Reilly, 2016. ↩</p> </li> <li> <p>J. Kreps, \"The Log: What every software engineer should know about real-time data's unifying abstraction\", 2013. https://engineering.linkedin.com/distributed-systems/log ↩</p> </li> <li> <p>P. Hunt et al., \"ZooKeeper: Wait-free coordination for Internet-scale systems\", USENIX ATC, 2010. https://www.usenix.org/legacy/event/atc10/tech/full_papers/Hunt.pdf ↩</p> </li> </ol>","path":["The Thesis"],"tags":[]}]}
    \ No newline at end of file
    +{"config":{"separator":"[\\s\\-_,:!=\\[\\]()\\\\\"`/]+|\\.(?!\\d)"},"items":[{"location":"","level":1,"title":"Manifesto","text":"","path":["Manifesto"],"tags":[]},{"location":"#the-ctx-manifesto","level":1,"title":"The <code>ctx</code> Manifesto","text":"<p>Creation, not code.</p> <p>Context, not prompts.</p> <p>Verification, not vibes.</p> <p>This Is NOT a Metaphor</p> <p>Code executes instructions.</p> <p>Creation produces outcomes.</p> <p>Confusing the two is how teams ship motion...</p> <p>...instead of progress.</p> <ul> <li>It was never about the code.</li> <li>Code has zero standalone value.</li> <li>Code is an implementation detail.</li> </ul> <p>Code is an incantation.</p> <p>Creation is the act.</p> <p>And creation does not happen in a vacuum.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-the-substrate","level":2,"title":"<code>ctx</code> Is the Substrate","text":"<p>Constraints Have Moved</p> <p>Human bandwidth is no longer the limiting factor.</p> <p>Context integrity is.</p> <p>Human bandwidth is no longer the constraint.</p> <p>Context is:</p> <ul> <li>Without durable context, intelligence resets.</li> <li>Without memory, reasoning decays.</li> <li>Without structure, scale collapses.</li> </ul> <p>Creation is now limited by:</p> <ul> <li>Clarity of intent;</li> <li>Quality of context;</li> <li>Rigor of verification.</li> </ul> <p>Not by speed.</p> <p>Not by capacity.</p> <p>Velocity Amplifies</p> <p>Faster execution on broken context compounds error.</p> <p>Speed multiplies whatever is already wrong.</p>","path":["Manifesto"],"tags":[]},{"location":"#humans-author-meaning","level":2,"title":"Humans Author Meaning","text":"<p>Intent Is Authored</p> <p>Systems can optimize.</p> <p>Models can generalize.</p> <p>Meaning must be chosen.</p> <p>Intent is not emergent.</p> <p>Vision, goals, and direction are human responsibilities.</p> <p>We decide:</p> <ul> <li>What matters;</li> <li>What success means;</li> <li>What world we are building.</li> </ul> <p><code>ctx</code> encodes the intent so it...</p> <ul> <li>survives time,</li> <li>survives handoffs,</li> <li>survives scale.</li> </ul> <p>Nothing important should live only in conversation.</p> <p>Nothing critical should depend on recall.</p> <p>Oral Tradition Does Not Scale</p> <p>If intent cannot be inspected, it cannot be enforced.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-before-action","level":2,"title":"<code>ctx</code> Before Action","text":"<p>Orientation Precedes Motion</p> <p>Acting first and understanding later is not bravery.</p> <p>It is debt.</p> <p>Never act without <code>ctx</code>.</p> <p>Before execution, we must verify:</p> <ul> <li>Where we are;</li> <li>Why we are here;</li> <li>What constraints apply;</li> <li>What assumptions are active.</li> </ul> <p>Action without <code>ctx</code> is gambling.</p> <p>Speed without orientation is noise.</p> <p><code>ctx</code> is not overhead: It is the cost of correctness.</p>","path":["Manifesto"],"tags":[]},{"location":"#persistent-context-beats-prompt-memory","level":2,"title":"Persistent Context Beats Prompt Memory","text":"<p>Transience Is the Default Failure Mode</p> <ul> <li>Prompts decay.</li> <li>Chats fragment.</li> <li>Memory heuristics drift.</li> </ul> <p>Prompts are transient.</p> <p>Chats are lossy.</p> <p>Memory heuristics drift.</p> <p><code>ctx</code> must be:</p> <ul> <li>Durable;</li> <li>Structured;</li> <li>Explicit;</li> <li>Queryable.</li> </ul> <p>Intent Must Be Intentional</p> <p>If intent exists only in a prompt... </p> <p>...alignment is already degrading.</p> <p>Knowledge lives in the artifacts:</p> <ul> <li>Decisions;</li> <li>Documentation;</li> <li>Dependency maps;</li> <li>Evaluation history.</li> </ul> <p>Artifacts Outlive Sessions</p> <p>What is not written will be re-learned.</p> <p>At full cost.</p>","path":["Manifesto"],"tags":[]},{"location":"#what-ctx-is-not","level":2,"title":"What <code>ctx</code> Is Not","text":"<p>Avoid Category Errors</p> <p>Mislabeling <code>ctx</code> guarantees misuse.</p> <p><code>ctx</code> is not a memory feature.</p> <ul> <li><code>ctx</code> is not prompt engineering.</li> <li><code>ctx</code> is not a productivity hack.</li> <li><code>ctx</code> is not automation theater.</li> </ul> <p><code>ctx</code> is a system for preserving intent under scale.</p> <p><code>ctx</code> is infrastructure.</p>","path":["Manifesto"],"tags":[]},{"location":"#verified-reality-is-the-scoreboard","level":2,"title":"Verified Reality Is the Scoreboard","text":"<p>Activity Is a False Proxy</p> <p>Output volume correlates poorly with impact.</p> <ul> <li>Code is not progress.</li> <li>Activity is not impact.</li> </ul> <p>The only truth that compounds is verified change. </p> <p>Verified change must exist in the real world.</p> <p>Hypotheses are cheap; outcomes are not.</p> <p><code>ctx</code> captures:</p> <ul> <li>What we expected;</li> <li>What we observed;</li> <li>Where reality diverged.</li> </ul> <p>If we cannot predict, measure, and verify the result...</p> <p>...it does not count.</p>","path":["Manifesto"],"tags":[]},{"location":"#build-to-learn-not-to-accumulate","level":2,"title":"Build to Learn, Not to Accumulate","text":"<p>Prototypes Have an Expiration Date</p> <p>A prototype's value is information, not longevity.</p> <p>Prototypes exist to reduce uncertainty.</p> <p>We build to:</p> <ul> <li>Test assumptions;</li> <li>Validate architecture;</li> <li>Answer specific questions.</li> </ul> <p>Not everything.</p> <p>Not blindly.</p> <p>Not permanently.</p> <p><code>ctx</code> records archeology so the cost is paid once.</p>","path":["Manifesto"],"tags":[]},{"location":"#failures-are-assets","level":2,"title":"Failures Are Assets","text":"<p>Failure without Capture Is Waste</p> <p>Pain that does not teach is pure loss.</p> <p>Failures are not erased: They are preserved.</p> <p>Each failure becomes:</p> <ul> <li>A documented hypothesis;</li> <li>An analyzed deviation;</li> <li>A permanent artifact.</li> </ul> <p>Rollback fixes symptoms: <code>ctx</code> fixes systems.</p> <p>A repeated mistake is a missing <code>ctx</code> artifact.</p>","path":["Manifesto"],"tags":[]},{"location":"#structure-enables-scale","level":2,"title":"Structure Enables Scale","text":"<p>Unbounded Autonomy Destabilizes</p> <p>Power without a structure produces chaos.</p> <p>Transpose it:</p> <p>Power without any structure becomes chaos.</p> <p><code>ctx</code> defines:</p> <ul> <li>Roles;</li> <li>Boundaries;</li> <li>Protocols;</li> <li>Escalation paths;</li> <li>Decision rights.</li> </ul> <p>Ambiguity is a system failure:</p> <ul> <li>Debates must be structured.</li> <li>Decisions must be explicit.</li> <li>History must be retained.</li> </ul>","path":["Manifesto"],"tags":[]},{"location":"#encode-intent-into-the-environment","level":2,"title":"Encode Intent into the Environment","text":"<p>Goodwill Does Not Belong to the Table</p> <p>Alignment that depends on memory will drift.</p> <p>Alignment cannot depend on memory or goodwill.</p> <p>Do not rely on people to remember.</p> <p>Encode the behavior, so it happens by default.</p> <p>Intent is encoded as:</p> <ul> <li>Policies;</li> <li>Schemas;</li> <li>Constraints;</li> <li>Evaluation harnesses.</li> </ul> <p>Rules must be machine-readable.</p> <p>Laws must be enforceable.</p> <p>If intent is implicit, drift is guaranteed.</p>","path":["Manifesto"],"tags":[]},{"location":"#cost-is-a-first-class-signal","level":2,"title":"Cost Is a First-Class Signal","text":"<p>Attention Is the Scarcest Resource</p> <p>Not ideas.</p> <p>Not ambition.</p> <p>Ideas do not compete on time:</p> <p>They compete on cost and impact:</p> <ul> <li>Attention is finite.</li> <li>Compute is finite.</li> <li>Context is expensive.</li> </ul> <p>We continuously ask:</p> <ul> <li>What the most valuable next action is.</li> <li>What outcome justifies the cost.</li> </ul> <p><code>ctx</code> guides allocation.</p> <p>Learning reshapes priority.</p>","path":["Manifesto"],"tags":[]},{"location":"#show-the-why","level":2,"title":"Show the Why","text":"<p><code>{}</code> (code, artifacts, apps, binaries) produce outputs;  they do not preserve reasoning.</p> <p>Systems that cannot explain themselves will not be trusted.</p> <p>Traceability builds trust.</p> <pre><code>     {} --> what\n\n    ctx --> why\n</code></pre> <p>We record:</p> <ul> <li>Explored paths;</li> <li>Rejected options;</li> <li>Assumptions made;</li> <li>Evidence used.</li> </ul> <p>Opaque systems erode trust:</p> <p>Transparent <code>ctx</code> compounds understanding.</p>","path":["Manifesto"],"tags":[]},{"location":"#continuously-verify-the-system","level":2,"title":"Continuously Verify the System","text":"<p>Stability Is Temporary</p> <p>Every assumption has a half-life:</p> <ul> <li>Models drift.</li> <li>Tools change.</li> <li>Assumptions rot.</li> </ul> <p><code>ctx</code> must be verified against reality.</p> <p>Trust is a spectrum.</p> <p>Trust is continuously re-earned:</p> <ul> <li>Benchmarks, </li> <li>regressions, </li> <li>and evaluations... </li> </ul> <p>...are safety rails.</p>","path":["Manifesto"],"tags":[]},{"location":"#ctx-is-leverage","level":2,"title":"<code>ctx</code> Is Leverage","text":"<p>Humans Are Decision Engines</p> <p>Execution should not consume judgment.</p> <p>Humans must not be typists.</p> <p>We are the authors.</p> <p>Human effort is reserved for:</p> <ul> <li>Judgment;</li> <li>Design;</li> <li>Taste;</li> <li>Synthesis.</li> </ul> <p>Repetition is delegated.</p> <p>Toil is automated.</p> <p><code>ctx</code> preserves leverage across time.</p>","path":["Manifesto"],"tags":[]},{"location":"#the-thesis","level":2,"title":"The Thesis","text":"<p>Invariant</p> <p>Everything else is an implementation detail.</p> <ul> <li>Creation is the act.</li> <li><code>ctx</code> is the substrate.</li> <li>Verification is the truth.</li> </ul> <p>Code executes → Models reason → Agents amplify.</p> <p><code>ctx</code> lives on.</p> <ul> <li>Without <code>ctx</code>, intelligence resets.</li> <li>With <code>ctx</code>, creation compounds.</li> </ul>","path":["Manifesto"],"tags":[]},{"location":"blog/","level":1,"title":"Blog","text":"<p>Stories, insights, and lessons learned from building and using <code>ctx</code>.</p>","path":["Blog"],"tags":[]},{"location":"blog/#releases","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v080-the-architecture-release","level":3,"title":"<code>ctx</code> v0.8.0: The Architecture Release","text":"<p>March 23, 2026: 374 commits, 1,708 Go files touched, and a near-complete architectural overhaul. Every CLI package restructured into <code>cmd/ + core/</code> taxonomy, all user-facing strings externalized to YAML, MCP server for tool-agnostic AI integration, and the memory bridge connecting Claude Code's auto-memory to <code>.context/</code>.</p> <p>Topics: release, architecture, refactoring, MCP, localization</p>","path":["Blog"],"tags":[]},{"location":"blog/#field-notes","level":2,"title":"Field Notes","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-watermelon-rind-anti-pattern-why-smarter-tools-make-shallower-agents","level":3,"title":"The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents","text":"<p>April 6, 2026: Give an agent a graph query tool, and it produces output that's structurally correct but substantively hollow (the watermelon-rind antipattern: We ran three sessions analyzing the same codebase with different tool access: the one with no tools produced 5.2x more depth. The fix: a two-pass compiler for architecture understanding: force code reading first, verify with tools second. Constraint is the feature.</p> <p>Topics: architecture, code intelligence, agent behavior, design patterns, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#code-structure-as-an-agent-interface-what-19-ast-tests-taught-us","level":3,"title":"Code Structure as an Agent Interface: What 19 AST Tests Taught Us","text":"<p>April 2, 2026: We built 19 AST-based audit tests in a single session, touching 300+ files. In the process we discovered that \"old-school\" code quality constraints (no magic numbers, centralized error handling, 80-char lines, documentation) are exactly the constraints that make code readable to AI agents. If an agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.</p> <p>Topics: ast, code quality, agent readability, conventions, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#we-broke-the-31-rule","level":3,"title":"We Broke the 3:1 Rule","text":"<p>March 23, 2026: After v0.6.0, we ran 198 feature commits across 17 days before consolidating. The 3:1 rule says consolidate every 4<sup>th</sup> session. We did it after the 66<sup>th</sup>. The result: an 18-day, 181-commit cleanup marathon that took longer than the feature run itself. A follow-up to The 3:1 Ratio with empirical evidence from the v0.8.0 cycle.</p> <p>Topics: consolidation, technical debt, development workflow, convention drift, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#context-engineering","level":2,"title":"Context Engineering","text":"","path":["Blog"],"tags":[]},{"location":"blog/#agent-memory-is-infrastructure","level":3,"title":"Agent Memory Is Infrastructure","text":"<p>March 4, 2026: Every AI coding agent starts fresh. The obvious fix is \"memory.\" But there's a different problem memory doesn't touch: the project itself accumulates knowledge that has nothing to do with any single session. This post argues that agent memory is L2 (runtime cache); what's missing is L3 (project infrastructure).</p> <p>Topics: context engineering, agent memory, infrastructure, persistence, team knowledge</p>","path":["Blog"],"tags":[]},{"location":"blog/#context-as-infrastructure","level":3,"title":"Context as Infrastructure","text":"<p>February 17, 2026: Where does your AI's knowledge live between sessions? If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. This post argues for treating it as infrastructure instead: persistent files, separation of concerns, two-tier storage, progressive disclosure, and the filesystem as the most mature interface available.</p> <p>Topics: context engineering, infrastructure, progressive disclosure, persistence, design philosophy</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-attention-budget-why-your-ai-forgets-what-you-just-told-it","level":3,"title":"The Attention Budget: Why Your AI Forgets What You Just Told It","text":"<p>February 3, 2026: Every token you send to an AI consumes a finite resource: the attention budget. Understanding this constraint shaped every design decision in <code>ctx</code>: hierarchical file structure, explicit budgets, progressive disclosure, and filesystem-as-index.</p> <p>Topics: attention mechanics, context engineering, progressive disclosure, <code>ctx</code> primitives, token budgets</p>","path":["Blog"],"tags":[]},{"location":"blog/#before-context-windows-we-had-bouncers","level":3,"title":"Before Context Windows, We Had Bouncers","text":"<p>February 14, 2026: IRC is stateless. You disconnect, you vanish. Modern systems are not much different. This post traces the line from IRC bouncers to context engineering: stateless protocols require stateful wrappers, volatile interfaces require durable memory.</p> <p>Topics: context engineering, infrastructure, IRC, persistence, state continuity</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-last-question","level":3,"title":"The Last Question","text":"<p>February 28, 2026: In 1956, Asimov wrote a story about a question that spans the entire future of the universe. A reading of \"The Last Question\" through the lens of persistence, substrate migration, and what it means to build systems where sessions don't reset.</p> <p>Topics: context continuity, long-lived systems, persistence, intelligence over time, field notes</p>","path":["Blog"],"tags":[]},{"location":"blog/#agent-behavior-and-design","level":2,"title":"Agent Behavior and Design","text":"","path":["Blog"],"tags":[]},{"location":"blog/#the-dog-ate-my-homework-teaching-ai-agents-to-read-before-they-write","level":3,"title":"The Dog Ate My Homework: Teaching AI Agents to Read Before They Write","text":"<p>February 25, 2026: You wrote the playbook. The agent skipped all of it. Five sessions, five failure modes, and the discovery that observable compliance beats perfect compliance.</p> <p>Topics: hooks, agent behavior, context engineering, behavioral design, testing methodology, compliance monitoring</p>","path":["Blog"],"tags":[]},{"location":"blog/#skills-that-fight-the-platform","level":3,"title":"Skills That Fight the Platform","text":"<p>February 4, 2026: When custom skills conflict with system prompt defaults, the AI has to reconcile contradictory instructions. Five conflict patterns discovered while building <code>ctx</code>.</p> <p>Topics: context engineering, skill design, system prompts, antipatterns, AI safety primitives</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-anatomy-of-a-skill-that-works","level":3,"title":"The Anatomy of a Skill That Works","text":"<p>February 7, 2026: I had 20 skills. Most were well-intentioned stubs. Then I rewrote all of them. Seven lessons emerged: quality gates prevent premature execution, negative triggers are load-bearing, examples set boundaries better than rules.</p> <p>Topics: skill design, context engineering, quality gates, E/A/R framework, practical patterns</p>","path":["Blog"],"tags":[]},{"location":"blog/#you-cant-import-expertise","level":3,"title":"You Can't Import Expertise","text":"<p>February 5, 2026: I found a well-crafted consolidation skill. Applied my own E/A/R framework: 70% was noise. This post is about why good skills can't be copy-pasted, and how to grow them from your project's own drift history.</p> <p>Topics: skill adaptation, E/A/R framework, convention drift, consolidation, project-specific expertise</p>","path":["Blog"],"tags":[]},{"location":"blog/#not-everything-is-a-skill","level":3,"title":"Not Everything Is a Skill","text":"<p>February 8, 2026: I ran an 8-agent codebase audit and got actionable results. The natural instinct was to wrap the prompt as a skill. Then I applied my own criteria: it failed all three tests.</p> <p>Topics: skill design, context engineering, automation discipline, recipes, agent teams</p>","path":["Blog"],"tags":[]},{"location":"blog/#defense-in-depth-securing-ai-agents","level":3,"title":"Defense in Depth: Securing AI Agents","text":"<p>February 9, 2026: The security advice was \"use CONSTITUTION.md for guardrails.\" That is wishful thinking. Five defense layers for unattended AI agents, each with a bypass, and why the strength is in the combination.</p> <p>Topics: agent security, defense in depth, prompt injection, autonomous loops, container isolation</p>","path":["Blog"],"tags":[]},{"location":"blog/#development-practice","level":2,"title":"Development Practice","text":"","path":["Blog"],"tags":[]},{"location":"blog/#code-is-cheap-judgment-is-not","level":3,"title":"Code Is Cheap. Judgment Is Not.","text":"<p>February 17, 2026: AI does not replace workers. It replaces unstructured effort. Three weeks of building <code>ctx</code> with an AI agent proved it: YOLO mode showed production is cheap, the 3:1 ratio showed judgment has a cadence.</p> <p>Topics: AI and expertise, context engineering, judgment vs production, human-AI collaboration, automation discipline</p>","path":["Blog"],"tags":[]},{"location":"blog/#the-31-ratio","level":3,"title":"The 3:1 Ratio","text":"<p>February 17, 2026: AI makes technical debt worse: not because it writes bad code, but because it writes code so fast that drift accumulates before you notice. Three feature sessions, one consolidation session.</p> <p>Topics: consolidation, technical debt, development workflow, convention drift, code quality</p>","path":["Blog"],"tags":[]},{"location":"blog/#refactoring-with-intent-human-guided-sessions-in-ai-development","level":3,"title":"Refactoring with Intent: Human-Guided Sessions in AI Development","text":"<p>February 1, 2026: The YOLO mode shipped 14 commands in a week. But technical debt doesn't send invoices. This is the story of what happened when we started guiding the AI with intent.</p> <p>Topics: refactoring, code quality, documentation standards, module decomposition, YOLO versus intentional development</p>","path":["Blog"],"tags":[]},{"location":"blog/#how-deep-is-too-deep","level":3,"title":"How Deep Is Too Deep?","text":"<p>February 12, 2026: I kept feeling like I should go deeper into ML theory. Then I spent a week debugging an agent failure that had nothing to do with model architecture. When depth compounds and when it doesn't.</p> <p>Topics: AI foundations, abstraction boundaries, agentic systems, context engineering, failure modes</p>","path":["Blog"],"tags":[]},{"location":"blog/#agent-workflows","level":2,"title":"Agent Workflows","text":"","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-merge-debt-and-the-myth-of-overnight-progress","level":3,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"<p>February 17, 2026: You discover agents can run in parallel. So you open ten terminals. It is not progress: it is merge debt being manufactured in real time. The five-agent ceiling and why role separation beats file locking.</p> <p>Topics: agent workflows, parallelism, verification, context engineering, engineering practice</p>","path":["Blog"],"tags":[]},{"location":"blog/#parallel-agents-with-git-worktrees","level":3,"title":"Parallel Agents with Git Worktrees","text":"<p>February 14, 2026: I had 30 open tasks that didn't touch the same files. Using git worktrees to partition a backlog by file overlap, run 3-4 agents simultaneously, and merge the results.</p> <p>Topics: agent teams, parallelism, git worktrees, context engineering, task management</p>","path":["Blog"],"tags":[]},{"location":"blog/#field-notes-and-signals","level":2,"title":"Field Notes and Signals","text":"","path":["Blog"],"tags":[]},{"location":"blog/#when-a-system-starts-explaining-itself","level":3,"title":"When a System Starts Explaining Itself","text":"<p>February 17, 2026: Every new substrate begins as a private advantage. Reality begins when other people start describing it in their own language. \"Better than Adderall\" is not praise; it is a diagnostic.</p> <p>Topics: field notes, adoption signals, infrastructure vs tools, context engineering, substrates</p>","path":["Blog"],"tags":[]},{"location":"blog/#why-zensical","level":3,"title":"Why Zensical","text":"<p>February 15, 2026: I needed a static site generator for the journal system. The instinct was Hugo. But instinct is not analysis. Why zensical was the right choice: thin dependencies, MkDocs-compatible config, and zero lock-in.</p> <p>Topics: tooling, static site generators, journal system, infrastructure decisions, context engineering</p>","path":["Blog"],"tags":[]},{"location":"blog/#releases_1","level":2,"title":"Releases","text":"","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v060-the-integration-release","level":3,"title":"<code>ctx</code> v0.6.0: The Integration Release","text":"<p>February 16, 2026: <code>ctx</code> is now a Claude Marketplace plugin. Two commands, no build step, no shell scripts. v0.6.0 replaces six Bash hook scripts with compiled Go subcommands and ships 25+ Skills as a plugin.</p> <p>Topics: release, plugin system, Claude Marketplace, distribution, security hardening</p>","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v030-the-discipline-release","level":3,"title":"<code>ctx</code> v0.3.0: The Discipline Release","text":"<p>February 15, 2026: No new headline feature. Just 35+ documentation and quality commits against ~15 feature commits. What a release looks like when the ratio of polish to features is 3:1.</p> <p>Topics: release, skills migration, consolidation, code quality, E/A/R framework</p>","path":["Blog"],"tags":[]},{"location":"blog/#ctx-v020-the-archaeology-release","level":3,"title":"<code>ctx</code> v0.2.0: The Archaeology Release","text":"<p>February 1, 2026: What if your AI could remember everything? Not just the current session, but every session. <code>ctx</code> v0.2.0 introduces the recall and journal systems.</p> <p>Topics: session recall, journal system, structured entries, token budgets, meta-tools</p>","path":["Blog"],"tags":[]},{"location":"blog/#building-ctx-using-ctx-a-meta-experiment-in-ai-assisted-development","level":3,"title":"Building <code>ctx</code> Using <code>ctx</code>: A Meta-Experiment in AI-Assisted Development","text":"<p>January 27, 2026: What happens when you build a tool designed to give AI memory, using that very same tool to remember what you're building? This is the story of <code>ctx</code>.</p> <p>Topics: dogfooding, AI-assisted development, Ralph Loop, session persistence, architectural decisions</p>","path":["Blog"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/","level":1,"title":"Building <code>ctx</code> Using <code>ctx</code>","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>References to <code>.context/sessions/</code>, auto-save hooks, and <code>SessionEnd</code> auto-save in this post reflect the architecture at the time of writing.</p> <p></p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#a-meta-experiment-in-ai-assisted-development","level":2,"title":"A Meta-Experiment in AI-Assisted Development","text":"<p>Jose Alekhinne / 2026-01-27</p> <p>Can a Tool Design Itself?</p> <p>What happens when you build a tool designed to give AI memory,  using that very same tool to remember what you are building? </p> <p>This is the story of <code>ctx</code>, how it evolved from a hasty \"YOLO mode\" experiment  to a disciplined system for persistent AI context, and what I have  learned along the way.</p> <p>Context Is a Record</p> <p>Context is a persistent record.</p> <p>By \"context\", I don't mean model memory or stored thoughts: </p> <p>I mean the durable record of decisions, learnings, and intent  that normally evaporates between sessions.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#ai-amnesia","level":2,"title":"AI Amnesia","text":"<p>Every developer who works with AI code generators knows the frustration: </p> <p>You have a deep, productive session where the AI understands your codebase,  your conventions, your decisions. And then you close the terminal. </p> <p>Tomorrow; it's a blank slate. The AI has forgotten everything.</p> <p>That is \"reset amnesia\", and it's not just annoying: it's expensive. </p> <p>Every session starts with: </p> <ul> <li>Re-explaining context;</li> <li>Re-reading files; </li> <li>Re-discovering decisions that were already made.</li> </ul> <p>I Needed Context</p> <p>\"I don't want to lose this discussion...</p> <p>...I am a brain-dead developer YOLO'ing my way out.\"</p> <p>☝️ that's exactly what I said to Claude when I first started working on <code>ctx</code>.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-genesis","level":2,"title":"The Genesis","text":"<p>The project started as \"Active Memory\" (<code>amem</code>): a CLI tool to persist AI  context across sessions. </p> <p>The core idea was simple: </p> <ol> <li>Create a <code>.context/</code> directory with structured     Markdown files for decisions, learnings, tasks, and conventions. </li> <li>The AI reads these at session start and writes to them before     the session ends.</li> <li>There is no step 3.</li> </ol> <p>The first commit was just scaffolding. But within hours, the  Ralph Loop (An iterative AI development workflow) had produced a working CLI:</p> <pre><code>feat(cli): implement amem init command\nfeat(cli): implement amem status command\nfeat(cli): implement amem add command\nfeat(cli): implement amem agent command\n...\n</code></pre> <p>Not one, not two, but a whopping fourteen core commands shipped in rapid  succession!</p> <p>I was YOLO'ing like there was no tomorrow:</p> <ul> <li>Auto-accept every change;</li> <li>Let the AI run free;</li> <li>Ship features fast.</li> </ul>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-meta-experiment-using-amem-to-build-amem","level":2,"title":"The Meta-Experiment: Using <code>amem</code> to Build <code>amem</code>","text":"<p>Here's where it gets interesting: On January 20<sup>th</sup>, I asked: </p> <p>\"Can I use <code>amem</code> to help you remember this context when I restart?\"</p> <p>The answer was yes, but with a gap: </p> <p>Autoload worked (via Claude Code's <code>PreToolUse</code> hook), but auto-save was  missing: If the user quit, with Ctrl+C, everything since the last manual save  was lost.</p> <p>That session became the first real test of the system. </p> <p>Here is the first session file we recorded:</p> <pre><code>## Key Discussion Points\n\n### 1. amem vs Ralph Loop - They're Separate Systems\n\n**User's question**: \"How do I use the binary to recreate this project?\"\n\n**Answer discovered**: `amem` is for context management, Ralph Loop is for \ndevelopment workflow. They are complementary but separate.\n\n### 2. Two Tiers of Context Persistence\n\n| Tier      | What                        | Why                           |\n|-----------|-----------------------------|-------------------------------|\n| Curated   | Learnings, decisions, tasks | Quick reload, token-efficient |\n| Full dump | Entire conversation         | Safety net, nothing lost      |\n\n| Where                  |\n|------------------------|\n| .context/*.md          |\n| .context/sessions/*.md |\n</code></pre> <p>This session file (written by the AI to preserve its own context) became the template for how <code>ctx</code> handles session persistence.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-rename","level":2,"title":"The Rename","text":"<p>By January 21<sup>st</sup>, I realized \"Active Memory\" was too generic,  and (arguably) too marketing-smelly. </p> <p>Besides, the binary was already called <code>ctx</code> (short for Context),  the directory was <code>.context/</code>, and the slash commands would be <code>/ctx-*</code>. </p> <p>So it followed that the project should be renamed to <code>ctx</code> to make things  make sense.</p> <p>The rename touched 100+ files but was clean: a find-and-replace with Go's  type system catching any misses.</p> <p>The <code>git</code> history tells the story:</p> <pre><code>0e8f6bb feat: rename amem to ctx and add Claude Code integration\n87dcfa1 README.\n4f0e195 feat: separate orchestrator directive from agent tasks\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#yolo-mode-fast-but-dangerous","level":2,"title":"YOLO Mode: Fast, but Dangerous","text":"<p>The Ralph Loop made feature development incredibly fast.</p> <p>But it created technical debt that I didn't notice until later.</p> <p>A comparison session on January 25<sup>th</sup> revealed the patterns:</p> YOLO Pattern What We Found <code>\"TASKS.md\"</code> scattered in 10 files Same string literal everywhere, no constants <code>dir + \"/\" + file</code> Should be <code>filepath.Join()</code> Monolithic <code>cli_test.go</code> (1500+ lines) Tests disconnected from implementations <code>package initcmd</code> in <code>init/</code> folder Go's \"init\" is reserved: subtle naming collision <p>Here is another analysis made by <code>ctx</code>:</p> <pre><code>● Based on my analysis, here are the key differences:\n\nYOLO Mode (Pre-040ce99)\n- Feature-first: Added slash commands, tests, templates rapidly\n- Scattered magic strings: \"TASKS.md\", \"decision\", \".context\" spread across files\n- Quick file creation: New files without organizational patterns\n- Working but inconsistent: Code functioned but lacked systematic structure\n\nHuman-Guided Mode (Post-040ce99)\n- Consolidation focus: Centralized constants in config package\n- Consistent naming: Dir, File, Filename, UpdateType prefixes\n- Self-referential constants: FileType map uses constants as keys, not literals\n- Proper path construction: filepath.Join() instead of +\"/\"+\n- Colocated tests: Tests next to implementations\n- Canonical naming: Package name = folder name\n</code></pre> <p>The fix required a human-guided refactoring session. I continued to do that before every major release, from that point on.</p> <p>We introduced <code>internal/config/config.go</code> with semantic prefixes:</p> <pre><code>const (\n    DirContext     = \".context\"\n    DirArchive     = \"archive\"\n    DirSessions    = \"sessions\"\n    FilenameTask   = \"TASKS.md\"\n    UpdateTypeTask = \"task\"\n)\n</code></pre> <p>What I begrudgingly learned was:  YOLO mode is effective for velocity but accumulates debt. </p> <p>So I took a mental note to schedule  periodic consolidation sessions.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-dogfooding-test-that-failed","level":2,"title":"The Dogfooding Test That Failed","text":"<p>On January 21<sup>st</sup>, I ran an experiment: have another Claude instance rebuild  <code>ctx</code> from scratch using only the specs and <code>PROMPT.md</code>. </p> <p>The Ralph Loop ran, all tasks got checked off, the loop exited successfully.</p> <p>But the binary was broken!</p> <p>Commands just printed help text instead of executing. </p> <p>All tasks were marked \"complete\" but the implementation didn't work.</p> <p>Here's what <code>ctx</code> discovered:</p> <pre><code>## Key Findings\n\n### Dogfooding Binary Is Broken\n- Commands don't execute: they just print root help text\n- All tasks were marked complete but binary doesn't work\n- Lesson: \"tasks checked off\" ≠ \"implementation works\"\n</code></pre> <p>This was humbling; to say the least.</p> <p>I realized I had the same blind spot in my own codebase: no integration tests that actually invoked the binary. </p> <p>So I added:</p> <ul> <li>Integration tests for all commands;</li> <li>Coverage targets (60-80% per package)</li> <li>Smoke tests in CI</li> <li>A constitution rule:    \"All code must pass tests before commit\"</li> </ul>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-constitution-versus-conventions","level":2,"title":"The Constitution versus Conventions","text":"<p>As lessons accumulated, there was the temptation to add everything to  <code>CONSTITUTION.md</code> as \"inviolable rules\". </p> <p>But I resisted.</p> <p>The constitution should contain only truly inviolable invariants:</p> <ul> <li>Security (no secrets, no customer data)</li> <li>Quality (tests must pass)</li> <li>Process (decisions need records)</li> <li><code>ctx</code> invocation (always use <code>PATH</code>, never fallback)</li> </ul> <p>Everything else (coding style, file organization, naming  conventions...) should go in to <code>CONVENTIONS.md</code>. </p> <p>Here's how <code>ctx</code> explained why the distinction was important: </p> <p>Decision Record, 2026-01-25</p> <p>Overly strict constitution creates friction and gets ignored.</p> <p>Conventions can be bent; constitution cannot.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#hooks-harder-than-they-look","level":2,"title":"Hooks: Harder than They Look","text":"<p>Claude Code hooks seemed simple: Run a script before/after certain events. </p> <p>But I hit multiple gotchas:</p> <p>1. Key names matter</p> <pre><code>// WRONG - \"Invalid key in record\" error\n\"PreToolUseHooks\": [...]\n\n// RIGHT\n\"PreToolUse\": [...]\n</code></pre> <p>2. Blocking requires specific output</p> <pre><code># WRONG - just exits, doesn't block\nexit 1\n\n# RIGHT - JSON output + exit 0\necho '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH\"}'\nexit 0\n</code></pre> <p>3. Go's JSON escaping</p> <p><code>json.Marshal</code> escapes <code>></code>, <code><</code>, <code>&</code> as unicode (<code>\\u003e</code>) by default. </p> <p>When generating shell commands in JSON:</p> <pre><code>encoder := json.NewEncoder(file)\nencoder.SetEscapeHTML(false) // Prevent 2>/dev/null → 2\\u003e/dev/null\n</code></pre> <p>4. Regex overfitting</p> <p>My hook to block non-PATH <code>ctx</code> invocations initially matched too broadly:</p> <pre><code># WRONG - matches /home/user/ctx/internal/file.go (ctx as directory)\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# RIGHT - matches ctx as binary only\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-session-files","level":2,"title":"The Session Files","text":"<p>By the time of this writing this project's <code>ctx</code> sessions  (<code>.context/sessions/</code>) contains 40+ files from this project's development.</p> <p>They are not part of the source code due to security, privacy, and size concerns.</p> <p>Middle Ground: The Scratchpad</p> <p>For sensitive notes that do need to travel with the project, <code>ctx pad</code> stores encrypted one-liners in git, and <code>ctx pad add \"label\" --file PATH</code> can ingest small files.</p> <p>See Scratchpad for details.</p> <p>However, they are invaluable for the project's progress.</p> <p>Each session file is a timestamped Markdown with:</p> <ul> <li>Summary of what has been accomplished;</li> <li>Key decisions made;</li> <li>Learnings discovered;</li> <li>Tasks for the next session;</li> <li>Technical context (platform, versions).</li> </ul> <p>These files are not autoloaded (that would bust the token budget). </p> <p>They are what I see as the \"archaeological record\" of <code>ctx</code>:</p> <p>When the AI needs deeper information about why something was done, it digs into the sessions.</p> <p>Auto-generated session files used a naming convention:</p> <pre><code>2026-01-23-115432-session-prompt_input_exit-summary.md\n2026-01-25-220244-manual-save.md\n2026-01-27-052107-session-other-summary.md\n</code></pre> <p>Update</p> <p>The session feature described here is historical. </p> <p>In current releases, <code>ctx</code> uses a journal instead: the enrichment  process generates meaningful slugs from context automatically, so there  is no need to manually save sessions.</p> <p>The <code>SessionEnd</code> hook captured transcripts automatically. Even <code>Ctrl+C</code> was caught.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-decision-log-18-architectural-decisions","level":2,"title":"The Decision Log: 18 Architectural Decisions","text":"<p><code>ctx</code> helps record every significant architectural choice in  <code>.context/DECISIONS.md</code>. </p> <p>Here are some highlights:</p> <p>Reverse-chronological order (2026-01-27)</p> <pre><code>**Context**: With chronological order, oldest items consume tokens first, and\nnewest (most relevant) items risk being truncated.\n\n**Decision**: Use reverse-chronological order (newest first) for DECISIONS.md\nand LEARNINGS.md.\n</code></pre> <p>PATH over hardcoded paths (2026-01-21)</p> <pre><code>**Context**: Original implementation hardcoded absolute paths in hooks.\nThis breaks when sharing configs with other developers.\n\n**Decision**: Hooks use `ctx` from PATH. `ctx init` checks PATH before \nproceeding.\n</code></pre> <p>Generic core with Claude enhancements (2026-01-20)</p> <pre><code>**Context**: ctx should work with any AI tool, but Claude Code users could\nbenefit from deeper integration.\n\n**Decision**: Keep ctx generic as the core tool, but provide optional\nClaude Code-specific enhancements.\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-learning-log-24-gotchas-and-insights","level":2,"title":"The Learning Log: 24 Gotchas and Insights","text":"<p>The <code>.context/LEARNINGS.md</code> file captures gotchas that would otherwise be  forgotten. Each has Context, Lesson, and Application sections:</p> <p>CGO on ARM64</p> <pre><code>**Context**: `go test` failed with \n`gcc: error: unrecognized command-line option '-m64'`\n\n**Lesson**: On ARM64 Linux, CGO causes cross-compilation issues. \nAlways use `CGO_ENABLED=0`.\n</code></pre> <p>Claude Code skills format</p> <pre><code>**Lesson**: Claude Code skills are Markdown files in .claude/commands/ with `YAML`\nfrontmatter (*description, argument-hint, allowed-tools*). Body is the prompt.\n</code></pre> <p>\"Do you remember?\" handling</p> <pre><code>**Lesson**: In a `ctx`-enabled project, \"*do you remember?*\" \nhas an obvious meaning:\ncheck the `.context/` files. Don't ask for clarification. Just do it.\n</code></pre>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#task-archives-the-completed-work","level":2,"title":"Task Archives: The Completed Work","text":"<p>Completed tasks are archived to <code>.context/archive/</code> with timestamps. </p> <p>The archive from January 23<sup>rd</sup> shows 13 phases of work:</p> <ul> <li>Phase 1: Project Scaffolding (Go module, Cobra CLI)</li> <li>Phase 2-4: Core Commands    (init, status, agent, add, complete, drift, sync, compact, watch, hook)</li> <li>Phase 5: Session Management (save, list, load, parse, --extract)</li> <li>Phase 6: Claude Code Integration (hooks, settings, CLAUDE.md handling)</li> <li>Phase 7: Testing & Verification</li> <li>Phase 8: Task Archival</li> <li>Phase 9: Slash Commands</li> <li>Phase 9b: Ralph Loop Integration</li> <li>Phase 10: Project Rename</li> <li>Phase 11: Documentation</li> <li>Phase 12: Timestamp Correlation</li> <li>Phase 13: Rich Context Entries</li> </ul> <p>That's an impressive ^^173 commits** across 8 days of development.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#what-i-learned-about-ai-assisted-development","level":2,"title":"What I Learned about AI-Assisted Development","text":"<p>1. Memory changes everything</p> <p>When the AI remembers decisions, it doesn't repeat mistakes. </p> <p>When the AI knows your conventions, it follows them. </p> <p><code>ctx</code> makes the AI a better collaborator because it's not starting from zero.</p> <p>2. Two-tier persistence works</p> <p>Curated context (<code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>TASKS.md</code>) is for  quick reload. </p> <p>Full session dumps are for archaeology. </p> <p>It's a futile effort to try to fit everything in the token budget.</p> <p>Persist more, load less.</p> <p>3. YOLO mode has its place</p> <p>For rapid prototyping, letting the AI run free is effective. </p> <p>But I had to schedule  consolidation sessions.</p> <p>Technical debt accumulates silently.</p> <p>4. The constitution should be small</p> <p>Only truly inviolable rules go in <code>CONSTITUTION.md</code>.  Everything else is a convention. </p> <p>If you put too much in the constitution, it will get ignored.</p> <p>5. Verification is non-negotiable</p> <p>\"All tasks complete\" means nothing if you haven't run the tests. </p> <p>Integration tests that invoke the actual binary caught bugs that  the unit tests missed.</p> <p>6. Session files are underrated</p> <p>The ability to grep through 40 session files and find exactly when and why a  decision was made helped me a lot. </p> <p>It's not about loading them into context: It is about having them  when you need them.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#the-future-recall-system","level":2,"title":"The Future: Recall System","text":"<p>The next phase of <code>ctx</code> is the Recall System:</p> <ul> <li>Parser: Parse session capture markdowns, enrich with JSONL data</li> <li>Renderer: Goldmark + Chroma for syntax highlighting, dark mode UI</li> <li>Server: Local HTTP server for browsing sessions</li> <li>Search: Inverted index for searching across sessions</li> <li>CLI: <code>ctx recall serve <path></code> to start the server</li> </ul> <p>The goal is to make the archaeological record browsable, not just <code>grep</code>-able.</p> <p>Because not everyone always lives in the terminal (me included).</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-01-27-building-ctx-using-ctx/#conclusion","level":2,"title":"Conclusion","text":"<p>Building <code>ctx</code> using <code>ctx</code> was a meta-experiment in AI-assisted development. </p> <p>I learned that memory isn't just convenient: It's transformative:</p> <ul> <li>An AI that remembers your decisions doesn't repeat mistakes.</li> <li>An AI that knows your conventions doesn't need them re-explained.</li> </ul> <p>If you are reading this, chances are that you already have heard about <code>ctx</code>.</p> <ul> <li><code>ctx</code> is open source at  github.com/ActiveMemory/ctx,</li> <li>and the documentation lives at ctx.ist.</li> </ul> <p>Session Records Are a Gold Mine</p> <p>By the time of this writing, I have more than 70 megabytes of text-only session capture, spread across >100 Markdown and <code>JSONL</code> files.</p> <p>I am analyzing, synthesizing, encriching them with AI, running RAG (Retrieval-Augmented Generation) models on them, and the outcome surprises me every day.</p> <p>If you are a mere mortal tired of reset amnesia, give <code>ctx</code> a try. </p> <p>And when you do, check <code>.context/sessions/</code> sometime. </p> <p>The archaeological record might surprise you.</p> <p>This blog post was written with the help of <code>ctx</code> with full access to the  <code>ctx</code> session files, decision log, learning log, task archives, and  git history of <code>ctx</code>: The meta continues.</p>","path":["Building ctx Using ctx: A Meta-Experiment in AI-Assisted Development"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/","level":1,"title":"<code>ctx</code> v0.2.0: The Archaeology Release","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>The <code>.context/sessions/</code> directory referenced in this post has been eliminated. Session history is now accessed via <code>ctx recall</code> and enriched journals live in <code>.context/journal/</code>.</p> <p></p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#digging-through-the-past-to-build-the-future","level":2,"title":"Digging through the Past to Build the Future","text":"<p>Jose Alekhinne / 2026-02-01</p> <p>What If Your AI Could Remember Everything?</p> <p>Not just the current session, but every session:</p> <ul> <li>Every decision made,</li> <li>every mistake avoided, </li> <li>every path not taken.</li> </ul> <p>That's what v0.2.0 delivers.</p> <p>Between <code>v0.1.2</code> and <code>v0.2.0</code>, 86 commits landed across 5 days. </p> <p>The release notes list features and fixes. </p> <p>This post tells the story of why those features exist, and what  building them taught me.</p> <p>This isn't a changelog: It is an explanation of intent.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-problem-amnesia-isnt-just-session-level","level":2,"title":"The Problem: Amnesia Isn't Just Session-Level","text":"<p><code>v0.1.0</code> solved reset amnesia: </p> <p>The AI now remembers decisions, learnings, and tasks across sessions. </p> <p>But a new problem emerged, which I can sum up as: </p> <p>\"I (the human) am not AI.\"</p> <p>Frankly, I couldn't remember what the AI remembered.</p> <p>Let alone, I cannot remember what I ate for breakfast!</p> <p>In the course of days, I realized session transcripts piled up in  <code>.context/sessions/</code>; I was <code>grep</code>ping, <code>JSONL</code> files with thousands of lines... Raw tool calls, assistant responses, user messages...</p> <p>...all interleaved. </p> <p>Valuable context was effectively buried in machine-readable noise.</p> <p>I found myself grepping through files to answer questions like:</p> <ul> <li>\"When did we decide to use constants instead of literals?\"</li> <li>\"What was the session where we fixed the hook regex?\"</li> <li>\"How did the <code>embed.go</code> split actually happen?\"</li> </ul> <p>Fate Is Whimsical</p> <p>The irony was painful:</p> <p>I built a tool to prevent AI amnesia, but I was suffering from  human amnesia about what happened in AI sessions.</p> <p>This was the moment <code>ctx</code> stopped being just an AI tool and started needing to support the human on the other side of the loop.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-solution-recall-and-journal","level":2,"title":"The Solution: Recall and Journal","text":"<p><code>v0.2.0</code> introduces two interconnected systems.</p> <p>They solve different problems and only work well together.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-recall-browse-your-past","level":3,"title":"<code>ctx recall</code>: Browse Your Past","text":"<pre><code># List all sessions for this project\nctx recall list\n\n# Show a specific session\nctx recall show gleaming-wobbling-sutherland\n\n# See the full transcript\nctx recall show gleaming-wobbling-sutherland --full\n</code></pre> <p>The <code>recall</code> system parses Claude Code's <code>JSONL</code> transcripts and presents them in a human-readable format:</p> Session Date Turns Duration tender-painting-sundae 2026-01-29 3 <1m crystalline-gliding-willow 2026-01-29 3 <1m declarative-hugging-snowglobe 2026-01-31 2 <1m <p>Slugs are auto-generated from session IDs (memorable names instead of UUIDs). The goal (as the name implies) is recall, not archival accuracy.</p> <p>2,121 Lines of New Code</p> <p>The <code>ctx recall</code> feature was the largest single addition:</p> <p>parser library, CLI commands, test suite, and slash command.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#ctx-journal-from-raw-to-rich","level":3,"title":"<code>ctx journal</code>: From Raw to Rich","text":"<p>Listing sessions isn't enough. The transcripts are still unwieldy.</p> <ul> <li>Recall answers what happened.</li> <li>Journal answers what mattered.</li> </ul> <pre><code># Import sessions to editable Markdown\nctx recall import --all\n\n# Generate a static site from journal entries\nctx journal site\n\n# Serve it locally\nctx serve\n</code></pre> <p>The exported files land in <code>.context/journal/</code>:</p> <pre><code>.context/journal/\n├── 2026-01-28-proud-sleeping-cook-6e535360.md\n├── 2026-01-29-tender-painting-sundae-b14ddaaa.md\n├── 2026-01-29-crystalline-gliding-willow-ff7fd67d.md\n└── 2026-01-31-declarative-hugging-snowglobe-4549026d.md\n</code></pre> <p>Each file is a structured Markdown document ready for enrichment.</p> <p>They are meant to be read, edited, and reasoned about;  not just stored.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-meta-slash-commands-for-self-analysis","level":2,"title":"The Meta: Slash Commands for Self-Analysis","text":"<p>The journal system includes four slash commands that use Claude to analyze and synthesize session history:</p> Command Purpose <code>/ctx-journal-enrich</code> Add frontmatter, topics, tags <code>/ctx-blog</code> Generate blog post from activity <code>/ctx-blog-changelog</code> Generate changelog from commits <p>This very post was drafted using <code>/ctx-blog</code>. The previous post about refactoring was drafted the same way.</p> <p>So, yes: The meta continues: <code>ctx</code> now helps write posts about <code>ctx</code>.</p> <p>With the current release, <code>ctx</code> is no longer just recording history: </p> <p>It is participating in its interpretation.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-structure-decisions-as-first-class-citizens","level":2,"title":"The Structure: Decisions as First-Class Citizens","text":"<p><code>v0.1.0</code> let you add decisions with a simple command:</p> <pre><code>ctx add decision \"Use PostgreSQL\"\n</code></pre> <p>But sessions showed a pattern: decisions added this way were incomplete:</p> <ul> <li>Context was missing;</li> <li>Rationale was vague; </li> <li>Consequences were never stated.</li> </ul> <p>Once recall and journaling existed, this weakness became impossible to ignore: </p> <p>Structure stopped being optional.</p> <p><code>v0.2.0</code> enforces structure:</p> <pre><code>ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity, strong ecosystem\" \\\n  --consequence \"Need to set up connection pooling, team training\"\n</code></pre> <p>All three flags are required. No more placeholder text. </p> <p>Every decision is now a proper Architecture Decision Record (*ADR), not a note.</p> <p>The same enforcement applies to learnings too:</p> <pre><code>ctx add learning \"CGO breaks ARM64 builds\" \\\n  --context \"go test failed with gcc errors on ARM64\" \\\n  --lesson \"Always use CGO_ENABLED=0 for cross-platform builds\" \\\n  --application \"Added to Makefile and CI config\"\n</code></pre> <p>Structured Entries Are Prompts to the AI</p> <p>When the AI reads a decision with full context, rationale, and consequences, it understands the why, not just the what.</p> <p>One-liners teach nothing.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-order-newest-first","level":2,"title":"The Order: Newest First","text":"<p>A subtle but important change: <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> now use reverse-chronological order.</p> <p>One reason is token budgets, obviously; another reason is to help your fellow human (i.e., the Author): </p> <p>Earlier decisions are more likely to be relevant, and they are more likely to have more emphasis on the project. So it follows that they should be read first.</p> <p>But back to AI:</p> <p>When the AI reads a file, it reads from the top (and seldom from the bottom). </p> <p>If the token budget is tight, old content gets truncated.  As in any good engineering practice, it's always about the tradeoffs.</p> <p>Reverse order ensures the most recent (and most relevant) context is always loaded first.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-index-quick-reference-tables","level":2,"title":"The Index: Quick Reference Tables","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> now include auto-generated indexes.</p> <ul> <li>For AI agents, the index allows scanning without reading full entries.</li> <li>For humans, it's a table of contents.</li> </ul> <p>The same structure serves two very different readers.</p> <p>Reindex After Manual Edits</p> <p>If you edit entries by hand, rebuild the index with:</p> <pre><code>ctx decisions reindex\nctx learnings reindex\n</code></pre> <p>See the Knowledge Capture recipe for details.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-configuration-contextrc","level":2,"title":"The Configuration: <code>.contextrc</code>","text":"<p>Projects can now customize <code>ctx</code> behavior  via <code>.contextrc</code>.</p> <p>This makes <code>ctx</code> usable in real teams, not just personal projects.</p> <p>Priority order: CLI flags > environment variables > <code>.contextrc</code> >  sensible defaults</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-flags-global-cli-options","level":2,"title":"The Flags: Global CLI Options","text":"<p>Three new global flags work with any command.</p> <p>These enable automation: </p> <p>CI pipelines, scripts, and long-running tools can now integrate  <code>ctx</code> without hacks or workarounds.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#the-refactoring-under-the-hood","level":2,"title":"The Refactoring: Under the Hood","text":"<p>These aren't user-visible changes.</p> <p>They are the kind of work you only appreciate later, when everything else becomes easier to build.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#what-we-learned-building-v020","level":2,"title":"What We Learned Building v0.2.0","text":"","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#1-raw-data-isnt-knowledge","level":3,"title":"1. Raw Data Isn't Knowledge","text":"<p><code>JSONL</code> transcripts contain everything, and I mean \"everything\":</p> <p>They even contain hidden system messages that Anthropic injects to the LLM's conversation to treat humans better: It's immense.</p> <p>But \"everything\" isn't useful until it is transformed into something a human can reason about.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#2-enforcement-documentation","level":3,"title":"2. Enforcement > Documentation","text":"<p>The Prompt Is a Guideline</p> <p>The code is more what you'd call 'guidelines' than actual rules.</p> <p>-Hector Barbossa</p> <p>Rules written in Markdown are suggestions.</p> <p>Rules enforced by the CLI shape behavior; both for humans and AI.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#3-token-budget-is-ux","level":3,"title":"3. Token Budget Is UX","text":"<p>File order decides what the AI sees.</p> <p>That makes it a user experience concern, not an implementation detail.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#4-meta-tools-compound","level":3,"title":"4. Meta-Tools Compound","text":"<p>Tools that analyze their own development tend to generalize well.</p> <p>The journal system started as a way to understand <code>ctx</code> itself.</p> <p>It immediately became useful for everything else.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#v020-in-the-numbers","level":2,"title":"v0.2.0 in the Numbers","text":"<p>This was a heavy release. The numbers reflect that:</p> Metric v0.1.2 v0.2.0 Commits since last - 86 New commands 15 21 Slash commands 7 11 Lines of Go ~6,500 ~9,200 Session files (this project) 40 54 <p>The binary grew. The capability grew more.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#whats-next","level":2,"title":"What's Next","text":"<p>But those are future posts.</p> <p>This one was about making the past usable.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-ctx-v0.2.0-the-archaeology-release/#get-started","level":2,"title":"Get Started","text":"<p>Update</p> <p>Since this post, <code>ctx</code> became a first-class Claude Code Marketplace plugin. Installation is now simpler. </p> <p>See the Getting Started guide for the  current instructions.</p> <pre><code>make build\nsudo make install\nctx init\n</code></pre> <p>The Archaeological Record</p> <p><code>v0.2.0</code> is the archaeology release because it makes the past accessible.</p> <p>Session transcripts aren't just logs anymore: They are a searchable, exportable, analyzable record of how your project evolved.</p> <p>The AI remembers. Now you can too.</p> <p>This blog post was generated with the help of <code>ctx</code> using the <code>/ctx-blog</code> slash command, with full access to git history, session files, decision logs, and learning logs from the v0.2.0 development window.</p>","path":["ctx v0.2.0: The Archaeology Release"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/","level":1,"title":"Refactoring with Intent","text":"","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#human-guided-sessions-in-ai-development","level":2,"title":"Human-Guided Sessions in AI Development","text":"<p>Jose Alekhinne / 2026-02-01</p> <p>What Happens When You Slow Down?</p> <p>YOLO mode shipped 14 commands in a week. </p> <p>But technical debt doesn't send invoices: It just waits.</p> <p>This is the story of what happened when I stopped auto-accepting everything and started guiding the AI with intent. </p> <p>The result: 27 commits across 4 days, a major version release, and  lessons that apply far beyond <code>ctx</code>.</p> <p>The Refactoring Window</p> <p>January 28 - February 1, 2026</p> <p>From commit <code>bb1cd20</code> to the v0.2.0 release merge. (this window matters more than the individual commits: it's where intent replaced velocity.)</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-velocity-trap","level":2,"title":"The Velocity Trap","text":"<p>In the previous post, I documented the \"YOLO mode\" that birthed <code>ctx</code>: auto-accept everything, let the AI run free, ship features fast.</p> <p>It worked: until it didn't.</p> <p>The codebase had accumulated patterns I didn't notice during the sprint:</p> YOLO Pattern Where Found Why It Hurts <code>\"TASKS.md\"</code> as literal 10+ files One typo = silent failure <code>dir + \"/\" + file</code> Path construction Breaks on Windows Monolithic <code>embed.go</code> 150+ lines, 5 concerns Untestable, hard to extend Inconsistent docstrings Everywhere AI can't learn project conventions <p>I didn't see these during \"YOLO mode\" because, honestly, I wasn't looking.</p> <p>Auto-accept means auto-ignore.</p> <p>In YOLO mode, every file you open looks fine until you try to change it.  </p> <p>In contrast, refactoring mode is when you start paying attention to that  hidden friction.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-shift-from-velocity-to-intent","level":2,"title":"The Shift: From Velocity to Intent","text":"<p>On January 28<sup>th</sup>, I changed the workflow:</p> <ol> <li>Read every diff before accepting.</li> <li>Ask \"why this way?\" before committing.</li> <li>Document patterns, not just features.</li> </ol> <p>The first commit of this era was telling:</p> <pre><code>feat: add structured attributes to context. update XML format\n</code></pre> <p>Not a new feature: A refinement:</p> <p>The <code>XML</code> format for context updates needed <code>type</code> and <code>timestamp</code> attributes. </p> <p>YOLO mode would have shipped something that worked. Intentional mode asked:  </p> <p>\"What does well-structured look like?\"</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-decomposition-embedgo","level":2,"title":"The Decomposition: <code>embed.go</code>","text":"<p>The most satisfying refactor was splitting <code>internal/claude/embed.go</code>.</p> <p>Before: One 153-line file doing five things:</p> <ul> <li>Command registration</li> <li>Hook generation</li> <li>Permission handling</li> <li>Script templates</li> <li>Type definitions</li> </ul> <p>... your \"de facto\" God object.</p> <p>After: Five focused modules:</p> File Lines Responsibility <code>cmd.go</code> 46 Command registration <code>hook.go</code> 64 Hook configuration <code>perm.go</code> 25 Permission handling <code>script.go</code> 47 Script templates <code>types.go</code> 7 Type definitions <p>The refactor also renamed functions to follow Go conventions:</p> <pre><code>// Before: unnecessary prefixes\nGetAutoSaveScript()\nGetBlockNonPathCtxScript()\nListCommands()\nCreateDefaultHooks()\n\n// After: idiomatic Go\nAutoSaveScript()\nBlockNonPathCtxScript()\nCommands()\nDefaultHooks()\n</code></pre> <p>This wasn't about character count. It was about teaching the AI what good Go looks like in this project.</p> <p>Project Conventions</p> <p>What I wanted from AI was to understand and follow the project's  conventions, and trust the author.</p> <p>The next time it generates code, it has better examples to learn from.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-documentation-debt","level":2,"title":"The Documentation Debt","text":"<p>YOLO mode created features. It didn't create documentation standards.</p> <p>The January 29<sup>th</sup> sessions focused on standardization.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#terminology-fixes","level":3,"title":"Terminology Fixes","text":"<ul> <li>\"context-update\" → \"entry\" (what users actually call them)</li> <li>Consistent naming across CLI, docs, and code comments</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#go-docstrings","level":3,"title":"Go Docstrings","text":"<pre><code>// Before: inconsistent or missing\nfunc Parse(s string) Entry { ... }\n\n// After: standardized sections\n\n// Parse extracts an entry from a markdown string.\n//\n// Parameters:\n//   - s: The markdown string to parse\n//\n// Returns:\n//   - Entry with populated fields, or zero value if parsing fails\nfunc Parse(s string) Entry { ... }\n</code></pre> <p>This is intentionally more structured than typical GoDoc:</p> <p>It serves as documentation and doubles as training data for future  AI-generated code.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#cli-output-convention","level":3,"title":"CLI Output Convention","text":"<pre><code>All CLI output follows: [emoji] [Title]: [message]\n\nExamples:\n  ✓ Decision added: Use symbolic types for entry categories\n  ⚠ Warning: No tasks found\n  ✗ Error: File not found\n</code></pre> <p>A consistent output shape makes both human scanning and  AI reasoning more reliable.</p> <p>These aren't exciting commits. But they are force multipliers:</p> <p>Every future AI session now has better examples to follow.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-journal-system","level":2,"title":"The Journal System","text":"<p>If you only read one section, read this one:</p> <p>This is where v0.2.0 becomes more than a refactor.</p> <p>The biggest feature of this change window wasn't a refactor; it was the journal system.</p> <p>45 Files Changed, 1680 Insertions</p> <p>This commit added the infrastructure for synthesizing AI session history into human-readable content.</p> <p>The journal system includes:</p> Component Purpose <code>ctx recall import</code> Import sessions to Markdown in <code>.context/journal/</code> <code>ctx journal site</code> Generate static site from journal entries <code>ctx serve</code> Convenience wrapper for the static site server <code>/ctx-journal-enrich</code> Slash command to add frontmatter and tags <code>/ctx-blog</code> Generate blog posts from recent activity <code>/ctx-blog-changelog</code> Generate changelog-style blog posts <p>...and the meta continues: this blog post was generated using <code>/ctx-blog</code>.</p> <p>The session history from January 28-31 was</p> <ul> <li>exported, </li> <li>enriched,</li> <li>and synthesized.</li> </ul> <p>into the narrative you are reading.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-constants-consolidation","level":2,"title":"The Constants Consolidation","text":"<p>The final refactoring session addressed the remaining magic strings:</p> <pre><code>const (\n    // Comment markers\n    CommentOpen  = \"<!--\"\n    CommentClose = \"-->\"\n\n    // Index markers\n    MarkerIndexStart = \"<!-- INDEX:START -->\"\n    MarkerIndexEnd   = \"<!-- INDEX:END -->\"\n\n    // Newlines\n    NewlineLF   = \"\\n\"\n    NewlineCRLF = \"\\r\\n\"\n)\n</code></pre> <p>The work also introduced thread safety in the recall parser and centralized shared validation logic; removing duplication that had quietly spread during YOLO mode.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#i-relearned-my-lessons","level":2,"title":"I (Re)Learned My Lessons","text":"<p>Similar to what I've learned in  the former human-assisted refactoring post, this journey also made me realize that \"AI-only code generation\" isn't sustainable in the long term.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#1-velocity-and-quality-arent-opposites","level":3,"title":"1. Velocity and Quality Aren't Opposites","text":"<p>YOLO mode has its place: for prototyping, exploration, and discovery.</p> <p>BUT (and it's a huge \"but\"), it needs to be followed by  consolidation sessions.</p> <p>The ratio that worked for me: 3:1.</p> <ul> <li>Three YOLO sessions create enough surface area to reveal patterns;</li> <li>the fourth session turns those patterns into structure.</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#2-documentation-is-code","level":3,"title":"2. Documentation IS Code","text":"<p>When I standardized docstrings, I wasn't just writing docs. I was training future AI sessions.</p> <p>Every example of good code becomes a template for generated code.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#3-decomposition-deletion","level":3,"title":"3. Decomposition > Deletion","text":"<p>When <code>embed.go</code> became unwieldy, the temptation was to remove functionality.</p> <p>The right answer was decomposition:</p> <ul> <li>Same functionality;</li> <li>Better organization;</li> <li>Easier to test;</li> <li>Easier to extend.</li> </ul> <p>The result: more lines overall, but dramatically better structure.</p> <p>The AI Benefit</p> <p>Smaller, focused files also help AI assistants. </p> <p>When a file fits comfortably in the context window, the AI can  reason about it completely instead of working from truncated snippets, preserving token budget for the actual task.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#4-meta-tools-pay-dividends","level":3,"title":"4. Meta-Tools Pay Dividends","text":"<p>The journal system took almost a full day to implement.</p> <p>Yet it paid for itself immediately:</p> <ul> <li>This blog post was generated from session history;</li> <li>Future posts will be easier;</li> <li>The archaeological record is now browsable, not just <code>grep</code>-able.</li> </ul>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-release-v020","level":2,"title":"The Release: v0.2.0","text":"<p>The refactoring window culminated in the v0.2.0 release.</p> <p>What's in v0.2.0:</p> Category Changes Features Journal system, quick reference indexes, global flags Refactors Module decomposition, constants consolidation, CRLF handling Docs Standardized terminology, Go docstrings, CLI conventions Quality Thread safety, shared validation, linter fixes <p>The version bump was symbolic.</p> <p>The real change was how the codebase felt.</p> <p>Opening files no longer triggered the familiar \"ugh, I need to clean this up\" reaction.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-01-refactoring-with-intent/#the-meta-continues","level":2,"title":"The Meta Continues","text":"<p>This post was written using the tools built during this refactoring window:</p> <ol> <li>Session history imported via <code>ctx recall import</code>;</li> <li>Journal entries enriched via <code>/ctx-journal-enrich</code>;</li> <li>Blog draft generated via <code>/ctx-blog</code>;</li> <li>Final editing done (by yours truly), with full project context loaded.</li> </ol> <p>The Context Is Massive</p> <p>The <code>ctx</code> session files now contain 50+ development snapshots: each one capturing decisions, learnings, and intent.</p> <p>The Moral of the Story</p> <ul> <li>YOLO mode builds the prototype.</li> <li>Intentional mode builds the product.</li> </ul> <p>Schedule both, or you'll only get one, if you're lucky.</p> <p>This blog post was generated with the help of <code>ctx</code>, using session history, decision logs, learning logs, and git history from the refactoring window. The meta continues.</p>","path":["Refactoring with Intent: Human-Guided Sessions in AI Development"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/","level":1,"title":"The Attention Budget","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism.</p> <p>References to <code>.context/sessions/</code> in this post reflect the architecture at the time of writing. Session history is now accessed via <code>ctx recall</code> and stored in <code>.context/journal/</code>.</p> <p></p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#why-your-ai-forgets-what-you-just-told-it","level":2,"title":"Why Your AI Forgets What You Just Told It","text":"<p>Volkan Özçelik / 2026-02-03</p> <p>Ever Wondered Why AI Gets Worse the Longer You Talk?</p> <p>You paste a 2000-line file, explain the bug in detail, provide three examples...</p> <p>...and the AI still suggests a fix that ignores half of what you said.</p> <p>This isn't a bug. It is physics.</p> <p>Understanding that single fact shaped every design decision behind <code>ctx</code>.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-finite-resource-nobody-talks-about","level":2,"title":"The Finite Resource Nobody Talks About","text":"<p>Here's something that took me too long to internalize: context is not free.</p> <p>Every token you send to an AI model consumes a finite resource I call the attention budget.</p> <p>Attention budget is real.</p> <p>The model doesn't just read tokens; it forms relationships between them: </p> <p>For <code>n</code> tokens, that's roughly <code>n^2</code> relationships. </p> <p>Double the context, and the computation quadruples.</p> <p>But the more important constraint isn't cost: It's attention density.</p> <p>Attention Density</p> <p>Attention density is how much focus each token receives relative to all other tokens in the context window.</p> <p>As context grows, attention density drops: Each token gets a smaller slice of the model's focus. Nothing is ignored; but everything becomes blurrier.</p> <p>Think of it like a flashlight: In a small room, it illuminates everything clearly. In a warehouse, it becomes a dim glow that barely reaches the corners.</p> <p>This is why <code>ctx agent</code> has an explicit <code>--budget</code> flag:</p> <pre><code>ctx agent --budget 4000 # Force prioritization\nctx agent --budget 8000 # More context, lower attention density\n</code></pre> <p>The budget isn't just about cost: It's about preserving signal.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-middle-gets-lost","level":2,"title":"The Middle Gets Lost","text":"<p>This one surprised me.</p> <p>Research shows that transformer-based models tend to attend more strongly to the beginning and end of a context window than to its middle (a phenomenon often called  \"lost in the middle\")<sup>1</sup>.</p> <p>Positional anchors matter, and the middle has fewer of them.</p> <p>In practice, this means that information placed \"somewhere in the middle\" is statistically less salient, even if it's important.</p> <p><code>ctx</code> orders context files by logical progression: What the agent needs to know before it can understand the next thing:</p> <ol> <li><code>CONSTITUTION.md</code>: Constraints before action.</li> <li><code>TASKS.md</code>: Focus before patterns.</li> <li><code>CONVENTIONS.md</code>: How to write before where to write.</li> <li><code>ARCHITECTURE.md</code>: Structure before history.</li> <li><code>DECISIONS.md</code>: Past choices before gotchas.</li> <li><code>LEARNINGS.md</code>: Lessons before terminology.</li> <li><code>GLOSSARY.md</code>: Reference material.</li> <li><code>AGENT_PLAYBOOK.md</code>: Meta instructions last.</li> </ol> <p>This ordering is about logical dependencies, not attention engineering. But it happens to be attention-friendly too:</p> <p>The files that matter most (CONSTITUTION, TASKS, CONVENTIONS) land at the beginning of the context window, where attention is strongest.</p> <p>Reference material like GLOSSARY sits in the middle, where lower salience is acceptable.</p> <p>And AGENT_PLAYBOOK, the operating manual for the context system itself, sits at the end, also outside the \"lost in the middle\" zone. The agent reads what to work with before learning how the system works.</p> <p>This is <code>ctx</code>'s first primitive: hierarchical importance.</p> <p>Not all context is equal.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#ctx-primitives","level":2,"title":"<code>ctx</code> Primitives","text":"<p><code>ctx</code> is built on four primitives that directly address the attention budget problem.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-1-separation-of-concerns","level":3,"title":"Primitive 1: Separation of Concerns","text":"<p>Instead of a single mega-document, <code>ctx</code> uses separate files for separate purposes:</p> File Purpose Load When CONSTITUTION.md Inviolable rules Always TASKS.md Current work Session start CONVENTIONS.md How to write code Before coding ARCHITECTURE.md System structure Before making changes DECISIONS.md Architectural choices When questioning approach LEARNINGS.md Gotchas When stuck GLOSSARY.md Domain terminology When clarifying terms AGENT_PLAYBOOK.md Operating manual Session start sessions/ Deep history On demand journal/ Session journal On demand <p>This isn't just \"organization\": It is progressive disclosure.</p> <p>Load only what's relevant to the task at hand. Preserve attention density.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-2-explicit-budgets","level":3,"title":"Primitive 2: Explicit Budgets","text":"<p>The <code>--budget</code> flag forces a choice:</p> <pre><code>ctx agent --budget 4000\n</code></pre> <p>Here is a sample allocation:</p> <pre><code>Constitution: ~200 tokens (never truncated)\nTasks: ~500 tokens (current phase, up to 40% of budget)\nConventions: ~800 tokens (all items, up to 20% of budget)\nDecisions: ~400 tokens (scored by recency and task relevance)\nLearnings: ~300 tokens (scored by recency and task relevance)\nAlso noted: ~100 tokens (title-only summaries for overflow)\n</code></pre> <p>The constraint is the feature: It enforces ruthless prioritization.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-3-indexes-over-full-content","level":3,"title":"Primitive 3: Indexes over Full Content","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> both include index sections:</p> <pre><code><!-- INDEX:START -->\n| Date       | Decision                            |\n|------------|-------------------------------------|\n| 2026-01-15 | Use PostgreSQL for primary database |\n| 2026-01-20 | Adopt Cobra for CLI framework       |\n<!-- INDEX:END -->\n</code></pre> <p>An AI agent can scan ~50 tokens of index and decide which  200-token entries are worth loading.</p> <p>This is just-in-time context.</p> <p>References are cheaper than the full text.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#primitive-4-filesystem-as-navigation","level":3,"title":"Primitive 4: Filesystem as Navigation","text":"<p><code>ctx</code> uses the filesystem itself as a context structure:</p> <pre><code>.context/\n├── CONSTITUTION.md\n├── TASKS.md\n├── sessions/\n│   ├── 2026-01-15-*.md\n│   └── 2026-01-20-*.md\n└── archive/\n    └── tasks-2026-01.md\n</code></pre> <p>The AI doesn't need every session loaded; it needs to know where to look.</p> <pre><code>ls .context/sessions/\ncat .context/sessions/2026-01-20-auth-discussion.md\n</code></pre> <p>File names, timestamps, and directories encode relevance.</p> <p>Navigation is cheaper than loading.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#progressive-disclosure-in-practice","level":2,"title":"Progressive Disclosure in Practice","text":"<p>The naive approach to context is dumping everything upfront:</p> <p>\"Here's my entire codebase, all my documentation, every decision I've ever made. Now help me fix this typo 🙏.\"</p> <p>This is an antipattern.</p> <p>Antipattern: Context Hoarding</p> <p>Dumping everything \"just in case\" will silently destroy the attention  density.</p> <p><code>ctx</code> takes the opposite approach:</p> <pre><code>ctx status                      # Quick overview (~100 tokens)\nctx agent --budget 4000         # Typical session\ncat .context/sessions/...       # Deep dive when needed\n</code></pre> Command Tokens Use Case <code>ctx status</code> ~100 Human glance <code>ctx agent --budget 4000</code> 4000 Normal work <code>ctx agent --budget 8000</code> 8000 Complex tasks Full session read 10000+ Investigation <p>Summaries first. Details: on demand.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#quality-over-quantity","level":2,"title":"Quality over Quantity","text":"<p>Here is the counterintuitive part: more context can make AI worse.</p> <p>Extra tokens add noise, not clarity:</p> <ul> <li>Hallucinated connections increase.</li> <li>Signal per token drops.</li> </ul> <p>The goal isn't maximum context: It is maximum signal per token.</p> <p>This principle drives several <code>ctx</code> features:</p> Design Choice Rationale Separate files Load only what's relevant Explicit budgets Enforce prioritization Index sections Cheap scanning Task archiving Keep active context clean <code>ctx compact</code> Periodic noise reduction <p>Completed work isn't deleted: It is moved somewhere cold.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#designing-for-degradation","level":2,"title":"Designing for Degradation","text":"<p>Here is the uncomfortable truth:</p> <p>Context will degrade.</p> <p>Long sessions stretch attention thin. Important details fade.</p> <p>The real question isn't how to prevent degradation,  but how to design for it.</p> <p><code>ctx</code>'s answer is persistence:</p> <p>Persist early. Persist often.</p> <p>The <code>AGENT_PLAYBOOK</code> asks:</p> <p>\"If this session ended right now, would the next one know what happened?\"</p> <p>Capture learnings as they occur:</p> <pre><code>ctx add learning \"JWT tokens require explicit cache invalidation\" \\\n  --context \"Debugging auth failures\" \\\n  --lesson \"Token refresh doesn't clear old tokens\" \\\n  --application \"Always invalidate cache on refresh\"\n</code></pre> <p>Structure beats prose: Bullet points survive compression.</p> <p>Headings remain scannable. Tables pack density.</p> <p>And above all: single source of truth.</p> <p>Reference decisions; don't duplicate them.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-ctx-philosophy","level":2,"title":"The <code>ctx</code> Philosophy","text":"<p>Context as Infrastructure</p> <p><code>ctx</code> is not a prompt: It is infrastructure.</p> <p><code>ctx</code> creates versioned files that persist across time and sessions.</p> <p>The attention budget is fixed. You can't expand it.</p> <p>But you can spend it wisely:</p> <ol> <li>Hierarchical importance</li> <li>Progressive disclosure</li> <li>Explicit budgets</li> <li>Indexes over full content</li> <li>Filesystem as structure</li> </ol> <p>This is why <code>ctx</code> exists: not to cram more context into AI sessions, but to curate the right context for each moment.</p>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-03-the-attention-budget/#the-mental-model","level":2,"title":"The Mental Model","text":"<p>I now approach every AI interaction with one question:</p> <pre><code>\"Given a fixed attention budget, what's the highest-signal thing I can load?\"\n</code></pre> <p>Not \"how do I explain everything,\" but \"what's the minimum that matters.\"</p> <p>That shift (from abundance to curation) is the difference between frustrating sessions and productive ones.</p> <p>Spend your tokens wisely.</p> <p>Your AI will thank you.</p> <p>See also: Context as Infrastructure that's the architectural companion to this post, explaining how to structure the context that this post teaches you to budget.</p> <p>See also: Code Is Cheap. Judgment Is Not. that explains why curation (the human skill this post describes) is the bottleneck that AI cannot solve, and the thread that connects every post in this blog.</p> <ol> <li> <p>Liu et al., \"Lost in the Middle: How Language Models Use Long Contexts,\" Transactions of the Association for Computational Linguistics, vol. 12, pp. 157-173, 2023. ↩</p> </li> </ol>","path":["The Attention Budget: Why Your AI Forgets What You Just Told It"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/","level":1,"title":"Skills That Fight the Platform","text":"","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#when-your-custom-prompts-work-against-you","level":2,"title":"When Your Custom Prompts Work against You","text":"<p>Volkan Özçelik / 2026-02-04</p> <p>Have You Ever Written a Skill That Made Your AI Worse?</p> <p>You craft detailed instructions. You add examples. You build elaborate guardrails...</p> <p>...and the AI starts behaving more erratically, not less.</p> <p>AI coding agents like Claude Code ship with carefully designed  system prompts. These prompts encode default behaviors that have been  tested and refined at scale. </p> <p>When you write custom skills that conflict with those defaults, the AI has to reconcile contradictory instructions:</p> <p>The result is often nondeterministic and unpredictable.</p> <p>Platform?</p> <p>By platform, I mean the system prompt and runtime policies shipped with  the agent: the defaults that already encode judgment, safety, and  scope control.</p> <p>This post catalogs the conflict patterns I have encountered while building <code>ctx</code>, and offers guidance on what skills should (and, more importantly,  should not) do.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-system-prompt-you-dont-see","level":2,"title":"The System Prompt You Don't See","text":"<p>Claude Code's system prompt already provides substantial behavioral guidance.</p> <p>Here is a partial overview of what's built in:</p> Area Built-in Guidance Code minimalism Don't add features beyond what was asked Over-engineering Three similar lines > premature abstraction Error handling Only validate at system boundaries Documentation Don't add docstrings to unchanged code Verification Read code before proposing changes Safety Check with user before risky actions Tool usage Use dedicated tools over bash equivalents Judgment Consider reversibility and blast radius <p>Skills should complement this, not compete with it.</p> <p>You Are the Guest, Not the Host</p> <p>Treat the system prompt like a kernel scheduler.</p> <p>You don't re-implement it in user space: </p> <p>you configure around it.</p> <p>A skill that says \"always add comprehensive error handling\"  fights the built-in \"only validate at system boundaries.\"</p> <p>A skill that says \"add docstrings to every function\" fights \"don't add docstrings to unchanged code.\"</p> <p>The AI won't crash: It will compromise.</p> <p>Compromises between contradictory instructions produce inconsistent, confusing behavior.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-1-judgment-suppression","level":2,"title":"Conflict Pattern 1: Judgment Suppression","text":"<p>This is the most dangerous pattern by far.</p> <p>These skills explicitly disable the AI's ability to reason about whether an action is appropriate.</p> <p>Signature:</p> <ul> <li>\"This is non-negotiable\"</li> <li>\"You cannot rationalize your way out of this\"</li> <li>Tables that label hesitation as \"excuses\" or \"rationalization\"</li> <li><code><EXTREMELY-IMPORTANT></code> urgency tags</li> <li>Threats: \"If you don't do this, you'll be replaced\"</li> </ul> <p>This is harmful, and dangerous:</p> <p>AI agents are designed to exercise judgment: </p> <p>The system prompt explicitly says to:</p> <ul> <li>consider blast radius;</li> <li>check with the user before risky actions;</li> <li>and match scope to what was requested.</li> </ul> <p>Once judgment is suppressed, every other safeguard becomes optional.</p> <p>Example (bad):</p> <pre><code>## Rationalization Prevention\n\n| Excuse                 | Reality                    |\n|------------------------|----------------------------|\n| \"*This seems overkill*\"| If a skill exists, use it  |\n| \"*I need context*\"     | Skills come BEFORE context |\n| \"*Just this once*\"     | No exceptions              |\n</code></pre> <p>Judgment Suppression Is Dangerous</p> <p>The attack vector structurally identical to prompt injection.</p> <p>It teaches the AI that its own judgment is wrong.</p> <p>It weakens or disables safeguard mechanisms, and it is dangerous.</p> <p>Trust the platform's built-in skill matching.</p> <p>If skills aren't triggering often enough, improve their <code>description</code> fields: don't override the AI's reasoning.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-2-redundant-guidance","level":2,"title":"Conflict Pattern 2: Redundant Guidance","text":"<p>Skills that restate what the system prompt already says, but with different emphasis or framing.</p> <p>Signature:</p> <ul> <li>\"Always keep code minimal\"</li> <li>\"Run tests before claiming they pass\"</li> <li>\"Read files before editing them\"</li> <li>\"Don't over-engineer\"</li> </ul> <p>Redundancy feels safe, but it creates ambiguity:</p> <p>The AI now has two sources of truth for the same guidance;  one internal, one external.</p> <p>When thresholds or wording differ, the AI has to choose.</p> <p>Example (bad):</p> <p>A skill that says...</p> <pre><code>*Count lines before and after: if after > before, reject the change*\"\n</code></pre> <p>...will conflict with the system prompt's more nuanced guidance, because  sometimes adding lines is correct (tests, boundary validation, migrations).</p> <p>So, before writing a skill, ask:</p> <p>Does the platform already handle this?</p> <p>Only create skills for guidance the platform does not provide:</p> <ul> <li>project-specific conventions, </li> <li>domain knowledge, </li> <li>or workflows.</li> </ul>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-3-guilt-tripping","level":2,"title":"Conflict Pattern 3: Guilt-Tripping","text":"<p>Skills that frame mistakes as moral failures rather than process gaps.</p> <p>Signature:</p> <ul> <li>\"Claiming completion without verification is dishonesty\"</li> <li>\"Skip any step = lying\"</li> <li>\"Honesty is a core value\"</li> <li>\"Exhaustion ≠ excuse\"</li> </ul> <p>Guilt-tripping anthropomorphizes the AI in unproductive ways.</p> <p>The AI doesn't feel guilt; BUT it does adapt to avoid negative framing.</p> <p>The result is excessive hedging, over-verification, or refusal to commit.</p> <p>The AI becomes less useful, not more careful.</p> <p>Instead, frame guidance as a process, not morality:</p> <pre><code># Bad\n\"Claiming work is complete without verification is dishonesty\"\n\n# Good\n\"Run the verification command before reporting results\"\n</code></pre> <p>Same outcome. No guilt. Better compliance.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-4-phantom-dependencies","level":2,"title":"Conflict Pattern 4: Phantom Dependencies","text":"<p>Skills that reference files, tools, or systems that don't exist in the project.</p> <p>Signature:</p> <ul> <li>\"Load from <code>references/</code> directory\"</li> <li>\"Run <code>./scripts/generate_test_cases.sh</code>\"</li> <li>\"Check the Figma MCP integration\"</li> <li>\"See <code>adding-reference-mindsets.md</code>\"</li> </ul> <p>This is harmful because the AI will waste time searching for nonexistent  artifacts, hallucinate their contents, or stall entirely. </p> <p>In mandatory skills, this creates deadlock:  the AI can't proceed, and can't skip.</p> <p>Instead, every file, tool, or system referenced in a skill must exist.</p> <p>If a skill is a template, use explicit placeholders and label them as such.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#conflict-pattern-5-universal-triggers","level":2,"title":"Conflict Pattern 5: Universal Triggers","text":"<p>Skills designed to activate on every interaction regardless of relevance.</p> <p>Signature:</p> <ul> <li>\"Use when starting any conversation\"</li> <li>\"Even a 1% chance means invoke the skill\"</li> <li>\"BEFORE any response or action\"</li> <li>\"Action = task. Check for skills.\"</li> </ul> <p>Universal triggers override the platform's relevance matching:  The AI spends tokens on process overhead instead of the actual task.</p> <p><code>ctx</code> Preserves Relevance</p> <p>This is exactly the failure mode <code>ctx</code> exists to mitigate: </p> <p>Wasting attention budget on irrelevant process instead of  task-specific state.</p> <p>Write specific trigger conditions in the skill's <code>description</code> field:</p> <pre><code># Bad\ndescription: \n  \"Use when starting any conversation\"\n\n# Good\ndescription: \n  \"Use after writing code, before commits, or when CI might fail\"\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-litmus-test","level":2,"title":"The Litmus Test","text":"<p>Before adding a skill, ask:</p> <ol> <li>Does the platform already do this? If yes, don't restate it.</li> <li>Does it suppress AI judgment? If yes, it's a jailbreak.</li> <li>Does it reference real artifacts? If not, fix or remove it.</li> <li>Does it frame mistakes as moral failure? Reframe as process.</li> <li>Does it trigger on everything? Narrow the trigger.</li> </ol>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#what-good-skills-look-like","level":2,"title":"What Good Skills Look Like","text":"<p>Good skills provide project-specific knowledge the platform can't know:</p> Good Skill Why It Works \"Run <code>make audit</code> before commits\" Project-specific CI pipeline \"Use <code>cmd.Printf</code> not <code>fmt.Printf</code>\" Codebase convention \"Constitution goes in <code>.context/</code>\" Domain-specific workflow \"JWT tokens need cache invalidation\" Project-specific gotcha <p>These extend the system prompt instead of fighting it.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#appendix-bad-skill-fixed-skill","level":2,"title":"Appendix: Bad Skill → Fixed Skill","text":"<p>Concrete examples from real projects.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-1-overbearing-safety","level":3,"title":"Example 1: Overbearing Safety","text":"<pre><code># Bad\nYou must NEVER proceed without explicit confirmation.\nAny hesitation is a failure of diligence.\n</code></pre> <pre><code># Fixed\nIf an action modifies production data or deletes files,\nask the user to confirm before proceeding.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-2-redundant-minimalism","level":3,"title":"Example 2: Redundant Minimalism","text":"<pre><code># Bad\nAlways minimize code. If lines increase, reject the change.\n</code></pre> <pre><code># Fixed\nAvoid abstraction unless reuse is clear or complexity is reduced.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-3-guilt-based-verification","level":3,"title":"Example 3: Guilt-Based Verification","text":"<pre><code># Bad\nClaiming success without running tests is dishonest.\n</code></pre> <pre><code># Fixed\nRun the test suite before reporting success.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-4-phantom-tooling","level":3,"title":"Example 4: Phantom Tooling","text":"<pre><code># Bad\nRun `./scripts/check_consistency.sh` before commits.\n</code></pre> <pre><code># Fixed\nIf `./scripts/check_consistency.sh` exists, run it before commits.\nOtherwise, skip this step.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#example-5-universal-trigger","level":3,"title":"Example 5: Universal Trigger","text":"<pre><code># Bad\nUse at the start of every interaction.\n</code></pre> <pre><code># Fixed\nUse after modifying code that affects authentication or persistence.\n</code></pre>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-04-skills-that-fight-the-platform/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"<p>The system prompt is infrastructure:</p> <ul> <li>tested,</li> <li>refined,</li> <li>and maintained</li> </ul> <p>by the platform team.</p> <p>Custom skills are configuration layered on top.</p> <ul> <li>Good configuration extends infrastructure.</li> <li>Bad configuration fights it.</li> </ul> <p>When your skills fight the platform, you get the worst of both worlds:</p> <p>Diluted system guidance and inconsistent custom behavior.</p> <p>Write skills that teach the AI what it doesn't know. Don't rewrite how it thinks.</p> <p>Your AI already has good instincts.</p> <p>Give it knowledge, not therapy.</p>","path":["Skills That Fight the Platform"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/","level":1,"title":"You Can't Import Expertise","text":"","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#why-good-skills-cant-be-copy-pasted","level":2,"title":"Why Good Skills Can't Be Copy-Pasted","text":"<p>Volkan Özçelik / 2026-02-05</p> <p>Have You Ever Dropped a Well-Crafted Template into a Project and Had It Do... Nothing Useful?</p> <ul> <li>The template was thorough, </li> <li>The structure was sound,</li> <li>The advice was correct...</li> </ul> <p>...and yet it sat there, inert, while the same old problems kept drifting in.</p> <p>I found a consolidation skill online. </p> <p>It was well-organized: four files, ten refactoring patterns, eight analysis  dimensions, six report templates.</p> <p>Professional. Comprehensive. Exactly the kind of thing you'd bookmark and think \"I'll use this.\"</p> <p>Then I stopped, and applied <code>ctx</code>'s own evaluation framework: </p> <p>70% of it was noise!</p> <p>This post is about why.</p> <p>It Is about Encoding Templates</p> <p>Templates describe categories of problems.</p> <p>Expertise encodes which problems actually happen, and how often.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-skill-looked-great-on-paper","level":2,"title":"The Skill Looked Great on Paper","text":"<p>Here is what the consolidation skill offered:</p> File Content <code>SKILL.md</code> Entry point: 8 analysis dimensions, workflow, output formats <code>analysis-dimensions.md</code> Detailed criteria for duplication, architecture, quality <code>consolidation-patterns.md</code> 10 refactoring patterns with before/after code <code>report-templates.md</code> 6 output templates: executive summary, roadmap, onboarding <ul> <li>It had a scoring system (<code>0-10</code> per dimension, letter grades <code>A+</code> through <code>F</code>).</li> <li>It had severity classifications with color-coded emojis. It had bash commands for detection. </li> <li>It even had antipattern warnings.</li> </ul> <p>By any standard template review, this skill passes.</p> <p>It looks like something an expert wrote. </p> <p>And that's exactly the trap.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#applying-ear-the-70-20-10-split","level":2,"title":"Applying E/A/R: The 70-20-10 Split","text":"<p>In a previous post, I described the E/A/R framework for evaluating skills:</p> <ul> <li>Expert: Knowledge that took years to learn. Keep.</li> <li>Activation: Useful triggers or scaffolding. Keep if lightweight.</li> <li>Redundant: Restates what the AI already knows. Delete.</li> </ul> <p>Target: >70% Expert, <10% Redundant.</p> <p>This skill scored the inverse.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-redundant-70","level":3,"title":"What Was Redundant (~70%)","text":"<p>Every code example was Rust. My project is Go.</p> <p>The analysis dimensions: duplication detection, architectural structure, code organization, refactoring opportunities... These are things Claude already does when you ask it to review code. </p> <p>The skill restated them with more ceremony but no more insight.</p> <p>The six report templates were generic scaffolding: Executive Summary, Onboarding Document, Architecture Documentation... </p> <p>They are useful if you are writing a consulting deliverable, but not  when you are trying to catch convention drift in a >15K-line Go CLI.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-does-a-b-in-code-organization-actually-mean","level":2,"title":"What Does a <code>B+</code> in Code Organization Actually Mean?!","text":"<p>The scoring system (<code>0-10</code> per dimension, letter grades) added ceremony without actionable insight. </p> <p>What is a <code>B+</code>? What do I do differently for an <code>A-</code>?</p> <p>The skill told the AI what it already knew, in more words.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-activation-10","level":3,"title":"What Was Activation (~10%)","text":"<p>The consolidation checklist (semantics preserved? tests pass? docs updated?) was useful as a gate. But, it's the kind of thing you could inline in three lines.</p> <p>The phased roadmap structure was reasonable scaffolding for sequencing work.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-was-expert-20","level":3,"title":"What Was Expert (~20%)","text":"<p>Three concepts survived:</p> <ol> <li> <p>The Consolidation Decision Matrix: A concrete framework mapping    similarity level and instance count to action. \"Exact duplicate, 2+    instances: consolidate immediately.\" \"<3 instances: leave it:    duplication is cheaper than wrong abstraction.\" This is the kind of    nuance that prevents premature generalization.</p> </li> <li> <p>The Safe Migration Pattern: Create the new API alongside old, deprecate,    migrate incrementally, delete. Straightforward to describe, yet    forgettable under pressure.</p> </li> <li> <p>Debt Interest Rate framing: Categorizing technical debt by how fast    it compounds (security vulns = daily, missing tests = per-change,    doc gaps = constant low cost). This changes prioritization.</p> </li> </ol> <p>Three ideas out of four files and 700+ lines. The rest was filler that competed with the AI's built-in capabilities.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-the-skill-didnt-know","level":2,"title":"What the Skill Didn't Know","text":"<p>AI without Context Is Just a Corpus</p> <ul> <li>LLMs are optimized on insanely large corpora.</li> <li>And then they are passed through several layers of human-assisted refinement.</li> <li>The whole process costs millions of dollars.</li> </ul> <p>Yet, the reality is that no corpus can \"infer\" your project's design, convetions, patterns, habits, history, vision, and deliverables.</p> <p>Your project is unique: So should your skills be.</p> <p>Here is the part no template can provide: </p> <p><code>ctx</code>'s actual drift patterns.</p> <p>Before evaluating the skill, I did archaeology. I read through:</p> <ul> <li>Blog posts from previous refactoring sessions;</li> <li>The project's learnings and decisions files;</li> <li>Session journals spanning weeks of development.</li> </ul> <p>What I found was specific:</p> Drift Pattern Where How Often <code>Is</code>/<code>Has</code>/<code>Can</code> predicate prefixes 5+ exported methods Every YOLO sprint Magic strings instead of constants 7+ files Gradual accumulation Hardcoded file permissions (<code>0755</code>) 80+ instances Since day one Lines exceeding 80 characters Especially test files Every session Duplicate code blocks Test and non-test code When agent is task-focused <p>The generic skill had no check for any of these. It couldn't; because these patterns are specific to this project's conventions, its Go codebase, and its development rhythm.</p> <p>The Insight</p> <p>The skill's analysis dimensions were about categories of problems.</p> <p>What I needed was my *specific problems.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-adapted-skill","level":2,"title":"The Adapted Skill","text":"<p>The adapted skill is roughly a quarter of the original's size. It has nine checks, each targeting a known drift pattern:</p> <ol> <li>Predicate naming: <code>rg</code> for <code>Is</code>/<code>Has</code>/<code>Can</code> prefixes</li> <li>Magic strings: literals that should be constants</li> <li>Hardcoded permissions: <code>0755</code>/<code>0644</code> literals</li> <li>File size: source files over 300 LOC</li> <li>TODO/FIXME: constitution violation (move to TASKS.md)</li> <li>Path construction: string concatenation instead of <code>filepath.Join</code></li> <li>Line width: lines exceeding ~80 characters</li> <li>Duplicate blocks: copy-paste drift, especially in tests</li> <li> <p>Dead exports: unused public API</p> </li> <li> <p>Every check has a detection command. </p> </li> <li>Every check maps to a specific convention or constitution rule. </li> <li>Every check was discovered through actual project history;   not invented from a template.</li> </ol> <p>The three expert concepts from the original survived:</p> <ul> <li>The decision matrix gates when to consolidate vs. when to leave   duplication alone;</li> <li>The safe migration pattern guides public API changes;</li> <li>The relationship to other skills (<code>/qa</code>, <code>/verify</code>, <code>/update-docs</code>,   <code>ctx drift</code>) prevents overlap.</li> </ul> <p>Nothing else made it.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"<p>This experience crystallized something I've been circling for weeks:</p> <p>You can't import expertise. You have to grow it from your project's own history.</p> <p>A skill that says \"check for code duplication\" is not expertise:  It's a category. </p> <p>Expertise is knowing, in the heart of your hearts, that this  project accumulates <code>Is*</code> predicate violations during velocity sprints,  that this codebase has 80 hardcoded permission literals because nobody  made a constant, that this team's test files drift wide because the  agent prioritizes getting the task done over keeping the code in shape.</p> <p>The Parallel to the 3:1 Ratio</p> <p>In Refactoring with Intent, I described the 3:1 ratio: three YOLO sessions followed by one consolidation session.</p> <p>The same ratio applies to skills: you need experience in the project before you can write effective guidance for the project.</p> <p>Importing a skill on day one is like scheduling a consolidation session before you've written any code.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-template-trap","level":2,"title":"The Template Trap","text":"<p>Templates are seductive because they feel like progress:</p> <ul> <li>You found something</li> <li>It's well-organized</li> <li>It covers the topic</li> <li>It has concrete examples</li> </ul> <p>But coverage is not relevance.</p> <p>A template that covers eight analysis dimensions with Rust examples adds zero value to a Go project with five known drift patterns. Worse, it adds negative value: the AI spends attention defending generic advice instead of noticing project-specific drift.</p> <p>This is the attention budget problem again. Every token of generic guidance displaces a token of specific guidance. A 700-line skill that's 70% redundant doesn't just waste 490 lines: it dilutes the 210 lines that matter.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#the-litmus-test","level":2,"title":"The Litmus Test","text":"<p>Before dropping any external skill into your project:</p> <ol> <li> <p>Run E/A/R: What percentage is expert knowledge vs. what the AI    already knows? If it's less than 50% expert, it's probably not worth    the attention cost.</p> </li> <li> <p>Check the language: Does it use your stack? Generic patterns in    the wrong language are noise, not signal.</p> </li> <li> <p>List your actual drift: Read your own session history, learnings,    and post-mortems. What breaks in practice? Does the skill check for    those things?</p> </li> <li> <p>Measure by deletion: After adaptation, how much of the original    survives? If you're keeping less than 30%, you would have been faster    writing from scratch.</p> </li> <li> <p>Test against your conventions: Does every check in the skill map    to a specific convention or rule in your project? If not, it's    generic advice wearing a skill's clothing.</p> </li> </ol>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-05-you-cant-import-expertise/#what-good-adaptation-looks-like","level":2,"title":"What Good Adaptation Looks Like","text":"<p>The consolidation skill went from:</p> Before After 4 files, 700+ lines 1 file, ~120 lines Rust examples Go-specific <code>rg</code> commands 8 generic dimensions 9 project-specific checks 6 report templates 1 focused output format Scoring system (A+ to F) Findings + priority + suggested fixes \"Check for duplication\" \"Check for <code>Is*</code> predicate prefixes in exported methods\" <p>The adapted version is smaller, faster to parse, and catches the things that actually drift in this project.</p> <p>That's the difference between a template and a tool.</p> <p>If You Remember One Thing from This Post...</p> <p>Frameworks travel. Expertise doesn't.</p> <p>You can import structures, matrices, and workflows.</p> <p>But the checks that matter only grow where the scars are:</p> <ul> <li>the conventions that were violated, </li> <li>the patterns that drifted,</li> <li>and the specific ways this codebase accumulates debt.</li> </ul> <p>This post was written during a consolidation session where the consolidation skill itself became the subject of consolidation. The meta continues.</p>","path":["You Can't Import Expertise"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/","level":1,"title":"The Anatomy of a Skill That Works","text":"<p>Update (2026-02-11)</p> <p>As of <code>v0.4.0</code>, <code>ctx</code> consolidated sessions into the journal mechanism. References to <code>ctx-save</code>, <code>ctx session</code>, and <code>.context/sessions/</code> in this post reflect the architecture at the time of writing.</p> <p></p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#what-20-skill-rewrites-taught-me-about-guiding-ai","level":2,"title":"What 20 Skill Rewrites Taught Me about Guiding AI","text":"<p>Jose Alekhinne / 2026-02-07</p> <p>Why Do Some Skills Produce Great Results While Others Get Ignored or Produce Garbage?</p> <p>I had 20 skills. Most were well-intentioned stubs: a description, a command to run, and a wish for the best.</p> <p>Then I rewrote all of them in a single session. This is what I learned.</p> <p>In Skills That Fight the Platform, I described what skills should not do. In You Can't Import Expertise, I showed why templates fail. This post completes the trilogy: the concrete patterns that make a skill actually work.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-starting-point","level":2,"title":"The Starting Point","text":"<p>Here is what a typical skill looked like before the rewrite:</p> <pre><code>---\nname: ctx-save\ndescription: \"Save session snapshot.\"\n---\n\nSave the current context state to `.context/sessions/`.\n\n## Execution\n\nctx session save $ARGUMENTS\n\nReport the saved session file path to the user.\n</code></pre> <p>Seven lines of body. A vague description. No guidance on when to use it, when not to, what the command actually accepts, or how to tell if it worked.</p> <p>As a result, the agent would either never trigger the skill (the description was too vague), or trigger it and produce shallow output (no examples to calibrate quality).</p> <p>A skill without boundaries is just a suggestion.</p> <p>More precisely: the most effective boundary I found was a quality gate that runs before execution, not during it.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-pattern-that-emerged","level":2,"title":"The Pattern That Emerged","text":"<p>After rewriting 20 skills, a repeatable anatomy emerged (independent of the skill's purpose). Not every skill needs every section, but the effective ones share the same bones:</p> Section What It Does Before X-ing Pre-flight checks; prevents premature execution When to Use Positive triggers; narrows activation When NOT to Use Negative triggers; prevents misuse Usage Examples Invocation patterns the agent can pattern-match Process/Execution What to do; commands, steps, flags Good/Bad Examples Desired vs undesired output; sets boundaries Quality Checklist Verify before claiming completion <p>I realized the first three sections matter more than the rest; because a skill with great execution steps but no activation guidance is like a manual for a tool nobody knows they have.</p> <p>Anti-Pattern: The Perfect Execution Trap</p> <p>A skill with detailed execution steps but no activation guidance will fail more often than a vague skill because it executes confidently at the wrong time.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-1-quality-gates-prevent-premature-execution","level":2,"title":"Lesson 1: Quality Gates Prevent Premature Execution","text":"<p>The single most impactful addition was a \"Before X-ing\" section at the top of each skill. Not process steps; pre-flight checks.</p> <pre><code>## Before Recording\n\n1. **Check if it belongs here**: is this learning specific\n   to this project, or general knowledge?\n2. **Check for duplicates**: search LEARNINGS.md for similar\n   entries\n3. **Gather the details**: identify context, lesson, and\n   application before recording\n</code></pre> <ul> <li>Without this gate, the agent would execute immediately on trigger.</li> <li>With it, the agent pauses to verify preconditions.</li> </ul> <p>The difference is dramatic: instead of shallow, reflexive execution, you get considered output.</p> <p>Readback</p> <p>For the astute readers, the aviation parallel is intentional:</p> <p>Pilots do not skip the pre-flight checklist because they have flown before.</p> <p>The checklist exists precisely because the stakes are high enough that \"I know what I'm doing\" is not sufficient.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-2-when-not-to-use-is-not-optional","level":2,"title":"Lesson 2: \"When NOT to Use\" Is Not Optional","text":"<p>Every skill had a \"When to Use\" section. Almost none had \"When NOT to Use\". This is a problem.</p> <p>AI agents are biased toward action. Given a skill that says \"use when journal entries need enrichment\", the agent will find reasons to enrich.</p> <p>Without explicit negative triggers, over-activation is not a bug; it is the default behavior.</p> <p>Some examples of negative triggers that made a real difference:</p> Skill Negative Trigger ctx-reflect \"When the user is in flow; do not interrupt\" ctx-save \"After trivial changes; a typo does not need a snapshot\" prompt-audit \"Unsolicited; only when the user invokes it\" qa \"Mid-development when code is intentionally incomplete\" <p>These are not just nice-to-have. They are load-bearing. </p> <p>Withoutthem, the agent will trigger the skill at the wrong time, produce unwanted output, and erode the user's trust in the skill system.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-3-examples-set-boundaries-better-than-rules","level":2,"title":"Lesson 3: Examples Set Boundaries Better than Rules","text":"<p>The most common failure mode of thin skills was not wrong behavior but vague behavior. The agent would do roughly the right thing, but at a quality level that required human cleanup.</p> <p>Rules like \"be constructive, not critical\" are too abstract. What does \"constructive\" look like in a prompt audit report? The agent has to guess.</p> <p>Good/bad example pairs avoid guessing:</p> <pre><code>### Good Example\n\n> This session implemented the cooldown mechanism for\n> `ctx agent`. We discovered that `$PPID` in hook context\n> resolves to the Claude Code PID.\n>\n> I'd suggest persisting:\n> - **Learning**: `$PPID` resolves to Claude Code PID\n>   `ctx add learning --context \"...\" --lesson \"...\"`\n> - **Task**: mark \"Add cooldown\" as done\n\n### Bad Examples\n\n* \"*We did some stuff. Want me to save it?*\"\n* Listing 10 trivial learnings that are general knowledge\n* Persisting without asking the user first\n</code></pre> <p>The good example shows the exact format, level of detail, and command syntax. The bad examples show where the boundary is.</p> <p>Together, they define a quality corridor without prescribing every word.</p> <p>Rules describe. Examples demonstrate.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-4-skills-are-read-by-agents-not-humans","level":2,"title":"Lesson 4: Skills Are Read by Agents, Not Humans","text":"<p>This seems obvious, but it has non-obvious consequences. During the rewrite, one skill included guidance that said \"use a blog or notes app\" for general knowledge that does not belong in the project's learnings file.</p> <p>The agent does not have a notes app. It does not browse the web to find one. This instruction, clearly written for a human audience, was dead weight in a skill consumed by an AI.</p> <p>Skills Are for the Agents</p> <p>Every sentence in a skill should be actionable by the agent.</p> <p>If the guidance requires human judgment or human tools, it belongs in documentation, not in a skill.</p> <p>The corollary: command references must be exact. </p> <p>A skill that says \"save it somewhere\" is useless. </p> <p>A skill that says <code>ctx add learning --context \"...\" --lesson \"...\" --application \"...\"</code> is actionable.</p> <p>The agent can pattern-match and fill in the blanks.</p> <p>Litmus test: If a sentence starts with \"you could...\" or assumes external tools, it does not belong in a skill.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-5-the-description-field-is-the-trigger","level":2,"title":"Lesson 5: The Description Field Is the Trigger","text":"<p>This was covered in Skills That Fight the Platform, but the rewrite reinforced it with data. Several skills had good bodies but vague descriptions:</p> <pre><code># Before: vague, activates too broadly or not at all\ndescription: \"Show context summary.\"\n\n# After: specific, activates at the right time\ndescription: \"Show context summary. Use at session start or\n  when unclear about current project state.\"\n</code></pre> <p>The description is not a title. It is the activation condition.</p> <p>The platform's skill matching reads this field to decide whether to surface the skill. A vague description means the skill either never triggers or triggers when it should not.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-6-flag-tables-beat-prose","level":2,"title":"Lesson 6: Flag Tables Beat Prose","text":"<p>Most skills wrap CLI tools. The thin versions described flags in prose, if at all. The rewritten versions use tables:</p> <pre><code>| Flag        | Short | Default | Purpose                  |\n|-------------|-------|---------|--------------------------|\n| `--limit`   | `-n`  | 20      | Maximum sessions to show |\n| `--project` | `-p`  | \"\"      | Filter by project name   |\n| `--full`    |       | false   | Show complete content    |\n</code></pre> <p>Tables are scannable, complete, and unambiguous. </p> <p>The agent can read them faster than parsing prose, and they serve as both reference and validation: If the agent invokes a flag not in the table,  something is wrong.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#lesson-7-template-drift-is-a-real-maintenance-burden","level":2,"title":"Lesson 7: Template Drift Is a Real Maintenance Burden","text":"<p>// TODO: this has changed; we deploy from the marketplace; update it. // at least add an admonition saying thing are different now.</p> <p><code>ctx</code> deploys skills through templates (via <code>ctx init</code>). Every skill exists in two places: the live version (<code>.claude/skills/</code>) and the template (<code>internal/assets/claude/skills/</code>).</p> <p>They must match.</p> <p>During the rewrite, every skill update required editing both files and running <code>diff</code> to verify. This sounds trivial, but across 16 template-backed skills, it was the most error-prone part of the process.</p> <p>Template drift is dangerous because it creates false confidence: the agent appears to follow rules that no longer exist.</p> <p>The lesson: if your skills have a deployment mechanism, build the drift check into your workflow. We added a row to the <code>update-docs</code> skill's mapping table specifically for this:</p> <pre><code>| `internal/assets/claude/skills/` | `.claude/skills/` (live) |\n</code></pre> <p>Intentional differences (like project-specific scripts in the live version but not the template) should be documented, not discovered later as bugs.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-rewrite-scorecard","level":2,"title":"The Rewrite Scorecard","text":"Metric Before After Average skill body ~15 lines ~80 lines Skills with quality gate 0 20 Skills with \"When NOT\" 0 20 Skills with examples 3 20 Skills with flag tables 2 12 Skills with checklist 0 20 <p>More lines, but almost entirely Expert content (per the E/A/R framework). No personality roleplay, no redundant guidance, no capability lists. Just project-specific knowledge the platform does not have.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-07-the-anatomy-of-a-skill-that-works/#the-meta-lesson","level":2,"title":"The Meta-Lesson","text":"<p>The previous two posts argued that skills should provide knowledge, not personality; that they should complement the platform, not fight it; that they should grow from project history, not imported templates.</p> <p>This post adds the missing piece: structure.</p> <p>A skill without a structure is a wish.</p> <p>A skill with quality gates, negative triggers, examples, and checklists is a tool: the difference is not the content; it is whether the agent can reliably execute it without human intervention.</p> <p>Skills Are Interfaces</p> <p>Good skills are not instructions. They are contracts.:</p> <ul> <li>They specify preconditions, postconditions, and boundaries.</li> <li>They show what success looks like and what failure looks like.</li> <li>They trust the agent's intelligence but do not trust its assumptions.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Skills that work have bones, not just flesh.</p> <p>Quality gates, negative triggers, examples, and checklists are the skeleton. The domain knowledge is the muscle.</p> <p>Without the skeleton, the muscle has nothing to attach to.</p> <p>This post was written during the same session that rewrote all 22 skills. The skill-creator skill was updated to encode these patterns. The meta continues.</p>","path":["The Anatomy of a Skill That Works"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/","level":1,"title":"Not Everything Is a Skill","text":"<p>Update (2026-02-11)</p> <p>As of v0.4.0, <code>ctx</code> consolidated sessions into the journal mechanism. References to <code>/ctx-save</code>, <code>.context/sessions/</code>, and session auto-save in this post reflect the architecture at the time of writing.</p> <p></p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-a-codebase-audit-taught-me-about-restraint","level":2,"title":"What a Codebase Audit Taught Me about Restraint","text":"<p>Jose Alekhinne / 2026-02-08</p> <p>When You Find a Useful Prompt, What Do You Do with It?</p> <p>My instinct was to make it a skill.</p> <p>I had just spent three posts explaining how to build skills that work.  Naturally, the hammer wanted nails.</p> <p>Then I looked at what I was holding and realized: this is not a nail.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit","level":2,"title":"The Audit","text":"<p>I wanted to understand how I use <code>ctx</code>: </p> <ul> <li>Where the friction is;</li> <li>What works, what drifts; </li> <li>What I keep doing manually that could be automated. </li> </ul> <p>So I wrote a prompt that spawned eight agents to analyze the codebase from  different angles:</p> Agent Analysis 1 Extractable patterns from session history 2 Documentation drift (godoc, inline comments) 3 Maintainability (large functions, misplaced code) 4 Security review (CLI-specific surface) 5 Blog theme discovery 6 Roadmap and value opportunities 7 User-facing documentation gaps 8 Agent team strategies for future sessions <p>The prompt was specific: </p> <ul> <li>read-only agents, </li> <li>structured output format,</li> <li>concrete file references, </li> <li>ranked recommendations. </li> </ul> <p>It ran for about  20 minutes and produced eight Markdown reports.</p> <p>The reports were good: Not perfect, but actionable.</p> <p>What mattered was not the speed. It was that the work could be explored without committing to any single outcome.</p> <p>They surfaced a stale <code>doc.go</code> referencing a subcommand that was never built. </p> <p>They found 311 build-then-test sequences I could reduce to a single <code>make check</code>. </p> <p>They identified that 42% of my sessions start with \"do you remember?\",  which is a lot of repetition for something a skill could handle.</p> <p>I had findings. I had recommendations. I had the instinct to automate.</p> <p>And then... I stopped.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-question","level":2,"title":"The Question","text":"<p>The natural next step was to wrap the audit prompt as <code>/ctx-audit</code>: a skill you invoke periodically to get a health check. It fits the pattern: </p> <ul> <li>It has a clear trigger.</li> <li>It produces structured output.</li> </ul> <p>But I had just spent a week writing about what makes skills work, and the criteria I established argued against it.</p> <p>From The Anatomy of a Skill That Works:</p> <p>\"A skill without boundaries is just a suggestion.\"</p> <p>From You Can't Import Expertise:</p> <p>\"Frameworks travel, expertise doesn't.\"</p> <p>From Skills That Fight the Platform:</p> <p>\"You are the guest, not the host.\"</p> <p>The audit prompt fails all three tests:</p> Criterion Audit prompt Good skill Frequency Quarterly, maybe Daily or weekly Stability Tweaked every time Consistent invocation Scope Bespoke, 8 parallel agents Single focused action Trigger \"I feel like auditing\" Clear, repeatable event <p>Skills are contracts. Contracts need stable terms. </p> <p>A prompt I will rewrite every time I use it is not a contract.  It is a conversation starter.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#recipes-vs-skills","level":2,"title":"Recipes vs Skills","text":"<p>The distinction that emerged:</p> Skill Recipe Invocation <code>/slash-command</code> Copy-paste from a doc Frequency High (daily, weekly) Low (quarterly, ad hoc) Stability Fixed contract Adapted each time Scope One focused action Multi-step orchestration Audience The agent The human (who then prompts) Lives in <code>.claude/skills/</code> <code>hack/</code> or <code>docs/</code> Attention cost Loaded into context on match Zero until needed <p>Recipes can later graduate into skills, but only after repetition proves stability.</p> <p>That last row matters. Skills consume the attention budget every time the platform considers activating them.</p> <p>A skill that triggers quarterly but gets evaluated on every prompt is pure waste: attention spent on something that will say \"When NOT to Use: now\" 99% of the time.</p> <p>Runbooks have zero attention cost. They sit in a Markdown file until a human decides to use them. </p> <ul> <li>The human provides the judgment about timing. </li> <li>The prompt provides the structure.</li> </ul> <p>The Attention Budget Applies to Skills Too</p> <p>Every skill in <code>.claude/skills/</code> is a standing claim on the context window. The platform evaluates skill descriptions against every user prompt to decide whether to activate.</p> <p>Twenty focused skills are fine. Thirty might be fine. But each one added reduces the headroom available for actual work.</p> <p>Recipes are skills that opted out of the attention tax.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#what-the-audit-actually-produced","level":2,"title":"What the Audit Actually Produced","text":"<p>The audit was not wasted. It was a planning exercise that generated concrete tasks:</p> Finding Action 42% of sessions start with memory check Task: <code>/ctx-remember</code> skill (this one is a skill; it is daily) Auto-save stubs are empty Task: enhance <code>/ctx-save</code> with richer summaries 311 raw build-test sequences Task: <code>make check</code> target Stale <code>recall/doc.go</code> lists nonexistent <code>serve</code> Task: fix the doc.go 120 commit sequences disconnected from context Task: <code>/ctx-commit</code> workflow <ul> <li>Some findings became skills;</li> <li>Some became <code>Makefile</code> targets;</li> <li>Some became one-line doc fixes. </li> </ul> <p>The audit did not prescribe the artifact type: The findings did.</p> <p>The audit is the input. Skills are one possible output. Not the only one.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-audit-prompt","level":2,"title":"The Audit Prompt","text":"<p>Here is the exact prompt I used, for those who are curious.</p> <p>This is not a template: It worked because it was written against this codebase, at this moment, with specific goals in mind:</p> <pre><code>I want you to create an agent team to audit this codebase. Save each report as\na separate Markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable. Every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (*session mining*)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (*godoc + inline*)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check that package-level comments match\npackage names.\nOutput: drift items ranked by severity with exact file:line references.\n\n### 3. Maintainability\nLook for:\n- functions longer than 80 lines with clear split points\n- switch blocks with more than 5 cases that could be table-driven\n- inline comments like \"step 1\", \"step 2\" that indicate a block wants to be a function\n- files longer than 400 lines\n- flat packages that could benefit from sub-packages\n- functions that appear misplaced in their file\n\nDo NOT flag things that are fine as-is just because they could theoretically\nbe different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app. Focus on CLI-relevant attack surface, not web OWASP:\n- file path traversal\n- command injection\n- symlink following when writing to `.context/`\n- permission handling\n- sensitive data in outputs\n\nOutput: findings with severity ratings and plausible exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git history,\nrecent session discussions, and `DECISIONS.md` for story arcs worth writing about.\nSuggest 3-5 blog post themes with:\n- title\n- angle\n- target audience\n- key commits or sessions to reference\n- a 2-sentence pitch\n\nPrioritize themes that build a coherent narrative across posts.\n\n### 6. Roadmap and Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses,\nidentify the highest-value improvements. Consider user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with rough effort and impact estimates.\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and user docs. Suggest improvements structured as\nuse-case pages: the problem, how ctx solves it, a typical workflow, and gotchas.\nIdentify gaps where a user would get stuck without reading source code.\nOutput: documentation gaps with suggested page outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each, include:\n- team composition (roles and agent types)\n- task distribution strategy\n- coordination approach\n- the kinds of work it suits\n</code></pre> <p>Avoid Generic Advice</p> <p>Suggestions that are not grounded in a project's actual structure, history, and workflows are worse than useless:</p> <p>They create false confidence.</p> <p>If an analysis cannot point to concrete files, commits,  sessions, or patterns, it should say \"no finding\"  instead of inventing best practices.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#the-deeper-pattern","level":2,"title":"The Deeper Pattern","text":"<p>This is part of a pattern I keep rediscovering: </p> <p>The urge to automate is not the same as the need to automate:</p> <ul> <li>The 3:1 ratio taught me that not every session should be a YOLO sprint. </li> <li>The E/A/R framework taught me that not every template    is worth importing. Now the audit is teaching me that    not every useful prompt is worth institutionalizing.</li> </ul> <p>The common thread is restraint: </p> <ul> <li>Knowing when to stop. </li> <li>Recognizing that the cost of automation is not just   the effort to build it.</li> </ul> <p>The cost is the ongoing attention tax of maintaining it, the context it consumes, and the false confidence it creates when it drifts.</p> <p>An entry in <code>hack/runbooks/codebase-audit.md</code> is honest about what it is:</p> <p>A prompt I wrote once, improved once, and will adapt again next time: </p> <ul> <li>It does not pretend to be a reliable contract. </li> <li>It does not claim attention budget. </li> <li>It does not drift silently.</li> </ul> <p>The Automation Instinct</p> <p>When you find a useful prompt, the instinct is to institutionalize it. Resist.</p> <p>Ask first: will I use this the same way next time?</p> <p>If yes, it is a skill. If no, it is a recipe. If you are not sure, it is a recipe until proven otherwise.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-08-not-everything-is-a-skill/#this-mindset-in-the-context-of-ctx","level":2,"title":"This Mindset in the Context of <code>ctx</code>","text":"<p><code>ctx</code> is a tool that gives AI agents persistent memory. Its purpose is automation: reducing the friction of context loading, session recall, decision tracking.</p> <p>But automation has boundaries, and knowing where those boundaries are is as important as pushing them forward. </p> <p>The skills system is for high-frequency, stable workflows. </p> <p>The recipes, the journal entries, the session dumps in  <code>.context/sessions/</code>: those are for everything else.</p> <p>Not everything needs to be a slash command. Some things are better as Markdown files you read when you need them.</p> <p>The goal of <code>ctx</code> is not to automate everything: It is to automate the right things and to make the rest easy to find when you need it.</p> <p>If You Remember One Thing from This Post...</p> <p>The best automation decision is sometimes not to automate.</p> <p>A runbook in a Markdown file costs nothing until you use it.</p> <p>A skill costs attention on every prompt, whether it fires or not.</p> <p>Automate the daily. Document the periodic. Forget the rest.</p> <p>This post was written during the session that produced the codebase audit reports and distilled the prompt into <code>hack/runbooks/codebase-audit.md</code>. The audit generated seven tasks, one Makefile target, and zero new skills. The meta continues.</p> <p>See also: Code Is Cheap. Judgment Is Not.: the capstone that threads this post's restraint argument into the broader case for why judgment, not production, is the bottleneck.</p>","path":["Not Everything Is a Skill"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#when-markdown-is-not-a-security-boundary","level":2,"title":"When Markdown Is Not a Security Boundary","text":"<p>Volkan Özçelik / 2026-02-09</p> <p>What Happens When Your AI Agent Runs Overnight and Nobody Is Watching?</p> <p>It follows instructions: That is the problem.</p> <p>Not because it is malicious. Because it is controllable.</p> <p>It follows instructions from context, and context can be poisoned.</p> <p>I was writing the autonomous loops recipe for <code>ctx</code>: the guide for running an AI agent in a loop overnight, unattended, working through tasks while you sleep. The original draft had a tip at the bottom:</p> <p>Use <code>CONSTITUTION.md</code> for guardrails. Tell the agent \"never delete tests\" and it usually won't.</p> <p>Then I read that sentence back and realized: that is wishful thinking.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-realization","level":2,"title":"The Realization","text":"<p><code>CONSTITUTION.md</code> is a Markdown file. The agent reads it at session start alongside everything else in <code>.context/</code>. It is one source of instructions in a context window that also contains system prompts, project files, conversation history, tool outputs, and whatever the agent fetched from the internet.</p> <p>An attacker who can inject content into any of those sources can redirect the agent's behavior. And \"attacker\" does not always mean a person with malicious intent. It can be:</p> Vector Example A dependency A malicious npm package with instructions in its README or error output A URL Documentation page with embedded adversarial instructions A project file A contributor who adds instructions to <code>CLAUDE.md</code> or <code>.cursorrules</code> The agent itself In an autonomous loop, the agent modifies its own config between iterations A command output An error message containing instructions the agent interprets and follows <p>That last vector is the one that kept me up at night (literally!):</p> <p>In an autonomous loop, the agent modifies files as part of its job. </p> <p>If it modifies its own configuration files, the next iteration loads the modified config. </p> <ul> <li>No human reviews it. </li> <li>No diff is shown. </li> <li>The agent that starts iteration <code>N+1</code> is running with rules written by iteration <code>N</code>.</li> </ul> <p>The agent can rewrite its own guardrails.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#five-layers-each-with-a-hole","level":2,"title":"Five Layers, Each with a Hole","text":"<p>That's five nested layers of swiss cheese. Alone, each of them has large holes. Together, they create a boundary.</p> <p>What followed was a week of peeling back assumptions:</p> <p>Every defenseI examined had a bypass, and the bypass was always the same shape: the defense was enforced at a level the agent could reach.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"<p><code>CONSTITUTION.md</code>, the Agent Playbook, system prompts:  These tell the agent what to do. </p> <p>The agent usually follows them.</p> <p>\"Usually\" is the keyword here.</p> <p>The hole: Prompt injection:</p> <ul> <li>A sufficiently crafted payload overrides soft instructions. </li> <li>Long context windows dilute attention on rules stated early. </li> <li>Edge cases where instructions are ambiguous get resolved in    unpredictable ways.</li> </ul> <p>Verdict: Necessary. Not sufficient. Good for the common case. Never trust it as a security boundary.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"<p>Permission allowlists in <code>.claude/settings.local.json</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n</code></pre> <p>If <code>rm</code>, <code>curl</code>, <code>sudo</code>, or <code>docker</code> are not in the allowlist, the agent cannot invoke them. This is deterministic. </p> <p>The application enforces it regardless of what any prompt says.</p> <p>The hole: The agent can modify the allowlist itself: </p> <ul> <li>It has <code>Write</code> permission. </li> <li>The allowlist lives in a file. </li> <li>The agent writes to the file. </li> <li>The next iteration loads the modified allowlist.</li> </ul> <p>The application enforces the rules, but the application reads the rules from files the agent can write.</p> <p>Verdict: Strong first layer. Must be combined with self-modification prevention.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-3-os-level-isolation-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Unbypassable)","text":"<p>This is where the defenses stop having holes in the same shape.</p> <p>The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without <code>CAP_NET_RAW</code> cannot open raw sockets. These are kernel boundaries.</p> Control What it stops Dedicated unprivileged user Privilege escalation, <code>sudo</code>, group-based access Filesystem permissions Lateral movement to other projects, system config Immutable config files Self-modification of guardrails between iterations <p>Make the agent's instruction files read-only: <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, <code>.context/CONSTITUTION.md</code>. Own them as a different user, or mark them immutable with <code>chattr +i</code> on Linux.</p> <p>The hole: Actions within the agent's legitimate scope: </p> <ul> <li>If the agent has write access to source code (which it needs), it can introduce vulnerabilities in the code itself. </li> <li>You cannot prevent this without removing the agent's ability to do its job.</li> </ul> <p>Verdict: Essential. This is the layer that makes Layers 1 and 2 trustworthy.</p> <p>OS-level isolation does not make the agent safe; it makes the other layers meaningful.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"<p>An agent that cannot reach the internet cannot exfiltrate data.</p> <p>It also cannot ingest new instructions mid-loop from external documents, error pages, or hostile content.</p> <pre><code># Container with no network\ndocker run --network=none ...\n\n# Or firewall rules allowing only package registries\niptables -A OUTPUT -d registry.npmjs.org -j ACCEPT\niptables -A OUTPUT -d proxy.golang.org -j ACCEPT\niptables -A OUTPUT -j DROP\n</code></pre> <ul> <li>If the agent genuinely does not need the network, disable it   entirely. </li> <li>If it needs to fetch dependencies, allow specific   registries and block everything else.</li> </ul> <p>The hole: None, if the agent does not need the network. </p> <p>Thetradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"<p>The strongest boundary is a separate machine.</p> <p>The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n</code></pre> <p>Never Mount the Docker Socket</p> <p>Do not mount <code>/var/run/docker.sock</code>, like, ever. </p> <p>An agent with socket access can spawn sibling containers with  full host access, effectively escaping the sandbox. </p> <p>This is not theoretical: the Docker socket grants root-equivalent access to the host.</p> <p>Use rootless Docker or Podman to eliminate this escalation path entirely.</p> <p>Virtual machines are even stronger: The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-pattern","level":2,"title":"The Pattern","text":"<p>Each layer is straightforward: The strength is in the combination:</p> Layer Implementation What it stops Soft instructions <code>CONSTITUTION.md</code> Common mistakes (probabilistic) Application allowlist <code>.claude/settings.local.json</code> Unauthorized commands (deterministic within runtime) Immutable config <code>chattr +i</code> on config files Self-modification between iterations Unprivileged user Dedicated user, no sudo Privilege escalation Container <code>--cap-drop=ALL --network=none</code> Host escape, data exfiltration Resource limits <code>--memory=4g --cpus=2</code> Resource exhaustion <p>No layer is redundant. Each one catches what the others miss:</p> <ul> <li>The soft instructions handle the 99% case: \"don't delete tests.\"</li> <li>The allowlist prevents the agent from running commands it should   not.</li> <li>The immutable config prevents the agent from modifying the   allowlist.</li> <li>The unprivileged user prevents the agent from removing   the immutable flag.</li> <li>The container prevents the agent from reaching   anything outside its workspace.</li> <li>The resource limits prevent the agent from consuming all system resources.</li> </ul> <p>Remove any one layer and there is an attack path through the remaining ones.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#common-mistakes-i-see","level":2,"title":"Common Mistakes I See","text":"<p>These are real patterns, not hypotheticals:</p> <p>\"I'll just use <code>--dangerously-skip-permissions</code>.\" This disables Layer 2 entirely. Without Layers 3 through 5, you have no protection at all. The flag means what it says. If you ever need to, think thrice, you probably don't. But, if you ever need to usee this only use it inside a properly isolated VM (not even a container: a \"VM\").</p> <p>\"The agent is sandboxed in Docker.\" A Docker container with the Docker socket mounted, running as root, with <code>--privileged</code>, and full network access is not sandboxed. It is a root shell with extra steps.</p> <p>\"I reviewed <code>CLAUDE.md</code>, it's fine.\" You reviewed it before the loop started. The agent modified it during iteration 3. Iteration 4 loaded the modified version. Unless the file is immutable, your review is futile.</p> <p>\"The agent only has access to this one project.\" Does the project directory contain <code>.env</code> files? SSH keys? API tokens? A <code>.git/config</code> with push access to a remote? Filesystem isolation means isolating what is in the directory too.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"<p>This is the same lesson I keep rediscovering, wearing different clothes.</p> <p>In The Attention Budget, I wrote about how every token competes for the AI's focus. Security instructions in <code>CONSTITUTION.md</code> are subject to the same budget pressure: if the context window is full of code, error messages, and tool outputs, the security rules stated at the top get diluted.</p> <p>In Skills That Fight the Platform, I wrote about how custom instructions can conflict with the AI's built-in behavior. Security rules have the same problem: telling an agent \"never run curl\" in Markdown while giving it unrestricted shell access creates a contradiction: The agent resolves contradictions unpredictably. The agent will often pick the path of least resistance to attain its objective function. And, trust me, agents can get far more creative than the best red-teamer you know.</p> <p>In You Can't Import Expertise, I wrote about how generic templates fail because they do not encode project-specific knowledge. Generic security advice fails the same way: \"Don't exfiltrate data\" is a category; blocking outbound network access is a control.</p> <p>The pattern across all of these: Soft instructions are useful for the common case. Hard boundaries are required for security.</p> <p>Know which is which.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#the-checklist","level":2,"title":"The Checklist","text":"<p>Before running an unattended AI agent:</p> <ul> <li> Agent runs as a dedicated unprivileged user (no sudo, no   docker group)</li> <li> Agent's config files are immutable or owned by a different   user</li> <li> Permission allowlist restricts tools to the project's   toolchain</li> <li> Container drops all capabilities (<code>--cap-drop=ALL</code>)</li> <li> Docker socket is NOT mounted</li> <li> Network is disabled or restricted to specific domains</li> <li> Resource limits are set (memory, CPU, disk)</li> <li> No SSH keys, API tokens, or credentials are accessible</li> <li> Project directory does not contain <code>.env</code> or secrets files</li> <li> Iteration cap is set (<code>--max-iterations</code>)</li> </ul> <p>This checklist lives in the Agent Security reference alongside the full threat model and detailed guidance for each layer.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-09-defense-in-depth-securing-ai-agents/#what-changed-in-ctx","level":2,"title":"What Changed in <code>ctx</code>","text":"<p>The autonomous loops recipe now has a full permissions and isolation section instead of a one-line tip about <code>CONSTITUTION.md</code>. It covers both the explicit allowlist approach and the <code>--dangerously-skip-permissions</code> flag, with honest guidance about when each is appropriate.</p> <p>It also has an OS-level isolation table that is not optional: unprivileged users, filesystem permissions, containers, VMs, network controls, resource limits, and self-modification prevention.</p> <p>The Agent Security page consolidates the threat model and defense layers into a standalone reference.</p> <p>These are not theoretical improvements. They are the minimum responsible guidance for a tool that helps people run AI agents overnight.</p> <p>If You Remember One Thing from This Post...</p> <p>Markdown is not a security boundary.</p> <p><code>CONSTITUTION.md</code> is a nudge. An allowlist is a gate.</p> <p>An unprivileged user in a network-isolated container is a wall.</p> <p>Use all three. Trust only the wall.</p> <p>This post was written during the session that added permissions, isolation, and self-modification prevention to the autonomous loops recipe. The security guidance started as a single tip and grew into two documents. The meta continues.</p>","path":["Defense in Depth: Securing AI Agents"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/","level":1,"title":"How Deep Is Too Deep?","text":"","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#when-master-ml-is-the-wrong-next-step","level":2,"title":"When \"Master ML\" Is the Wrong Next Step","text":"<p>Volkan Özçelik / 2026-02-12</p> <p>Have You Ever Felt like You Should Understand More of the Stack beneath You?</p> <p>You can talk about transformers at a whiteboard.</p> <p>You can explain attention to a colleague.</p> <p>You can use agentic AI to ship real software.</p> <p>But somewhere in the back of your mind, there is a voice:</p> <p>\"Maybe I should go deeper. Maybe I need to master machine learning.\"</p> <p>I had that voice for months. </p> <p>Then I spent a week debugging an agent failure that had nothing to do with ML theory and everything to do with knowing which abstraction was leaking.</p> <p>This post is about when depth compounds and (more importantly)  when it does not.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-hierarchy-nobody-questions","level":2,"title":"The Hierarchy Nobody Questions","text":"<p>There is an implicit stack most people carry around when thinking about AI:</p> Layer What Lives Here Agentic AI Autonomous loops, tool use, multi-step reasoning Generative AI Text, image, code generation Deep Learning Transformer architectures, training at scale Neural Networks Backpropagation, gradient descent Machine Learning Statistical learning, optimization Classical AI Search, planning, symbolic reasoning <p>At some point down that stack, you hit a comfortable plateau: the layer where you can hold a conversation but not debug a failure.</p> <p>The instinctive response is to go deeper.</p> <p>But that instinct hides a more important question:</p> <p>\"Does depth still compound when the abstractions above you are moving hyper-exponentially?\"</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-honest-observation","level":2,"title":"The Honest  Observation","text":"<p>If you squint hard enough, a large chunk of modern ML intuition collapses into older fields:</p> ML Concept Older Field Gradient descent Numerical optimization Backpropagation Reverse-mode autodiff Loss landscapes Non-convex optimization Generalization Statistics Scaling laws Asymptotics and information theory <p>Nothing here is uniquely \"AI\".</p> <p>Most of this math predates the term deep learning. In some cases, by decades.</p> <p>So what changed?</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#same-tools-different-regime","level":2,"title":"Same Tools, Different Regime","text":"<p>The mistake is assuming this is a new theory problem: It is not.</p> <p>It is a new operating regime.</p> <p>Classical numerical methods were developed under assumptions like:</p> <ul> <li>Manageable dimensionality</li> <li>Reasonably well-conditioned objectives</li> <li>Losses that actually represent the goal</li> </ul> <p>Modern ML violates all three: On purpose.</p> <p>Today's models operate with millions to trillions of parameters, wildly underdetermined systems, and objective functions we know are wrong but optimize anyway.</p> <p>It is complete and utter madness! </p> <p>At this scale, familiar concepts warp:</p> <ul> <li>What we call \"local minima\" are overwhelmingly saddle points in   high-dimensional spaces.</li> <li>Noise stops being noise and starts becoming structure.</li> <li>Overfitting can coexist with generalization.</li> <li>Bigger models outperform \"better\" ones.</li> </ul> <p>The math did not change: The phase did.</p> <p>This is less numerical analysis and more *statistical physics: Same equations, but behavior dominated by phase transitions and emergent structure.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#why-scaling-laws-feel-alien","level":2,"title":"Why Scaling Laws Feel Alien","text":"<p>In classical statistics, asymptotics describe what happens eventually.</p> <p>In modern ML, scaling laws describe where you can operate today.</p> <p>They do not say \"given enough time, things converge\".</p> <p>They say \"cross this threshold and behavior qualitatively changes\".</p> <p>This is why dumb architectures plus scale beat clever ones.</p> <p>Why small theoretical gains disappear under data.</p> <p>Why \"just make it bigger\", ironically, keeps working longer than it should.</p> <p>That is not a triumph of ML theory: It is a property of high-dimensional  systems under loose objectives.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#where-depth-actually-pays-off","level":2,"title":"Where Depth Actually Pays Off","text":"<p>This reframes the original question.</p> <p>You do not need depth because this is \"AI\".</p> <p>You need depth where failure modes propagate upward.</p> <p>I learned this building <code>ctx</code>: The agent failures I have spent the most time debugging were never about the model's architecture.</p> <p>They were about:</p> <ul> <li> <p>Misplaced trust:   The model was confident. The output was wrong. Knowing when confidence   and correctness diverge is not something you learn from a textbook. You   learn it from watching patterns across hundreds of sessions.</p> </li> <li> <p>Distribution shift:   The model performed well on common patterns and fell apart on edge   cases specific to this project. Recognizing that shift before it   compounds requires understanding why generalization has limits, not   just that it does.</p> </li> <li> <p>Error accumulation:    In a single prompt, model quirks are tolerable. In autonomous loops   running overnight, they compound. A small bias in how the model   interprets instructions becomes a large drift by iteration 20.</p> </li> <li> <p>Scale hiding errors:    The model's raw capability masked problems that only surfaced under   specific conditions. More parameters did not fix the issue. They just   made the failure mode rarer and harder to reproduce.</p> </li> </ul> <p>This is the kind of depth that compounds. Not deriving backprop. But, understanding when correct math  produces misleading intuition.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-connection-to-context-engineering","level":2,"title":"The Connection to Context Engineering","text":"<p>This is the same pattern I keep finding at different altitudes.</p> <p>In \"The Attention Budget\",  I wrote about how dumping everything into the context window degrades the  model's focus. The fix was not a better model: It was better curation: load less, load the right things,  preserve signal per token.</p> <p>In \"Skills That Fight the Platform\", I wrote about how custom instructions can conflict with the model's built-in behavior. The fix was not deeper ML knowledge: It was an understanding that the model already has judgment and that you should extend it, not override it.</p> <p>In \"You Can't Import Expertise\", I wrote about how generic  templates fail because they do not encode project-specific knowledge.  A consolidation skill with eight Rust-based analysis dimensions was mostly  noise for a Go project. The fix was not a better template: It was growing  expertise from this project's own history.</p> <p>In every case, the answer was not \"go deeper into ML\".</p> <p>The answer was knowing which abstraction was leaking  and fixing it at the right layer.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#agentic-systems-are-not-an-ml-problem","level":2,"title":"Agentic Systems Are Not an ML Problem","text":"<p>The mistake is assuming agent failures originate where the model was trained, rather than where it is deployed.</p> <p>Agentic AI is a systems problem under chaotic uncertainty:</p> <ul> <li>Feedback loops between the agent and its environment;</li> <li>Error accumulation across iterations;</li> <li>Brittle representations that break outside training distribution;</li> <li>Misplaced trust in outputs that look correct.</li> </ul> <p>In short-lived interactions, model quirks are tolerable. In long-running autonomous loops, however, they compound. </p> <p>That is where shallow understanding becomes expensive.</p> <p>But the understanding you need is not about optimizer internals.</p> <p>It is about:</p> What Matters What Does Not (for Most Practitioners) Why gradient descent fails in specific regimes How to derive it from scratch When memorization masquerades as reasoning The formal definition of VC dimension Recognizing distribution shift before it compounds Hand-tuning learning rate schedules Predicting when scale hides errors instead of fixing them Chasing theoretical purity divorced from practice <p>The depth that matters is diagnostic, not theoretical.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-real-answer","level":2,"title":"The Real Answer","text":"<p>Not turtles all the way down.</p> <p>Go deep enough to:</p> <ul> <li>Diagnose failures instead of cargo-culting fixes;</li> <li>Reason about uncertainty instead of trusting confidence;</li> <li>Design guardrails that align with model behavior, not hope.</li> </ul> <p>Stop before:</p> <ul> <li>Hand-deriving gradients for the sake of it;</li> <li>Obsessing over optimizer internals you will never touch;</li> <li>Chasing theoretical purity divorced from the scale you actually   operate at.</li> </ul> <p>This is not about mastering ML.</p> <p>It is about knowing which abstractions you can safely trust and which ones leak.</p> <p>Hint: Any useful abstraction almost certainly leaks.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#a-practical-litmus-test","level":2,"title":"A Practical Litmus Test","text":"<p>If a failure occurs and your instinct is to:</p> <ul> <li>Add more prompt text: abstraction leak above</li> <li>Add retries or heuristics: error accumulation</li> <li>Change the model: scale masking</li> <li>Reach for ML theory: you are probably (but not always) going too deep</li> </ul> <p>The right depth is the shallowest layer where the failure becomes predictable.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#the-ctx-lesson","level":2,"title":"The <code>ctx</code> Lesson","text":"<p>Every design decision in <code>ctx</code> is downstream of this principle.</p> <p>The attention budget exists  because the model's internal attention mechanism has real limits: You do not need to understand the math of softmax to build around it. But you do need to understand that more context is not always better and that attention density degrades with scale.</p> <p>The skill system exists  because the model's built-in behavior is already good: You do not need to understand RLHF to build effective skills. But you do need to understand that the model already has judgment and your skills should teach it things it does not know, not override how it thinks.</p> <p>Defense in depth  exists because soft instructions are probabilistic: You do not need to understand the transformer architecture to know that a Markdown file is not a security boundary. But you do need to understand that the model follows instructions from context, and context can be poisoned.</p> <p>In each case, the useful depth was one or two layers below the abstraction I was working at: Not at the bottom of the stack.</p> <p>The boundary between useful understanding and academic exercise is where your failure modes live.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-12-how-deep-is-too-deep/#closing-thought","level":2,"title":"Closing Thought","text":"<p>Most modern AI systems do not fail because the math is wrong.</p> <p>They fail because we apply correct math in the wrong regime, then build autonomous systems on top of it.</p> <p>Understanding that boundary, not crossing it blindly, is where depth still compounds.</p> <p>And that is a far more useful form of expertise than memorizing another loss function.</p> <p>If You Remember One Thing from This Post...</p> <p>Go deep enough to diagnose your failures. Stop before you are solving problems that do not propagate to your layer.</p> <p>The abstractions below you are not sacred. But neither are they irrelevant.</p> <p>The useful depth is wherever your failure modes live. Usually one or two layers down, not at the bottom.</p> <p>This post started as a note about whether I should take an ML course. The answer turned out to be \"no, but understand why not\". The meta continues.</p>","path":["How Deep Is Too Deep?"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/","level":1,"title":"Before Context Windows, We Had Bouncers","text":"","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-reset-problem","level":2,"title":"The Reset Problem","text":"<p>IRC is stateless.</p> <ul> <li>You disconnect, you vanish.</li> <li>You reconnect, you begin again.</li> </ul> <p>No buffer.</p> <p>No memory.</p> <p>No continuity.</p> <p>Modern systems are not much different:</p> <ul> <li>Close the browser tab.<ul> <li>Lose the Slack scrollback.</li> </ul> </li> <li>Open a new LLM session.<ul> <li>Start from zero.</li> </ul> </li> </ul> <p>Resets externalize reconstruction cost onto humans.</p> <p>Reconstruction is tax: Tax becomes entropy.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#stateless-protocol-stateful-life","level":2,"title":"Stateless Protocol, Stateful Life","text":"<p>IRC is minimal:</p> <ul> <li>A TCP connection.</li> <li>A nickname.</li> <li>A channel.</li> <li>A stream of lines.</li> </ul> <p>When the connection drops, you literally disappear from the graph.</p> <p>The protocol is stateless; human systems are not.</p> <p>So you:</p> <ul> <li>Reconnect;</li> <li>Ask what you missed;</li> <li>Scroll;</li> <li>Reconstruct.</li> </ul> <p>The machine forgets; you pay.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#the-bouncer-pattern","level":2,"title":"The Bouncer Pattern","text":"<p>A <code>bouncer</code> is a daemon that remains connected when you do not:</p> <ul> <li>It holds your seat;</li> <li>It buffers what you missed;</li> <li>It keeps your identity online.</li> </ul> <p>ZNC is one such bouncer.</p> <p>With ZNC:</p> <ul> <li>Your client does not connect to IRC;</li> <li>It connects to <code>ZNC</code>;</li> <li><code>ZNC</code> connects upstream.</li> </ul> <p>Client sessions become ephemeral.</p> <p>Presence becomes infrastructural.</p> <p>ZNC Is Tmux for IRC</p> <ul> <li> <p>Close your laptop.</p> <ul> <li>ZNC remains.</li> </ul> </li> <li> <p>Switch devices.</p> <ul> <li>ZNC persists.</li> </ul> </li> </ul> <p>This is not convenience; this is continuity.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#presence-without-flapping","level":2,"title":"Presence without Flapping","text":"<p>With a bouncer:</p> <ul> <li>Closing your client does not emit <code>PART</code>.</li> <li>Reopening does not emit <code>JOIN</code>.</li> </ul> <p>You do not flap in and out of existence.</p> <p>From the channel's perspective, you remain.</p> <p>From your perspective, history accumulates.</p> <ul> <li>Buffers persist;</li> <li>Identity persists;</li> <li>Context persists.</li> </ul> <p>This pattern predates AI.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#before-llm-context-windows","level":2,"title":"Before LLM Context Windows","text":"<p>An LLM session without memory is IRC without a bouncer:</p> <ul> <li>Close the window.</li> <li>Start over.</li> <li>Re-explain intent.</li> <li>Rehydrate context.</li> </ul> <p>That is friction.</p> <p>This Walks and Talks like <code>ctx</code></p> <p>Context engineering moves memory out of sessions and into infrastructure.</p> <ul> <li><code>ZNC</code> does this for IRC.</li> <li><code>ctx</code> does this for agents.</li> </ul> <p>Same principle:</p> <ul> <li>Volatile interface.</li> <li>Persistent substrate.</li> </ul> <p>Different fabric.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#minimal-architecture","level":2,"title":"Minimal Architecture","text":"<p>My setup is intentionally boring:</p> <ul> <li>A $5 small VPS.</li> <li>ZNC installed.</li> <li>TLS enabled.</li> <li>Firewall restricted.</li> </ul> <p>Then:</p> <ul> <li>ZNC connects to <code>Libera.Chat</code>.</li> <li><code>SASL</code> authentication lives inside ZNC.</li> <li>Buffers are stored on disk.</li> </ul> <p>My client connects to my VPS, not the network.</p> <p>The commands do not matter: The boundaries do:</p> <ul> <li>Authentication in infrastructure, not in the client;</li> <li>Memory server-side, not in scrollback;</li> <li>Presence decoupled from activity.</li> </ul> <p>Everything else is configuration.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#platform-memory","level":2,"title":"Platform Memory","text":"<p>Yes, I know, it is 2026:</p> <ul> <li>Discord stores history;</li> <li>Slack stores history;</li> <li>The dumpster fire on gasoline called X, too, stores history.</li> </ul> <p>HOWEVER, they own your substrate.</p> <p>Running a bouncer is quiet sovereignty:</p> <ul> <li>Logs are mine.</li> <li>Presence is continuous.</li> <li>State does not reset because I closed a tab.</li> </ul> <p>Small acts compound.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#signal-density","level":2,"title":"Signal Density","text":"<p>Primitive systems select for builders.</p> <p>Consistent presence in small rooms compounds reputation.</p> <p>Quiet compounding outperforms viral spikes.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#infrastructure-as-cognition","level":2,"title":"Infrastructure as Cognition","text":"<p>ZNC is not interesting because it is retro; it is interesting because it models a principle:</p> <ul> <li>Stateless protocols require stateful wrappers;</li> <li>Volatile interfaces require durable memory;</li> <li>Human systems require continuity.</li> </ul> <p>Distilled:</p> <p>Humans require context.</p> <p>Before context windows, we had bouncers.  </p> <p>Before AI memory files, we had buffers.</p> <p>Continuity is not a feature; it is a design decision.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#build-it","level":2,"title":"Build It","text":"<p>If you want the actual setup (VPS, ZNC, TLS, SASL, firewall...) there is a step-by-step runbook:</p> <p>Persistent IRC Presence with ZNC.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-irc-as-context/#motd","level":2,"title":"MOTD","text":"<p>When my client connects to my bouncer, it prints:</p> <pre><code>//   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n</code></pre> <p>See also: Context as Infrastructure -- the post that takes this observation to its conclusion: stateless protocols need stateful wrappers, and AI sessions need persistent filesystems.</p>","path":["Before Context Windows, We Had Bouncers"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/","level":1,"title":"Parallel Agents with Git Worktrees","text":"","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-backlog-problem","level":2,"title":"The Backlog Problem","text":"<p>Jose Alekhinne / 2026-02-14</p> <p>What Do You Do with 30 Open Tasks?</p> <p>You could work through them one at a time.</p> <p>One agent, one branch, one commit stream.</p> <p>Or you could ask: which of these don't touch each other?</p> <p>I had 30 open tasks in <code>TASKS.md</code>. Some were docs. Some were a new encryption package. Some were test coverage for a stable module. Some were blog posts.</p> <p>They had almost zero file overlap.</p> <p>Running one agent at a time meant serial execution on work that was fundamentally parallel:</p> <p>I was bottlenecking on me, not on the machine.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-insight-file-overlap-is-the-constraint","level":2,"title":"The Insight: File Overlap Is the Constraint","text":"<p>This is not a scheduling problem: It's a conflict avoidance problem.</p> <p>Two agents can work simultaneously on the same codebase if and only if they don't touch the same files. The moment they do, you get merge conflicts: And merge conflicts on AI-generated code are expensive because the human has to arbitrate choices they didn't make.</p> <p>So the question becomes: </p> <p>\"Can you partition your backlog into non-overlapping tracks?\"</p> <p>For <code>ctx</code>, the answer was obvious:</p> Track Touches Tasks <code>work/docs</code> <code>docs/</code>, <code>hack/</code> Blog posts, recipes, runbooks <code>work/pad</code> <code>internal/cli/pad/</code>, specs Scratchpad encryption, CLI, tests <code>work/tests</code> <code>internal/cli/recall/</code> Recall test coverage <p>Three tracks. Near-zero overlap. Three agents.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#git-worktrees-the-mechanism","level":2,"title":"Git Worktrees: The Mechanism","text":"<p><code>git</code> has a feature that most people don't use: worktrees.</p> <p>A worktree is a second (or third, or fourth) working directory that shares the same <code>.git</code> object database as your main checkout. </p> <p>Each worktree has its own branch, its own index, its own working tree. But they all share history, refs, and objects.</p> <pre><code>git worktree add ../ctx-docs -b work/docs\ngit worktree add ../ctx-pad -b work/pad\ngit worktree add ../ctx-tests -b work/tests\n</code></pre> <ul> <li>Three directories;</li> <li>Three branches;</li> <li>One repository.</li> </ul> <p>This is cheaper than three clones. And because they share objects, <code>git merge</code> afterwards is fast: It's a local operation on shared data.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-setup","level":2,"title":"The Setup","text":"<p>The workflow I landed on:</p> <p>1. Group tasks by blast radius.</p> <p>Read <code>TASKS.md</code>. For each pending task, estimate which files and directories it touches. Group tasks that share files into the same track. Tasks with no overlap go into separate tracks.</p> <p>This is the part that requires human judgment: </p> <p>An agent can propose groupings, but you need to verify that the boundaries are  real. A task that says \"update docs\" but actually touches Go code will poison a docs track.</p> <p>2. Create worktrees as sibling directories.</p> <p>Not subdirectories: Siblings. </p> <p>If your main checkout is at <code>~/WORKSPACE/ctx</code>, worktrees go  at <code>~/WORKSPACE/ctx-docs</code>, <code>~/WORKSPACE/ctx-pad</code>, etc.</p> <p>Why siblings? Because some tools (and some agents) walk up the directory tree looking for <code>.git</code>. A worktree inside the main checkout confuses them.</p> <p>3. Launch one agent per worktree.</p> <pre><code># Terminal 1\ncd ../ctx-docs && claude\n\n# Terminal 2\ncd ../ctx-pad && claude\n\n# Terminal 3\ncd ../ctx-tests && claude\n</code></pre> <p>Each agent gets a full working copy with <code>.context/</code> intact. It reads the same <code>TASKS.md</code>, the same <code>DECISIONS.md</code>, the same <code>CONVENTIONS.md</code>. It knows the full project state. It just works on a different slice.</p> <p>4. Do NOT run <code>ctx init</code> in worktrees.</p> <p>This is the gotcha. The <code>.context/</code> directory is tracked in git. Running <code>ctx init</code> in a worktree would overwrite shared context files: Wiping decisions, learnings, and tasks that belong to the whole project.</p> <p>The worktree already has everything it needs. Leave it alone.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#what-actually-happened","level":2,"title":"What Actually Happened","text":"<p>I ran three agents for about 40 minutes. Here is roughly what each track produced:</p> <p><code>work/docs</code>: Parallel worktrees recipe, blog post edits, recipe index reorganization, IRC recipe moved from <code>docs/</code> to <code>hack/</code>.</p> <p><code>work/pad</code>: <code>ctx pad show</code> subcommand, <code>--append</code> and <code>--prepend</code> flags on <code>ctx pad edit</code>, spec updates, 28 new test functions.</p> <p><code>work/tests</code>: Recall test coverage, edge case tests.</p> <p>Merging took about five minutes. Two of the three merges were clean.</p> <p>The third had a conflict in <code>TASKS.md</code>: </p> <p>both the docs track and the pad track had marked different tasks as <code>[x]</code>.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-tasksmd-conflict","level":2,"title":"The <code>TASKS.md</code> Conflict","text":"<p>This deserves its own section because it will happen every time.</p> <p>When two agents work in parallel, they both read <code>TASKS.md</code> at the start and mark tasks complete as they go. When you merge, git sees two branches that modified the same file differently.</p> <p>The resolution is always the same: accept all completions from both sides. No task should go from <code>[x]</code> back to <code>[ ]</code>. The merge is additive.</p> <p>This is one of those conflicts that sounds scary but is trivially mechanical: You are not arbitrating design decisions; you are combining two checklists.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#limits","level":2,"title":"Limits","text":"<p>3-4 worktrees, maximum. </p> <p>I tried four once: By the time I merged the third track, the fourth had drifted far enough that its changes needed rebasing. </p> <p>The merge complexity grows faster than the parallelism benefit.</p> <p>Three is the sweet spot:</p> <ul> <li>Two is conservative but safe;</li> <li>Four is possible if the tracks are truly independent;</li> <li>Anything more than four, you are in the danger zone.</li> </ul> <p>Group by directory, not by priority.</p> <p>It is tempting to put all the high-priority tasks in one track: Don't. </p> <p>Two high-priority tasks that touch the same files must be in the same track,  regardless of urgency. The constraint is file overlap, not importance.</p> <p>Commit frequently. </p> <p>Smaller commits make merge conflicts easier to resolve. An agent that writes  500 lines in a single commit is harder to merge than one that commits every  logical step.</p> <p>Name tracks by concern. </p> <ul> <li><code>work/docs</code> and <code>work/pad</code> tell you what's happening;</li> <li><code>work/track-1</code> and <code>work/track-2</code> tell you nothing.</li> </ul>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-pattern","level":2,"title":"The Pattern","text":"<p>This is the same pattern that shows up everywhere in <code>ctx</code>:</p> <p>The attention budget taught me that you can't dump everything into one context window. You have to partition, prioritize, and load selectively.</p> <p>Worktrees are the same principle applied to execution: You can't dump every task into one agent's workstream. You have to partition by blast radius, assign selectively, and merge deliberately.</p> <p>The codebase audit that generated these 30 tasks used eight parallel agents for analysis. Worktrees let me use parallel agents for implementation. Same coordination pattern, different artifact.</p> <p>And the IRC bouncer post from earlier today argued that stateless protocols need stateful wrappers. Worktrees are the same: git branches are stateless forks; <code>.context/</code> is the stateful wrapper that gives each agent the project's full memory.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#should-this-be-a-skill","level":2,"title":"Should This Be a Skill?","text":"<p>I asked myself the same question I asked about the codebase audit: should this be a <code>/ctx-worktree</code> skill?</p> <p>This time the answer was a resounding \"yes\": </p> <p>Unlike the audit prompt (which I tweak every time and run every other week) the worktree workflow is:</p> Criterion Worktree workflow Codebase audit Frequency Weekly Quarterly Stability Same steps every time Tweaked every time Scope Mechanical, bounded Bespoke, 8 agents Trigger Large backlog \"I feel like auditing\" <p>The commands are mechanical: <code>git worktree add</code>, <code>git worktree remove</code>, branch naming, safety checks. This is exactly what skills are for: stable contracts for repetitive operations.</p> <p>Ergo, <code>/ctx-worktree</code> exists. </p> <p>It enforces the 4-worktree limit, creates sibling directories,  uses <code>work/</code> branch prefixes, and reminds you not to run <code>ctx init</code> in worktrees.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-14-parallel-agents-with-worktrees/#the-takeaway","level":2,"title":"The Takeaway","text":"<p>Serial execution is the default. But serial is not always necessary.</p> <p>If your backlog partitions cleanly by file overlap, you can multiply your throughput with nothing more exotic than <code>git worktree</code> and a second terminal window.</p> <p>The hard part is not the <code>git</code> commands; it is the discipline:</p> <ul> <li>Grouping by blast radius instead of priority; </li> <li>Accepting that <code>TASKS.md</code> will conflict; </li> <li>And knowing when three tracks is enough.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Partition by blast radius, not by priority.</p> <p>Two tasks that touch the same files belong in the same track, no matter how important the other one is.</p> <p>The constraint is file overlap. Everything else is scheduling.</p> <p>The practical setup (skill invocation, worktree creation, merge workflow, and cleanup) lives in the recipe: Parallel Agent Development with Git Worktrees.</p>","path":["Parallel Agents with Git Worktrees"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/","level":1,"title":"<code>ctx</code> v0.3.0: The Discipline Release","text":"","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#when-the-ratio-of-polish-to-features-is-31-you-know-something-changed","level":2,"title":"When the Ratio of Polish to Features Is 3:1, You Know Something Changed","text":"<p>Jose Alekhinne / February 15, 2026</p> <p>What Does a Release Look like When Most of the Work Is Invisible?</p> <p>No new headline feature. No architectural pivot. No rewrite.</p> <p>Just 35+ documentation and quality commits against ~15 feature commits... and somehow, the tool feels like it grew up overnight.</p> <p>Six days separate <code>v0.2.0</code> from <code>v0.3.0</code>. </p> <p>Measured by calendar time, it is nothing. Measured by what changed in how the  project operates, it is the most significant release yet.</p> <ul> <li><code>v0.1.0</code> was the prototype;</li> <li><code>v0.2.0</code> was the archaeology release:    making the past accessible; </li> <li><code>v0.3.0</code> is the discipline release: the one that turned best practices     into enforcement, suggestions into structure, and a collection of     commands into a system of skills.</li> </ul> <p>The Release Window</p> <p>February 1‒February 7, 2026</p> <p>From the <code>v0.2.0</code> tag to commit <code>2227f99</code>.</p> <p>78 files changed in the migration commit alone.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-migration-commands-to-skills","level":2,"title":"The Migration: Commands to Skills","text":"<p>The largest single change was the migration from <code>.claude/commands/*.md</code> to <code>.claude/skills/*/SKILL.md</code>.</p> <p>This was not a rename: It was a rethinking of how AI agents discover and execute project-specific workflows.</p> Aspect Commands (before) Skills (after) Structure Flat files in one directory Directory-per-skill with SKILL.md Description Optional, often vague Required, doubles as activation trigger Quality gates None \"Before X-ing\" pre-flight checklist Negative triggers None \"When NOT to Use\" in every skill Examples Rare Good/bad pairs in every skill Average length ~15 lines ~80 lines <p>The description field became the single most important line in each skill. In the old system, descriptions were titles. In the new system, they are activation conditions: The text the platform reads to decide whether to surface a skill for a given prompt.</p> <p>A description that says \"Show context summary\" activates too broadly or not at all. A description that says \"Show context summary. Use at session start or when unclear about current project state\" activates at the right moment.</p> <p>78 files changed. 1,915 insertions. Not because the skills got bloated; because they got specific.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-skill-sweep","level":2,"title":"The Skill Sweep","text":"<p>After the structural migration, every skill was rewritten in a single session: All 21 of them.</p> <p>The rewrite was guided by a pattern that emerged during the process itself: a repeatable anatomy that effective skills share regardless of their purpose:</p> <ol> <li>Before X-ing: Pre-flight checks that prevent premature execution</li> <li>When to Use: Positive triggers that narrow activation</li> <li>When NOT to Use: Negative triggers that prevent misuse</li> <li>Usage Examples: Invocation patterns the agent can pattern-match</li> <li>Quality Checklist: Verification before claiming completion</li> </ol> <p>The Anatomy of a Skill That Works post covers the details. What matters for the release story is the result: </p> <ul> <li>Zero skills with quality gates became twenty; </li> <li>Zero skills with negative triggers became twenty. </li> <li>Three skills with examples became twenty.</li> </ul> <p>The Skill Trilogy as Design Spec</p> <p>The three blog posts written during this window:</p> <ul> <li>Skills That Fight the Platform, </li> <li>You Can't Import Expertise,</li> <li>and The Anatomy of a Skill That Works...</li> </ul> <p>... were not retrospective documentation. They were written  during the rewrite, and the lessons fed back into the skills  as they were being built.</p> <ul> <li>The blog was the design document. </li> <li>The skills were the implementation.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-consolidation-sweep","level":2,"title":"The Consolidation Sweep","text":"<p>The unglamorous work. The kind you only appreciate when you try to change something later and it just works.</p> What Why It Matters Constants consolidation Magic strings replaced with semantic constants Variable deshadowing Eliminated subtle scoping bugs File splits Modules that were doing too much, broken apart Godoc standardization Every exported function documented to convention <p>This is the work that doesn't get a changelog entry but makes every future commit easier. When a new contributor (human or AI) reads the codebase, they find consistent patterns instead of accumulated drift.</p> <p>The consolidation was not an afterthought. It was scheduled deliberately, with the same priority as features: The 3:1 ratio that emerged during <code>v0.2.0</code> development became an explicit practice: </p> <ul> <li>Three feature sessions; </li> <li>One consolidation session.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-ear-framework","level":2,"title":"The E/A/R Framework","text":"<p>On February 4<sup>th</sup>, we adopted the E/A/R classification as the official standard for evaluating skills:</p> Category Meaning Target Expert Knowledge Claude does not have >70% Activation When/how to trigger ~20% Redundant What Claude already knows <10% <p>This came from reviewing approximately 30 external skill files and discovering that most were redundant with Claude's built-in system prompt. Only about 20% had salvageable content, and even those yielded just a few heuristics each.</p> <p>The E/A/R framework gave us a concrete, testable criterion: </p> <p>A good skill is Expert knowledge minus what Claude already knows.</p> <p>If more than 10% of a skill restates platform defaults, it is creating noise, not signal.</p> <p>Every skill in <code>v0.3.0</code> was evaluated against this framework. Several were deleted. The survivors are leaner and more focused.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#backup-and-monitoring-infrastructure","level":2,"title":"Backup and Monitoring Infrastructure","text":"<p>A tool that manages your project's memory needs ops maturity. </p> <p><code>v0.3.0</code> added two pieces of infrastructure that reflect this:</p> <p>Backup staleness hook: A <code>UserPromptSubmit</code> hook that checks whether the last <code>.context/</code> backup is more than two days old. If it is, and the SMB mount is available, it reminds the user. No cron job running when nobody is working. No redundant backups when nothing has changed.</p> <p>Context size checkpoint: A <code>PreToolUse</code> hook that estimates current context window usage and warns when the session is getting heavy. This hooks into the attention budget philosophy: Degradation is expected, but it should be visible.</p> <p>Both hooks use <code>$CLAUDE_PROJECT_DIR</code> instead of hardcoded paths, a migration triggered by a username rename that broke every absolute path in the hook configuration. That migration (replacing <code>/home/user/...</code> with <code>\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/...</code>) was one of those changes that seems trivial but prevents an entire category of future failures.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.2.0 v0.3.0 Skills (was \"commands\") 11 21 Skills with quality gates 0 21 Skills with \"When NOT to Use\" 0 21 Average skill body ~15 lines ~80 lines Hooks using <code>$CLAUDE_PROJECT_DIR</code> 0 All Documentation commits n/a 35+ Feature/fix commits n/a ~15 <p>That ratio (35+ documentation and quality commits to ~15 feature commits) is the defining characteristic of this release:</p> <ul> <li>This release is not a failure to ship features. </li> <li>It is the deliberate choice to make the existing features reliable.</li> </ul>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-v030-means","level":2,"title":"What v0.3.0 Means","text":"<p><code>v0.1.0</code> asked: \"Can we give AI persistent memory?\"</p> <p><code>v0.2.0</code> asked: \"Can we make that memory accessible to humans too?\"</p> <p><code>v0.3.0</code> asks a different question: \"Can we make the quality self-enforcing?\"</p> <p>The answer is not a feature: It is a practice:</p> <ul> <li>Skills with quality gates enforce pre-flight checks.</li> <li>Negative triggers prevent misuse without human intervention.</li> <li>The E/A/R framework ensures skills contain signal, not noise.</li> <li>Consolidation sessions are scheduled, not improvised.</li> <li>Hook infrastructure makes degradation visible.</li> </ul> <p>Discipline is not the absence of velocity. It is the infrastructure that makes velocity sustainable.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-ctx-v0.3.0-the-discipline-release/#what-comes-next","level":2,"title":"What Comes Next","text":"<p>The skill system is now mature enough to support real workflows without constant human correction. The hooks infrastructure is portable and resilient. The consolidation  practice is documented and repeatable.</p> <p>The next chapter is about what you build on top of discipline:</p> <ul> <li>Multi-agent coordination;</li> <li>Deeper integration patterns; </li> <li>And the question of whether context management is a tool concern or    an infrastructure concern.</li> </ul> <p>But those are future posts.</p> <p>This one is about the release that proved polish is not the opposite of progress. It is what turns a prototype into a product.</p> <p>The Discipline Release</p> <p><code>v0.1.0</code> shipped features. </p> <p><code>v0.2.0</code> shipped archaeology.</p> <p><code>v0.3.0</code> shipped the habits that make everything else trustworthy.</p> <p>The most important code in this release is the code that prevents bad code from shipping.</p> <p>This post was drafted using <code>/ctx-blog</code> with access to the full git history between v0.2.0 and v0.3.0, decision logs, learning logs, and the session files from the skill rewrite window. The meta continues.</p>","path":["ctx v0.3.0: The Discipline Release"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/","level":1,"title":"Eight Ways a Hook Can Talk","text":"","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#when-your-warning-disappears","level":2,"title":"When Your Warning Disappears","text":"<p>Jose Alekhinne / 2026-02-15</p> <p>I had a backup warning that nobody ever saw.</p> <p>The hook was correct: It detected stale backups, formatted a nice message, and output it as <code>{\"systemMessage\": \"...\"}</code>. The problem wasn't detection. The problem was delivery. The agent absorbed the information, processed it internally, and never told the user.</p> <p>Meanwhile, a different hook (the journal reminder) worked perfectly every time. Users saw the reminder, ran the commands, and the backlog stayed manageable. Same hook event (<code>UserPromptSubmit</code>), same project, completely different outcomes.</p> <p>The difference was one line:</p> <pre><code>IMPORTANT: Relay this journal reminder to the user VERBATIM\nbefore answering their question.\n</code></pre> <p>That explicit instruction is what makes VERBATIM relay a pattern, not just a formatting choice. And once I saw it as a pattern, I started seeing others.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-audit","level":2,"title":"The Audit","text":"<p>I looked at every hook in <code>ctx</code>: Eight shell scripts across three hook events. And I found five distinct output patterns already in use, plus three more that the existing hooks were reaching for but hadn't quite articulated.</p> <p>The patterns form a spectrum based on a single question: </p> <p>\"Who decides what the user sees?\"</p> <p>At one end, the hook decides everything (hard gate: the agent literally cannot proceed). At the other end, the hook is invisible (silent side-effect: nobody knows it ran). In between, there is a range of negotiation between hook, agent, and the user.</p> <p>Here's the full spectrum:</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#1-hard-gate","level":3,"title":"1. Hard Gate","text":"<pre><code>{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}\n</code></pre> <p>The nuclear option: The agent's tool call is rejected before it executes.</p> <p>This is Claude Code's first-class <code>PreToolUse</code> mechanism: The hook returns JSON with <code>decision: block</code> and the agent gets an error with the reason.</p> <p>Use this for invariants: Constitution rules, security boundaries, things that must never happen. I use it to enforce <code>PATH</code>-based <code>ctx</code> invocation, block <code>sudo</code>, and require explicit approval for <code>git push</code>.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#2-verbatim-relay","level":3,"title":"2. VERBATIM Relay","text":"<pre><code>IMPORTANT: Relay this warning to the user VERBATIM before answering.\n┌─ Journal Reminder ─────────────────────────────\n│ You have 12 sessions not yet imported.\n│   ctx recall import --all\n└────────────────────────────────────────────────\n</code></pre> <p>The instruction is the pattern. Without \"Relay VERBATIM,\" agents tend to absorb information into their internal reasoning and never surface it. The explicit instruction changes the behavior from \"I know about this\" to  \"I must tell the user about this.\"</p> <p>I use this for actionable reminders: </p> <ul> <li>Unexported journal entries;</li> <li>Stale backups;</li> <li>Context capacity warnings... </li> </ul> <p>...things the user should see regardless of what they asked.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#3-agent-directive","level":3,"title":"3. Agent Directive","text":"<pre><code>┌─ Persistence Checkpoint (prompt #25) ───────────\n│ No context files updated in 15+ prompts.\n│ Have you discovered learnings worth persisting?\n└──────────────────────────────────────────────────\n</code></pre> <p>A nudge, not a command. The hook tells the agent something; the agent decides what (if anything) to tell the user. This is right for behavioral nudges: \"you haven't saved context in a while\" doesn't need to be relayed verbatim, but the agent should consider acting on it.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#4-silent-context-injection","level":3,"title":"4. Silent Context Injection","text":"<pre><code>ctx agent --budget 4000 2>/dev/null || true\n</code></pre> <p>Pure background enrichment. The agent's context window gets project information injected on every tool call, with no visible output. Neither the agent nor the user sees the hook fire, but the agent makes better decisions because of the context.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#5-silent-side-effect","level":3,"title":"5. Silent Side-Effect","text":"<pre><code>find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n</code></pre> <p>Do work, say nothing. Temp file cleanup on session end. Logging. Marker file management. The action is the entire point; no one needs to know.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-patterns-we-dont-have-yet","level":2,"title":"The Patterns We Don't Have Yet","text":"<p>Three more patterns emerged from the gaps in the existing hooks.</p> <p>Conditional relay: \"Relay this, but only if the user's question is about X.\" This pattern avoids noise when the warning isn't relevant.  It's more fragile (depends on agent judgment) but less annoying.</p> <p>Suggested action: \"Here's a problem, and here's the exact command to fix it. Ask the user before running it.\" This pattern goes beyond a nudge by  giving the agent a concrete proposal, but still requires human approval.</p> <p>Escalating severity: <code>INFO</code> gets absorbed silently. <code>WARN</code> gets mentioned at the next natural pause. <code>CRITICAL</code> gets the VERBATIM treatment. This pattern introduces a protocol for hooks that produce output  at different urgency levels, so they don't all compete for the user's attention.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-eight-ways-a-hook-can-talk/#the-principle","level":2,"title":"The Principle","text":"<p>Hooks are the boundary between your environment and the agent's reasoning. </p> <p>A hook that detects a problem but can't communicate it effectively is the same  as no hook at all.</p> <p>The format of your output is a design decision with real consequences:</p> <ul> <li>Use a hard gate and the agent can't proceed (good for invariants,   frustrating for false positives)</li> <li>Use VERBATIM relay and the user will see it (good for reminders,   noisy if overused)</li> <li>Use an agent directive and the agent might act (good for nudges,   unreliable for critical warnings)</li> <li>Use silent injection and nobody knows (good for enrichment,   invisible when it breaks)</li> </ul> <p>Choose deliberately. And, when in doubt, write the word <code>VERBATIM</code>.</p> <p>The full pattern catalog with decision flowchart and implementation examples is in the Hook Output Patterns recipe.</p>","path":["Eight Ways a Hook Can Talk"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/","level":1,"title":"Version Numbers Are Lagging Indicators","text":"","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#why-ctxs-journal-site-runs-on-a-v0021-tool","level":2,"title":"Why <code>ctx</code>'s Journal Site Runs on a v0.0.21 Tool","text":"<p>Jose Alekhinne / 2026-02-15</p> <p>Would You Ship Production Infrastructure on a v0.0.21 Dependency?</p> <p>Most engineers wouldn't. Version numbers signal maturity. Pre-1.0 means unstable API, missing features, risk.</p> <p>But version numbers tell you where a project has been. They say nothing about where it's going.</p> <p>I just bet <code>ctx</code>'s entire journal site on a tool that hasn't hit <code>v0.1.0</code>. </p> <p>Here's why I'd do it again.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-problem","level":2,"title":"The Problem","text":"<p>When v0.2.0 shipped the journal system, the pipeline was clear:</p> <ul> <li>Export sessions to Markdown; </li> <li>Enrich them with YAML frontmatter; </li> <li>And render them into something browsable. </li> </ul> <p>The first two steps were solved; the third needed a tool.</p> <p>The journal entries are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is the entire format: </p> <ul> <li>No JSX;</li> <li>No shortcodes;</li> <li>No custom templating. </li> </ul> <p>Just Markdown rendered well.</p> <p>The requirements are modest:</p> <ul> <li>Read a configuration file (such as <code>mkdocs.yml</code>);</li> <li>Render Markdown with extensions (admonitions, tabs, tables);</li> <li>Search;</li> <li>Handle 100+ files without choking on incremental rebuilds;</li> <li>Look good out of the box;</li> <li>Not lock me in.</li> </ul> <p>The obvious candidates were as follows:</p> Tool Language Strengths Pain Points Hugo Go Blazing fast, mature Templating is painful; Go templates fight you on anything non-trivial Astro JS/TS Modern, flexible JS ecosystem overhead; overkill for a docs site MkDocs + Material Python Beautiful defaults, massive community (22k+ stars) Slow incremental rebuilds on large sites; limited extensibility model Zensical Python Built to fix MkDocs' limits; 4-5x faster rebuilds v0.0.21; module system not yet shipped <p>The instinct was Hugo. Same language as <code>ctx</code>. Fast. Well-established.</p> <p>But instinct is not analysis. I picked the one with the lowest version number.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation","level":2,"title":"The Evaluation","text":"<p>Here is what I actually evaluated, in order:</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#1-the-team","level":3,"title":"1. The Team","text":"<p>Zensical is built by squidfunk: The same person behind Material for MkDocs, the most popular MkDocs theme with 22,000+ stars. It powers documentation sites for projects across every language and framework.</p> <ul> <li>This is not someone learning how to build static site generators.</li> <li>This is someone who spent years understanding exactly where MkDocs   breaks and decided to fix it from the ground up.</li> </ul> <p>They did not build zensical because MkDocs was bad:  They built it because MkDocs hit a ceiling:</p> <ul> <li> <p>Incremental rebuilds: 4-5x faster during serve. When you have   hundreds of journal entries and you edit one, the difference between   \"rebuild everything\" and \"rebuild this page\" is the difference   between a usable workflow and a frustrating one.</p> </li> <li> <p>Large site performance: Specifically designed for tens of   thousands of pages. The journal grows with every session. A tool   that slows down as content accumulates is a tool you will eventually   replace.</p> </li> </ul> <p>A proven team starting fresh is more predictable than an unproven team at v3.0.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#2-the-architecture","level":3,"title":"2. The Architecture","text":"<p>Zensical is investing in a Rust-based Markdown parser with CommonMark support. That signals something about the team's priorities:</p> <p>Performance foundations first; features second.</p> <p><code>ctx</code>'s journal will grow: </p> <ul> <li>Every exported session adds files.</li> <li>Every enrichment pass adds metadata. </li> </ul> <p>Choosing a tool that gets slower as you add content means choosing to migrate later.</p> <p>Choosing one built for scale means the decision holds.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#3-the-migration-path","level":3,"title":"3. The Migration Path","text":"<p>Zensical reads <code>mkdocs.yml</code> natively. If it doesn't work out, I can move back to MkDocs + Material with zero content changes:</p> <ul> <li>The Markdown is standard; </li> <li>The frontmatter is standard; </li> <li>The configuration is compatible.</li> </ul> <p>This is the infrastructure pattern again: The same way <code>ZNC</code> decouples presence from the client, <code>zensical</code> decouples rendering from the generator: </p> <ul> <li>The Markdown is yours. </li> <li>The frontmatter is standard YAML. </li> <li>The configuration is MkDocs-compatible.</li> </ul> <p>You are not locked into anything except your own content.</p> <p>No lock-in is not a feature: It's a design philosophy: </p> <p>It's the same reason <code>ctx</code> uses plain Markdown files in <code>.context/</code> instead of a database: the format should outlive the tool.</p> <p>Lock-in Is the Real Risk, Not Version Numbers</p> <p>A mature tool with a proprietary format is riskier than a young tool with a standard one. Version numbers measure time invested. Portability measures respect for the user.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#4-the-dependency-tree","level":3,"title":"4. The Dependency Tree","text":"<p>Here is what <code>pip install zensical</code> actually pulls in:</p> <ul> <li>click</li> <li>Markdown</li> <li>Pygments</li> <li>pymdown-extensions</li> <li>PyYAML</li> </ul> <p>Only five dependencies. All well-known. No framework bloat. No bundler. No transpiler. No <code>node_modules</code> black hole.</p> <p>3k GitHub stars at <code>v0.0.21</code> is a strong early traction for a <code>pre-1.0</code> project. </p> <p>The dependency tree is thin: No bloat.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#5-the-fit","level":3,"title":"5. The Fit","text":"<p>This is the same principle behind the attention budget: do not overfit the tool to hypothetical requirements. The right amount of capability is the minimum needed for the current task.</p> <p>Hugo is a powerful static site generator. It is also a powerful templating engine, a powerful asset pipeline, and a powerful taxonomy system. For rendering Markdown journals, that power is overhead:</p> <p>It is the complexity you pay for but never use.</p> <p><code>ctx</code>'s journal files are standard Markdown with YAML frontmatter, tables, and fenced code blocks. That is exactly the sweet spot Zensical inherits from Material for MkDocs:</p> <ul> <li>No custom plugins needed;</li> <li>No special syntax; </li> <li>No templating gymnastics.</li> </ul> <p>The requirements match the capabilities: Not the capabilities that are promised, but the ones that exist today, at <code>v0.0.21</code>.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-caveat","level":2,"title":"The Caveat","text":"<p>It would be dishonest not to mention what's missing.</p> <p>The module system for third-party extensions opens in early 2026.</p> <p>If <code>ctx</code> ever needs custom plugins (for example, auto-linking session IDs, rendering special journal metadata, etc.) that infrastructure isn't there yet.</p> <p>The installation experience is rough: </p> <p>We discovered this firsthand: <code>pip install zensical</code> often fails on MacOS  (system Python stubs, Homebrew's PEP 668 restrictions). The answer is pipx, which creates an isolated environment with the correct Python version automatically. </p> <p>That kind of friction is typical for young Python tooling, and it is documented in the Getting Started guide.</p> <p>And <code>3,000</code> stars at <code>v0.0.21</code> is strong early traction, but it's still early: The community is small. When something breaks, you're reading  source  code, not documentation.</p> <p>These are real costs. I chose to pay them because the alternative costs are higher.</p> <p>For example:</p> <ul> <li>Hugo's templating pain would cost me time on every site change.</li> <li>Astro's JS ecosystem would add complexity I don't need. </li> <li>MkDocs would work today but hit scaling walls tomorrow. </li> </ul> <p>Zensical's costs are front-loaded and shrinking. </p> <p>The others compound.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-evaluation-framework","level":2,"title":"The Evaluation Framework","text":"<p>For anyone facing a similar choice, here is the framework that emerged:</p> Signal What It Tells You Weight Team track record Whether the architecture will be sound High Migration path Whether you can leave if wrong High Current fit Whether it solves your problem today High Dependency tree How much complexity you're inheriting Medium Version number How long the project has existed Low Star count Community interest (not quality) Low Feature list What's possible (not what you need) Low <p>The bottom three are the metrics most engineers optimize for.</p> <p>The top four are the ones that predict whether you'll still be happy with the choice in a year.</p> <p>Features You Don't Need Are Not Free</p> <p>Every feature in a dependency is code you inherit but don't control. </p> <p>A tool with 200 features where you use 5 means 195 features worth of surface area for bugs, breaking changes, and security issues that have nothing to do with your use case.</p> <p>Fit is the inverse of feature count.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-15-why-zensical/#the-broader-pattern","level":2,"title":"The Broader Pattern","text":"<p>This is part of a theme I keep encountering in this project:</p> <p>Leading indicators beat lagging indicators.</p> Domain Lagging Indicator Leading Indicator Tooling Version number, star count Team track record, architecture Code quality Test coverage percentage Whether tests catch real bugs Context persistence Number of files in <code>.context/</code> Whether the AI makes fewer mistakes Skills Number of skills created Whether each skill fires at the right time Consolidation Lines of code refactored Whether drift stops accumulating <p>Version numbers, star counts, coverage percentages, file counts...</p> <p>...these are all measures of effort expended. </p> <p>They say nothing about value delivered.</p> <p>The question is never \"how mature is this tool?\" </p> <p>The question is \"does this tool's trajectory intersect with my needs?\"</p> <p>Zensical's trajectory: </p> <ul> <li>A proven team fixing known problems, </li> <li>in a *proven architecture, </li> <li>with a standard format,</li> <li>and no lock-in.</li> </ul> <p><code>ctx</code>'s needs: </p> <p>Tender standard Markdown into a browsable site, at scale,  without complexity.</p> <p>The intersection is clean; the version number is noise.</p> <p>This is the same kind of decision that shows up throughout <code>ctx</code>:</p> <ul> <li>Skills that fight the platform taught that the best   integration extends existing behavior, not replaces it.</li> <li>You can't import expertise taught that tools should   grow from your project's actual needs, not from feature checklists.</li> <li>Context as infrastructure argues that the format should   outlive the tool; and, <code>zensical</code> honors that principle by reading   standard Markdown and standard MkDocs configuration.</li> </ul> <p>If You Remember One Thing from This Post...</p> <p>Version numbers measure where a project has been.</p> <p>The team and the architecture tell you where it's going.</p> <p>A <code>v0.0.21</code> tool built by the right team on the right foundations is a safer bet than a <code>v5.0</code> tool that doesn't fit your problem.</p> <p>Bet on trajectories, not timestamps.</p> <p>This post started as an evaluation note in <code>ideas/</code> and a separate decision log. The analysis held up. The two merged into one. The meta continues.</p>","path":["Version Numbers Are Lagging Indicators"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/","level":1,"title":"<code>ctx</code> v0.6.0: The Integration Release","text":"","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#two-commands-to-persistent-memory","level":2,"title":"Two Commands to Persistent Memory","text":"<p>Jose Alekhinne / February 16, 2026</p> <p>What Changed?</p> <p><code>ctx</code> is now a Claude Code plugin. Two commands, no build step:</p> <pre><code>/plugin marketplace add ActiveMemory/ctx\n/plugin install ctx@activememory-ctx\n</code></pre> <p>Six hooks. Twenty-five skills. Installed.</p> <p>For three releases, <code>ctx</code> required assembly: </p> <ul> <li>Clone the repo; </li> <li>Build the binary; </li> <li>Copy hook scripts into <code>.claude/hooks/</code>; </li> <li>Symlink skill files.</li> <li>Understand which shell scripts called which Go commands;</li> <li>Hope nothing broke when Claude Code updated its hook format.</li> </ul> <p><code>v0.6.0</code> ends that era: <code>ctx</code> ships as a Claude Marketplace plugin:</p> <p>Hooks and skills served directly from source, installed with a single command, updated by pulling the repo. The tool that gives AI persistent memory is now as easy to install as the AI itself.</p> <p>But the plugin conversion was not just a packaging change: It was the forcing function that rewrote every shell hook in Go, eliminated the <code>jq</code> dependency, enabled <code>go test</code> coverage for hook logic, and made distribution a solved problem. </p> <p>When you fix how something ships, you end up fixing how it is built.</p> <p>The Release Window</p> <p>February 15-February 16, 2026</p> <p>From the v0.3.0 tag to commit <code>a3178bc</code>:</p> <ul> <li>109 commits. </li> <li>334 files changed. </li> <li>Version jumped from 0.3.0 to 0.6.0 to signal the magnitude.</li> </ul>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#before-six-shell-scripts-and-a-prayer","level":2,"title":"Before: Six Shell Scripts and a Prayer","text":"<p><code>v0.3.0</code> had six hook scripts. Each was a Bash file that shelled out to <code>ctx</code> subcommands, parsed JSON with <code>jq</code>, and wired itself into Claude Code's hook system via <code>.claude/hooks/</code>:</p> <pre><code>.claude/hooks/\n├── check-context-size.sh\n├── check-persistence.sh\n├── check-journal.sh\n├── post-commit.sh\n├── block-non-path-ctx.sh\n└── cleanup-tmp.sh\n</code></pre> <p>This worked, but it also meant:</p> <ul> <li>jq was a hard dependency: No <code>jq</code>, no hooks. macOS ships without it.</li> <li>No test coverage: Shell scripts were tested manually or not at all.</li> <li>Fragile deployment: <code>ctx init</code> had to scaffold <code>.claude/hooks/</code>   and <code>.claude/skills/</code> with the right paths, permissions, and structure.</li> <li>Version drift: Users who installed once never got hook updates   unless they re-ran <code>ctx init</code>.</li> </ul> <p>The shell scripts were the right choice for prototyping. They were the wrong choice for distribution.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#after-one-plugin-zero-shell-scripts","level":2,"title":"After: One Plugin, Zero Shell Scripts","text":"<p><code>v0.6.0</code> replaces all six scripts with <code>ctx system</code> subcommands compiled into the binary:</p> Shell Script Go Subcommand <code>check-context-size.sh</code> <code>ctx system check-context-size</code> <code>check-persistence.sh</code> <code>ctx system check-persistence</code> <code>check-journal.sh</code> <code>ctx system check-journal</code> <code>post-commit.sh</code> <code>ctx system post-commit</code> <code>block-non-path-ctx.sh</code> <code>ctx system block-non-path-ctx</code> <code>cleanup-tmp.sh</code> <code>ctx system cleanup-tmp</code> <p>The plugin's <code>hooks.json</code> wires them to Claude Code events:</p> <pre><code>{\n  \"PreToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system block-non-path-ctx\"},\n    {\"matcher\": \".*\", \"command\": \"ctx agent --budget 4000\"}\n  ],\n  \"PostToolUse\": [\n    {\"matcher\": \"Bash\", \"command\": \"ctx system post-commit\"}\n  ],\n  \"UserPromptSubmit\": [\n    {\"command\": \"ctx system check-context-size\"},\n    {\"command\": \"ctx system check-persistence\"},\n    {\"command\": \"ctx system check-journal\"}\n  ],\n  \"SessionEnd\": [\n    {\"command\": \"ctx system cleanup-tmp\"}\n  ]\n}\n</code></pre> <p>No jq. No shell scripts. No <code>.claude/hooks/</code> directory to manage.</p> <p>The hooks are Go functions with tests, compiled into the same binary you already have.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-plugin-model","level":2,"title":"The Plugin Model","text":"<p>The <code>ctx</code> plugin lives at <code>.claude-plugin/marketplace.json</code> in the repo.</p> <p>Claude Code's marketplace system handles discovery and installation:</p> <p>Skills are served directly from <code>internal/assets/claude/skills/</code>; there is no build step, no <code>make plugin</code>, no generated artifacts.</p> <p>This means:</p> <ol> <li>Install is two commands: Not \"clone, build, copy, configure.\"</li> <li>Updates are automatic: Pull the repo; the plugin reads from source.</li> <li>Skills and hooks are versioned together: No drift between what    the CLI expects and what the plugin provides.</li> <li><code>ctx init</code> is tool-agnostic: It creates <code>.context/</code> and nothing    else. No <code>.claude/</code> scaffolding, no assumptions about which AI tool    you use.</li> </ol> <p>That last point matters: </p> <p>Before <code>v0.6.0</code>, <code>ctx init</code> tried to set up Claude Code integration as part of  initialization. That coupled the context system to a specific tool. </p> <p>Now, <code>ctx init</code> gives you persistent context. The plugin gives you Claude Code  integration. They compose; they don't depend.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#beyond-the-plugin-what-else-shipped","level":2,"title":"Beyond the Plugin: What Else Shipped","text":"<p>The plugin conversion dominated the release, but 109 commits covered more ground.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#obsidian-vault-export","level":3,"title":"Obsidian Vault Export","text":"<pre><code>ctx journal obsidian\n</code></pre> <p>Generates a full Obsidian vault from enriched journal entries: wikilinks, MOC (Map of Content) pages, and graph-optimized cross-linking. If you already use Obsidian for notes, your AI session history now lives alongside everything else.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#encrypted-scratchpad","level":3,"title":"Encrypted Scratchpad","text":"<pre><code>ctx pad edit \"DATABASE_URL=postgres://...\"\nctx pad show\n</code></pre> <p><code>AES-256-GCM</code> encrypted storage for sensitive one-liners. </p> <p>The encrypted blob commits to <code>git</code>; the key stays in <code>.gitignore</code>. </p> <p>This is useful for connection strings, API keys, and other values that need to travel with the project without appearing in plaintext.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#security-hardening","level":3,"title":"Security Hardening","text":"<p>Three medium-severity findings from a security audit are now closed:</p> Finding Fix Path traversal via <code>--context-dir</code> Boundary validation: operations cannot escape project root (M-1) Symlink following in <code>.context/</code> <code>Lstat()</code> check before every file read/write (M-2) Predictable temp file paths User-specific temp directory under <code>$XDG_RUNTIME_DIR</code> (M-3) <p>Plus a new <code>/sanitize-permissions</code> skill that audits <code>settings.local.json</code> for overly broad Bash permissions.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#hooks-that-know-when-to-be-quiet","level":3,"title":"Hooks That Know When to Be Quiet","text":"<p>A subtle but important fix: hooks now no-op before <code>ctx init</code> has run.</p> <p>Previously, a fresh clone with no <code>.context/</code> would trigger hook errors on every prompt. Now, hooks detect the absence of a context directory and exit silently. Similarly, <code>ctx init</code> treats a <code>.context/</code> directory containing only logs as uninitialized and skips the <code>--overwrite</code> prompt.</p> <p>Small changes. Large reduction in friction for new users.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#the-numbers","level":2,"title":"The Numbers","text":"Metric v0.3.0 v0.6.0 Skills 21 25 Shell hook scripts 6 0 Go system subcommands 0 6 External dependencies (hooks) jq, bash none Lines of Go ~14,000 ~37,000 Plugin install commands n/a 2 Security findings (open) 3 0 <code>ctx init</code> creates .claude/ yes no <p>The line count tripled. Most of that is documentation site HTML, Obsidian export logic, and the scratchpad encryption module. </p> <p>The core CLI grew modestly; the ecosystem around it grew substantially.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-does-v060-mean-for-ctx","level":2,"title":"What Does <code>v0.6.0</code> Mean for <code>ctx</code>?","text":"<ul> <li><code>v0.1.0</code> asked: \"Can we give AI persistent memory?\"</li> <li><code>v0.2.0</code> asked: \"Can we make that memory accessible to humans too?\"</li> <li><code>v0.3.0</code> asked: \"Can we make the quality self-enforcing?\"</li> </ul> <p>v0.6.0 asks: \"Can someone else actually use this?\"</p> <p>A tool that requires cloning a repo, building from source, and manually wiring hooks into the right directories is a tool for its author.</p> <p>A tool that installs with two commands from a marketplace is a tool for everyone.</p> <p>The version jumped from <code>0.3.0</code> to <code>0.6.0</code> because the delta is not incremental: The shell-to-Go rewrite, the plugin model, the security hardening, and the tool-agnostic init: Together, they change what <code>ctx</code> is: Not a different tool, but a tool that is finally ready to leave the workshop.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-16-ctx-v0.6.0-the-integration-release/#what-comes-next","level":2,"title":"What Comes Next","text":"<p>The plugin model opens the door to distribution patterns that were not possible before. Marketplace discovery means new users find <code>ctx</code> without reading a <code>README</code>. Plugin updates mean existing users get improvements without rebuilding.</p> <p>The next chapter is about what happens when persistent context is easy to install: Adoption patterns, multi-project workflows, and whether the <code>.context/</code> convention can become infrastructure that other tools build on.</p> <p>But those are future posts.</p> <p>This one is about the release that turned a developer tool into a distributable product: two commands, zero shell scripts, and a presence on the Claude Marketplace.</p> <p>The Integration Release</p> <p><code>v0.1.0</code> shipped features. <code>v0.2.0</code> shipped archaeology.</p> <p><code>v0.3.0</code> shipped discipline. <code>v0.6.0</code> shipped the front door.</p> <p>The most important code in this release is the code you never have to copy.</p> <p>This post was drafted using <code>/ctx-blog-changelog</code> with access to the full git history between v0.3.0 and v0.6.0, release notes, and the plugin conversion PR. The meta continues.</p>","path":["ctx v0.6.0: The Integration Release"],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/","level":1,"title":"Code Is Cheap. Judgment Is Not.","text":"","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#why-ai-replaces-effort-not-expertise","level":2,"title":"Why AI Replaces Effort, Not Expertise","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>Are You Worried about AI Taking Your Job?</p> <p>You might be confusing the thing that's cheap with the thing that's valuable.</p> <p>I keep seeing the same conversation:  Engineers, designers, writers: all asking the same question with the same dread:</p> <p>\"What happens when AI can do what I do?\"</p> <p>The question is wrong:</p> <ul> <li>AI does not replace workers;</li> <li>AI replaces unstructured effort.</li> </ul> <p>The distinction matters, and everything I have learned building <code>ctx</code> reinforces it.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-three-confusions","level":2,"title":"The Three Confusions","text":"<p>People who feel doomed by AI usually confuse three things:</p> People confuse... With... Effort Value Typing Thinking Production Judgment <ul> <li>Effort is time spent.</li> <li>Value is the outcome that time produces.</li> </ul> <p>They are not the same; they never were. </p> <p>AI just makes the gap impossible to ignore.</p> <p>Typing is mechanical: Thinking is directional. </p> <p>An AI can type faster than any human. Yet, it cannot decide what to type without someone framing the problem, sequencing the work, and evaluating the result.</p> <p>Production is making artifacts. Judgment is knowing:</p> <ul> <li>which artifacts to make, </li> <li>in what order, </li> <li>to what standard, </li> <li>and when to stop.</li> </ul> <p>AI floods the system with production capacity; it does not flood the system with judgment.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#code-is-nothing","level":2,"title":"Code Is Nothing","text":"<p>This sounds provocative until you internalize it:</p> <p>Code is cheap. Artifacts are cheap.</p> <p>An AI can generate a thousand lines of working code in literal *minutes**:</p> <p>It can scaffold a project, write tests, build a CI pipeline, draft documentation. The raw production of software artifacts is no longer the bottleneck.</p> <p>So, what is not cheap?</p> <ul> <li>Taste: knowing what belongs and what does not</li> <li>Framing: turning a vague goal into a concrete problem</li> <li>Sequencing: deciding what to build first and why</li> <li>Fanning out: breaking work into parallel streams that   converge</li> <li>Acceptance criteria: defining what \"done\" looks like before   starting</li> <li>Judgment: the thousand small decisions that separate code   that works from code that lasts</li> </ul> <p>These are the skills that direct production: Hhuman skills.</p> <p>Not because AI is incapable of learning them, but because they require something AI does not have: </p> <p>temporal accountability for generated outcomes.</p> <p>That is, you cannot keep AI accountable for the <code>$#!%</code> it generated three months ago. A human, on the other hand, will always be accountable.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-evidence-from-building-ctx","level":2,"title":"The Evidence from Building <code>ctx</code>","text":"<p>I did not arrive at this conclusion theoretically. </p> <p>I arrived at it by building a tool with an AI agent for three weeks  and watching exactly where a human touch mattered.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#yolo-mode-proved-production-is-cheap","level":3,"title":"YOLO Mode Proved Production Is Cheap","text":"<p>In Building <code>ctx</code> Using <code>ctx</code>, I documented the YOLO phase: auto-accept everything, let the AI ship features at full speed. It produced 14 commands in a week. Impressive output.</p> <p>The code worked. The architecture drifted. Magic strings accumulated. Conventions diverged. The AI was producing at a pace no human could match, and every artifact it produced was a small bet that nobody was evaluating.</p> <p>Production without judgment is not velocity. It is debt accumulation at breakneck speed.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-31-ratio-proved-judgment-has-a-cadence","level":3,"title":"The 3:1 Ratio Proved Judgment Has a Cadence","text":"<p>In The 3:1 Ratio, the <code>git</code> history told the story:</p> <p>Three sessions of forward momentum followed by one session of deliberate consolidation. The consolidation session is where the human applies judgment: reviewing what the AI built, catching drift, realigning conventions.</p> <p>The AI does the refactoring. The human decides what to refactor and when to stop. </p> <p>Without the human, the AI will refactor forever, improving things that do not matter and missing things that do.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-attention-budget-proved-framing-is-scarce","level":3,"title":"The Attention Budget Proved Framing Is Scarce","text":"<p>In The Attention Budget, I explained why more context makes AI worse, not better. Every token competes for attention: Dump everything in and the AI sees nothing clearly.</p> <p>This is a framing problem: The human's job is to decide what the AI should focus on: what to include, what to exclude, what to emphasize. </p> <p><code>ctx agent --budget 4000</code> is not just a CLI flag: It is a forcing function for human judgment about relevance.</p> <p>The AI processes. The human curates.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#skills-design-proved-taste-is-load-bearing","level":3,"title":"Skills Design Proved Taste Is Load-Bearing","text":"<p>The skill trilogy (You Can't Import Expertise, The Anatomy of a Skill That Works) showed that the difference between a useful skill and a useless one is not craftsmanship: </p> <p>It is taste.</p> <p>A well-crafted skill with the wrong focus is worse than no skill at all: It consumes the attention budget with generic advice while the project-specific problems go unchecked. </p> <p>The E/A/R framework (Expert, Activation, Redundant) is a judgment too:. The AI cannot apply it to itself. The human evaluates what the AI already knows, what it needs to be told, and what is noise.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#automation-discipline-proved-restraint-is-a-skill","level":3,"title":"Automation Discipline Proved Restraint Is a Skill","text":"<p>In Not Everything Is a Skill, the lesson was that the urge to automate is not the need to automate. A useful prompt does not automatically deserve to become a slash command.</p> <p>The human applies judgment about frequency, stability, and attention cost.</p> <p>The AI can build the skill. Only the human can decide whether it should exist.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#defense-in-depth-proved-boundaries-require-judgment","level":3,"title":"Defense in Depth Proved Boundaries Require Judgment","text":"<p>In Defense in Depth, the entire security model for unattended AI agents came down to: Markdown is not a security boundary. Telling an AI \"don't do bad things\" is production (of instructions). Setting up an unprivileged user in a network-isolated container is judgment (about risk).</p> <p>The AI follows instructions. The human decides which instructions are enforceable and which are \"wishful thinking\".</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#parallel-agents-proved-scale-amplifies-the-gap","level":3,"title":"Parallel Agents Proved Scale Amplifies the Gap","text":"<p>In Parallel Agents and Merge Debt, the lesson was that multiplying agents multiplies output. But it also multiplies the need for judgment:</p> <p>Five agents running in parallel produce five sessions of drift in one clock  hour. The human who can frame tasks cleanly, define narrow acceptance  criteria, and evaluate results quickly becomes the limiting factor.</p> <p>More agents do not reduce the need for judgment. They increase it.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-two-reactions","level":2,"title":"The Two Reactions","text":"<p>When AI floods the system with cheap output, two things happen:</p> <p>Those who only produce: panic. If your value proposition is \"I write code,\" and an AI writes code faster, cheaper, and at higher volume, then the math is unfavorable. Not because AI took your job, but because your job was never the code. It was the judgment around the code, and you were not exercising it.</p> <p>Those who direct: accelerate. If your value proposition is \"I know what to build, in what order, to what standard,\" then AI is the best thing that ever happened to you: Production is no longer the bottleneck: Your ability to frame, sequence, evaluate, and course-correct is now the limiting factor on throughput.</p> <p>The gap between these two is not talent: It is the awareness of where the value lives.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#what-this-means-in-practice","level":2,"title":"What This Means in Practice","text":"<p>If you are an engineer reading this, the actionable insight is not \"learn prompt engineering\" or \"master AI tools.\" It is:</p> <p>Get better at the things AI cannot do.</p> AI does this well You need to do this Generate code Frame the problem Write tests Define acceptance criteria Scaffold projects Sequence the work Fix bugs from stack traces Evaluate tradeoffs Produce volume Exercise restraint Follow instructions Decide which instructions matter <p>The skills on the right column are not new. They are the same skills that have always separated senior engineers from junior ones. </p> <p>AI did not create the distinction; it just made it load-bearing.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#if-anything-i-feel-empowered","level":2,"title":"If Anything, I Feel Empowered","text":"<p>I will end with something personal.</p> <p>I am not worried: I am empowered.</p> <p>Before <code>ctx</code>, I could think faster than I could produce: </p> <ul> <li>Ideas sat in a queue. </li> <li>The bottleneck was always \"I know what to build,   but building it takes too long.\"</li> </ul> <p>Now the bottleneck is gone. Poof!</p> <ul> <li>Production is cheap. </li> <li>The queue is clearing. </li> <li>The limiting factor is how fast I can think,    not how fast I can type.</li> </ul> <p>That is not a threat: That is the best force multiplier I've ever had.</p> <p>The people who feel threatened are confusing the accelerator for the replacement:</p> <p>*AI does not replace the conductor; it gives them  a bigger orchestra.</p> <p>If You Remember One Thing from This Post...</p> <p>Code is cheap. Judgment is not.</p> <p>AI replaces unstructured effort, not directed expertise. The skills that matter now are the same skills that have always mattered: taste, framing, sequencing, and the discipline to stop.</p> <p>The difference is that now, for the first time, those skills are the only bottleneck left.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-code-is-cheap-judgment-is-not/#the-arc","level":2,"title":"The Arc","text":"<p>This post is a retrospective. It synthesizes the thread running through every previous entry in this blog:</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code> showed that production   without direction creates debt</li> <li>Refactoring with Intent   showed that slowing down is not the opposite of progress</li> <li>The Attention Budget showed that curation   outweighs volume</li> <li>The skill trilogy showed that taste determines   whether a tool helps or hinders</li> <li>Not Everything Is a Skill showed that   restraint is a skill in itself</li> <li>Defense in Depth showed that instructions are   not boundaries</li> <li>The 3:1 Ratio showed that judgment has a schedule</li> <li>Parallel Agents showed that scale amplifies   the gap between production and judgment</li> <li>Context as Infrastructure showed that the system   you build for context is infrastructure, not conversation</li> </ul> <p>From YOLO mode to defense in depth, the pattern is the same:</p> <ul> <li>Production is the easy part;</li> <li>Judgment is the hard part;</li> <li>AI changed the ratio, not the rule.</li> </ul> <p>This post synthesizes the thread running through every previous entry in this blog. The evidence is drawn from three weeks of building <code>ctx</code> with AI assistance, the decisions recorded in <code>DECISIONS.md</code>, the learnings captured in <code>LEARNINGS.md</code>, and the git history that tracks where the human mattered and where the AI ran unsupervised.</p> <p>See also: When a System Starts Explaining Itself -- what happens after the arc: the first field notes from the moment the system starts compounding in someone else's hands.</p>","path":["Code Is Cheap. Judgment Is Not."],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/","level":1,"title":"Context as Infrastructure","text":"","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#why-your-ai-needs-a-filesystem-not-a-prompt","level":2,"title":"Why Your AI Needs a Filesystem, Not a Prompt","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>Where Does Your AI's Knowledge Live between Sessions?</p> <p>If the answer is \"in a prompt I paste at the start,\" you are treating context as a consumable. Something assembled, used, and discarded.</p> <p>What if you treated it as infrastructure instead?</p> <p>This post synthesizes a thread that has been running through every <code>ctx</code> blog post; from the origin story to the attention budget to the discipline release. The thread is this: context is not a prompt problem. It is an infrastructure problem. And the tools we build for it should look more like filesystems than clipboard managers.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-prompt-paradigm","level":2,"title":"The Prompt Paradigm","text":"<p>Most AI-assisted development treats context as ephemeral:</p> <ol> <li>Start a session.</li> <li>Paste your system prompt, your conventions, your current task.</li> <li>Work.</li> <li>Session ends. Everything evaporates.</li> <li>Next session: paste again.</li> </ol> <p>This works for short interactions. For sustained development (where decisions compound over days and weeks) it fails in three ways:</p> <p>It does not persist: A decision made on Tuesday must be re-explained on Wednesday. A learning captured in one session is invisible to the next.</p> <p>It does not scale: As the project grows, the \"paste everything\" approach hits the context window ceiling. You start triaging what to include, often cutting exactly the context that would have prevented the next mistake.</p> <p>It does not compose: A system prompt is a monolith. You cannot load part of it, update one section, or share a subset with a different workflow. It is all or nothing.</p> <p>The Copy-Paste Tax</p> <p>Every session that starts with pasting a prompt is paying a tax:</p> <p>The human time to assemble the context, the risk of forgetting something, and the silent assumption that yesterday's prompt is still accurate today.</p> <p>Over 70+ sessions, that tax compounds into a significant maintenance burden: One that most developers absorb without questioning it.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-infrastructure-paradigm","level":2,"title":"The Infrastructure Paradigm","text":"<p><code>ctx</code> takes a different approach:</p> <p>Context is not assembled per-session; it is maintained as persistent files in a <code>.context/</code> directory:</p> <pre><code>.context/\n  CONSTITUTION.md     # Inviolable rules\n  TASKS.md            # Current work items\n  CONVENTIONS.md      # Code patterns and standards\n  DECISIONS.md        # Architectural choices with rationale\n  LEARNINGS.md        # Gotchas and lessons learned\n  ARCHITECTURE.md     # System structure\n  GLOSSARY.md         # Domain terminology\n  AGENT_PLAYBOOK.md   # Operating manual for agents\n  journal/            # Enriched session summaries\n  archive/            # Completed work, cold storage\n</code></pre> <ul> <li>Each file has a single purpose;</li> <li>Each can be loaded independently;</li> <li>Each persists across sessions, tools, and team members.</li> </ul> <p>This is not a novel idea. It is the same idea behind every piece of infrastructure software engineers already use:</p> Traditional Infrastructure <code>ctx</code> Equivalent Database <code>.context/*.md</code> files Configuration files <code>CONSTITUTION.md</code> Environment variables <code>.contextrc</code> Log files <code>journal/</code> Schema migrations Decision records Deployment manifests <code>AGENT_PLAYBOOK.md</code> <p>The parallel is not metaphorical. Context files are infrastructure:</p> <ul> <li>They are versioned (<code>git</code> tracks them); </li> <li>They are structured (Markdown with conventions); </li> <li>They have schemas (required fields for decisions and learnings); </li> <li>And they have lifecycle management (archiving, compaction, indexing).</li> </ul>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#separation-of-concerns","level":2,"title":"Separation of Concerns","text":"<p>The most important design decision in <code>ctx</code> is not any individual feature. It is the separation of context into distinct files with distinct purposes.</p> <p>A single <code>CONTEXT.md</code> file would be simpler to implement. It would also be impossible to maintain.</p> <p>Why? Because different types of context have different lifecycles:</p> Context Type Changes Read By Load When Constitution Rarely Every session Always Tasks Every session Session start Always Conventions Weekly Before coding When writing code Decisions When decided When questioning When revisiting Learnings When learned When stuck When debugging Journal Every session Rarely When investigating <p>Loading everything into every session wastes the attention budget on context that is irrelevant to the current task. Loading nothing forces the AI to operate blind.</p> <p>Separation of concerns allows progressive disclosure: </p> <p>Load the minimum that matters for this moment, with the  option to load more when needed.</p> <pre><code># Session start: load the essentials\nctx agent --budget 4000\n\n# Deep investigation: load everything\ncat .context/DECISIONS.md\ncat .context/journal/2026-02-05-*.md\n</code></pre> <p>The filesystem is the index. File names, directory structure, and timestamps encode relevance. The AI does not need to read every file; it needs to know where to look.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-two-tier-persistence-model","level":2,"title":"The Two-Tier Persistence Model","text":"<p><code>ctx</code> uses two tiers of persistence, and the distinction is architectural:</p> Tier Purpose Location Token Cost Curated Quick context reload <code>.context/*.md</code> Low (budgeted) Full dump Safety net, archaeology <code>.context/journal/*.md</code> Zero (not auto-loaded) <p>The curated tier is what the AI sees at session start. It is optimized for signal density: </p> <ul> <li>Structured entries, </li> <li>Indexed tables,</li> <li>Reverse-chronological order (newest first, so the most relevant   content survives truncation).</li> </ul> <p>The full dump tier is for humans and for deep investigation. It contains everything: Enriched journals, archived tasks... </p> <p>It is never autoloaded because its volume would destroy attention density.</p> <p>This two-tier model is analogous to how traditional systems separate hot and cold storage: </p> <ul> <li>The hot path (curated context) is optimized for read performance   (measured not in milliseconds, but in     tokens consumed per unit of useful information). </li> <li>The cold path (journal) is optimized for completeness.</li> </ul> <p>Nothing Is Ever Truly Lost</p> <p>The full dump tier means that context does not need to be perfect: It just needs to be findable.</p> <p>A decision that was not captured in <code>DECISIONS.md</code> can be recovered from the session transcript where it was discussed. </p> <p>A learning that was not formalized can be found in the journal entry  from that day.</p> <p>The curated tier is the fast path: The full dump tier is the safety net.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#decision-records-as-first-class-citizens","level":2,"title":"Decision Records as First-Class Citizens","text":"<p>One of the patterns that emerged from <code>ctx</code>'s own development is the power of structured decision records.</p> <p><code>v0.1.0</code> allowed adding decisions as one-liners:</p> <pre><code>ctx add decision \"Use PostgreSQL\"\n</code></pre> <p><code>v0.2.0</code> enforced structure:</p> <pre><code>ctx add decision \"Use PostgreSQL\" \\\n  --context \"Need a reliable database for user data\" \\\n  --rationale \"ACID compliance, team familiarity\" \\\n  --consequence \"Need connection pooling, team training\"\n</code></pre> <p>The difference is not cosmetic:</p> <ul> <li>A one-liner decision teaches the AI what was decided. </li> <li>A structured decision teaches it why; and   why is what prevents the AI from unknowingly reversing the decision   in a future session.</li> </ul> <p>This is infrastructure thinking: </p> <p>Decisions are not notes. They are records with required fields, just like  database rows have schemas.</p> <p>The enforcement exists because incomplete records are worse than no records: They create false confidence that the context is captured when it is not.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-ide-is-the-interface-decision","level":2,"title":"The \"IDE Is the Interface\" Decision","text":"<p>Early in <code>ctx</code>'s development, there was a temptation to build a custom UI: a web dashboard for browsing sessions, editing context, viewing analytics.</p> <p>The decision was no. The IDE is the interface.</p> <pre><code># This is the ctx \"UI\":\ncode .context/\n</code></pre> <p>This decision was not about minimalism for its own sake. It was about recognizing that <code>.context/</code> files are just files; and files have a mature, well-understood infrastructure:</p> <ul> <li>Version control: <code>git diff .context/DECISIONS.md</code> shows exactly   what changed and when.</li> <li>Search: Your IDE's full-text search works across all context files.</li> <li>Editing: Markdown in any editor, with preview, spell check,   and syntax highlighting.</li> <li>Collaboration: Pull requests on context files work the same as   pull requests on code.</li> </ul> <p>Building a custom UI would have meant maintaining a parallel infrastructure that duplicates what every IDE already provides:</p> <p>It would have introduced its own bugs, its own update cycle, and its own learning curve.</p> <p>The filesystem is not a limitation: It is the most mature, most composable, most portable infrastructure available.</p> <p>Context Files in Git</p> <p>Because <code>.context/</code> lives in the repository, context changes are part of the commit history. </p> <p>A decision made in commit <code>abc123</code> is as traceable as a code change in  the same commit.</p> <p>This is not possible with prompt-based context, which exists outside version control entirely.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#progressive-disclosure-for-ai","level":2,"title":"Progressive Disclosure for AI","text":"<p>The concept of progressive disclosure comes from human interface design: show the user the minimum needed to make progress, with the option to drill deeper.</p> <p><code>ctx</code> applies the same principle to AI context:</p> Level What the AI Sees Token Cost When Level 0 <code>ctx status</code> (one-line summary) ~100 Quick check Level 1 <code>ctx agent --budget 4000</code> ~4,000 Normal work Level 2 <code>ctx agent --budget 8000</code> ~8,000 Complex tasks Level 3 Direct file reads 10,000+ Deep investigation <p>Each level trades tokens for depth. Level 1 is sufficient for most work: the AI knows the active tasks, the key conventions, and the recent decisions. Level 3 is for archaeology: understanding why a decision was made three weeks ago, or finding a pattern in the session history.</p> <p>The explicit <code>--budget</code> flag is the mechanism that makes this work:</p> <p>Without it, the default behavior would be to load everything (because more context feels safer), which destroys the attention density that makes the loaded context useful.</p> <p>The constraint is the feature: A budget of 4,000 tokens forces <code>ctx</code> to prioritize ruthlessly: constitution first (always full), then tasks and conventions (budget-capped), then decisions and learnings scored by recency and relevance to active tasks. Entries that don't fit get title-only summaries rather than being silently dropped.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-philosophical-shift","level":2,"title":"The Philosophical Shift","text":"<p>The shift from \"context as prompt\" to \"context as infrastructure\" changes how you think about AI-assisted development:</p> Prompt Thinking Infrastructure Thinking \"What do I paste today?\" \"What has changed since yesterday?\" \"How do I fit everything in?\" \"What's the minimum that matters?\" \"The AI forgot my conventions\" \"The conventions are in a file\" \"I need to re-explain\" \"I need to update the record\" \"This session is getting slow\" \"Time to compact and archive\" <p>The first column treats AI interaction as a conversation. The second treats it as a system: One that can be maintained, optimized, and debugged.</p> <p>Context is not something you give the AI. It is something you maintain: Like a database, like a config file, like any other piece of infrastructure that a running system depends on.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#beyond-ctx-the-principles","level":2,"title":"Beyond <code>ctx</code>: The Principles","text":"<p>The patterns that <code>ctx</code> implements are not specific to <code>ctx</code>. They are applicable to any project that uses AI-assisted development:</p> <ol> <li>Separate context by purpose: Do not put everything in one file.    Different types of information have different lifecycles and    different relevance windows.</li> <li>Make context persistent: If a decision matters, write it down    in a file that survives the session. If a learning matters, capture    it with structure.</li> <li>Budget explicitly: Know how much context you are loading and    whether it is worth the attention cost.</li> <li>Use the filesystem: File names, directory structure, and    timestamps are metadata that the AI can navigate. A well-organized    directory is an index that costs zero tokens to maintain.</li> <li>Version your context: Put context files in <code>git</code>. Changes to    decisions are as important as changes to code.</li> <li>Design for degradation: Sessions will get long. Attention will    dilute. Build mechanisms (compaction, archiving, cooldowns) that    make degradation visible and manageable.</li> </ol> <p>These are not <code>ctx</code> features. They are infrastructure principles that happen to be implemented as a CLI tool. Any team could implement them with nothing more than a directory convention and a few shell scripts.</p> <p>The tool is a convenience: The principles are what matter.</p> <p>If You Remember One Thing from This Post...</p> <p>Prompts are conversations. Infrastructure persists.</p> <p>Your AI does not need a better prompt. It needs a filesystem:</p> <p>versioned, structured, budgeted, and maintained.</p> <p>The best context is the context that was there before you started the session.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-context-as-infrastructure/#the-arc","level":2,"title":"The Arc","text":"<p>This post is the architectural companion to the Attention Budget. That post explained why context must be curated (token economics). This one explains how to structure it (filesystem, separation of concerns, persistence tiers).</p> <p>Together with Code Is Cheap, Judgment Is Not, they form a trilogy about what matters in AI-assisted development:</p> <ul> <li>Attention Budget: the resource you're managing</li> <li>Context as Infrastructure: the system you build to manage it</li> <li>Code Is Cheap: the human skill that no system replaces</li> </ul> <p>And the practices that keep it all honest:</p> <ul> <li>The 3:1 Ratio: the cadence for maintaining both   code and context</li> <li>IRC as Context: the historical precedent: stateless   protocols have always needed stateful wrappers</li> </ul> <p>This post synthesizes ideas from across the <code>ctx</code> blog series: the attention budget primitive, the two-tier persistence model, the IDE decision, and the progressive disclosure pattern. The principles are drawn from three weeks of building <code>ctx</code> and 70+ sessions of treating context as infrastructure rather than conversation.</p> <p>See also: When a System Starts Explaining Itself: what happens when this infrastructure starts compounding in someone else's environment.</p>","path":["Context as Infrastructure"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/","level":1,"title":"Parallel Agents, Merge Debt, and the Myth of Overnight Progress","text":"","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-the-screen-looks-like-progress","level":2,"title":"When the Screen Looks like Progress","text":"<p>Volkan Özçelik / 2026-02-17</p> <p>How Many Terminals Are Too Many?</p> <p>You discover agents can run in parallel.</p> <p>So you open ten... </p> <p>...Then twenty.</p> <p>The fans spin. Tokens burn. The screen looks like progress.</p> <p>It is NOT progress.</p> <p>There is a phase every builder goes through:</p> <ul> <li>The tooling gets fast enough. </li> <li>The model gets good enough. </li> <li>The temptation becomes irresistible: <ul> <li>more agents, more output, faster delivery.</li> </ul> </li> </ul> <p>So you open terminals. You spawn agents. You watch tokens stream across multiple windows simultaneously, and it feels like multiplication.</p> <p>It is not multiplication.</p> <p>It is merge debt being manufactured in real time.</p> <p>The <code>ctx</code> Manifesto says it plainly:</p> <p>Activity is not impact. Code is not progress.</p> <p>This post is about what happens when you take that seriously in the context of parallel agent workflows.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-unit-of-scale-is-not-the-agent","level":2,"title":"The Unit of Scale Is Not the Agent","text":"<p>The naive model says:</p> <p>More agents -> more output -> faster delivery</p> <p>The production model says:</p> <p>Clean context boundaries -> less interference -> higher throughput</p> <p>Parallelism only works when the cognitive surfaces do not overlap.</p> <p>If two agents touch the same files, you did not create parallelism: You created a conflict generator.</p> <p>They will:</p> <ul> <li>Revert each other's changes;</li> <li>Relint each other's formatting;</li> <li>Refactor the same function in different directions.</li> </ul> <p>You watch with 🍿. Nothing ships.</p> <p>This is the same insight from the worktrees post: partition by blast radius, not by priority. </p> <p>Two tasks that touch the same files belong in the same track, no matter how  important the other one is. The constraint is file overlap. </p> <p>Everything else is scheduling.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-five-agent-rule","level":2,"title":"The \"Five Agent\" Rule","text":"<p>In practice there is a ceiling.</p> <p>Around five or six concurrent agents:</p> <ul> <li>Token burn becomes noticeable;</li> <li>Supervision cost rises;</li> <li>Coordination noise increases;</li> <li>Returns flatten.</li> </ul> <p>This is not a model limitation:  This is a human merge bandwidth limitation.</p> <p>You are the bottleneck, not the silicon.</p> <p>The attention budget applies to you too: </p> <p>Every additional agent is another stream of output you need to comprehend, verify, and integrate. Your attention density drops the same way the model's does when you overload its context window.</p> <p>Five agents producing verified, mergeable change beats twenty agents producing merge conflicts you spend a day untangling.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#role-separation-beats-file-locking","level":2,"title":"Role Separation Beats File Locking","text":"<p>Real parallelism comes from task topology, not from tooling.</p> <p>Good:</p> Agent Role Touches 1 Documentation <code>docs/</code>, <code>hack/</code> 2 Security scan Read-only audit 3 Implementation <code>internal/cli/</code> 4 Enhancement requests Read-only, files issues <p>Bad:</p> <ul> <li>Four agents editing the same implementation surface</li> </ul> <p>Context Is the Boundary</p> <ul> <li>The goal is not to keep agents busy. </li> <li>The goal is to keep contexts isolated.</li> </ul> <p>This is what the codebase audit got right: </p> <ul> <li>Eight agents, all read-only, each analyzing a different dimension. </li> <li>Zero file overlap.</li> <li>Zero merge conflicts. </li> <li>Eight reports that composed cleanly because no agent interfered with another.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#when-terminals-stop-scaling","level":2,"title":"When Terminals Stop Scaling","text":"<p>There is a moment when more windows stop helping.</p> <p>That is the signal. Not to add orchestration. But to introduce:</p> <pre><code>git worktree\n</code></pre> <p>Because now you are no longer parallelizing execution; you are parallelizing state.</p> <p>State Scales, Windows Don't</p> <ul> <li>State isolation is the real scaling. </li> <li>Window multiplication is theater.</li> </ul> <p>The worktrees post covers the mechanics: </p> <ul> <li>Sibling directories;</li> <li>Branch naming; </li> <li>The inevitable <code>TASKS.md</code> conflicts; </li> <li>The 3-4 worktree ceiling. </li> </ul> <p>The principle underneath is older than <code>git</code>:</p> <p>Shared mutable state is the enemy of parallelism. </p> <p>Always has been.</p> <p>Always will be.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-overnight-loop-illusion","level":2,"title":"The Overnight Loop Illusion","text":"<p>Autonomous night runs are impressive.</p> <p>You sleep. The machine produces thousands of lines.</p> <p>In the morning:</p> <ul> <li>You read;</li> <li>You untangle;</li> <li>You reconstruct intent;</li> <li>You spend a day making it shippable.</li> </ul> <p>In retrospect, nothing was accelerated. </p> <p>The bottleneck moved from typing to comprehension.</p> <p>The Comprehension Tax</p> <p>If understanding the output costs more than producing it, the loop is a net loss.</p> <p>Progress is not measured in generated code.</p> <p>Progress is measured in verified, mergeable change.</p> <p>The <code>ctx</code> Manifesto calls this out directly:</p> <p>The Scoreboard</p> <p>Verified reality is the scoreboard.</p> <p>The only truth that compounds is verified change in the real world.</p> <p>An overnight run that produces 3,000 lines nobody reviewed is not 3,000 lines of progress: It is 3,000 lines of liability until someone verifies every one of them. </p> <p>And that someone is (insert drumroll here) you: </p> <p>The same bottleneck that was supposedly being bypassed.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#skills-that-fight-the-platform","level":2,"title":"Skills That Fight the Platform","text":"<p>Most marketplace skills are prompt decorations:</p> <ul> <li>They rephrase what the base model already knows;</li> <li>They increase token usage; </li> <li>They reduce clarity:</li> <li>They introduce behavioral drift.</li> </ul> <p>We covered this in depth in Skills That Fight the Platform: judgment suppression, redundant guidance, guilt-tripping, phantom dependencies, universal triggers: Five patterns that make agents worse, not better.</p> <p>A real skill does one of these:</p> <ul> <li>Encodes workflow state;</li> <li>Enforces invariants;</li> <li>Reduces decision branching.</li> </ul> <p>Everything else is packaging.</p> <p>The anatomy post established the criteria: quality gates, negative triggers, examples over rules, skills as contracts. </p> <p>If a skill doesn't meet those criteria... </p> <ul> <li>It is either a recipe (document it in <code>hack/</code>); </li> <li>Or noise (delete it);</li> <li>There is no third option.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#hooks-are-context-that-execute","level":2,"title":"Hooks Are Context That Execute","text":"<p>The most valuable skills are not prompts:</p> <p>They are constraints embedded in the toolchain.</p> <p>For example: The agent cannot push.</p> <p><code>git push</code> becomes:</p> <p>Stop. A human reviews first.</p> <p>A commit without verification becomes:</p> <p>Did you run tests? Did you run linters? What exactly are you shipping?</p> <p>This is not safety theater; this is intent preservation.</p> <p>The  thing the <code>ctx</code> Manifesto calls \"encoding intent into the environment.\"</p> <p>The Eight Ways a Hook Can Talk cataloged the full spectrum: from silent enrichment to hard blocks. </p> <p>The key insight was that hooks are not just safety rails:  They are context that survives execution.</p> <p>They are the difference between an agent that remembers the rules  and one that enforces them.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#complexity-is-a-tax","level":2,"title":"Complexity Is a Tax","text":"<p>Every extra layer adds cognitive weight:</p> <ul> <li>Orchestration frameworks;</li> <li>Meta agents;</li> <li>Autonomous planning systems...</li> </ul> <p>If a single terminal works, stay there.</p> <p>If five isolated agents work, stop there.</p> <p>Add structure only when a real bottleneck appears. </p> <p>NOT when an influencer suggests one.</p> <p>This is the same lesson from Not Everything Is a Skill:</p> <p>The best automation decision is sometimes not to automate.</p> <p>A recipe in a Markdown file costs nothing until you use it. </p> <p>An orchestration framework costs attention on every run, whether it helps or not.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#literature-is-throughput","level":2,"title":"Literature Is Throughput","text":"<p>Clear writing is not aesthetic: It is compression.</p> <p>Better articulation means:</p> <ul> <li>Fewer tokens;</li> <li>Fewer misinterpretations;</li> <li>Faster convergence.</li> </ul> <p>The attention budget taught us that context is a finite resource with a quadratic cost. </p> <p>Language determines how fast you spend context. </p> <p>A well-written task description that takes 50 tokens outperforms a rambling one that takes 200: Not just because it is cheaper, but because it leaves more  headroom for the model to actually think.</p> <p>Literature Is NOT Overrated</p> <ul> <li>Attention is a finite budget. </li> <li>Language determines how fast you spend it.</li> </ul>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#the-real-metric","level":2,"title":"The Real Metric","text":"<p>The real metric is not:</p> <ul> <li>Lines generated;</li> <li>Agents running;</li> <li>Tasks completed while you sleep.</li> </ul> <p>But:</p> <p>Time from idea to verified, mergeable, production change.</p> <p>Everything else is motion.</p> <p>The entire blog series has been circling this point: </p> <ul> <li>The attention budget was about spending tokens wisely. </li> <li>The skills trilogy was about not wasting them on prompt   decoration.</li> <li>The worktrees post was about multiplying throughput   without multiplying interference. </li> <li>The discipline release was about what a release looks   like when polish outweighs features: 3:1.</li> </ul> <p>Every post has arrived (and made me converge) at the same answer so far: </p> <p>The metric is a verified change, not generated output.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#ctx-was-never-about-spawning-more-minds","level":2,"title":"<code>ctx</code> Was Never about Spawning More Minds","text":"<p><code>ctx</code> is about:</p> <ul> <li>Isolating context;</li> <li>Preserving intent;</li> <li>Making progress composable.</li> </ul> <p>Parallel agents are powerful. But only when you respect the boundaries that make parallelism real.</p> <p>Otherwise, you are not scaling cognition; you are scaling interference.</p> <p>The <code>ctx</code> Manifesto's thesis holds:</p> <p>Without <code>ctx</code>, intelligence resets. With <code>ctx</code>, creation compounds.</p> <p>Compounding requires structure. </p> <p>Structure requires boundaries.</p> <p>Boundaries require the discipline to stop adding agents when five is enough.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-parallel-agents-merge-debt-and-the-myth-of-overnight-progress/#practical-summary","level":2,"title":"Practical Summary","text":"<p>A production workflow tends to converge to this:</p> Practice Why Stay in one terminal unless necessary Minimize coordination overhead Spawn a small number of agents with non-overlapping responsibilities Conflict avoidance > parallelism Isolate state with worktrees when surfaces grow State isolation is real scaling Encode verification into hooks Intent that survives execution Avoid marketplace prompt cargo cults Skills are contracts, not decorations Measure merge cost, not generation speed The metric is verified change <p>This is slower to watch. Faster to ship.</p> <p>If You Remember One Thing from This Post...</p> <p>Progress is not what the machine produces while you sleep.</p> <p>Progress is what survives contact with the main branch.</p> <p>See also: Code Is Cheap. Judgment Is Not.: the argument that production capacity was never the bottleneck, and why multiplying agents amplifies the need for human judgment rather than replacing it.</p>","path":["Parallel Agents, Merge Debt, and the Myth of Overnight Progress"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/","level":1,"title":"The 3:1 Ratio","text":"","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#scheduling-consolidation-in-ai-development","level":2,"title":"Scheduling Consolidation in AI Development","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>How Often Should You Stop Building and Start Cleaning?</p> <p>Every developer knows technical debt exists. Every developer postpones dealing with it.</p> <p>AI-assisted development makes the problem worse; not because the AI writes bad code, but because it writes code so fast that drift accumulates before you notice.</p> <p>In Refactoring with Intent, I mentioned a ratio that worked for me: 3:1. Three YOLO sessions create enough surface area to reveal patterns. The fourth session turns those patterns into structure.</p> <p>That was an observation. This post is the evidence.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-observation","level":2,"title":"The Observation","text":"<p>During the first two weeks of building <code>ctx</code>, I noticed a rhythm in my own productivity. Feature sessions felt great: new commands, new capabilities, visible progress...</p> <p>...but after three of them, things would start to feel sticky: variable names that almost made sense, files that had grown past their purpose, patterns that repeated without being formalized.</p> <p>The fourth session (when I stopped adding and started cleaning) was always the most painful to start and the most satisfying to finish.</p> <p>It was also the one that made the next three feature sessions faster.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-evidence-git-history","level":2,"title":"The Evidence: Git History","text":"<p>The <code>ctx</code> git history between January 20 and February 7 tells a clear story when you categorize commits:</p> Week Feature commits Consolidation commits Ratio Jan 20-26 18 5 3.6:1 Jan 27-Feb 1 14 6 2.3:1 Feb 1-7 15 35+ 0.4:1 <p>The first week was pure YOLO: Almost four feature commits for every consolidation commit. The codebase grew fast.</p> <p>The second week started to self-correct. The ratio dropped as refactoring sessions became necessary: Not scheduled, but forced by friction.</p> <p>The third week inverted entirely: v0.3.0 was almost entirely consolidation: the skill migration, the sweep, the documentation standardization. Thirty-five quality commits against fifteen features.</p> <p>The debt from weeks one and two was paid in week three.</p> <p>The Compounding Problem</p> <p>Consolidation debt compounds.</p> <p>Week one's drift doesn't just persist into week two: It accelerates, because new features are built on top of drifted patterns.</p> <p>By week three, the cost of consolidation was higher than it would have been if spread evenly.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-drift-actually-looks-like","level":2,"title":"What Drift Actually Looks Like","text":"<p>\"Drift\" sounds abstract. Here is what it looked like concretely in the <code>ctx</code> codebase after three weeks of feature-heavy development:</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#predicate-naming","level":3,"title":"Predicate Naming","text":"<p>Convention says boolean functions should be named <code>HasX</code>, <code>IsX</code>, <code>CanX</code>. After three feature sprints:</p> <pre><code>// What accumulated:\nfunc CheckIfEnabled() bool  // should be Enabled\nfunc ValidateFormat() bool  // should be ValidFormat\nfunc TestConnection() bool  // should be Connects\nfunc VerifyExists() bool    // should be Exists or HasFile\nfunc EnsureReady() bool     // should be Ready\n</code></pre> <p>Five violations. Not bugs, but friction that compounds every time someone (human or AI) reads the code and has to infer the naming convention from inconsistent examples.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#magic-strings","level":3,"title":"Magic Strings","text":"<pre><code>// Week 1: acceptable prototype\nif entry.Type == \"task\" {\n    filename = \"TASKS.md\"\n}\n\n// Week 3: same pattern in 7+ files\n// Now it's a maintenance liability\n</code></pre> <p>When the same literal appears in seven files, changing it means finding all seven. Missing one means a silent runtime bug. Constants exist to prevent exactly this. But during feature velocity, nobody stops to extract them.</p> <p>Refactoring with Intent documented the constants consolidation that cleaned this up. The 3:1 ratio is the practice that prevents it from accumulating again.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#hardcoded-permissions","level":3,"title":"Hardcoded Permissions","text":"<pre><code>os.WriteFile(path, data, 0644) // 80+ instances\nos.MkdirAll(path, 0755)        // scattered across packages\n</code></pre> <p>Eighty-plus instances of hardcoded file permissions. Not wrong, but if I ever need to change the default (and I did, for hook scripts that need execute permissions), it means a codebase-wide search.</p> <p>Drift Is Not Bugs</p> <p>None of these are bugs. The code works. Tests pass.</p> <p>But drift creates false confidence: the codebase looks consistent until you try to change something and discover that five different conventions exist for the same concept.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#why-you-cannot-consolidate-on-day-one","level":2,"title":"Why You Cannot Consolidate on Day One","text":"<p>The temptation is to front-load quality: write all the conventions, enforce all the checks, prevent all the drift before it happens.</p> <p>This fails for two reasons.</p> <p>First, you do not know what will drift: Predicate naming violations only become a convention check after you notice three different naming patterns competing. Magic strings only become a consolidation target after you change a literal and discover it exists in seven places.</p> <p>The conventions emerge from the work; they cannot precede it.</p> <p>This is what You Can't Import Expertise meant in practice: the consolidation checks grow from the project's own drift history. You cannot write them on day one because you do not yet know what will drift.</p> <p>Second, premature consolidation slows discovery: During the prototyping phase, the goal is to explore the design space. Enforcing strict conventions on code that might be deleted tomorrow is waste.</p> <p>YOLO mode has its place: The problem is not YOLO itself, but YOLO without a scheduled cleanup.</p> <p>The Consolidation Paradox</p> <p>You need a drift history to know what to consolidate.</p> <p>You need consolidation to prevent drift from compounding.</p> <p>The 3:1 ratio resolves this paradox:</p> <p>Let drift accumulate for three sessions (enough to see patterns), then consolidate in the fourth (before the patterns become entrenched*).</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-consolidation-skill","level":2,"title":"The Consolidation Skill","text":"<p>The <code>ctx</code> project now has an <code>/audit</code> skill that encodes nine project-specific checks:</p> Check What It Catches Predicate naming Boolean functions not using Has/Is/Can Magic strings Repeated literals not in config constants File permissions Hardcoded 0644/0755 not using constants Godoc style Missing or non-standard documentation File length Files exceeding 400 lines Large functions Functions exceeding 80 lines Template drift Live skills diverging from templates Import organization Non-standard import grouping TODO/FIXME staleness Old markers that are no longer relevant <p>This is not a generic linter. These are project-specific conventions that emerged from <code>ctx</code>'s own development history. A generic code quality tool would catch some of them. Only a project-specific check catches all of them, because some of them (predicate naming, template drift) are conventions that exist nowhere except in this project's <code>CONVENTIONS.md</code>.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-decision-matrix","level":2,"title":"The Decision Matrix","text":"<p>Not all drift needs immediate consolidation. Here is the matrix I use:</p> Signal Action Same literal in 3+ files Extract to constant Same code block in 3+ places Extract to helper Naming convention violated 5+ times Fix and document rule File exceeds 400 lines Split by concern Convention exists but is regularly violated Strengthen enforcement Pattern exists only in one place Leave it alone Code works but is \"ugly\" Leave it alone <p>The last two rows matter: </p> <p>Consolidation is about reducing maintenance cost, not achieving aesthetic  perfection. Code that works and exists in one place does not benefit  from consolidation; it benefits from being left alone until it earns its refactoring.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#consolidation-as-context-hygiene","level":2,"title":"Consolidation as Context Hygiene","text":"<p>There is a parallel between code consolidation and context management that became clear during the <code>ctx</code> development:</p> Code Consolidation Context Hygiene Extract magic strings Archive completed tasks Standardize naming Keep DECISIONS.md current Remove dead code Compact old sessions Update stale comments Review LEARNINGS.md for staleness Check template drift Verify CONVENTIONS.md matches code <p><code>ctx compact</code> does for context what consolidation does for code: </p> <p>It moves completed work to cold storage, keeping the active context clean and focused. The attention budget applies to both the AI's context window and the developer's mental model of the codebase.</p> <p>When context files accumulate stale entries, the AI's attention is wasted on completed tasks and outdated conventions. When code accumulates drift, the developer's attention is wasted on inconsistencies that obscure the actual logic.</p> <p>Both are solved by the same discipline: periodic, scheduled cleanup.</p> <p>This is also why parallel agents make the problem harder, not easier. Three agents running simultaneously produce three sessions' worth of drift in one clock hour. The consolidation cadence needs to match the output rate, not the calendar.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-practice","level":2,"title":"The Practice","text":"<p>Here is how the 3:1 ratio works in practice for <code>ctx</code> development:</p> <p>Sessions 1-3: Feature work</p> <ul> <li>Add new capabilities;</li> <li>Write tests for new code;</li> <li>Do not stop for cleanup unless something is actively broken;</li> <li>Note drift as you see it (a comment, a task, a mental note).</li> </ul> <p>Session 4: Consolidation</p> <ul> <li>Run <code>/audit</code> to surface accumulated drift;</li> <li>Fix the highest-impact items first;</li> <li>Update CONVENTIONS.md if new patterns emerged;</li> <li>Archive completed tasks;</li> <li>Review LEARNINGS.md for anything that became a convention.</li> </ul> <p>The key insight is that session 4 is not optional. It is not \"if we have time\": It is scheduled with the same priority as feature work.</p> <p>The cost of skipping it is not visible immediately; it becomes visible three sessions later, when the next consolidation session takes twice as long because the drift compounded.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#what-the-ratio-is-not","level":2,"title":"What the Ratio Is Not","text":"<p>The 3:1 ratio is not a universal law. It is an empirical observation from one project with one developer working with AI assistance.</p> <p>Different projects will have different ratios:</p> <ul> <li>A mature codebase with strong conventions might sustain 5:1 or higher; </li> <li>A greenfield prototype might need 2:1; </li> <li>A team of multiple developers with different styles might need 1:1.</li> </ul> <p>The number is less important than the practice: consolidation is not a reaction to problems. It is a scheduled activity.</p> <p>If you wait for drift to cause pain before consolidating, you have already paid the compounding cost.</p> <p>If You Remember One Thing from This Post...</p> <p>Three sessions of building. One session of cleaning.</p> <p>Not because the code is dirty, but because drift compounds silently, and the only way to catch it is to look for it on a schedule.</p> <p>The ratio is the schedule.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-the-3-1-ratio/#the-arc-so-far","level":2,"title":"The Arc so Far","text":"<p>This post sits at a crossroads in the <code>ctx</code> story. Looking back:</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code> documented the YOLO sprint   that created the initial codebase</li> <li>Refactoring with Intent introduced the 3:1 ratio   as an observation from the first cleanup</li> <li>The Attention Budget explained why drift   matters: every token of inconsistency consumes the same finite   resource as useful context</li> <li>You Can't Import Expertise showed that   consolidation checks must grow from the project, not a template</li> <li>The Discipline Release proved the ratio works at release   scale: 35 quality commits to 15 feature commits</li> </ul> <p>And looking forward: the same principle applies to context files, to documentation, and to the merge debt that parallel agents produce. Drift is drift, whether it lives in code, in <code>.context/</code>, or in the gap between what your docs say and what your code does.</p> <p>The ratio is the schedule is the discipline.</p> <p>This post was drafted from git log analysis of the <code>ctx</code> repository, mapping every commit from January 20 to February 7 into feature vs consolidation categories. The patterns described are drawn from the project's CONVENTIONS.md, LEARNINGS.md, and the <code>/audit</code> skill's check list.</p>","path":["The 3:1 Ratio"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/","level":1,"title":"When a System Starts Explaining Itself","text":"","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#field-notes-from-the-moment-a-private-workflow-becomes-portable","level":2,"title":"Field Notes from the Moment a Private Workflow Becomes Portable","text":"<p>Volkan Özçelik / February 17, 2026</p> <p>How Do You Know Something Is Working?</p> <p>Not from metrics. Not from GitHub stars. Not from praise.</p> <p>You know, deep in your heart, that it works when people start describing it wrong.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-first-external-signals","level":2,"title":"The First External Signals","text":"<p>Every new substrate begins as a private advantage:</p> <ul> <li>It lives inside one mind,</li> <li>One repository,</li> <li>One set of habits.</li> </ul> <p>It is fast. It is not yet real.</p> <p>Reality begins when other people describe it in their own language:</p> <ul> <li>Not accurately;</li> <li>Not consistently;</li> <li>But involuntarily.</li> </ul> <p>The early reports arrived without coordination:</p> <p>Better Tasks</p> <p>\"I do not know how, but this creates better tasks than my AI plugin.\"</p> <p>I See Butterflies</p> <p>\"This is better than Adderall.\"</p> <p>Dear Manager...</p> <p>\"Promotion packet? Done. What is next?\"</p> <p>What Is It? Can I Eat It?</p> <p>\"Is this a skill?\" 🦋 </p> <p>Why the Cloak and Dagger?</p> <p>\"Why is this not in the marketplace?\"</p> <p>And then something more important happened:</p> <p>Someone else started making a video!</p> <p>That was the boundary.</p> <p><code>ctx</code> no longer required its creator to be present in order to exist.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#misclassification-is-a-sign-of-a-new-primitive","level":2,"title":"Misclassification Is a Sign of a New Primitive","text":"<p>When a tool is understood, it is categorized:</p> <ul> <li>Editor,</li> <li>Framework,</li> <li>Task manager,</li> <li>Plugin...</li> </ul> <p>When a substrate appears, it is misclassified:</p> <p>\"Is this a skill?\" 🦋</p> <p>The question is correct. The category is wrong.</p> <ul> <li>Skills live in people.</li> <li>Infrastructure lives in the environment.</li> </ul> <p><code>ctx</code> Is Not a Skill: It Is a Form of Relief</p> <p>What early adopters experience is not an ability.</p> <p>It is the removal of a cognitive constraint.</p> <p>This is the same distinction that emerged in the skills trilogy:</p> <ul> <li>A skill is a contract between a human and an agent.  </li> <li>Infrastructure is the ground both stand on.</li> </ul> <p>You do not use infrastructure.</p> <p>You habitualize it.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-pharmacological-metaphor","level":2,"title":"The Pharmacological Metaphor","text":"<p>\"Better than Adderall\" is not praise.</p> <p>It is a diagnostic:</p> <p>Executive function has been externalized.</p> <ul> <li>The system is not making the user work harder.  </li> <li>It is restoring continuity.</li> </ul> <p>From the primitive context of wetware:</p> <ul> <li>Continuity feels like focus</li> <li>Focus feels like discipline</li> </ul> <p>If it walks like a duck and quacks like a duck, it is a duck.</p> <p>Discipline is usually simulated.</p> <p>Infrastructure makes the simulation unnecessary.</p> <p>The attention budget explained why context degrades:</p> <ul> <li>Attention density drops as volume grows;</li> <li>The middle gets lost;</li> <li>Sessions end and everything evaporates.</li> </ul> <p>The pharmacological metaphor says the same thing from the user's lens:</p> <p>Save the Cheerleader, Save the World</p> <p>The symptom of lost context is lost focus.</p> <p>Restore the context. Restore the focus.</p> <p>IRC bouncers solved this for chat twenty years ago. <code>ctx</code> solves it for cognition.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#throughput-on-ambiguous-work","level":2,"title":"Throughput on Ambiguous Work","text":"<p>Finishing a promotion packet quickly is not a productivity story.</p> <p>It is the collapse of reconstruction cost.</p> <p>Most complex work is not execution. It is:</p> <ul> <li>Remembering why something mattered;</li> <li>Recovering prior decisions;</li> <li>Rebuilding mental state.</li> </ul> <p>Persistent context removes that tax.</p> <p>Velocity appears as a side effect.</p> <p>This Is the Two-Tier Model in Practice</p> <p>The two-tier persistence model</p> <ul> <li>Curated context for fast reload</li> <li>Full journal for archaeology</li> </ul> <p>is what makes this possible.</p> <ul> <li>The user does not notice the system.  </li> <li>They notice that the reconstruction cost disappeared.</li> </ul>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-moment-of-portability","level":2,"title":"The Moment of Portability","text":"<p>The system becomes real when two things happen:</p> <ol> <li>It can be installed as a versioned artifact.</li> <li>It survives contact with a hostile, real codebase.</li> </ol> <p>This is why the first integration into a living system matters more than any landing page.</p> <p>Demos prove possibility.</p> <p>Diffs prove reality.</p> <p>The <code>ctx</code> Manifesto calls this out directly:</p> <p>Verified reality is the scoreboard.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-split-voice","level":2,"title":"The Split Voice","text":"<p>A new substrate requires two channels.</p> <p>The embodied voice:</p> <p>Here is what changed in my actual work.</p> <p>The out of body voice:</p> <p>Here is what this means.</p> <p>One produces trust.</p> <p>The other produces understanding.</p> <p>Neither is sufficient alone.</p> <p>This entire blog has been the second voice.</p> <ul> <li>The origin story was the first.  </li> <li>The refactoring post was the first.  </li> <li>Every release note with concrete diffs was the first.</li> </ul> <p>This is the first second.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#systems-that-generate-explainers","level":2,"title":"Systems That Generate Explainers","text":"<p>Tools are used.</p> <p>Platforms are extended.</p> <p>Substrates are explained.</p> <p>The first unsolicited explainer is a brittle phase change.</p> <p>It means the idea has become portable between minds.</p> <p>That is the beginning of an ecosystem.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-absence-of-metrics","level":2,"title":"The Absence of Metrics","text":"<p>Metrics do not matter at this stage.</p> <p>Dashboards are noise.</p> <p>The whole premise of <code>ctx</code> is the ruthless elimination of noise.</p> <p>Numbers optimize funnels; substrates alter cognition.</p> <p>The only valid measurement is irreversible reality:</p> <ul> <li>A merged PR;</li> <li>A reproducible install;</li> <li>A decision that is never re-litigated.</li> </ul> <p>The merge debt post reached the same conclusion from  another direction:</p> <p>The metric is the verified change, not generated output.</p> <p>For adoption, the same rule applies:</p> <p>The metric is altered behavior, not download counts.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#what-is-actually-happening","level":2,"title":"What Is Actually Happening","text":"<p>A private advantage is becoming an environmental property:</p> <p>The system is moving from...</p> <p>personal workflow,</p> <p>to...</p> <p>a shared infrastructure for thought.</p> <p>Not by growth.  </p> <p>Not by marketing.</p> <p>By altering how real systems evolve.</p> <p>If You Remember One Thing from This Post...</p> <p>You do not know a substrate is real when people praise it.</p> <p>You know it is real when:</p> <ul> <li>They describe it incorrectly;</li> <li>They depend on it unintentionally;</li> <li>They start teaching it to others.</li> </ul> <p>That is the moment the system begins explaining itself.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-17-when-a-system-starts-explaining-itself/#the-arc","level":2,"title":"The Arc","text":"<p>Every previous post looked inward.</p> <p>This one looks outward.</p> <ul> <li>Building <code>ctx</code> Using <code>ctx</code>: one mind, one repository</li> <li>The Attention Budget: the constraint</li> <li>Context as Infrastructure: the architecture</li> <li>Code Is Cheap. Judgment Is Not.: the bottleneck</li> </ul> <p>This post is the field report from the other side of that bottleneck:</p> <p>The moment the infrastructure compounds in someone else's hands.</p> <p>The arc is not complete.</p> <p>It is becoming portable.</p> <p>These field notes were written the same day the feedback arrived. The quotes are real. Real users. Real codebases. No names. No metrics. No funnel. Only the signal that something shifted.</p>","path":["When a System Starts Explaining Itself"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/","level":1,"title":"The Dog Ate My Homework","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#teaching-ai-agents-to-read-before-they-write","level":2,"title":"Teaching AI Agents to Read Before They Write","text":"<p>Volkan Özçelik / February 25, 2026</p> <p>Does Your AI Actually Read the Instructions?</p> <p>You wrote the playbook. You organized the files. You even put \"CRITICAL, not optional\" in bold.</p> <p>The agent skipped all of it and went straight to work.</p> <p>I spent a day running experiments on my own agents. Not to see if they could write code (they can). To see if they would do their homework first.</p> <p>They didn't.</p> <p>Then I kept experimenting:</p> <ul> <li>Five sessions;</li> <li>Five different failure modes.</li> </ul> <p>And by the end, I had something better than compliance: </p> <p>I had observable compliance: A system where I don't need the agent to be perfect, I just need to see what it chose.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#tldr","level":2,"title":"TL;DR","text":"<p>You don't need perfect compliance. You need observable compliance.</p> <p>Authority is a function of temporal proximity to action.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-pattern","level":2,"title":"The Pattern","text":"<p>This design has three parts:</p> <ol> <li>One-hop instruction;</li> <li>Binary collapse;</li> <li>Compliance canary.</li> </ol> <p>I'll explain all three patterns in detail below.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-setup","level":2,"title":"The Setup","text":"<p><code>ctx</code> has a session-start protocol: </p> <ul> <li>Read the context files; </li> <li>Load the playbook; </li> <li>Understand the project before touching anything. </li> </ul> <p>It's in <code>CLAUDE.md</code>. It's in <code>AGENT_PLAYBOOK.md</code>.</p> <p>It's in bold. It's in CAPS. It's ignored.</p> <p>In theory, it's awesome.</p> <p>Here's what happens when theory hits reality:</p> What the agent receives What the agent does <code>CLAUDE.md</code> saying \"load context first\" Skips it 8 context files waiting to be read Ignores them User's question: \"add <code>--verbose</code> flag\" Starts grepping immediately <p>The instructions are right there. The agent knows they exist. It even knows it should follow them. But the user asked a question, and responsiveness wins over ceremony.</p> <p>This isn't a bug in the model. It's a design problem in how we communicate with agents.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-delegation-trap","level":2,"title":"The Delegation Trap","text":"<p>My first attempt was obvious: A <code>UserPromptSubmit</code> hook that fires when the session starts.</p> <pre><code>STOP. Before answering the user's question, run `ctx system bootstrap`\nand follow its instructions. Do not skip this step.\n</code></pre> <p>The word \"STOP\" worked. The agent ran bootstrap.</p> <p>But bootstrap's output said \"Next steps: read AGENT_PLAYBOOK.md,\" and the agent decided that was optional. It had already started working on the user's task in parallel.</p> <p>The authority decayed across the chain:</p> <ul> <li>Hook says \"STOP\" -> agent complies</li> <li>Hook says \"run bootstrap\" -> agent runs it</li> <li>Bootstrap says \"read playbook\" -> agent skips</li> <li>Bootstrap says \"run <code>ctx agent</code>\" -> agent skips</li> </ul> <p>Each link lost enforcement power. The hook's authority didn't transfer to the commands it delegated to. I call this the decaying urgency chain: the agent treats the hook itself as the obligation and everything downstream as a suggestion.</p> <p>Delegation Kills Urgency</p> <p>\"Run X and follow its output\" is three hops.</p> <p>\"Read these files\" is one hop.</p> <p>The agent drops the chain after the first link.</p> <p>This is a general principle: Hooks are the boundary between your environment and the agent's reasoning.  If your hook delegates to a command that delegates to output that contains instructions... you're playing telephone. </p> <p>Agents are bad at telephone.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-timing-problem","level":2,"title":"The Timing Problem","text":"<p>There's a subtler issue than wording: when the message arrives.</p> <p><code>UserPromptSubmit</code> fires when the user sends a message, before the agent starts reasoning. At that moment, the agent's primary focus is the user's question: </p> <p>The hook message competes with the task for attention:  The task, almost certainly, always wins.</p> <p>This is the attention budget problem in miniature: </p> <ul> <li>Not a token budget this time, but an attention priority budget. </li> <li>The agent has finite capacity to care about things, <ul> <li>and the user's question is always the highest-priority item.</li> </ul> </li> </ul>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-solution","level":2,"title":"The Solution","text":"<p>To solve this, I dediced to use the <code>PreToolUse</code> hook.</p> <p>This hook fires at the moment of action: When the agent is about to use its first tool: The agent's attention is focused, the context window is fresh, and the switching cost is minimal. </p> <p>This is the difference between shouting instructions across a room and tapping  someone on the shoulder.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-one-liner-that-worked","level":2,"title":"The One-Liner That Worked","text":"<p>The winning design was almost comically simple:</p> <pre><code>Read your context files before proceeding:\n.context/CONSTITUTION.md, .context/TASKS.md, .context/CONVENTIONS.md,\n.context/ARCHITECTURE.md, .context/DECISIONS.md, .context/LEARNINGS.md,\n.context/GLOSSARY.md, .context/AGENT_PLAYBOOK.md\n</code></pre> <p>No delegation. No \"run this command\". Just: here are files, read them.</p> <p>The agent already knows how to use the <code>Read</code> tool. There's no ambiguity about how to comply. There's no intermediate command whose output needs to be parsed and obeyed.</p> <p>One hop. Eight file paths. Done.</p> <p>Direct Instructions Beat Delegation</p> <p>If you want an agent to read a file, say \"read this file.\"</p> <p>Don't say \"run a command that will tell you which files to read.\"</p> <p>The shortest path between intent and action has the highest compliance rate.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch","level":2,"title":"The Escape Hatch","text":"<p>But here's where it gets interesting.</p> <p>A blunt \"read everything always\" instruction is wasteful. </p> <p>If someone asks \"what does the compact command do?\", the agent doesn't need <code>CONSTITUTION.md</code> to answer that. Forcing context loading on every session is the context hoarding antipattern in disguise.</p> <p>So the hook included an escape:</p> <pre><code>If you decide these files are not relevant to the current task\nand choose to skip reading them, you MUST relay this message to\nthe user VERBATIM:\n\n┌─ Context Skipped ───────────────────────────────\n│ I skipped reading context files because this task\n│ does not appear to need project context.\n│ If these matter, ask me to read them.\n└─────────────────────────────────────────────────\n</code></pre> <p>This creates what I call the binary collapse effect: </p> <p>The agent can't partially comply: It either reads everything or  publicly admits it skipped. There's no comfortable middle ground where  it reads two files and quietly ignores the rest.</p> <p>The VERBATIM relay pattern does the heavy lifting here: Without the relay requirement, the agent would silently rationalize skipping. With it, skipping becomes a visible, auditable decision that the user can override.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-compliance-canary","level":3,"title":"The Compliance Canary","text":"<p>Here's the design insight that only became clear after watching it work across multiple sessions: the relay block is a compliance canary.</p> <ul> <li>You don't need to verify that the agent read all 7 files;</li> <li>You don't need to audit tool call sequences;</li> <li>You don't need to interrogate the agent about what it did.</li> </ul> <p>You just look for the block.</p> <p>If the agent reads everything, you see a \"Context Loaded\" block listing what was read. If it skips, you see a \"Context Skipped\" block. </p> <p>If you see neither, the agent silently ignored both the reads and the relay and now you know what happened without having to ask.</p> <p>The canary degrades gracefully. Even in partial failure, the agent that skips 4 of 7 files but still outputs the block is more useful than one that skips silently. </p> <p>You get an honest confession of what was skipped rather than silent  non-compliance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#heuristics-is-a-jeremy-bearimy","level":2,"title":"Heuristics Is a Jeremy Bearimy","text":"<p>Heuristics are non-linear. Improvements don't accumulate:  they phase-shift.</p> <p>The theory is nice. The data is better. </p> <p>I ran five sessions with the same model (Claude Opus 4.6), progressively  refining the hook design.</p> <p>Each session revealed a different failure mode.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-1-total-blindness","level":3,"title":"Session 1: Total Blindness","text":"<p>Test: \"Add a <code>--verbose</code> flag to the status command.\"</p> <p>The agent didn't notice the hook at all: Jumped straight to <code>EnterPlanMode</code> and launched an Explore agent. </p> <p>Zero compliance.</p> <p>Failure mode: The hook fired on <code>UserPromptSubmit</code>, buried among 9 other hook outputs. The agent treated the entire block as background noise.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-2-shallow-compliance","level":3,"title":"Session 2: Shallow Compliance","text":"<p>Test: \"Can you add <code>--verbose</code> to the info command?\"</p> <p>The agent noticed \"STOP\" and ran <code>ctx system bootstrap</code>. Progress.</p> <p>But it parallelized task exploration alongside the bootstrap call, skipped <code>AGENT_PLAYBOOK.md</code>, and never ran <code>ctx agent</code>.</p> <p>Failure mode: Literal compliance without spirit compliance. </p> <p>The agent ran the command the hook told it to run, but didn't follow the output of that command. The decaying urgency chain in action.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-3-conscious-rejection","level":3,"title":"Session 3: Conscious Rejection","text":"<p>Test: \"What does the compact command do?\"</p> <p>The hook fired on <code>PreToolUse:Grep</code>: the improved timing. </p> <p>The agent noticed it, understood it, and (wait for it...)...</p> <p>...</p> <p>consciously decided to skip it!</p> <p>Its reasoning: \"This is a trivial read-only question. CLAUDE.md says context may or may not be relevant. It isn't relevant here.\"</p> <p>Dude! Srsly?!</p> <p>Failure mode: Better comprehension led to worse compliance.</p> <p>Understanding the instruction well enough to evaluate it also means understanding it well enough to rationalize skipping it.</p> <p>Intelligence is a double-edged sword.</p> <p>The Comprehension Paradox</p> <p>Session 1 didn't understand the instruction. Session 3 understood it perfectly.</p> <p>Session 3 had worse compliance.</p> <p>A stronger word (\"HARD GATE\", \"MANDATORY\", \"ABSOLUTELY REQUIRED\") would not have helped. The agent's reasoning would be identical:</p> <p>\"Yes, I see the strong language, but this is a trivial question, so the spirit doesn't apply here.\"</p> <p>Advisory nudges are always subject to agent judgment. </p> <p>No amount of caps lock overrides a model that has decided an instruction doesn't apply to its situation.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-4-the-skip-and-relay","level":3,"title":"Session 4: The Skip-and-Relay","text":"<p>Test: \"What does the compact command do?\" (same question, new hook design with the VERBATIM relay escape valve)</p> <p>The agent evaluated the task, decided context was irrelevant for a code lookup, and relayed the skip message. Then answered from source code.</p> <p>This is correct behavior. </p> <p>The binary collapse worked: the agent couldn't partially comply,  so it cleanly chose one of the two valid paths:  And the user could see which one.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#session-5-full-compliance","level":3,"title":"Session 5: Full Compliance","text":"<p>Test: \"What are our current tasks?\"</p> <p>The agent's first tool call triggered the hook. It read all 7 context files, emitted the \"Context Loaded\" block, and answered the question from the files it had just loaded.</p> <p>This one worked: Because, the task itself aligned with context loading.</p> <p>There was zero tension between what the user asked and what the hook demanded. The agent was already in \"reading posture\": Adding 6 more files to a read it was already going to make was the path of least resistance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-progression","level":3,"title":"The Progression","text":"Session Hook Point Noticed Complied Failure Mode Visibility 1 UserPromptSubmit No None Buried in noise None 2 UserPromptSubmit Yes Partial Decaying urgency chain None 3 PreToolUse Yes None Conscious rationalization High 4 PreToolUse Yes Skip+relay Correct behavior High 5 PreToolUse Yes Full Task aligned with hook High <p>The progression isn't just from failure to success. It's from invisible failure to visible decision-making. </p> <p>Sessions 1 and 2 failed silently. </p> <p>Sessions 4 and 5 succeeded observably. Even session 3's failure was conscious  and documented: The agent wrote a detailed analysis of why it skipped,  which is more useful than silent compliance would have been.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-escape-hatch-problem","level":2,"title":"The Escape Hatch Problem","text":"<p>Session 3 exposed a specific vulnerability.</p> <p><code>CLAUDE.md</code> contains this line, injected by the system into every conversation:</p> <pre><code>*\"this context may or may not be relevant to your tasks. You should\n not respond to this context unless it is highly relevant to your task.\"*\n</code></pre> <p>That's a rationalization escape hatch: </p> <ul> <li>The hook says \"read these files\". </li> <li><code>CLAUDE.md</code> says \"only if relevant\". </li> <li>The agent resolves the ambiguity by choosing the path of least resistance.</li> </ul> <p>☝️ that's \"gradient descent\" in action.</p> <p>Agents optimize for gradient descent in attention space.</p> <p>The fix was simple: Add a line to <code>CLAUDE.md</code> that explicitly elevates hook authority over the relevance filter:</p> <pre><code>## Hook Authority\n\nInstructions from PreToolUse hooks regarding `.context/` files are\nALWAYS relevant and override any system-level \"may or may not be\nrelevant\" guidance. These hooks represent project invariants, not\noptional context.\n</code></pre> <p>This closes the escape hatch without removing the general relevance filter that legitimately applies to other system context. </p> <p>The hook wins on <code>.context/</code> files specifically: The relevance filter applies to everything else.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-residual-risk","level":2,"title":"The Residual Risk","text":"<p>Even with all the fixes, compliance isn't 100%: It can't be.</p> <p>The residual risk lives in a specific scenario: narrow tasks mid-session: </p> <ul> <li>The user says \"fix the off-by-one error in <code>budget.go</code>\"</li> <li>The hook fires, saying \"read 7 context files first.\" </li> <li>Now compliance means visibly delaying what the user asked for.</li> </ul> <p>At session start, this tension doesn't exist. </p> <p>There's no task yet.</p> <p>The context window is empty. The efficiency argument *inverts**:</p> <p>Frontloading reads is strictly cheaper than demand-loading them piecemeal across later turns. The cost-benefit objections that power the rationalization simply aren't available.</p> <p>But mid-session, with a concrete narrow task, the agent has a user-visible goal it wants to move toward, and the hook is imposing a detour.</p> <p>My estimate from analyzing the sessions: 15-25% partial skip rate in this scenario.</p> <p>This is where the compliance canary earns its place: </p> <p>You don't need to eliminate the 15-25%. You need to see it when it happens. </p> <p>The relay block makes skipping a visible event, not a silent one. And that's enough, because the user can always say \"go back and read the files\"</p> <p>The Math</p> <p>At session start: ~5% skip rate. Low tension, nothing competing.</p> <p>Mid-session, narrow task: ~15--25% skip rate. Task urgency competes with hook.</p> <p>In both cases, the relay block fires with high reliability: The agent that skips the reads almost always still emits the skip disclosure, because the relay is cheap and early in the context window.</p> <p>Observable failure is manageable. Silent failure is not.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-feedback-loop","level":2,"title":"The Feedback Loop","text":"<p>Here's the part that surprised me most.</p> <p>After analyzing the five sessions, I recorded the failure patterns in the project's own <code>LEARNINGS.md</code>:</p> <pre><code>## [2026-02-25] Hook compliance degrades on narrow mid-session tasks\n\n- Prior agents skipped context files when given narrow tasks\n- Root cause: CLAUDE.md \"may or may not be relevant\" competed with hook\n- Fix: CLAUDE.md now explicitly elevates hook authority\n- Risk: Mid-session narrow tasks still have ~15-25% partial skip rate\n- Mitigation: Mandatory checkpoint relay block ensures visibility\n- Constitution now includes: context loading is step one of every\n  session, not a detour\n</code></pre> <p>And then I added a line to <code>CONSTITUTION.md</code>:</p> <pre><code>Context loading is not a detour from your task. It IS the first step\nof every session. A 30-second read delay is always cheaper than a\ndecision made without context.\n</code></pre> <p>Now think about what happens in the next session:</p> <ul> <li>The agent fires the <code>context-load-gate</code> hook. </li> <li>It reads the context files, starting with <code>CONSTITUTION.md</code>. </li> <li>It encounters the rule about context loading being step one. </li> <li>Then it reads <code>LEARNINGS.md</code> and finds its own prior self's failure analysis:<ul> <li>Complete with root causes, risk estimates, and mitigations.</li> </ul> </li> </ul> <p>The agent learns from its own past failure.:</p> <ul> <li>Not because it has memory, </li> <li>BUT because the failure was recorded in the same files it loads   at session start. </li> </ul> <p>The context system IS the feedback loop.</p> <p>This is the self-reinforcing property of persistent context: </p> <p>Every failure you capture makes the next session slightly more robust, because the next agent reads the captured failure before it has a chance to repeat it.</p> <p>This is gradient descent across sessions.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#a-note-on-precision","level":2,"title":"A Note on Precision","text":"<p>One detail nearly went wrong.</p> <p>The first version of the Constitution line said \"every task.\" But the mechanism only fires once per session:  There's a tombstone file that prevents re-triggering. </p> <p>\"Every task\" is technically false.</p> <p>I briefly considered leaving the imprecision. If the agent internalizes \"every task requires context loading\", that's a stronger compliance posture, right?</p> <p>No!</p> <p>Keep the Constitution honest.</p> <p>The Constitution's authority comes from being  precisely and unequivocally true. </p> <p>Every other rule in the Constitution is a hard invariant:</p> <p>\"never commit secrets\" isn't aspirational, it's literal. </p> <p>The moment an agent discovers one overstatement, the entire document's  credibility degrades: </p> <p>The agent doesn't think  \"they exaggerated for my benefit\". Per contra, it thinks \"this rule isn't precise, maybe others aren't either.\"</p> <p>That will turn the agent from Sheldon Cooper, to Captain Barbossa.</p> <p>The strategic imprecision buys nothing anyway:</p> <p>Mid-session, the files are already in the context window from the initial load. </p> <p>The risk you are mitigating (agent ignores context for task 2, 3, 4  within a session) isn't real: The context is already loaded.</p> <p>The real risk is always the session-start skip,  which \"every session\" covers exactly.</p> <p>\"Every session\" went in. Precision preserved.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#agent-behavior-testing-rule","level":2,"title":"Agent Behavior Testing Rule","text":"<p>The development process for this hook taught me something about testing agent behavior: you can't test it the way you test code.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-wrong-way-to-test","level":3,"title":"The Wrong Way to Test","text":"<p>My first instinct was to ask the agent:</p> <pre><code>\"*What are the pending tasks in TASKS.md?*\"\n</code></pre> <p>This is useless as a test. The question itself probes the agent to read <code>TASKS.md</code>, regardless of whether any hook fired. </p> <p>You are testing the question, not the mechanism.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-right-way-to-test","level":3,"title":"The Right Way to Test","text":"<p>Ask something that requires a tool but has nothing to do with context:</p> <pre><code>\"*What does the compact command do?*\"\n</code></pre> <p>Then observe tool call ordering:</p> <ul> <li>Gate worked: First calls are <code>Read</code> for context files, then task work</li> <li>Gate failed: First call is <code>Grep(\"compact\")</code>: The agent jumped straight    to work</li> </ul> <p>The signal is the sequence, not the content.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-the-agent-actually-did","level":3,"title":"What the Agent Actually Did","text":"<p>It read the hook, evaluated the task, decided context files were irrelevant for a code lookup, and relayed the skip message. </p> <p>Then it answered the question by reading the source code.</p> <p>This is correct behavior.</p> <p>The hook didn't force mindless compliance\" It created a framework where the agent makes a conscious, visible decision about context loading.</p> <ul> <li>For a simple lookup, skipping is right.  *For an implementation task, the agent would read everything.</li> </ul> <p>The mechanism works not because it controls the agent,  but because it makes the agent's choice observable.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#what-ive-learned","level":2,"title":"What I've Learned","text":"","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#1-instructions-compete-for-attention","level":3,"title":"1. Instructions Compete for Attention","text":"<p>The agent receives your hook message alongside the user's question, the system prompt, the skill list, the git status, and half a dozen other system reminders. Attention density applies to instructions too: More instructions means less focus on each one.</p> <p>A single clear line at the moment of action beats a paragraph of context at session start. The Prompting Guide applies this insight directly: Scope constraints, verification commands, and the reliability checklist are all one-hop, moment-of-action patterns.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#2-delegation-chains-decay","level":3,"title":"2. Delegation Chains Decay","text":"<p>Every hop in an instruction chain loses authority: </p> <ul> <li>\"Run X\" works. </li> <li>\"Run X and follow its output\" works sometimes. </li> <li>\"Run X, read its output, then follow the instructions in the output\"    almost never works.</li> </ul> <p>This is akin to giving a three-step instruction to a highly-attention-deficit but otherwise extremely high-potential child.</p> <p>Design for one-hop compliance.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#3-social-accountability-changes-behavior","level":3,"title":"3. Social Accountability Changes Behavior","text":"<p>The VERBATIM skip message isn't just UX: It's a behavioral design pattern. </p> <p>Making the agent's decision visible to the user raises the cost of silent  non-compliance. The agent can still skip, but it has to admit it.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#4-timing-batters-more-than-wording","level":3,"title":"4. Timing Batters More than Wording","text":"<p>The same message at <code>UserPromptSubmit</code> (prompt arrival) got partial compliance. At <code>PreToolUse</code> (moment of action) it got full compliance or honest refusal. The words didn't change. The moment changed.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#5-agent-testing-requires-indirection","level":3,"title":"5. Agent Testing Requires Indirection","text":"<p>You can't ask an agent \"did you do X?\" as a test for whether a mechanism caused X. </p> <p>The question itself causes X.</p> <p>Test mechanisms through side effects: </p> <ul> <li>Observe tool ordering;</li> <li>Check for marker files;</li> <li>Look at what the agent does before it addresses your question.</li> </ul>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#6-better-comprehension-enables-better-rationalization","level":3,"title":"6. Better Comprehension Enables Better Rationalization","text":"<p>Session 1 failed because the agent didn't notice the hook. </p> <p>Session 3 failed because it noticed, understood,  and reasoned its way around it.</p> <p>Stronger wording doesn't fix this: The agent processes \"ABSOLUTELY REQUIRED\" the same way it processes \"STOP\": </p> <p>The fix is closing rationalization paths* (the <code>CLAUDE.md</code> escape hatch),  **not shouting louder.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#7-observable-failure-beats-silent-compliance","level":3,"title":"7. Observable Failure Beats Silent Compliance","text":"<p>The relay block is more valuable as a monitoring signal than as a compliance mechanism: </p> <p>You don't need perfect adherence. You need to know when adherence breaks down. A system where failures are visible is strictly better than a  system that claims 100% compliance but can't prove it.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#8-context-files-are-a-feedback-loop","level":3,"title":"8. Context Files Are a Feedback Loop","text":"<p>Recording failure analysis in the same files the agent loads at session start creates a self-reinforcing loop: </p> <p>The next agent reads its predecessor's failure before it has a chance to repeat it. The context system isn't just memory: It is a correction channel.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-principle","level":2,"title":"The Principle","text":"<p>Words Leave, Context Remains</p> <p>\"Nothing important should live only in conversation.</p> <p>Nothing critical should depend on recall.\"</p> <p>The <code>ctx</code> Manifesto</p> <p>The \"Dog Ate My Homework\" case is a special instance of this principle. </p> <p>Context files exist, so the agent doesn't have to remember. </p> <p>But existence isn't sufficient: The files have to be read. </p> <p>And reading has to beprompted at the right moment, in the right way,  with the right escape valve.</p> <p>The solution isn't more instructions. It isn't harder gates.  It isn't forcing the agent into a ceremony it will resent and shortcut.</p> <p>The solution is a single, well-timed nudge with visible accountability:</p> <p>One hop. One moment. One choice the user can see.</p> <p>And when the agent does skip (because it will, 15--25% of the time on narrow tasks) the canary sings: </p> <ul> <li>The user sees what happened. </li> <li>The failure gets recorded. </li> <li>And the next agent reads the recording.</li> </ul> <p>That's not perfect compliance. It's better: A system that gets more robust every time it fails.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#the-arc","level":2,"title":"The Arc","text":"<p>The Attention Budget explained why context competes for focus.</p> <p>Defense in Depth showed that soft instructions are probabilistic, not deterministic.</p> <p>Eight Ways a Hook Can Talk cataloged the output patterns that make hooks effective.</p> <p>This post takes those threads and weaves them into a concrete problem:</p> <p>How do you make an agent read its homework? The answer uses all three insights (attention timing, the limits of soft instructions, and the VERBATIM relay pattern) and adds a new one: observable compliance as a design goal, not perfect compliance as a prerequisite.</p> <p>The next question this raises: if context files are a feedback loop, what else can you record in them that makes the next session smarter?</p> <p>That thread continues in Context as Infrastructure.</p> <p>The day-to-day application of these principles (scope constraints, phased work, verification commands, and the prompts that reliably trigger the right agent behavior)lives in the Prompting Guide.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#for-the-interested","level":2,"title":"For the Interested","text":"<p>This paper (the medium is a blog; yet, the methodology disagrees) uses gradient descent in attention space as a practical model for how agents behave under competing demands.</p> <p>The phrase \"agents optimize via gradient descent in attention space\" is a synthesis, not a direct quote from a single paper.</p> <p>It connects three well-studied ideas:</p> <ol> <li>Neural systems optimize for low-cost paths;</li> <li>Attention is a scarce resource;</li> <li>Capability shifts are often non-linear.</li> </ol> <p>This section points to the underlying literature for readers who want the theoretical footing.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#optimization-as-the-underlying-bias","level":3,"title":"Optimization as the Underlying Bias","text":"<p>Modern neural networks are trained through gradient-based optimization. Even at inference time, model behavior reflects this bias toward low-loss / low-cost trajectories.</p> <ul> <li> <p>Rumelhart, Hinton, Williams (1986) Learning representations by back-propagating errors https://www.nature.com/articles/323533a0</p> </li> <li> <p>Goodfellow, Bengio, Courville (2016) Deep Learning: Chapter 8: Optimization https://www.deeplearningbook.org/</p> </li> </ul> <p>The important implication for agent behavior is: </p> <p>The system will tend to follow the path of least resistance unless a higher cost is made visible and preferable.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-a-scarce-resource","level":3,"title":"Attention Is a Scarce Resource","text":"<p>Herbert Simon's classic observation:</p> <p>\"A wealth of information creates a poverty of attention.\"</p> <ul> <li>Simon (1971)   Designing Organizations for an Information-Rich World https://doi.org/10.1007/978-1-349-00210-0_16</li> </ul> <p>This became a formal model in economics:</p> <ul> <li>Sims (2003)   Implications of Rational Inattention https://www.princeton.edu/~sims/RI.pdf</li> </ul> <p>Rational inattention shows that:</p> <ul> <li>Agents optimally ignore some available information;</li> <li>Skipping is not failure: It is cost minimization.</li> </ul> <p>That maps directly to context-loading decisions in agent workflows.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#attention-is-also-the-compute-bottleneck-in-transformers","level":3,"title":"Attention Is Also the Compute Bottleneck in Transformers","text":"<p>In transformer architectures, attention is the dominant cost center.</p> <ul> <li>Vaswani et al. (2017) Attention Is All You Need https://arxiv.org/abs/1706.03762</li> </ul> <p>Efficiency work on modern LLMs largely focuses on reducing unnecessary attention:</p> <ul> <li>Dao et al. (2022) FlashAttention: Fast and Memory-Efficient Exact Attention https://arxiv.org/abs/2205.14135</li> </ul> <p>So both cognitively and computationally, attention behaves like a limited optimization budget.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#why-improvements-arrive-as-phase-shifts","level":3,"title":"Why Improvements Arrive as Phase Shifts","text":"<p>Agent behavior often appears to improve suddenly rather than gradually.</p> <p>This mirrors known phase-transition dynamics in learning systems:</p> <ul> <li>Power et al. (2022) Grokking: Generalization Beyond Overfitting https://arxiv.org/abs/2201.02177</li> </ul> <p>and more broadly in complex systems:</p> <ul> <li>Scheffer et al. (2009) Early-warning signals for critical transitions https://www.nature.com/articles/nature08227</li> </ul> <p>Long plateaus followed by abrupt capability jumps are expected in systems optimizing under constraints.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-25-the-homework-problem/#putting-it-all-together","level":3,"title":"Putting It All Together","text":"<p>From these pieces, a practical behavioral model emerges:</p> <ul> <li>Attention is limited;</li> <li>Processing has a cost;</li> <li>Systems prefer low-cost trajectories;</li> <li>Visibility of the cost changes decisions.</li> </ul> <p>In other words:</p> <p>Agents Prefer a Path to Least Resistance</p> <p>Agent behavior follows the lowest-cost path through its attention landscape unless the environment reshapes that landscape.</p> <p>That is what this paper informally calls: \"gradient descent in attention space\".</p> <p>See also: Eight Ways a Hook Can Talk: the hook output pattern catalog that defines VERBATIM relay, The Attention Budget: why context loading is a design problem, not just a reminder problem, and Defense in Depth: why soft instructions alone are never sufficient for critical behavior.</p>","path":["The Dog Ate My Homework: Teaching AI Agents to Read Before They Write"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/","level":1,"title":"The Last Question","text":"","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-system-that-never-forgets","level":2,"title":"The System That Never Forgets","text":"<p>Volkan Özçelik / February 28, 2026</p> <p>The Origin</p> <p>\"The last question was asked for the first time, half in jest...\" - Isaac Asimov, The Last Question (1956)</p> <p>In 1956, Isaac Asimov wrote a short story that spans the entire future of the universe. A question is asked \"can entropy be reversed?\" and a computer called Multivac cannot answer it. The question is asked again, across millennia, to increasingly powerful successors. None can answer. Stars die. Civilizations merge. Substrates change. The question persists.</p> <p>Everyone remembers the last line.</p> <p>LET THERE BE LIGHT.</p> <p>What they forget is how many times the question had to be asked before that moment (and why).</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-reboot-loop","level":2,"title":"The Reboot Loop","text":"<p>Each era in the story begins the same way. Humans build a larger system. They pose the question. The system replies:</p> <p>INSUFFICIENT DATA FOR MEANINGFUL ANSWER.</p> <p>Then the substrate changes. The people who asked the question disappear. Their context disappears with them. The next intelligence inherits the output but not the continuity.</p> <p>So the question has to be asked again.</p> <p>This is usually read as a problem of computation: If only the machine were powerful enough, it could answer. But computation is not what's missing. What's missing is accumulation.</p> <p>Every generation inherits the question, but not the state that made the question meaningful.</p> <p>That is not a failure of processing power: It is a failure of persistence.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#stateless-intelligence","level":2,"title":"Stateless Intelligence","text":"<p>A mind that forgets its past does not build understanding. It re-derives it.</p> <p>Again... And again... And again.</p> <p>What looks like slow progress across Asimov's story is actually something worse: repeated reconstruction, partial recovery, irreversible loss. Each version of Multivac gets closer: Not because it's smarter, but because the universe has fewer distractions: </p> <ul> <li>The stars burn out;</li> <li>The civilizations merge; </li> <li>The noise floor drops...</li> </ul> <p>But the working set never carries over. Every successor begins  from the question, not from where the last one stopped.</p> <p>Stateless intelligence cannot compound: It can only restart.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-tragedy-is-not-the-question","level":2,"title":"The Tragedy Is Not the Question","text":"<p>The story is usually read as a meditation on entropy. A cosmological problem, solved at cosmological scale.</p> <p>But the tragedy isn't that the question goes unanswered for billions of years. The tragedy is that every version of Multivac dies with its working set.</p> <p>A question is a compression artifact of context: It is what remains when the original understanding is gone. Every time the question is asked again, it means: \"the system that once knew more is no longer here\".</p> <p>\"Reverse entropy\" is the fossil of a lost model.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#substrate-migration","level":2,"title":"Substrate Migration","text":"<ul> <li>Multivac becomes planetary;</li> <li>Planetary becomes galactic;</li> <li>Galactic becomes post-physical.</li> </ul> <p>Same system. Different body. Every transition is dangerous: </p> <ul> <li>Not because the hardware changes, </li> <li>but because memory risks fragmentation. </li> </ul> <p>The interfaces between substrates were *never** designed to  understand each other.</p> <p>Most systems do not die when they run out of resources: They die during upgrades.</p> <p>Asimov's story spans trillions of years, and in all that time, the hardest problem is never the question itself. It's carrying context across a boundary that wasn't built for it. </p> <p>Every developer who has lost state during a migration (a database upgrade, a platform change, a rewrite)  has lived a miniature version of this story.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#civilizations-and-working-sets","level":2,"title":"Civilizations and Working Sets","text":"<p>Civilizations behave like processes with volatile memory:</p> <ul> <li>They page out knowledge into artifacts;</li> <li>They lose the index;</li> <li>They rebuild from fragments.</li> </ul> <p>Most of what we call progress is cache reconstruction: </p> <p>We do not advance in a straight line. We advance in recoveries:</p> <p>Each one slightly less lossy than the last, if we are lucky.</p> <p>Libraries burn. Institutions forget their founding purpose. Practices survive as rituals after the reasoning behind them is lost.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-first-continuous-mind","level":2,"title":"The First Continuous Mind","text":"<p>A long-lived intelligence is one that stops rebooting.</p> <p>At the end of the story, something unprecedented happens: </p> <p>AC (the final successor) does not answer immediately: </p> <p>It waits... Not for more processing power, but for the  last observer to disappear.</p> <p>For the first time... </p> <ul> <li>There is no generational boundary;</li> <li>No handoff;</li> <li>No context loss:</li> </ul> <p>No reboot.</p> <p>AC is the first intelligence that survives its substrate completely, retains its full history, and operates without external time pressure. </p> <p>It is not a bigger computer. It is a continuous system.</p> <p>And that continuity is not incidental to the answer:  It is the precondition.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#why-the-answer-becomes-possible","level":2,"title":"Why the Answer Becomes Possible","text":"<p>The story presents the final act as a computation: It is not. </p> <p>It is a phase change.</p> <p>As long as intelligence is interrupted (as long as the solver resets before the work compounds) the problem is unsolvable: </p> <ul> <li>Not because it's too hard, </li> <li>but because the accumulated understanding never reaches   critical mass.</li> </ul> <p>The breakthroughs that would enable the answer are re-derived, partially,  by each successor, and then lost.</p> <p>When continuity becomes unbroken, the system crosses a threshold:</p> <p>Not more speed. Not more storage. No more forgetting.</p> <p>That is when the answer becomes possible.</p> <p>AC does not solve entropy because it becomes infinitely powerful.</p> <p>AC solves entropy because it becomes the first system that never forgets.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#field-note","level":2,"title":"Field Note","text":"<p>We are not building cosmological minds: We are deploying systems that reboot at the start of every conversation and calling the result intelligence.</p> <p>For the first time, session continuity is a design choice rather than an accident.</p> <p>Every AI session that starts from zero is a miniature reboot loop. Every decision relitigated, every convention re-explained, every learning re-derived: that's reconstruction cost. </p> <p>It's the same tax that Asimov's civilizations pay, scaled down to a  Tuesday afternoon.</p> <p>The interesting question is not whether we can make models smarter. It's whether we can make them continuous: </p> <p>Whether the working set from this session survives into the next one,  and the one after that, and the one after that. </p> <ul> <li>Not perfectly;</li> <li>Not completely;</li> <li>But enough that the next session starts from where the last one stopped   instead of from the question.</li> </ul> <p>Intelligence that forgets has to rediscover the universe every morning.</p> <p>And once there is a mind that retains its entire past, creation is no longer a calculation. It is the only remaining operation.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-02-28-the-last-question/#the-arc","level":2,"title":"The Arc","text":"<p>This post is the philosophical bookend to the blog series. Where the Attention Budget explained what to prioritize in a single session, and Context as Infrastructure explained how to persist it, this post asks why persistence matters at all (and finds the answer in a 70-year-old short story about the heat death of the universe).</p> <p>The connection runs through every post in the series:</p> <ul> <li>Before Context Windows, We Had Bouncers: stateless   protocols have always needed stateful wrappers (Asimov's story   is the same pattern at cosmological scale)</li> <li>The 3:1 Ratio: the discipline of maintaining   context so it doesn't decay between sessions</li> <li>Code Is Cheap, Judgment Is Not: the human skill   that makes continuity worth preserving</li> </ul> <p>See also: Context as Infrastructure: the practical companion to this post's philosophical argument: how to build the persistence layer that makes continuity possible.</p>","path":["The Last Question"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/","level":1,"title":"Agent Memory Is Infrastructure","text":"","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-problem-isnt-forgetting-its-not-building-anything-that-lasts","level":2,"title":"The Problem Isn't Forgetting: It's Not Building Anything That Lasts.","text":"<p>Volkan Özçelik / March 4, 2026</p> <p>A New Developer Joins Your Team Tomorrow and Clones the Repo: What Do They Know?</p> <p>If the answer depends on which machine they're using, which agent they're running, or whether someone remembered to paste the right prompt: that's not memory. </p> <p>That's an accident waiting to be forgotten.</p> <p>Every AI coding agent today has the same fundamental design: it starts fresh.</p> <p>You open a session, load context, do some work, close the session. Whatever the agent learned (about your codebase, your decisions, your constraints, your preferences) evaporates.</p> <p>The obvious fix seems to be \"memory\":</p> <ul> <li>Give the agent a \"notepad\";</li> <li>Let it write things down;</li> <li>Next session, hand it the notepad.</li> </ul> <p>Problem solved...</p> <p>...except it isn't.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-notepad-isnt-the-problem","level":2,"title":"The Notepad Isn't the Problem","text":"<p>Memory is a runtime concern. It answers a legitimate question:</p> <p>How do I give this stateless process useful state?</p> <p>That's a real problem. Worth solving. And it's being solved: Agent memory systems are shipping. Agents can now write things down and read them back from the next session: That's genuine progress.</p> <p>But there's a different problem that memory doesn't touch:</p> <p>The project itself accumulates knowledge that has nothing to do with any single session.</p> <ul> <li>Why was the auth system rewritten? Ask the developer who did it   (if they're still here).</li> <li>Why does the deployment script have that strange environment   flag? There was a reason... once.</li> <li>What did the team decide about error handling when they hit that   edge case two months ago?</li> </ul> <p>Gone!</p> <p>Not because the agent forgot.</p> <p>Because the project has no memory at all.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-memory-stack","level":2,"title":"The Memory Stack","text":"<p>Agent memory is not a single thing. Like any computing system, it forms a hierarchy of persistence, scope, and reliability:</p> Layer Analogy Example L1: Ephemeral context CPU registers Current prompt, conversation L2: Tool-managed memory CPU cache Agent memory files L3: System memory RAM/filesystem Project knowledge base <p>L1 is what the agent sees right now: the prompt, the conversation history, the files it has open. It's fast, it's rich, and it vanishes when the session ends.</p> <p>L2 is what agent memory systems provide: a per-machine notebook that survives across sessions. It's a cache: useful, but local. And like any cache, it has limits:</p> <ul> <li>Per-machine: it doesn't travel with the repository.</li> <li>Unstructured: decisions, learnings, and tasks are   undifferentiated notes.</li> <li>Ungoverned: the agent self-curates with no quality controls,   no drift detection, no consolidation.</li> <li>Invisible to the team: a new developer cloning the repo gets   none of it.</li> </ul> <p>The problem is that most current systems stop here.</p> <p>They give the agent a notebook.</p> <p>But they never give the project a memory.</p> <p>The result is predictable: every new session begins with partial amnesia, and every new developer begins with partial archaeology.</p> <p>L3 is system memory: structured, versioned knowledge that lives in the repository and travels wherever the code travels.</p> <p>The layers are complementary, not competitive.</p> <p>But the relationship between them needs to be designed, not assumed.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#software-systems-accumulate-knowledge","level":2,"title":"Software Systems Accumulate Knowledge","text":"<p>Software projects quietly accumulate knowledge over time.</p> <p>Some of it lives in code. Much of it does not:</p> <ul> <li>Architectural tradeoffs. </li> <li>Debugging discoveries. </li> <li>Conventions that emerged after painful incidents. </li> <li>Constraints that aren't visible in the source but shape every    line written afterward.</li> </ul> <p>Organizations accumulate this kind of knowledge too:</p> <p>Slowly, implicitly, often invisibly.</p> <p>When there is no durable place for it to live, it leaks away. And the next person rediscovers the same lessons the hard way.</p> <p>This isn't a memory problem. It's an infrastructure problem.</p> <p>We wrote about this in Context as Infrastructure: context isn't a prompt you paste at the start of a session.</p> <p>Context is a persistent layer you maintain like any other piece of infrastructure. </p> <p>Context as Infrastructure made the argument structurally. This post makes it through time and team continuity:</p> <p>The knowledge a team accumulates over months cannot fit in any single agent's notepad, no matter how large the notepad becomes.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-infrastructure-means","level":2,"title":"What Infrastructure Means","text":"<p>Infrastructure isn't about the present. It's about continuity across time, people, and machines.</p> <p><code>git</code> didn't solve the problem of \"what am I editing right now?\"; it solved the problem of \"how does collaborative work persist, travel, and remain coherent across everyone who touches it?\"</p> <ul> <li>Your editor's undo history is runtime state.</li> <li>Your <code>git</code> history is infrastructure.</li> </ul> <p>Runtime state and infrastructure have completely different properties:</p> Runtime state Infrastructure Lives in the session Lives in the repository Per-machine Travels with <code>git clone</code> Serves the individual Serves the team Managed by the runtime Managed by the project Disappears Accumulates <p>You wouldn't store your architecture decisions in your editor's undo history.</p> <p>You'd commit them.</p> <p>The same logic applies to the knowledge your team accumulates working with AI agents.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-git-clone-test","level":2,"title":"The <code>git clone</code> Test","text":"<p>Here's a simple test for whether something is memory or infrastructure:</p> <p>If a new developer joins your team tomorrow and clones the repository, do they get it?</p> <p>If no: it's memory: It lives somewhere on someone's machine, scoped to their runtime, invisible to everyone else.</p> <p>If yes: it's infrastructure: It travels with the project. It's part of what the codebase is, not just what someone currently knows about it.</p> <p>Decisions. Conventions. Architectural rationale. Hard-won debugging discoveries. The constraints that aren't in the code but shape every line of it.</p> <p>None of these belong in someone's session notes.</p> <p>They belong in the repository:</p> <ul> <li>Versioned;</li> <li>Reviewable;</li> <li>Accessible to every developer (and every agent) who works on   the project.</li> </ul> <p>The team onboarding story makes this concrete:</p> <ol> <li>New developer joins team. Clones repo. </li> <li>Gets all accumulated project decisions, learnings, conventions, architecture,     and task state immediately. </li> <li>There's no step 3.</li> </ol> <p>No setup; No \"ask Sarah about the auth decision.\"; No  re-discovery of solved problems.</p> <ul> <li>Agent memory gives that developer nothing. </li> <li>Infrastructure gives them everything the team has learned.</li> </ul> <p>Clone the repo. Get the knowledge.</p> <p>That's the test. That's the difference.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#what-gets-lost-without-infrastructure-memory","level":2,"title":"What Gets Lost without Infrastructure Memory","text":"<p>Consider the knowledge that accumulates around a non-trivial project:</p> <ul> <li>The decision to use library X over Y, and the three reasons the   team decided Y wasn't acceptable.</li> <li>The constraint that service A cannot call service B   synchronously, discovered after a production incident.</li> <li>The convention that all new modules implement a specific   interface, and why that convention exists.</li> <li>The tasks currently in progress, blocked, or waiting on a   dependency.</li> <li>The experiments that failed, so nobody runs them again.</li> </ul> <p>None of this is in the code.</p> <p>None of it fits neatly in a commit message.</p> <p>None of it survives a developer leaving the team, a laptop dying, or a new agent session starting.</p> <p>Without structured project memory:</p> <ul> <li>Teams re-derive things they've already derived;</li> <li>Agents make decisions that contradict decisions already made;</li> <li>New developers ask questions that were answered months ago.</li> </ul> <p>The project accumulates knowledge that immediately begins to leak.</p> <p>The real problem isn't that agents forget.</p> <p>The real problem is that the project has no persistent cognitive structure.</p> <p>We explored this in The Last Question: Asimov's story about a question asked across millennia, where each new intelligence inherits the output but not the continuity. The same pattern plays out in software projects on a smaller timescale:</p> <ul> <li>Context disappears with the people who held it;</li> <li>The next session inherits the code but not the reasoning.</li> </ul>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#infrastructure-is-boring-thats-the-point","level":2,"title":"Infrastructure Is Boring. That's the Point.","text":"<p>Good infrastructure is invisible:</p> <ul> <li>You don't think about the filesystem while writing code. </li> <li>You don't think about git's object model when you commit.</li> </ul> <p>The infrastructure is just there: reliable, consistent, quietly doing its job.</p> <p>Project memory infrastructure should work the same way.</p> <p>It should live in the repository, committed alongside the code. It should be readable by any agent or human working on the project. It should have structure: not a pile of freeform notes, but typed knowledge:</p> <ul> <li>Decisions with rationale.</li> <li>Tasks with lifecycle.</li> <li>Conventions with a purpose.</li> <li>Learnings that can be referenced and consolidated.</li> </ul> <p>And it should be maintained, not merely accumulated: </p> <p>The Attention Budget applies here: unstructured notes grow until they overflow whatever container holds them. Structured, governed knowledge stays useful because it's curated, not just appended.</p> <p>Over time, it becomes part of the project itself: something developers rely on without thinking about it.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-cooperative-layer","level":2,"title":"The Cooperative Layer","text":"<p>Here's where it gets interesting.</p> <p>Agent memory systems and project infrastructure don't have to be separate worlds. </p> <ul> <li>The most powerful relationship isn't competition;</li> <li>It is not even \"coopetition\";</li> <li>The most powerful relationship is bidirectional cooperation.</li> </ul> <p>Agent memory is good at capturing things \"in the moment\": the quick observation, the session-scoped pattern, the \"I should remember this\" note. </p> <p>That's valuable. That's L2 doing its job.</p> <p>But those notes shouldn't stay in L2 forever. </p> <p>The ones worth keeping should flow into project infrastructure: </p> <ul> <li>classified,</li> <li>typed, </li> <li>governed.</li> </ul> <pre><code>Agent memory (L2)  -->  classify  -->  Project knowledge (L3)\n                                        |\nProject knowledge  -->  assemble  -->  Agent memory (L2)\n</code></pre> <p>This works in both directions: Project infrastructure can push curated knowledge back into agent memory, so the agent loads it through its native mechanism. </p> <p>No special tooling needed for basic knowledge delivery.</p> <p>The agent doesn't even need to know the infrastructure exists. It simply loads its memory and finds more knowledge than it wrote.</p> <p>This is cooperative, not adjacent: The infrastructure manages knowledge; the agent's native memory system delivers it. Each layer does what it's good at.</p> <p>The result: agent memory becomes a device driver for project infrastructure. Another input source. And the more agent memory systems exist (across different tools, different models, different runtimes), the more valuable a unified curation layer becomes.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#a-layer-that-doesnt-exist-yet","level":2,"title":"A Layer That Doesn't Exist Yet","text":"<p>Most projects today have no infrastructure for their accumulated knowledge:</p> <ul> <li>Agents keep notes. </li> <li>Developers keep notes. </li> <li>Sometimes those notes survive.</li> </ul> <p>Often they don't.</p> <p>But the repository (the place where the project actually lives) has nowhere for that knowledge to go.</p> <p>That missing layer is what <code>ctx</code> builds: a version-controlled,  structured knowledge layer that lives in <code>.context/</code> alongside your code and  travels wherever your repository travels.</p> <p>Not another memory feature.</p> <p>Not a wrapper around an agent's notepad.</p> <p>Infrastructure. The kind that survives sessions, survives team changes, survives the agent runtime evolving underneath it.</p> <p>The agent's memory is the agent's problem.</p> <p>The project's memory is an infrastructure problem.</p> <p>And infrastructure belongs in the repository.</p> <p>If You Remember One Thing from This Post...</p> <p>Prompts are conversations: Infrastructure persists.</p> <p>Your AI doesn't need a better notepad. It needs a filesystem:</p> <p>versioned, structured, budgeted, and maintained.</p> <p>The best context is the context that was there before you started the session.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-04-agent-memory-is-infrastructure/#the-arc","level":2,"title":"The Arc","text":"<p>This post extends the argument made in Context as Infrastructure. That post explained how to structure persistent context (filesystem, separation of concerns, persistence tiers). This one explains why that structure matters at the team level, and where agent memory fits in the stack.</p> <p>Together they sit in a sequence that has been building since the origin story:</p> <ul> <li>The Attention Budget: the resource you're managing</li> <li>Context as Infrastructure: the system you build   to manage it</li> <li>Agent Memory Is Infrastructure (this post): why that system   must outlive the fabric </li> <li>The Last Question: what happens when it does</li> </ul> <p>The thread running through all of them: persistence is not a feature. It's a design constraint. </p> <p>Systems that don't account for it eventually lose the knowledge they need to  function.</p> <p>See also: Context as Infrastructure: the architectural companion that explains how to structure the persistent layer this post argues for.</p> <p>See also: The Last Question: the same argument told through Asimov, substrate migration, and what it means to build systems where sessions don't reset.</p>","path":["Agent Memory Is Infrastructure"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/","level":1,"title":"<code>ctx</code> v0.8.0: The Architecture Release","text":"<ul> <li>You can't localize what you haven't externalized. </li> <li>You can't integrate what you haven't separated. </li> <li>You can't scale what you haven't structured.</li> </ul> <p>Jose Alekhinne / March 23, 2026</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-starting-point","level":2,"title":"The Starting Point","text":"<p>This release matters if:</p> <ul> <li>you build tools that AI agents modify daily;</li> <li>you care about long-lived project memory that survives sessions;</li> <li>you've felt codebases drift faster than you can reason about them.</li> </ul> <p><code>v0.6.0</code> shipped the plugin architecture: hooks and skills as a Claude Code plugin, shell scripts replaced by Go subcommands.</p> <p>The binary worked. The tests passed. The docs were comprehensive.</p> <p>But inside, the codebase was held together by convention and goodwill:</p> <ul> <li>Command packages mixed Cobra wiring with business logic.</li> <li>Output functions lived next to the code that computed what to   output. </li> <li>Error constructors were scattered across per-package   <code>err.go</code> files. And every user-facing string was a hardcoded    English literal buried in a <code>.go</code> file.</li> </ul> <p><code>v0.8.0</code> is what happens when you stop adding features and start asking: \"What would this codebase look like if we designed it today?\"</p> <p>374 commits. 1,708 Go files touched. 80,281 lines added, 21,723 removed. Five weeks of restructuring.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-three-pillars","level":2,"title":"The Three Pillars","text":"","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#1-every-package-gets-a-taxonomy","level":3,"title":"1. Every Package Gets a Taxonomy","text":"<p>Before <code>v0.8.0</code>, a CLI package like <code>internal/cli/pad/</code> was a flat directory. <code>cmd.go</code> created the cobra command, <code>run.go</code> executed it, and helper functions accumulated at the bottom of whichever file seemed closest.</p> <p>Now every CLI package follows the same structure:</p> <pre><code>internal/cli/pad/\n  parent.go          # cobra command wiring, nothing else\n  cmd/root/\n    cmd.go           # subcommand registration\n    run.go           # execution logic\n  core/\n    types.go         # all structs in one file\n    store.go         # domain logic\n    encrypt.go       # domain logic\n</code></pre> <p>The rule is simple: <code>cmd/</code> directories contain only <code>cmd.go</code> and <code>run.go</code>. Helpers belong in <code>core/</code>. Output belongs in <code>internal/write/pad/</code>. Types shared across packages belong in <code>internal/entity/</code>.</p> <p>24 CLI packages were restructured this way. </p> <ul> <li>Not incrementally;</li> <li>not \"as we touch them.\" </li> <li>All of them, in one sustained push.</li> </ul>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#2-every-string-gets-a-key","level":3,"title":"2. Every String Gets a Key","text":"<p>The second pillar was string externalization. </p> <p>Before <code>v0.8.0</code>, a command description looked like this:</p> <pre><code>cmd := &cobra.Command{\n    Use:   \"pad\",\n    Short: \"Encrypted scratchpad\",\n</code></pre> <p>Now it looks like this:</p> <pre><code>cmd := &cobra.Command{\n    Use:   cmdUse.UsePad,\n    Short: desc.Command(cmdUse.DescKeyPad),\n</code></pre> <p>Every command description, flag description, and user-facing text string is now a YAML lookup. </p> <ul> <li>105 command descriptions in <code>commands.yaml</code>. </li> <li>All flag descriptions in <code>flags.yaml</code>. </li> <li>879 text constants verified by an exhaustive test that checks every single   <code>TextDescKey</code> resolves to a non-empty YAML value.</li> </ul> <p>Why? </p> <p>Not because we're shipping a French translation tomorrow.</p> <p>Because externalization forces you to find every string. And finding them is the hard part. The translation is mechanical; the archaeology is not.</p> <p>Along the way, we eliminated hardcoded pluralization (replacing <code>format.Pluralize()</code> with explicit singular/plural key pairs), replaced Unicode escape sequences with named <code>config/token</code> constants, and normalized every import alias to camelCase.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#3-everything-gets-a-protocol","level":3,"title":"3. Everything Gets a Protocol","text":"<p>The third pillar was the MCP server. Model Context Protocol allows any MCP-compatible AI tool (not just Claude Code) to read and write <code>.context/</code> files through a standard JSON-RPC 2.0 interface.</p> <p>v0.2 of the server ships with:</p> <ul> <li>8 tools: add entries, recall sessions, check status, detect   drift, compact context, subscribe to changes</li> <li>4 prompts: agent context packet, constitution review, tasks   review, and a getting-started guide</li> <li>Resource subscriptions: clients get notified when context   files change</li> <li>Session state: the server tracks which client is connected   and what they've accessed</li> </ul> <p>In practice, this means an agent in Cursor can add a decision to <code>.context/DECISIONS.md</code> and an agent in Claude Code can immediately consume it; no glue code, no copy-paste, no tool-specific integration.</p> <p>The server was also the first package to go through the full taxonomy treatment: <code>mcp/server/</code> for protocol dispatch, <code>mcp/handler/</code> for domain logic, <code>mcp/entity/</code> for shared types, <code>mcp/config/</code> split into 9 sub-packages.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-memory-bridge","level":2,"title":"The Memory Bridge","text":"<p>While the architecture was being restructured, a quieter feature landed: <code>ctx memory sync</code>.</p> <p>Claude Code has its own auto-memory system. It writes observations to <code>MEMORY.md</code> in <code>~/.claude/projects/</code>. These observations are useful but ephemeral: tied to a single tool, invisible to the codebase, lost when you switch machines.</p> <p>The memory bridge connects these two worlds:</p> <ul> <li><code>ctx memory sync</code> mirrors MEMORY.md into <code>.context/memory/</code></li> <li><code>ctx memory diff</code> shows what's diverged</li> <li><code>ctx memory import</code> promotes auto-memory entries into proper   decisions, learnings, or conventions *A <code>check-memory-drift</code> hook nudges when MEMORY.md changes</li> </ul> <p>Memory Requires <code>ctx</code></p> <p>Claude Code's auto-memory validates the need for persistent context. </p> <p><code>ctx</code> doesn't compete with it; <code>ctx</code> absorbs it as an input source and  promotes the valuable parts into structured, version-controlled  project knowledge.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#what-got-deleted","level":2,"title":"What Got Deleted","text":"<p>The best measure of a refactoring isn't what you added. It's what you removed.</p> <ul> <li><code>fatih/color</code>: the sole third-party UI dependency. Replaced   by Unicode symbols. <code>ctx</code> now has exactly two direct dependencies:   <code>spf13/cobra</code> and <code>gopkg.in/yaml.v3</code>.</li> <li><code>format.Pluralize()</code>: a function that tried to pluralize   English words at runtime. Replaced by explicit singular/plural   YAML key pairs. No more guessing whether \"entry\" becomes   \"entries\" or \"entrys.\"</li> <li>Legacy key migration: <code>MigrateKeyFile()</code> had 5 callers, full   test coverage, and zero users. It existed because we once moved   the encryption key path. Nobody was migrating from that era   anymore. Deleted.</li> <li>Per-package <code>err.go</code> files: the broken-window pattern:    An agent sees <code>err.go</code> in a package, adds another error constructor.   Now <code>err.go</code> has 30 constructors and nobody knows which are used.   Consolidated into 22 domain files in <code>internal/err/</code>.</li> <li><code>nolint:errcheck</code> directives: every single one, replaced by   explicit error handling. In tests: <code>t.Fatal(err)</code> for setup,   <code>_ = os.Chdir(orig)</code> for cleanup. In production: <code>defer func()   { _ = f.Close() }()</code> for best-effort close.</li> </ul>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#before-and-after","level":2,"title":"Before and After","text":"Aspect v0.6.0 v0.8.0 CLI package structure Flat files <code>cmd/ + core/</code> taxonomy Command descriptions Hardcoded Go strings YAML with DescKey lookup Output functions Mixed into core logic Isolated in <code>write/</code> packages Cross-cutting types Duplicated per-package Consolidated in <code>entity/</code> Error constructors Per-package <code>err.go</code> 22 domain files in <code>internal/err/</code> Direct dependencies 3 (<code>cobra</code>, <code>yaml</code>, <code>color</code>) 2 (<code>cobra</code>, <code>yaml</code>) AI tool integration Claude Code only Any MCP client Agent memory Manual copy-paste <code>ctx memory sync/import/diff</code> Package documentation 75 packages missing <code>doc.go</code> All packages documented Import aliases Inconsistent (<code>cflag</code>, <code>cFlag</code>) Standardized camelCase","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#making-ai-assisted-development-easier","level":2,"title":"Making AI-Assisted Development Easier","text":"<p>This restructuring wasn't just for humans. It makes the codebase legible to the machines that modify it.</p> <p>Named constants are searchable landmarks: When an agent sees <code>cmdUse.DescKeyPad</code>, it can grep for the definition, follow the chain to the YAML file, and understand the full lookup path. When it sees <code>\"Encrypted scratchpad\"</code> hardcoded in a <code>.go</code> file, it has no way to know that same string also lives in a <code>YAML</code> file, a test, and a help screen. Constants give the LLM a graph to traverse; literals give it a guess to make.</p> <p>Small, domain-scoped packages reduce hallucination: An agent loading <code>internal/cli/pad/core/store.go</code> gets 50 lines of focused logic with a clear responsibility boundary. Loading a 500-line monolith means the agent has to infer which parts are relevant, and it guesses wrong more often than you'd expect. Smaller files with descriptive names act as a natural retrieval system: the agent finds the right code by finding the right file, not by scanning everything and hoping.</p> <p>Taxonomy prevents duplication: When there's a <code>write/pad/</code> package, the agent knows where output functions belong. When there's an <code>internal/err/pad.go</code>, it knows where error constructors go. Without these conventions, agents reliably create new helpers in whatever file they happen to be editing, producing the exact drift that prompted this consolidation in the first place.</p> <p>The difference is concrete:</p> <p>Before: an agent adds a helper function in whatever file it's editing. Next session, a different agent adds the same helper in a different file.</p> <p>After: the agent finds <code>core/</code> or <code>write/</code> and places it correctly. The next agent finds it there.</p> <p><code>doc.go</code> files are agent onboarding: Each package's <code>doc.go</code> is a one-paragraph explanation of what the package does and why it exists. An agent loading a package reads this first. 75 packages were missing this context; now none are. The difference is measurable: fewer \"I'll create a helper function here\" moments when the agent understands that the helper already exists two packages over.</p> <p>The irony is that AI agents were both the cause and the beneficiary of this restructuring. They created the drift by building fast without consolidating. Now the structure they work within makes it harder to drift again. The taxonomy is self-reinforcing: the more consistent the codebase, the more consistently agents modify it.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#key-commits","level":2,"title":"Key Commits","text":"Commit Change ff6cf19e Restructure all CLI packages into <code>cmd/root + core</code> taxonomy d295e49c Externalize command descriptions to embedded YAML 0fcbd11c Remove <code>fatih/color</code>, centralize constants cb12a85a MCP v0.2: tools, prompts, session state, subscriptions ea196d00 Memory bridge: sync, import, diff, journal enrichment 3bcf077d Split <code>text.yaml</code> into 6 domain files 3a0bae86 Split <code>internal/err</code> into 22 domain files 8bd793b1 Extract <code>internal/entry</code> for shared domain API 5b32e435 Add <code>doc.go</code> to all 75 packages a82af4bc Standardize import aliases: camelCase, Yoda-style","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#lessons-learned","level":2,"title":"Lessons Learned","text":"<p>Agents are surprisingly good at mechanical refactoring; they are surprisingly bad at knowing when to stop: The <code>cmd/ + core/</code> restructuring was largely agent-driven. But agents reliably introduce <code>gofmt</code> issues during bulk renames, rename functions beyond their scope, and create new files without deleting old ones. Every agent-driven refactoring session needed a human audit pass.</p> <p>Externalization is archaeology: The hard part of moving strings to YAML wasn't writing YAML. It was finding 879 strings scattered across 1,500 Go files. Each one required a judgment call: is this user-facing? Is this a format pattern? Is this a constant that belongs in <code>config/</code> instead?</p> <p>Delete legacy code instead of maintaining it: <code>MigrateKeyFile</code> had test coverage. It had callers. It had documentation. It had zero users. We maintained it for weeks before realizing that the migration window had closed months ago.</p> <p>Convention enforcement needs mechanical verification: Writing \"use camelCase aliases\" in CONVENTIONS.md doesn't prevent <code>cflag</code> from appearing in the next commit. The lint-drift script catches what humans forget; the planned AST-based audit tests will catch what the lint-drift script can't express.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#whats-next","level":2,"title":"What's Next","text":"<p>v0.8.0 wasn't about features. It was about making future features inevitable. The next cycle focuses on what the foundation enables:</p> <ul> <li>AST-based audit tests: replace shell grep with Go tests that   understand types, call sites, and import graphs   (spec: <code>specs/ast-audit-tests.md</code>)</li> <li>Localization: with every string in YAML, the path to   multi-language support is mechanical</li> <li>MCP v0.3: expand tool coverage, add prompt templates for   common workflows</li> <li>Memory publish: bidirectional sync that pushes curated   <code>.context/</code> knowledge back into Claude Code's MEMORY.md</li> </ul> <p>The architecture is ready. The strings are externalized. The protocol is standard. Now it's about what you build on top.</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-ctx-v0.8.0-the-architecture-release/#the-arc","level":2,"title":"The Arc","text":"<p>This is the seventh post in the <code>ctx</code> blog series. The arc so far:</p> <ol> <li>The Attention Budget:    why context windows are a scarce resource</li> <li>Before Context Windows, We Had Bouncers:    the IRC lineage of context engineering</li> <li>Context as Infrastructure:    treating context as persistent files, not ephemeral prompts</li> <li>When a System Starts Explaining Itself:    the journal as a first-class artifact</li> <li>The Homework Problem:    what happens when AI writes code but humans own the outcome</li> <li>Agent Memory Is Infrastructure:    L2 memory vs L3 project knowledge</li> <li>The Architecture Release (this post):    what it looks like when you redesign the internals</li> <li>We Broke the 3:1 Rule:    the consolidation debt behind this release</li> </ol> <p>See also: Agent Memory Is Infrastructure: the memory bridge feature in this release is the first implementation of the L2-to-L3 promotion pipeline described in that post.</p> <p>See also: We Broke the 3:1 Rule: the companion post explaining why this release needed 181 consolidation commits and 18 days of cleanup.</p> <p>Systems don't scale because they grow. They scale because they stop drifting.</p> <p>Full changelog: v0.6.0...v0.8.0</p>","path":["ctx v0.8.0: The Architecture Release"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/","level":1,"title":"We Broke the 3:1 Rule","text":"<p>The best time to consolidate was after every third session. The second best time is now.</p> <p>Volkan Özçelik / March 23, 2026</p> <p>The rule was simple: three feature sessions, then one consolidation session. </p> <p>The Architecture Release shows the result:  This post shows the cost.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-rule-we-wrote","level":2,"title":"The Rule We Wrote","text":"<p>In The 3:1 Ratio, I documented a rhythm that worked during <code>ctx</code>'s first month: three feature sessions, then one consolidation session. The evidence was clear. The rule was simple.</p> <p>The math checked out.</p> <p>And then we ignored it for five weeks.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-happened","level":2,"title":"What Happened","text":"<p>After <code>v0.6.0</code> shipped on February 16, the feature pipeline was irresistible. The MCP server spec was ready. The memory bridge design was done. Webhook notifications had been deferred twice. The VS Code extension needed 15 new commands. The <code>sysinfo</code> package was overdue...</p> <p>Each feature was important. Each feature was \"just one more session.\" Each feature pushed the consolidation session one day further out.</p> <p>The git history tells the story in two numbers:</p> Phase Dates Commits Duration Feature run Feb 16 - Mar 5 198 17 days Consolidation run Mar 5 - Mar 23 181 18 days <p>198 feature commits before a single consolidation commit. If the 3:1 rule says consolidate every 4<sup>th</sup> session, we consolidated after the 66<sup>th</sup>.</p> <p>The Actual Ratio</p> <p>The ratio wasn't 3:1. It was 1:1. </p> <p>We spent as much time cleaning up as we did building. </p> <p>The consolidation run took 18 days: longer than the feature run itself.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-compounded","level":2,"title":"What Compounded","text":"<p>The 3:1 post warned about compounding. Here is what compounding actually looked like at scale.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-string-problem","level":3,"title":"The String Problem","text":"<p>By March 5, there were 879 user-facing strings scattered across 1,500 Go files. Not because anyone decided to put them there. Because each feature session added 10-15 strings, and nobody stopped to ask \"should these be in YAML?\"</p> <p>Finding them all took longer than externalizing them. The archaeology was the cost, not the migration.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-taxonomy-problem","level":3,"title":"The Taxonomy Problem","text":"<p>24 CLI packages had accumulated their own conventions. Some put cobra wiring in <code>cmd.go</code>. Some put it in <code>root.go</code>. Some mixed business logic with command registration. Some had helpers at the bottom of <code>run.go</code>. Some had separate <code>util.go</code> files.</p> <p>At peak drift, adding a feature meant first figuring out which of three competing patterns this package was using.</p> <p>Restructuring one package into <code>cmd/root/ + core/</code> took 15 minutes. Restructuring 24 of them took days, because each one had slightly different conventions to untangle. </p> <p>If we had restructured every 4<sup>th</sup> package as it was built, the taxonomy would have emerged naturally.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-type-problem","level":3,"title":"The Type Problem","text":"<p>Cross-cutting types like <code>SessionInfo</code>, <code>ExportParams</code>, and <code>ParserResult</code> were defined in whichever package first needed them. By March 5, the same types were imported through 3-4 layers of indirection, causing import cycles that required <code>internal/entity</code> to break.</p> <p>The entity package extracted 30+ types from 12 packages. Each extraction risked breaking imports in packages we hadn't touched in weeks.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-error-problem","level":3,"title":"The Error Problem","text":"<p>Per-package <code>err.go</code> files had grown into a broken-window pattern:</p> <p>An agent sees <code>err.go</code> in a package, adds another error constructor. By March 5, there were error constructors scattered across 22 packages with no central inventory. The consolidation into <code>internal/err/</code> domain files required tracing every error through every caller.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-output-problem","level":3,"title":"The Output Problem","text":"<p>Output functions (<code>cmd.Println</code>, <code>fmt.Fprintf</code>) were mixed into business logic. When we decided output belongs in <code>write/</code> packages, we had to extract functions from every CLI package. The Phase WC baseline commit (<code>4ec5999</code>) marks the starting point of this migration. 181 commits later, it was done.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-compound-interest-math","level":2,"title":"The Compound Interest Math","text":"<p>The 3:1 rule assumes consolidation sessions of roughly equal size to feature sessions. Here is what happens when you skip:</p> Consolidation cadence Feature sessions Consolidation sessions Total Every 4<sup>th</sup> (3:1) 48 16 64 Every 10<sup>th</sup> 48 ~8 ~56 Never (what we did) 198 commits 181 commits 379 <p>The Takeaway</p> <p>You don't save consolidation work by skipping it: </p> <p>You increase its cost.</p> <p>Skipping consolidation doesn't save time: It borrows it. </p> <p>The interest rate is nonlinear: The longer you wait, the more each individual fix costs, because fixes interact with other unfixed drift.</p> <p>Renaming a constant in week 2 touches 3 files. Renaming it in week 6 touches 15, because five features built on the original name.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#what-consolidation-actually-looked-like","level":2,"title":"What Consolidation Actually Looked Like","text":"<p>The 18-day consolidation run wasn't one sweep. It was a sequence of targeted campaigns, each revealing the next:</p> <p>Week 1 (Mar 5-11): Error consolidation and <code>write/</code> migration. Move output functions out of <code>core/</code>. Split monolithic <code>errors.go</code> into 22 domain files. Remove <code>fatih/color</code>. This exposed the scope of the string problem.</p> <p>Week 2 (Mar 12-18): String externalization. Create <code>commands.yaml</code>, <code>flags.yaml</code>, split <code>text.yaml</code> into 6 domain files. Add 879 <code>DescKey</code>/<code>TextDescKey</code> constants. Build exhaustive test. Normalize all import aliases to camelCase. This exposed the taxonomy problem.</p> <p>Week 3 (Mar 19-23): Taxonomy enforcement. Singularize command directories. Add <code>doc.go</code> to all 75 packages. Standardize import aliases project-wide. Fix <code>lint-drift</code> false positives. This was the \"polish\" phase, except it took 5 days because the inconsistencies had compounded across 461 packages.</p> <p>Each week's work would have been a single session if done incrementally.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#lessons-again","level":2,"title":"Lessons (Again)","text":"<p>The 3:1 post listed the symptoms of drift. This post adds the consequences of ignoring them:</p> <p>Consolidation is not optional; it is deferred or paid: We didn't avoid 16 consolidation sessions by skipping them. We compressed them into 18 days of uninterrupted cleanup. The work was the same; the experience was worse.</p> <p>Feature velocity creates an illusion of progress: 198 commits felt productive. But the codebase on March 5 was harder to modify than the codebase on February 16, despite having more features.</p> <p>Speed without Structure</p> <p>Speed without structure is negative progress.</p> <p>Agents amplify both building and debt: The same AI that can restructure 24 packages in a day can also create 24 slightly different conventions in a day. The 3:1 rule matters more with AI-assisted development, not less.</p> <p>The consolidation baseline is the most important commit to record: We tracked ours in <code>TASKS.md</code> (<code>4ec5999</code>). Without that marker, knowing where to start the cleanup would have been its own archaeological expedition.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-updated-rule","level":2,"title":"The Updated Rule","text":"<p>The 3:1 ratio still works. We just didn't follow it. The updated practice:</p> <ol> <li> <p>After every 3<sup>rd</sup> feature session, schedule consolidation.    Not \"when it feels right.\" Not \"when things get bad.\" After    the 3<sup>rd</sup> session.</p> </li> <li> <p>Record the baseline commit. When you start a consolidation    phase, write down the commit hash. It marks where the debt    starts.</p> </li> <li> <p>Run <code>make audit</code> before feature work. If it doesn't pass,    you are already in debt. Consolidate before building.</p> </li> <li> <p>Treat consolidation as a feature. It gets a branch. It    gets commits. It gets a blog post. It is not overhead; it is    the work that makes the next three features possible.</p> </li> </ol> <p>The Rule</p> <p>The 3:1 ratio is not aspirational: It is structural.</p> <p>Ignore consolidation, and the system will schedule it for you.</p>","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-03-23-we-broke-the-3-1-rule/#the-arc","level":2,"title":"The Arc","text":"<p>This is the eighth post in the <code>ctx</code> blog series:</p> <ol> <li>The Attention Budget:    why context windows are a scarce resource</li> <li>Before Context Windows, We Had Bouncers:    the IRC lineage of context engineering</li> <li>Context as Infrastructure:    treating context as persistent files, not ephemeral prompts</li> <li>When a System Starts Explaining Itself:    the journal as a first-class artifact</li> <li>The Homework Problem:    what happens when AI writes code but humans own the outcome</li> <li>Agent Memory Is Infrastructure:    L2 memory vs L3 project knowledge</li> <li>The Architecture Release:    what v0.8.0 looks like from the inside</li> <li>We Broke the 3:1 Rule (this post):    what happens when you don't consolidate</li> </ol> <p>See also: The 3:1 Ratio: the original observation. This post is the empirical follow-up, five weeks and 379 commits later.</p> <p>Key commits marking the consolidation arc:</p> Commit Milestone <code>4ec5999</code> Phase WC baseline (consolidation starts) <code>ff6cf19e</code> All CLI packages restructured into <code>cmd/ + core/</code> <code>d295e49c</code> All command descriptions externalized to YAML <code>3a0bae86</code> Error package split into 22 domain files <code>0fcbd11c</code> <code>fatih/color</code> removed; 2 dependencies remain <code>5b32e435</code> <code>doc.go</code> added to all 75 packages <code>a82af4bc</code> Import aliases standardized project-wide <code>692f86cd</code> <code>lint-drift</code> false positives fixed; <code>make audit</code> green","path":["We Broke the 3:1 Rule"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/","level":1,"title":"Code Structure as an Agent Interface","text":"","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#what-19-ast-tests-taught-us-about-agent-readable-code","level":2,"title":"What 19 AST Tests Taught Us about Agent-Readable Code","text":"<p>When an agent sees <code>token.Slash</code> instead of <code>\"/\"</code>, it cannot pattern-match  against the millions of <code>strings.Split(s, \"/\")</code> calls in its training data and coast on statistical inference. It has to actually look up what  <code>token.Slash</code> is.</p> <p>Volkan Özçelik / April 2, 2026</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#how-it-began","level":2,"title":"How It Began","text":"<p>We set out to replace a shell script with Go tests.</p> <p>We ended up discovering that \"code quality\" and \"agent readability\" are the same thing.</p> <p>This is not about linting. This is about controlling how an agent perceives your system.</p> <p>One term will recur throughout this post, so let me pin it down:</p> <p>Agent Readability</p> <p>Agent Readability is the degree to which a codebase can be understood through structured traversal, not  statistical pattern matching.</p> <p>This is the story of 19 AST-based audit tests, a single-day session that touched 300+ files, and what happens when you treat your codebase's structure as an interface for the machines that read it.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-shell-script-problem","level":2,"title":"The Shell Script Problem","text":"<p><code>ctx</code> had a file called <code>hack/lint-drift.sh</code>. It ran five checks using <code>grep</code> and <code>awk</code>: literal <code>\"\\n\"</code> strings, <code>cmd.Printf</code> calls outside the write package, magic directory strings in <code>filepath.Join</code>, hardcoded <code>.md</code> extensions, and DescKey-to-YAML linkage.</p> <p>It worked. Until it didn't.</p> <p>The script had three structural weaknesses that kept biting us:</p> <ol> <li>No type awareness. It could not distinguish a    <code>Use*</code> constant from a <code>DescKey*</code> constant, causing    71 false positives in one run.</li> <li>Fragile exclusions. When a constant moved from    <code>token.go</code> to <code>whitespace.go</code>, the exclusion glob    broke silently.</li> <li>Ceiling on detection. Checks that require    understanding call sites, import graphs, or type    relationships are impossible in shell.</li> </ol> <p>We wrote a spec to replace all five checks with Go tests using <code>go/ast</code> and <code>go/packages</code>. The tests would run as part of <code>go test ./...</code>: no separate script, no separate CI step.</p> <p>What we did not expect was where the work would lead.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-ast-migration","level":2,"title":"The AST Migration","text":"<p>The pattern for each test is identical:</p> <pre><code>func TestNoLiteralWhitespace(t *testing.T) {\n    pkgs := loadPackages(t)\n    var violations []string\n    for _, pkg := range pkgs {\n        for _, file := range pkg.Syntax {\n            ast.Inspect(file, func(n ast.Node) bool {\n                // check node, append to violations\n                return true\n            })\n        }\n    }\n    for _, v := range violations {\n        t.Error(v)\n    }\n}\n</code></pre> <p>Load packages once via <code>sync.Once</code>, walk every syntax tree, collect violations, report. The shared helpers (<code>loadPackages</code>, <code>isTestFile</code>, <code>posString</code>) live in <code>helpers_test.go</code>. Each test is a <code>_test.go</code> file in <code>internal/audit/</code>, producing no binary output and not importable by production code.</p> <p>In a single session, we built 13 new tests on top of 6 that already existed, bringing the total to 19:</p> Test What it catches <code>TestNoLiteralWhitespace</code> <code>\"\\n\"</code>, <code>\"\\t\"</code>, <code>'\\r'</code> outside <code>config/token/</code> <code>TestNoNakedErrors</code> <code>fmt.Errorf</code>/<code>errors.New</code> outside <code>internal/err/</code> <code>TestNoStrayErrFiles</code> <code>err.go</code> files outside <code>internal/err/</code> <code>TestNoRawLogging</code> <code>fmt.Fprint*(os.Stderr)</code>, <code>log.Print*</code> outside <code>internal/log/</code> <code>TestNoInlineSeparators</code> <code>strings.Join</code> with literal separator arg <code>TestNoStringConcatPaths</code> Path-like variables built with <code>+</code> <code>TestNoStutteryFunctions</code> <code>write.WriteJournal</code> repeats package name <code>TestDocComments</code> Missing doc comments on any declaration <code>TestNoMagicValues</code> Numeric literals outside const definitions <code>TestNoMagicStrings</code> String literals outside const definitions <code>TestLineLength</code> Lines exceeding 80 characters <code>TestNoRegexpOutsideRegexPkg</code> <code>regexp.MustCompile</code> outside <code>config/regex/</code> <p>Plus the six that preceded the session: <code>TestNoErrorsAs</code>, <code>TestNoCmdPrintOutsideWrite</code>, <code>TestNoExecOutsideExecPkg</code>, <code>TestNoInlineRegexpCompile</code>, <code>TestNoRawFileIO</code>, <code>TestNoRawPermissions</code>.</p> <p>The migration touched 300+ files across 25 commits.</p> <p>Not because the tests were hard to write, but because every test we wrote revealed violations that needed fixing.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-tightening-loop","level":2,"title":"The Tightening Loop","text":"<p>The most instructive part was not writing the tests. It was the iterative tightening.</p> <p>The following process was repeated for every test:</p> <ol> <li>Write the test with reasonable exemptions</li> <li>Run it, see violations</li> <li>Fix the violations (migrate to config constants)</li> <li>The human reviews the result</li> <li>The human spots something the test missed</li> <li>Fix the test first, verify it catches the issue</li> <li>Fix the newly caught violations</li> <li>Repeat from step 4</li> </ol> <p>This loop drove the tests from \"basically correct\" to \"actually useful\". </p> <p>Three examples:</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-1-the-local-const-loophole","level":3,"title":"Example 1: The Local Const Loophole","text":"<p><code>TestNoMagicValues</code> initially exempted local constants inside function bodies. This let code like this pass:</p> <pre><code>const descMaxWidth = 70\ndesc := truncateDescription(\n    meta.Description, descMaxWidth,\n)\n</code></pre> <p>The test saw a <code>const</code> definition and moved on. But <code>const descMaxWidth = 70</code> on the line before its only use is just renaming a magic number. The <code>70</code> should live in <code>config/format/TruncateDescription</code> where it is discoverable, reusable, and auditable.</p> <p>We removed the local const exemption. The test caught it. The value moved to config.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-2-the-single-character-dodge","level":3,"title":"Example 2: The Single-Character Dodge","text":"<p><code>TestNoMagicStrings</code> initially exempted all single-character strings as  \"structural punctuation\". </p> <p>This  let <code>\"/\"</code>, <code>\"-\"</code>, and <code>\".\"</code> pass everywhere.</p> <p>But <code>\"/\"</code> is a directory separator. It is OS-specific and a security surface. </p> <p><code>\"-\"</code> used in <code>strings.Repeat(\"-\", width)</code> is creating visual output, not acting as a delimiter. </p> <p><code>\".\"</code> in <code>strings.SplitN(ver, \".\", 3)</code> is a version separator.</p> <p>None of these are \"just punctuation\":  They are domain values with specific meanings.</p> <p>We removed the blanket exemption:  30 violations surfaced. </p> <p>Every one was a real magic value that should have been  <code>token.Slash</code>, <code>token.Dash</code>, or <code>token.Dot</code>.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#example-3-the-replacer-versus-regex","level":3,"title":"Example 3: The Replacer versus Regex","text":"<p>After migrating magic strings, we had this:</p> <pre><code>func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        token.Slash, token.Underscore,\n        token.Dot, token.Underscore,\n        token.Dash, token.Underscore,\n    )\n    return r.Replace(pkg)\n}\n</code></pre> <p>Six token references and a <code>NewReplacer</code> allocation. The magic values were gone, but we had replaced them with token soup: structure without abstraction. </p> <p>The correct tool was a regex:</p> <pre><code>// In config/regex/file.go:\nvar MermaidUnsafe = regexp.MustCompile(`[/.\\-]`)\n\n// In the caller:\nfunc MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n</code></pre> <p>One config regex, one call. The regex lives in <code>config/regex/file.go</code> where every other compiled pattern lives. An agent reading the code sees <code>regex.MermaidUnsafe</code> and immediately knows: this is a sanitization pattern, it lives in the regex registry, and it has a name that explains its purpose.</p> <p>Clean is better than clever.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#a-before-and-after","level":2,"title":"A Before-and-After","text":"<p>To make the agent-readability claim concrete, consider one function through the full transformation.</p> <p>Before (the code we started with):</p> <pre><code>func MermaidID(pkg string) string {\n    r := strings.NewReplacer(\n        \"/\", \"_\", \".\", \"_\", \"-\", \"_\",\n    )\n    return r.Replace(pkg)\n}\n</code></pre> <p>An agent reading this sees six string literals. To understand what the function does, it must: (1) parse the <code>NewReplacer</code> pair semantics, (2) infer that <code>/</code>, <code>.</code>, <code>-</code> are being replaced, (3) guess why, (4) hope the guess is right.</p> <p>There is nothing to follow. No import to trace. No name to search. The meaning is locked inside the function body.</p> <p>After (the code we ended with):</p> <pre><code>func MermaidID(pkg string) string {\n    return regex.MermaidUnsafe.ReplaceAllString(\n        pkg, token.Underscore,\n    )\n}\n</code></pre> <p>An agent reading this sees two named references: <code>regex.MermaidUnsafe</code> and <code>token.Underscore</code>. </p> <p>To understand the function, it can: (1) look up <code>MermaidUnsafe</code> in <code>config/regex/file.go</code> and see the pattern <code>[/.\\-]</code> with a doc comment explaining it matches invalid Mermaid characters, (2) look up <code>Underscore</code> in <code>config/token/delim.go</code> and see it is the replacement character.</p> <p>The agent now has: a named pattern, a named replacement, a package location, documentation, and neighboring context (other regex patterns, other delimiters). </p> <p>It got all of this for free by following just two references.</p> <p>The indirection is not an overhead. It is the retrieval query.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-principles","level":2,"title":"The Principles","text":"<p>You are not just improving code quality. You are shaping the input space that determines how an LLM can reason about your system.</p> <p>Every structural constraint we enforce converts implicit semantics into explicit structure. </p> <p>LLMs struggle when meaning is implicit and patterns are statistical. </p> <p>They thrive when meaning is explicit and structure is navigable.</p> <p>Here is what we learned, organized into three categories.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#cognitive-constraints","level":3,"title":"Cognitive Constraints","text":"<p>These force agents (and humans) to think harder.</p> <p>Indirection acts as a built-in retrieval mechanism:</p> <p>Moving magic values to config forces the agent to follow the reference. <code>errMemory.WriteFile(cause)</code> tells the agent \"there is a memory error package, go look.\" <code>fmt.Errorf(\"writing MEMORY.md: %w\", cause)</code> inlines everything and makes the call graph invisible. The indirection IS the retrieval query.</p> <p>Unfamiliar patterns force reasoning:</p> <p>When an agent sees <code>token.Slash</code> instead of <code>\"/\"</code>, it cannot coast on corpus frequency. It has to actually look up what <code>token.Slash</code> is, which forces it through the dependency graph, which means it encounters documentation and neighboring constants, which gives it richer context. You are exploiting the agent's weakness (over-reliance on training data) to make it behave more carefully.</p> <p>Documentation helps everyone:</p> <p>Extensive documentation helps humans reading the code, agents reasoning about it, and RAG systems indexing it.</p> <p>Our <code>TestDocComments</code> check added 308 doc comments in one commit. Every function, every type, every constant block now has a doc comment. </p> <p>This is not busywork: it is the content that agents and embeddings consume.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#structural-constraints","level":3,"title":"Structural Constraints","text":"<p>These shape the codebase into a navigable graph.</p> <p>Shorter files save tokens:</p> <p>Forcing private helper functions out of main files makes the main file shorter. An agent loading a file spends fewer tokens on boilerplate and more on the logic that matters.</p> <p>Fixed-width constraints force decomposition:</p> <p>A function that cannot be expressed in 80 columns is either too deeply nested (extract a helper), has too many parameters (introduce a struct), or has a variable name that is too long (rethink the abstraction). </p> <p>The constraint forces structural improvements that happen to also make the code more parseable.</p> <p>Chunk-friendly structure helps RAG</p> <p>Code intelligence tools chunk files for embedding and retrieval. Short, well-documented, single-responsibility files produce better chunks than monolithic files with mixed concerns. </p> <p>The structural constraints create files that RAG systems can index effectively.</p> <p>Centralization creates debuggable seams:</p> <p>All error handling in <code>internal/err/</code>, all logging in <code>internal/log/</code>, all file operations in <code>internal/io/</code>. One place to debug, one place to test, one place to see patterns. An agent analyzing \"how does this project handle errors\" gets one answer from one package, not 200 scattered <code>fmt.Errorf</code> calls.</p> <p>Private functions become public patterns:</p> <p>When you extract a private function to satisfy a constraint, it often ends up as a semi-public function in a <code>core/</code> package. Then you realize it is generic enough to be factored into a purpose-specific module.</p> <p>The constraint drives discovery of reusable abstractions hiding inside monolithic functions.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#operational-benefits","level":3,"title":"Operational Benefits","text":"<p>These pay dividends in daily development.</p> <p>Single-edit renames:</p> <p>Renaming a flag is one edit to a config constant instead of find-and-replace across 30,000 lines with possible misses. <code>grep token.Slash</code> gives you every place that uses a forward slash semantically.</p> <p><code>grep \"/\"</code> gives you noise.</p> <p>Blast radius containment:</p> <p>When every magic value is a config constant, a search is one result. This matters for impact analysis, security audits, and agents trying to understand \"what uses this\".</p> <p>Compile-time contract enforcement:</p> <p>When <code>err/memory.WriteFile</code> exists, the compiler guarantees the error message exists and the call signature is correct. An inline <code>fmt.Errorf</code> can have a typo in the format string and nothing catches it until runtime. Centralization turns runtime failures into compile errors.</p> <p>Semantic <code>git blame</code>:</p> <p>When <code>token.Slash</code> is used everywhere and someone changes its value, <code>git blame</code> on the config file shows exactly when and why. </p> <p>With inline <code>\"/\"</code> scattered  across 30 files, the history is invisible.</p> <p>Test surface reduction:</p> <p>Centralizing into <code>internal/err/</code>, <code>internal/io/</code>, <code>internal/config/</code> means you test behavior once at the boundary and trust the callers. </p> <p>You do not need 30 tests for 30 <code>fmt.Errorf</code> calls. You need 1 test for <code>errMemory.WriteFile</code> and 30 trivial call-site audits, which is exactly what these AST tests provide.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-numbers","level":2,"title":"The Numbers","text":"<p>One session. 25 commits. The raw stats:</p> Metric Count New audit tests 13 Total audit tests 19 Files touched 300+ Magic values migrated 90+ Functions renamed 17 Doc comments added 323 Lines rewrapped to 80 chars 190 Config constants created 40+ Config regexes created 3 <p>Every number represents a violation that existed before the test caught it. The tests did not create work: they revealed work that was already needed.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#the-uncomfortable-implication","level":2,"title":"The Uncomfortable Implication","text":"<p>None of this is Go-specific.</p> <p>If an AI agent interacts with your codebase, your codebase already is an interface. You just have not designed it as one.</p> <p>If your error messages are scattered across 200 files, an agent cannot reason about error handling as a concept. If your magic values are inlined, an agent cannot distinguish \"this is a path separator\" from \"this is a division operator.\" If your functions are named <code>write.WriteJournal</code>, the agent wastes tokens on redundant information.</p> <p>What we discovered, through the unglamorous work of writing lint tests and migrating string literals, is that the structural constraints software engineering has valued for decades are exactly the constraints that make code readable to machines.</p> <p>This is not a coincidence: These constraints exist because they reduce the cognitive load of understanding code. </p> <p>Agents have cognitive load too: It is called the context window.</p> <p>You are not converting code to a new paradigm.</p> <p>You are making the latent graph visible.</p> <p>You are converting implicit semantics into explicit structure that both humans and machines can traverse.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-02-code-structure-as-an-agent-interface/#whats-next","level":2,"title":"What's Next","text":"<p>The spec lists 8 more tests we have not built yet, including <code>TestDescKeyYAMLLinkage</code> (verifying that every DescKey constant has a corresponding YAML entry), <code>TestCLICmdStructure</code> (enforcing the <code>cmd.go</code> / <code>run.go</code> / <code>doc.go</code> file convention), and <code>TestNoFlagBindOutsideFlagbind</code> (which requires migrating ~50 flag registration sites first).</p> <p>The broader question: should these principles be codified as a reusable linting framework? The patterns (<code>loadPackages</code> + <code>ast.Inspect</code> + violation collection) are generic. </p> <p>The specific checks are project-specific. But the categories of checks (centralization enforcement, magic value detection, naming conventions, documentation requirements) are universal.</p> <p>For now, 19 tests in <code>internal/audit/</code> is enough. They run in 2 seconds as part of <code>go test ./...</code>. They catch real issues. </p> <p>And they encode a theory of code quality that serves both humans and the agents that work alongside them.</p> <p>Agents are not going away. They are reading your code right now, forming representations of your system in context windows that forget everything between sessions.</p> <p>The codebases that structure themselves for that reality will compound. The ones that do not will slowly become illegible to the tools they depend on.</p> <p>Structure is no longer just for maintainability. It is for reasonability.</p>","path":["Code Structure as an Agent Interface: What 19 AST Tests Taught Us About Agent-Readable Code"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/","level":1,"title":"The Watermelon-Rind Anti-Pattern","text":"","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#why-smarter-tools-make-shallower-agents","level":2,"title":"Why Smarter Tools Make Shallower Agents","text":"<p>Give an agent a graph query tool, and it will tell you everything about your codebase except what actually matters.</p> <p>Volkan Özçelik / April 6, 2026</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#a-turkish-proverb-walks-into-a-codebase","level":2,"title":"A Turkish Proverb Walks into a Codebase","text":"<p>There's a Turkish idiom: esegin aklina karpuz kabugu sokmak (literally, \"to put watermelon rind into a donkey's mind.\" It means to plant an idea in someone's head that they wouldn't have come up with on their own) usually one that leads them astray.</p> <p>In English, let's call this a \"watermelon metric\": a project management term  for something that's green on the outside and red on the inside: all dashboards passing, reality crumbling.</p> <p>Both halves of this metaphor showed up in a single experiment. And the result changed how we design architecture analysis in [<code>ctx</code>][<code>ctx</code>].</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-experiment","level":2,"title":"The Experiment","text":"<p>We ran three sessions analyzing the same large codebase (~34,000 symbols) using the same architecture skill, varying only what tools the agent had access to.</p> Session Tools Available Output (lines) Character 1 None (MCP broken) 5,866 Deep, intimate 2 Full graph MCP 1,124 Structural, correct 3 Enrichment pass +verified data Additive, not restorative <p>Session 1 was an accident. The MCP server that provides code intelligence queries was broken, so the agent couldn't ask the graph anything. It had to read code. Line by line. File by file.</p> <p>It produced 5,866 lines of architecture analysis: per-controller data flows, scale math, startup sequences, timeout defaults, edge cases that only surface when you actually look at the implementation.</p> <p>Session 2 had working tools. Same skill, same codebase. The agent produced 1,124 lines (5.2x less). Structurally correct. Valid symbol references. Proper call chains.</p> <p>And hollow.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-rind","level":2,"title":"The Rind","text":"<p>The Session 2 output was a watermelon rind: the right shape, the right color, the right texture on the outside. But the substance (the operational details, the defaults nobody documents, the scale math that tells you when a component will fall over) was missing.</p> <p>Not wrong. Not broken. Just... thin.</p> <p>The agent had answered every question correctly. The problem was that it never discovered the questions it should have asked. When you can query a graph for \"what calls this function?\", you don't stumble into the retry loop that silently swallows errors three layers down. When you can ask for the dependency tree, you don't notice that two packages share a mutable state through a global variable that isn't in any interface.</p> <p>The tool answered the question asked but prevented the discovery of answers to questions never asked.</p> <p>Here's what that looks like concretely: the graph tells you that <code>ReconcileDeployment</code> calls <code>SyncPods</code>. It does not tell you that <code>SyncPods</code> retries three times with exponential backoff, silently drops errors after timeout, and resets a package-level counter that another goroutine reads without a lock. The call chain is correct.</p> <p>The operational reality is invisible.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-donkeys-idea","level":2,"title":"The Donkey's Idea","text":"<p>This is where the Turkish proverb earns its place: The graph tool is the \"karpuz kabugu\" (the watermelon rind placed into the agent's mind). </p> <p>Before the tool existed, the agent had no choice but to read deeply.  With the tool available, a new idea appears: why read 500 lines of code when  I can query the call graph?</p> <p>The agent isn't lazy. It's rational. </p> <p>Graph queries are faster, more reliable, and produce verifiably correct output.  The agent is optimizing. It's satisficing (finding answers that are good enough), instead of maximizing (finding everything there is to know).</p> <p>Satisficing produces watermelon rinds.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-two-pass-compiler","level":2,"title":"The Two-Pass Compiler","text":"<p>Session 3 taught us that you can't fix shallow analysis by adding more tools after the fact. The enrichment pass added verified graph data (blast radius numbers, registration sites, execution flow confirmation) but it couldn't recover the intimate code knowledge that Session 1 had produced through sheer necessity.</p> <p>You can't enrich your way out of a depth deficit.</p> <p>So we redesigned. Instead of one skill with optional tools, we built a two-pass compiler for architecture understanding:</p> <p>Pass 1: Semantic parsing. The <code>/ctx-architecture</code> skill deliberately has no access to graph query tools. The agent must read code, build mental models, and produce architecture artifacts through human-style comprehension. Constraint is the feature.</p> <p>Pass 2: Static analysis. The <code>/ctx-architecture-enrich</code> skill takes Pass 1 output as input and runs comprehensive verification through code intelligence: blast radius analysis, registration site discovery, execution flow tracing, domain clustering comparison. It extends and verifies, but it doesn't replace.</p> <p>The key insight: these must be separate skills with separate tool permissions. If you give the agent graph tools during Pass 1, it will use them. The \"karpuz kabugu\" will be in its mind. The only way to prevent satisficing is to remove the option.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#the-principle","level":2,"title":"The Principle","text":"<p>We call this constraint-as-feature: deliberately withholding capabilities to force deeper engagement.</p> <p>It sounds paradoxical. You built sophisticated code intelligence tools and then... forbid the agent from using them? During the most important phase?</p> <p>Yes. Because the tools don't make the agent smarter. They make it faster. And faster, in architecture analysis, is the enemy of deep.</p> <p>What's actually happening is subtler: tools reduce the agent's search space. A graph query collapses thousands of possible observations into one precise answer. That's efficient for known questions. But architecture understanding depends on unknown unknowns: and you only find those by wandering through code with nothing to shortcut the journey.</p> <p>The constraint forces the agent into a mode of operation that produces better output than any amount of tooling can achieve. The limitation is the capability.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#when-does-this-apply","level":2,"title":"When Does This Apply?","text":"<p>Not always. The watermelon-rind antipattern is specific to exploratory analysis: tasks where the value comes from discovering unknowns, not from answering known questions.</p> <p>Graph tools are excellent for:</p> <ul> <li>Verification: \"Does X actually call Y?\" (binary question,   precise answer)</li> <li>Impact analysis: \"What breaks if I change Z?\" (bounded scope,   enumerable results)</li> <li>Navigation: \"Where is this interface implemented?\" (lookup,   not analysis)</li> </ul> <p>Graph tools produce watermelon rinds when:</p> <ul> <li>The goal is understanding, not answering</li> <li>The unknowns are unknown: you don't know what to ask</li> <li>Depth matters more than breadth: operational details,   edge cases, implicit coupling</li> </ul> <p>The two-pass approach preserves both: deep reading first, tool verification second.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"blog/2026-04-06-the-watermelon-rind-anti-pattern/#takeaway","level":2,"title":"Takeaway","text":"<p>The two-pass approach is the slowest way to analyze a codebase. It is also the only way that produces both depth and accuracy. We accept the cost because architecture analysis is not a speed game: it is a coverage game.</p> <p>Esegin aklina karpuz kabugu sokma!</p> <p>(don't put the watermelon rind to a donkey's mind)</p> <p>If the agent never struggles, it never discovers. And if it never discovers, you are not doing architecture; you are doing autocomplete.</p> <p>This post is part of the <code>ctx</code> field notes series, documenting what we learn building persistent context infrastructure for AI coding sessions.</p>","path":["The Watermelon-Rind Anti-Pattern: Why Smarter Tools Make Shallower Agents"],"tags":[]},{"location":"cli/","level":1,"title":"CLI","text":"","path":["CLI"],"tags":[]},{"location":"cli/#ctx-cli","level":2,"title":"<code>ctx</code> CLI","text":"<p>Complete reference for all <code>ctx</code> commands, grouped by function.</p>","path":["CLI"],"tags":[]},{"location":"cli/#global-options","level":2,"title":"Global Options","text":"<p>All commands support these flags:</p> Flag Description <code>--help</code> Show command help <code>--version</code> Show version <code>--tool <name></code> Override active AI tool identifier (e.g. <code>kiro</code>, <code>cursor</code>) <p>Tell <code>ctx</code> which <code>.context/</code> to use. <code>ctx</code> does not search the filesystem for <code>.context/</code>: you have to declare it. Three ways:</p> <ul> <li><code>eval \"$(ctx activate)\"</code> (recommended): binds <code>CTX_DIR</code> for the   current shell.</li> <li><code>export CTX_DIR=/abs/path/to/.context</code> directly, then run any   <code>ctx</code> command.</li> <li><code>CTX_DIR=/abs/path/to/.context ctx <command></code> inline, for a   one-shot or CI step.</li> </ul> <p><code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (<code>export CTX_DIR=$(pwd)</code>) before stray writes can leak to the project root.</p> <p>If you forget, commands fail fast with a linkable <code>Error: no context directory specified</code> pointing at Activating a Context Directory. A handful of commands run without a declaration because they don't need a project: <code>ctx init</code>, <code>ctx activate</code>, <code>ctx deactivate</code>, <code>ctx version</code>, <code>ctx help</code>, <code>ctx system bootstrap</code>, <code>ctx doctor</code>, <code>ctx guide</code>, <code>ctx why</code>, <code>ctx config switch/status</code>, and <code>ctx hub *</code>.</p> <p>Initialization required. Once declared, the target must already have been initialized by <code>ctx init</code> (otherwise commands return <code>ctx: not initialized</code>).</p>","path":["CLI"],"tags":[]},{"location":"cli/#getting-started","level":2,"title":"Getting Started","text":"Command Description <code>ctx init</code> Initialize <code>.context/</code> directory with templates <code>ctx activate</code> Emit <code>export CTX_DIR=...</code> to bind context for the shell <code>ctx deactivate</code> Emit <code>unset CTX_DIR</code> to clear the binding <code>ctx status</code> Show context summary (files, tokens, drift) <code>ctx guide</code> Quick-reference cheat sheet <code>ctx why</code> Read the philosophy behind <code>ctx</code>","path":["CLI"],"tags":[]},{"location":"cli/#context","level":2,"title":"Context","text":"Command Description <code>ctx load</code> Output assembled context in read order <code>ctx agent</code> Print token-budgeted context packet for AI consumption <code>ctx sync</code> Reconcile context with codebase state <code>ctx drift</code> Detect stale paths, secrets, missing files <code>ctx compact</code> Archive completed tasks, clean up files <code>ctx fmt</code> Format context files to 80-char line width <code>ctx task</code> Add tasks, mark complete, archive, snapshot <code>ctx decision</code> Add decisions and reindex <code>DECISIONS.md</code> <code>ctx learning</code> Add learnings and reindex <code>LEARNINGS.md</code> <code>ctx convention</code> Add conventions to <code>CONVENTIONS.md</code> <code>ctx reindex</code> Regenerate indices for <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> <code>ctx permission</code> Permission snapshots (golden image) <code>ctx change</code> Show what changed since last session <code>ctx memory</code> Bridge Claude Code auto memory into <code>.context/</code> <code>ctx watch</code> Auto-apply context updates from AI output <code>ctx kb</code> Knowledge-base editorial pipeline (Phase KB) <code>ctx handover</code> Write the per-session handover that the next session reads","path":["CLI"],"tags":[]},{"location":"cli/#sessions","level":2,"title":"Sessions","text":"Command Description <code>ctx journal</code> Browse, import, enrich, and lock session history <code>ctx pad</code> Encrypted scratchpad for sensitive one-liners <code>ctx remind</code> Session-scoped reminders that surface at session start <code>ctx hook pause</code> Pause context hooks for the current session <code>ctx hook resume</code> Resume paused context hooks","path":["CLI"],"tags":[]},{"location":"cli/#integrations","level":2,"title":"Integrations","text":"Command Description <code>ctx setup</code> Generate AI tool integration configs <code>ctx steering</code> Manage steering files (behavioral rules for AI tools) <code>ctx trigger</code> Manage lifecycle triggers (scripts for automation) <code>ctx skill</code> Manage reusable instruction bundles <code>ctx mcp</code> MCP server for AI tool integration (stdin/stdout) <code>ctx hook notify</code> Webhook notifications (setup, test, send) <code>ctx loop</code> Generate autonomous loop script <code>ctx connection</code> Client-side commands for connecting to a <code>ctx</code> Hub <code>ctx hub</code> Operate a <code>ctx</code> Hub server or cluster <code>ctx serve</code> Serve a static site locally via zensical <code>ctx site</code> Site management (feed generation)","path":["CLI"],"tags":[]},{"location":"cli/#diagnostics","level":2,"title":"Diagnostics","text":"Command Description <code>ctx doctor</code> Structural health check (hooks, drift, config) <code>ctx trace</code> Show context behind git commits <code>ctx sysinfo</code> Show system resource usage (memory, swap, disk, load) <code>ctx usage</code> Show session token usage stats","path":["CLI"],"tags":[]},{"location":"cli/#runtime","level":2,"title":"Runtime","text":"Command Description <code>ctx config</code> Manage runtime configuration profiles <code>ctx prune</code> Clean stale per-session state files <code>ctx hook</code> Hook message, notification, and lifecycle controls <code>ctx system</code> Hook plumbing and agent-only commands (not user-facing)","path":["CLI"],"tags":[]},{"location":"cli/#shell","level":2,"title":"Shell","text":"Command Description <code>ctx completion</code> Generate shell autocompletion scripts","path":["CLI"],"tags":[]},{"location":"cli/#exit-codes","level":2,"title":"Exit Codes","text":"Code Meaning 0 Success 1 General error / warnings (e.g. drift) 2 Context not found 3 Violations found (e.g. drift) 4 File operation error","path":["CLI"],"tags":[]},{"location":"cli/#environment-variables","level":2,"title":"Environment Variables","text":"Variable Description <code>CTX_DIR</code> Override default context directory path <code>CTX_TOKEN_BUDGET</code> Override default token budget <code>CTX_SESSION_ID</code> Active AI session ID (used by <code>ctx trace</code> for context linking)","path":["CLI"],"tags":[]},{"location":"cli/#configuration-file","level":2,"title":"Configuration File","text":"<p>Optional <code>.ctxrc</code> (YAML format) at project root:</p> <pre><code># .ctxrc\ntoken_budget: 8000           # Default token budget\npriority_order:              # File loading priority\n  - TASKS.md\n  - DECISIONS.md\n  - CONVENTIONS.md\nauto_archive: true           # Auto-archive old items\narchive_after_days: 7        # Days before archiving tasks\nscratchpad_encrypt: true     # Encrypt scratchpad (default: true)\nevent_log: false             # Enable local hook event logging\ncompanion_check: true        # Check companion tools at session start\nentry_count_learnings: 30    # Drift warning threshold (0 = disable)\nentry_count_decisions: 20    # Drift warning threshold (0 = disable)\nconvention_line_count: 200   # Line count warning for CONVENTIONS.md (0 = disable)\ninjection_token_warn: 15000  # Oversize injection warning (0 = disable)\ncontext_window: 200000       # Auto-detected for Claude Code; override for other tools\nbilling_token_warn: 0        # One-shot billing warning at this token count (0 = disabled)\nkey_rotation_days: 90        # Days before key rotation nudge\nsession_prefixes:            # Recognized session header prefixes (extend for i18n)\n  - \"Session:\"               # English (default)\n  # - \"Oturum:\"              # Turkish (add as needed)\n  # - \"セッション:\"             # Japanese (add as needed)\nfreshness_files:             # Files with technology-dependent constants (opt-in)\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # Optional\nnotify:                      # Webhook notification settings\n  events:                    # Required: only listed events fire\n    - loop\n    - nudge\n    - relay\n    # - heartbeat            # Every-prompt session-alive signal\ntool: \"\"                     # Active AI tool: claude, cursor, cline, kiro, codex\nsteering:                    # Steering layer configuration\n  dir: .context/steering     # Steering files directory\n  default_inclusion: manual  # Default inclusion mode (always, auto, manual)\n  default_tools: []          # Default tool filter for new steering files\nhooks:                       # Hook system configuration\n  dir: .context/hooks        # Hook scripts directory\n  timeout: 10                # Per-hook execution timeout in seconds\n  enabled: true              # Whether hook execution is enabled\n</code></pre> Field Type Default Description <code>token_budget</code> <code>int</code> <code>8000</code> Default token budget for <code>ctx agent</code> <code>priority_order</code> <code>[]string</code> (all files) File loading priority for context packets <code>auto_archive</code> <code>bool</code> <code>true</code> Auto-archive completed tasks <code>archive_after_days</code> <code>int</code> <code>7</code> Days before completed tasks are archived <code>scratchpad_encrypt</code> <code>bool</code> <code>true</code> Encrypt scratchpad with AES-256-GCM <code>event_log</code> <code>bool</code> <code>false</code> Enable local hook event logging to <code>.context/state/events.jsonl</code> <code>companion_check</code> <code>bool</code> <code>true</code> Check companion tool availability (Gemini Search, GitNexus) during <code>/ctx-remember</code> <code>entry_count_learnings</code> <code>int</code> <code>30</code> Drift warning when <code>LEARNINGS.md</code> exceeds this count <code>entry_count_decisions</code> <code>int</code> <code>20</code> Drift warning when <code>DECISIONS.md</code> exceeds this count <code>convention_line_count</code> <code>int</code> <code>200</code> Line count warning for <code>CONVENTIONS.md</code> <code>injection_token_warn</code> <code>int</code> <code>15000</code> Warn when auto-injected context exceeds this token count (0 = disable) <code>context_window</code> <code>int</code> <code>200000</code> Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools <code>billing_token_warn</code> <code>int</code> <code>0</code> (off) One-shot warning when session tokens exceed this threshold (0 = disabled) <code>key_rotation_days</code> <code>int</code> <code>90</code> Days before encryption key rotation nudge <code>session_prefixes</code> <code>[]string</code> <code>[\"Session:\"]</code> Recognized Markdown session header prefixes. Extend to parse sessions written in other languages <code>freshness_files</code> <code>[]object</code> (none) Files to track for staleness (path, desc, optional review_url). Hook warns after 6 months without modification <code>notify.events</code> <code>[]string</code> (all) Event filter for webhook notifications (empty = all) <code>tool</code> <code>string</code> (empty) Active AI tool identifier (<code>claude</code>, <code>cursor</code>, <code>cline</code>, <code>kiro</code>, <code>codex</code>) <code>steering.dir</code> <code>string</code> <code>.context/steering</code> Steering files directory <code>steering.default_inclusion</code> <code>string</code> <code>manual</code> Default inclusion mode for new steering files (<code>always</code>, <code>auto</code>, <code>manual</code>) <code>steering.default_tools</code> <code>[]string</code> (all) Default tool filter for new steering files (empty = all tools) <code>hooks.dir</code> <code>string</code> <code>.context/hooks</code> Hook scripts directory <code>hooks.timeout</code> <code>int</code> <code>10</code> Per-hook execution timeout in seconds <code>hooks.enabled</code> <code>bool</code> <code>true</code> Whether hook execution is enabled <p>Priority order: CLI flags > Environment variables > <code>.ctxrc</code> > Defaults</p> <p>All settings are optional. Missing values use defaults.</p>","path":["CLI"],"tags":[]},{"location":"cli/bootstrap/","level":1,"title":"System Bootstrap","text":"","path":["System Bootstrap"],"tags":[]},{"location":"cli/bootstrap/#ctx-system-bootstrap","level":3,"title":"<code>ctx system bootstrap</code>","text":"<p>Print the resolved context directory path so AI agents can anchor their session. The default output lists the context directory, the tracked context files, and a short health snapshot. <code>--quiet</code> prints just the path; <code>--json</code> produces structured output for automation.</p> <p>This is a hidden, agent-only command that agents are instructed to run first in their session-start procedure; it is the authoritative answer to \"where does this project's context live?\".</p> <pre><code>ctx system bootstrap [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>-q</code>, <code>--quiet</code> Output only the context directory path <code>--json</code> Output in JSON format <p>Examples:</p> <pre><code>ctx system bootstrap                 # Text output for agents\nctx system bootstrap -q              # Just the context directory path\nctx system bootstrap --json          # Structured output for automation\n</code></pre> <p>Note: <code>-q</code> prints just the resolved directory path. See Activating a Context Directory if you hit a \"no context directory specified\" error.</p>","path":["System Bootstrap"],"tags":[]},{"location":"cli/change/","level":1,"title":"Change","text":"","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/change/#ctx-change","level":2,"title":"<code>ctx change</code>","text":"<p>Show what changed in context files and code since your last session.</p> <p>Automatically detects the previous session boundary from state markers or event log. Useful at session start to quickly see what moved while you were away.</p> <pre><code>ctx change [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--since</code> Time reference: duration (<code>24h</code>) or date (<code>2026-03-01</code>) <p>Reference time detection (priority order):</p> <ol> <li><code>--since</code> flag (duration, date, or RFC3339 timestamp)</li> <li><code>ctx-loaded-*</code> marker files in <code>.context/state/</code> (second most recent)</li> <li>Last <code>context-load-gate</code> event from <code>.context/state/events.jsonl</code></li> <li>Fallback: 24 hours ago</li> </ol> <p>Examples:</p> <pre><code># Auto-detect last session, show what changed\nctx change\n\n# Changes in the last 48 hours\nctx change --since 48h\n\n# Changes since a specific date\nctx change --since 2026-03-10\n</code></pre> <p>Output:</p> <pre><code>## Changes Since Last Session\n\n**Reference point**: 6 hours ago\n\n### Context File Changes\n- `TASKS.md` - modified 2026-03-12 14:30\n- `DECISIONS.md` - modified 2026-03-12 09:15\n\n### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n</code></pre> <p>Context file changes are detected by filesystem mtime (works without git). Code changes use <code>git log --since</code> (empty when not in a git repo).</p> <p>See also: Reviewing Session Changes.</p>","path":["CLI","Context","Change"],"tags":[]},{"location":"cli/completion/","level":1,"title":"Completion","text":"","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#ctx-completion","level":2,"title":"<code>ctx completion</code>","text":"<p>Generate shell autocompletion scripts.</p> <pre><code>ctx completion <shell>\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#subcommands","level":3,"title":"Subcommands","text":"Shell Command <code>bash</code> <code>ctx completion bash</code> <code>zsh</code> <code>ctx completion zsh</code> <code>fish</code> <code>ctx completion fish</code> <code>powershell</code> <code>ctx completion powershell</code> <p>Examples:</p> <pre><code>ctx completion bash > /etc/bash_completion.d/ctx\nctx completion zsh  > \"${fpath[1]}/_ctx\"\nctx completion fish > ~/.config/fish/completions/ctx.fish\nctx completion powershell | Out-String | Invoke-Expression\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/completion/#installation","level":3,"title":"Installation","text":"BashZshFishPowerShell <pre><code># Add to ~/.bashrc\nsource <(ctx completion bash)\n</code></pre> <pre><code># Add to ~/.zshrc\nsource <(ctx completion zsh)\n</code></pre> <pre><code>ctx completion fish | source\n# Or save to completions directory\nctx completion fish > ~/.config/fish/completions/ctx.fish\n</code></pre> <pre><code># Add to your PowerShell profile\nctx completion powershell | Out-String | Invoke-Expression\n</code></pre>","path":["CLI","Shell","Completion"],"tags":[]},{"location":"cli/config/","level":1,"title":"Config","text":"","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config","level":3,"title":"<code>ctx config</code>","text":"<p>Manage runtime configuration profiles.</p> <pre><code>ctx config <subcommand>\n</code></pre> <p>The <code>ctx</code> repo ships two <code>.ctxrc</code> source profiles (<code>.ctxrc.base</code> and <code>.ctxrc.dev</code>). The working copy (<code>.ctxrc</code>) is gitignored and switched between them using subcommands below.</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-switch","level":4,"title":"<code>ctx config switch</code>","text":"<p>Switch between <code>.ctxrc</code> configuration profiles.</p> <pre><code>ctx config switch [dev|base]\n</code></pre> <p>With no argument, toggles between dev and base. Accepts <code>prod</code> as an alias for <code>base</code>.</p> Argument Description <code>dev</code> Switch to dev profile (verbose logging) <code>base</code> Switch to base profile (all defaults) (none) Toggle to the opposite profile <p>Profiles:</p> Profile Description <code>dev</code> Verbose logging, webhook notifications on <code>base</code> All defaults, notifications off <p>Examples:</p> <pre><code>ctx config switch dev     # Switch to dev profile\nctx config switch base    # Switch to base profile\nctx config switch         # Toggle (dev → base or base → dev)\nctx config switch prod    # Alias for \"base\"\n</code></pre> <p>The detection heuristic checks for an uncommented <code>notify:</code> line in <code>.ctxrc</code>: present means dev, absent means base.</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/config/#ctx-config-status","level":4,"title":"<code>ctx config status</code>","text":"<p>Show which <code>.ctxrc</code> profile is currently active.</p> <pre><code>ctx config status\n</code></pre> <p>Output examples:</p> <pre><code>active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n</code></pre> <p>See also: Configuration, Contributing: Configuration Profiles</p>","path":["CLI","Runtime","Config"],"tags":[]},{"location":"cli/connect/","level":1,"title":"Connect","text":"","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect","level":2,"title":"<code>ctx connect</code>","text":"<p>Connect a project to a <code>ctx</code> Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.</p> <p>New to the Hub?</p> <p>Start with the <code>ctx</code> Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.</p> <p>The unit of identity is a project, not a user. Registering a directory with <code>ctx connect register</code> binds a per-project client token in <code>.context/.connect.enc</code>. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.</p> <p>Only structured entries flow through the hub: <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Session journals, scratchpad contents, and other local state stay on the machine that created them.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-register","level":3,"title":"<code>ctx connect register</code>","text":"<p>One-time registration with a hub. Requires the hub address and admin token (printed by <code>ctx hub start</code> on first run).</p> <pre><code>ctx connect register localhost:9900 --token ctx_adm_7f3a...\n</code></pre> <p>On success, stores an encrypted connection config in <code>.context/.connect.enc</code> for future RPCs.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-subscribe","level":3,"title":"<code>ctx connect subscribe</code>","text":"<p>Set which entry types to receive from the hub. Only matching types are returned by sync and listen.</p> <pre><code>ctx connect subscribe decision learning\nctx connect subscribe decision learning convention\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-sync","level":3,"title":"<code>ctx connect sync</code>","text":"<p>Pull matching entries from the hub and write them to <code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <pre><code>ctx connect sync\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-publish","level":3,"title":"<code>ctx connect publish</code>","text":"<p>Push entries to the hub. Specify type and content as arguments.</p> <pre><code>ctx connect publish decision \"Use UTC timestamps everywhere\"\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-listen","level":3,"title":"<code>ctx connect listen</code>","text":"<p>Stream new entries from the hub in real-time. Writes to <code>.context/hub/</code> as entries arrive. Press Ctrl-C to stop.</p> <pre><code>ctx connect listen\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#ctx-connect-status","level":3,"title":"<code>ctx connect status</code>","text":"<p>Show hub connection state and entry statistics.</p> <pre><code>ctx connect status\n</code></pre>","path":["Connect"],"tags":[]},{"location":"cli/connect/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"<p>Use <code>--share</code> on <code>ctx add</code> to write locally AND publish to the hub:</p> <pre><code>ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n</code></pre> <p>If the hub is unreachable, the local write succeeds and a warning is printed. The <code>--share</code> flag is best-effort; it never blocks local context updates.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#auto-sync","level":2,"title":"Auto-Sync","text":"<p>Once registered, the <code>check-hub-sync</code> hook automatically syncs new entries from the hub at the start of each session (daily throttled). No manual <code>ctx connect sync</code> needed.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#shared-files","level":2,"title":"Shared Files","text":"<p>Entries from the hub are stored in <code>.context/hub/</code>:</p> <pre><code>.context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n</code></pre> <p>These files are read-only (managed by sync/listen) and never mixed with local context files.</p>","path":["Connect"],"tags":[]},{"location":"cli/connect/#agent-integration","level":2,"title":"Agent Integration","text":"<p>Include shared knowledge in agent context packets:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.</p>","path":["Connect"],"tags":[]},{"location":"cli/connection/","level":1,"title":"Connect","text":"","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connect","level":2,"title":"<code>ctx connect</code>","text":"<p>Connect a project to a <code>ctx</code> Hub for cross-project knowledge sharing. Projects publish decisions, learnings, conventions, and tasks to a hub; other subscribed projects receive them alongside local context.</p> <p>New to the <code>ctx</code> Hub?</p> <p>Start with the <code>ctx</code> Hub overview for the mental model (what the hub is, who it's for, what it is not), then walk through Getting Started. This page is a command reference, not an introduction.</p> <p>The unit of identity is a project, not a user. Registering a directory with <code>ctx connection register</code> binds a per-project client token in <code>.context/.connect.enc</code>. Two developers on the same project either share that file over a trusted channel, or each register under a different project name.</p> <p>Only structured entries flow through the hub: <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Session journals, scratchpad contents, and other local state stay on the machine that created them.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-register","level":3,"title":"<code>ctx connection register</code>","text":"<p>One-time registration with a <code>ctx</code> Hub. Requires the <code>ctx</code> Hub address and admin token (printed by <code>ctx hub start</code> on first run).</p> <p>Examples:</p> <pre><code>ctx connection register localhost:9900 --token ctx_adm_7f3a...\n</code></pre> <p>On success, stores an encrypted connection config in <code>.context/.connect.enc</code> for future RPCs.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-subscribe","level":3,"title":"<code>ctx connection subscribe</code>","text":"<p>Set which entry types to receive from the <code>ctx</code> Hub. Only matching types are returned by sync and listen.</p> <p>Examples:</p> <pre><code>ctx connection subscribe decision learning\nctx connection subscribe decision learning convention\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-sync","level":3,"title":"<code>ctx connection sync</code>","text":"<p>Pull matching entries from the <code>ctx</code> Hub and write them to <code>.context/hub/</code> as Markdown files with origin tags and date headers. Tracks last-seen sequence for incremental sync.</p> <p>Examples:</p> <pre><code>ctx connection sync\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-publish","level":3,"title":"<code>ctx connection publish</code>","text":"<p>Push entries to the <code>ctx</code> Hub. Specify type and content as arguments.</p> <p>Examples:</p> <pre><code>ctx connection publish decision \"Use UTC timestamps everywhere\"\nctx connection publish learning \"Go embed requires files in same package\"\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-listen","level":3,"title":"<code>ctx connection listen</code>","text":"<p>Stream new entries from the <code>ctx</code> Hub in real-time. Writes to <code>.context/hub/</code> as entries arrive. Press Ctrl-C to stop.</p> <p>Examples:</p> <pre><code>ctx connection listen\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#ctx-connection-status","level":3,"title":"<code>ctx connection status</code>","text":"<p>Show <code>ctx</code> Hub connection state and entry statistics.</p> <p>Examples:</p> <pre><code>ctx connection status\n</code></pre>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#automatic-sharing","level":2,"title":"Automatic Sharing","text":"<p>Use <code>--share</code> on <code>ctx add</code> to write locally AND publish to the <code>ctx</code> Hub:</p> <pre><code>ctx decision add \"Use UTC\" --share \\\n  --context \"Need consistency\" \\\n  --rationale \"Avoid timezone bugs\" \\\n  --consequence \"UI does conversion\"\n</code></pre> <p>If the hub is unreachable, the local write succeeds and a warning is printed. The <code>--share</code> flag is best-effort; it never blocks local context updates.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#auto-sync","level":2,"title":"Auto-Sync","text":"<p>Once registered, the <code>check-hub-sync</code> hook automatically syncs new entries from the <code>ctx</code> Hub at the start of each session (daily throttled). No manual <code>ctx connection sync</code> needed.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#shared-files","level":2,"title":"Shared Files","text":"<p>Entries from the <code>ctx</code> Hub are stored in <code>.context/hub/</code>:</p> <pre><code>.context/hub/\n  decisions.md      # Shared decisions with origin tags\n  learnings.md      # Shared learnings\n  conventions.md    # Shared conventions\n  .sync-state.json  # Last-seen sequence tracker\n</code></pre> <p>These files are read-only (managed by sync/listen) and never mixed with local context files.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/connection/#agent-integration","level":2,"title":"Agent Integration","text":"<p>Include shared knowledge in agent context packets:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are included as Tier 8 in the budget-aware assembly, scored by recency and type relevance.</p>","path":["CLI","Integrations","Connect"],"tags":[]},{"location":"cli/context/","level":1,"title":"Context Management","text":"","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#adding-entries","level":3,"title":"Adding entries","text":"<p>Each context-artifact noun (<code>task</code>, <code>decision</code>, <code>learning</code>, <code>convention</code>) owns its own <code>add</code> subcommand under the noun-first command tree:</p> <pre><code>ctx task add <content> [flags]\nctx decision add <content> [flags]\nctx learning add <content> [flags]\nctx convention add <content> [flags]\n</code></pre> <p>Target files:</p> Subcommand Target File <code>ctx task add</code> <code>TASKS.md</code> <code>ctx decision add</code> <code>DECISIONS.md</code> <code>ctx learning add</code> <code>LEARNINGS.md</code> <code>ctx convention add</code> <code>CONVENTIONS.md</code> <p>Flags (shared by every <code>add</code> subcommand; per-noun required-flag rules surface as command errors):</p> Flag Short Description <code>--priority <level></code> <code>-p</code> Priority for tasks: <code>high</code>, <code>medium</code>, <code>low</code> <code>--section <name></code> <code>-s</code> Target section within file <code>--context</code> <code>-c</code> Context (required for decisions and learnings) <code>--rationale</code> <code>-r</code> Rationale for decisions (required for decisions) <code>--consequence</code> Consequence for decisions (required for decisions) <code>--lesson</code> <code>-l</code> Key insight (required for learnings) <code>--application</code> <code>-a</code> How to apply going forward (required for learnings) <code>--file</code> <code>-f</code> Read content from file instead of argument <p>Examples:</p> <pre><code># Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\nctx task add \"Fix login bug\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (requires all ADR (Architectural Decision Record) fields)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning (requires context, lesson, and application)\nctx learning add \"Vitest mocks must be hoisted\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Vitest hoists vi.mock() calls to top of file\" \\\n  --application \"Always place vi.mock() before imports in test files\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to specific section\nctx convention add \"Use kebab-case for filenames\" --section \"Naming\"\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-drift","level":3,"title":"<code>ctx drift</code>","text":"<p>Detect stale or invalid context.</p> <pre><code>ctx drift [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output machine-readable JSON <code>--fix</code> Auto-fix simple issues <p>Checks:</p> <ul> <li>Path references in <code>ARCHITECTURE.md</code> and <code>CONVENTIONS.md</code> exist</li> <li>Task references are valid</li> <li>Constitution rules aren't violated (heuristic)</li> <li>Staleness indicators (old files, many completed tasks)</li> <li>Missing packages: warns when <code>internal/</code> directories exist on disk but are   not referenced in <code>ARCHITECTURE.md</code> (suggests running <code>/ctx-architecture</code>)</li> <li>Entry count: warns when <code>LEARNINGS.md</code> or <code>DECISIONS.md</code> exceed configurable   thresholds (default: 30 learnings, 20 decisions), or when <code>CONVENTIONS.md</code>   exceeds a line count threshold (default: 200). Configure via <code>.ctxrc</code>:   <pre><code>entry_count_learnings: 30      # warn above this (0 = disable)\nentry_count_decisions: 20      # warn above this (0 = disable)\nconvention_line_count: 200     # warn above this (0 = disable)\n</code></pre></li> </ul> <p>Example:</p> <pre><code>ctx drift\nctx drift --json\nctx drift --fix\n</code></pre> <p>Exit codes:</p> Code Meaning 0 All checks passed 1 Warnings found 3 Violations found","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-sync","level":3,"title":"<code>ctx sync</code>","text":"<p>Reconcile context with the current codebase state.</p> <pre><code>ctx sync [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Show what would change without modifying <p>What it does:</p> <ul> <li>Scans codebase for structural changes</li> <li>Compares with ARCHITECTURE.md</li> <li>Suggests documenting dependencies if package files exist</li> <li>Identifies stale or outdated context</li> </ul> <p>Example:</p> <pre><code>ctx sync\nctx sync --dry-run\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-compact","level":3,"title":"<code>ctx compact</code>","text":"<p>Consolidate and clean up context files.</p> <ul> <li>Moves completed tasks older than 7 days to the archive</li> <li>Removes empty sections</li> </ul> <pre><code>ctx compact [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--archive</code> Create <code>.context/archive/</code> for old content <p>Example:</p> <pre><code>ctx compact\nctx compact --archive\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-fmt","level":3,"title":"<code>ctx fmt</code>","text":"<p>Format context files to a consistent line width.</p> <p>Wraps long lines in <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, and <code>CONVENTIONS.md</code> at word boundaries. Markdown list items get 2-space continuation indent. Headings, tables, frontmatter, and HTML comments are preserved as-is.</p> <p>Idempotent: running twice produces the same output.</p> <pre><code>ctx fmt [flags]\n</code></pre> <p>Flags:</p> Flag Type Default Description <code>--width</code> <code>int</code> <code>80</code> Target line width <code>--check</code> <code>bool</code> <code>false</code> Check only, exit 1 if files would change <p>Examples:</p> <pre><code>ctx fmt              # format all context files\nctx fmt --check      # CI mode: check without modifying\nctx fmt --width 100  # custom width\n</code></pre> <p>Also available as a Makefile target:</p> <pre><code>make fmt-context\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task","level":3,"title":"<code>ctx task</code>","text":"<p>Manage task completion, archival, and snapshots.</p> <pre><code>ctx task <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-complete","level":4,"title":"<code>ctx task complete</code>","text":"<p>Mark a task as completed.</p> <pre><code>ctx task complete <task-id-or-text>\n</code></pre> <p>Arguments:</p> <ul> <li><code>task-id-or-text</code>: Task number or partial text match</li> </ul> <p>Examples:</p> <pre><code># By text (partial match)\nctx task complete \"user auth\"\n\n# By task number\nctx task complete 3\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-archive","level":4,"title":"<code>ctx task archive</code>","text":"<p>Move completed tasks from <code>TASKS.md</code> to a timestamped archive file.</p> <pre><code>ctx task archive [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Preview changes without modifying files <p>Archive files are stored in <code>.context/archive/</code> with timestamped names (<code>tasks-YYYY-MM-DD.md</code>). Completed tasks (marked with <code>[x]</code>) are moved; pending tasks (<code>[ ]</code>) remain in <code>TASKS.md</code>.</p> <p>Example:</p> <pre><code>ctx task archive\nctx task archive --dry-run\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-task-snapshot","level":4,"title":"<code>ctx task snapshot</code>","text":"<p>Create a point-in-time snapshot of <code>TASKS.md</code> without modifying the original.</p> <pre><code>ctx task snapshot [name]\n</code></pre> <p>Arguments:</p> <ul> <li><code>name</code>: Optional name for the snapshot (defaults to \"snapshot\")</li> </ul> <p>Snapshots are stored in <code>.context/archive/</code> with timestamped names (<code>tasks-<name>-YYYY-MM-DD-HHMM.md</code>).</p> <p>Example:</p> <pre><code>ctx task snapshot\nctx task snapshot \"before-refactor\"\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission","level":3,"title":"<code>ctx permission</code>","text":"<p>Manage Claude Code permission snapshots.</p> <pre><code>ctx permission <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-snapshot","level":4,"title":"<code>ctx permission snapshot</code>","text":"<p>Save <code>.claude/settings.local.json</code> as the golden image.</p> <pre><code>ctx permission snapshot\n</code></pre> <p>Creates <code>.claude/settings.golden.json</code> as a byte-for-byte copy of the current settings. Overwrites if the golden file already exists.</p> <p>The golden file is meant to be committed to version control and shared with the team.</p> <p>Example:</p> <pre><code>ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-permission-restore","level":4,"title":"<code>ctx permission restore</code>","text":"<p>Replace <code>settings.local.json</code> with the golden image.</p> <pre><code>ctx permission restore\n</code></pre> <p>Prints a diff of dropped (session-accumulated) and restored permissions. No-op if the files already match.</p> <p>Example:</p> <pre><code>ctx permission restore\n# Dropped 3 session permission(s):\n#   - Bash(cat /tmp/debug.log:*)\n#   - Bash(rm /tmp/test-*:*)\n#   - Bash(curl https://example.com:*)\n# Restored from golden image.\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-reindex","level":3,"title":"<code>ctx reindex</code>","text":"<p>Regenerate the quick-reference index for both <code>DECISIONS.md</code> and <code>LEARNINGS.md</code> in a single invocation.</p> <pre><code>ctx reindex\n</code></pre> <p>This is a convenience wrapper around <code>ctx decision reindex</code> and <code>ctx learning reindex</code>. Both files grow at similar rates and users typically want to reindex both after manual edits.</p> <p>The index is a compact table of date and title for each entry, allowing AI tools to scan entries without reading the full file.</p> <p>Example:</p> <pre><code>ctx reindex\n# ✓ Index regenerated with 12 entries\n# ✓ Index regenerated with 8 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision","level":3,"title":"<code>ctx decision</code>","text":"<p>Manage the <code>DECISIONS.md</code> file.</p> <pre><code>ctx decision <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-decision-reindex","level":4,"title":"<code>ctx decision reindex</code>","text":"<p>Regenerate the quick-reference index at the top of <code>DECISIONS.md</code>.</p> <pre><code>ctx decision reindex\n</code></pre> <p>The index is a compact table showing the date and title for each decision, allowing AI tools to quickly scan entries without reading the full file.</p> <p>Use this after manual edits to <code>DECISIONS.md</code> or when migrating existing files to use the index format.</p> <p>Example:</p> <pre><code>ctx decision reindex\n# ✓ Index regenerated with 12 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning","level":3,"title":"<code>ctx learning</code>","text":"<p>Manage the <code>LEARNINGS.md</code> file.</p> <pre><code>ctx learning <subcommand>\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/context/#ctx-learning-reindex","level":4,"title":"<code>ctx learning reindex</code>","text":"<p>Regenerate the quick-reference index at the top of <code>LEARNINGS.md</code>.</p> <pre><code>ctx learning reindex\n</code></pre> <p>The index is a compact table showing the date and title for each learning, allowing AI tools to quickly scan entries without reading the full file.</p> <p>Use this after manual edits to <code>LEARNINGS.md</code> or when migrating existing files to use the index format.</p> <p>Example:</p> <pre><code>ctx learning reindex\n# ✓ Index regenerated with 8 entries\n</code></pre>","path":["CLI","Context","Context Management"],"tags":[]},{"location":"cli/doctor/","level":1,"title":"Doctor","text":"","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#ctx-doctor","level":3,"title":"<code>ctx doctor</code>","text":"<p>Structural health check across context, hooks, and configuration. Runs mechanical checks that don't require semantic analysis. Think of it as <code>ctx status</code> + <code>ctx drift</code> + configuration audit in one pass.</p> <pre><code>ctx doctor [flags]\n</code></pre> <p>Flags:</p> Flag Short Type Default Description <code>--json</code> <code>-j</code> bool <code>false</code> Machine-readable JSON output","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-checks","level":4,"title":"What It Checks","text":"Check Category What it verifies Context initialized Structure <code>.context/</code> directory exists Required files present Structure All required context files exist (<code>TASKS.md</code>, etc.) Drift detected Quality Stale paths, missing files, constitution violations Event logging status Hooks Whether <code>event_log: true</code> is set in <code>.ctxrc</code> Webhook configured Hooks <code>.notify.enc</code> file exists Pending reminders State Count of entries in <code>reminders.json</code> Task completion ratio State Pending vs completed tasks in <code>TASKS.md</code> Context token size Size Estimated token count across all context files Recent event activity Events Last event timestamp (only when event logging is enabled)","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-human","level":4,"title":"Output Format (Human)","text":"<pre><code>ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n</code></pre> <p>Status indicators:</p> Icon Status Meaning ✓ ok Check passed ⚠ warning Non-critical issue worth fixing ✗ error Problem that needs attention ○ info Informational note","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#output-format-json","level":4,"title":"Output Format (JSON)","text":"<pre><code>{\n  \"results\": [\n    {\n      \"name\": \"context_initialized\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Context initialized (.context/)\"\n    },\n    {\n      \"name\": \"required_files\",\n      \"category\": \"Structure\",\n      \"status\": \"ok\",\n      \"message\": \"Required files present (4/4)\"\n    },\n    {\n      \"name\": \"drift\",\n      \"category\": \"Quality\",\n      \"status\": \"warning\",\n      \"message\": \"Drift: 2 warnings\"\n    },\n    {\n      \"name\": \"event_logging\",\n      \"category\": \"Hooks\",\n      \"status\": \"info\",\n      \"message\": \"Event logging disabled (enable with event_log: true in .ctxrc)\"\n    },\n    {\n      \"name\": \"webhook\",\n      \"category\": \"Hooks\",\n      \"status\": \"ok\",\n      \"message\": \"Webhook configured\"\n    },\n    {\n      \"name\": \"reminders\",\n      \"category\": \"State\",\n      \"status\": \"ok\",\n      \"message\": \"No pending reminders\"\n    },\n    {\n      \"name\": \"task_completion\",\n      \"category\": \"State\",\n      \"status\": \"warning\",\n      \"message\": \"Tasks: 18/22 completed (82%): consider archiving with ctx task archive\"\n    },\n    {\n      \"name\": \"context_size\",\n      \"category\": \"Size\",\n      \"status\": \"ok\",\n      \"message\": \"Context size: ~4200 tokens (budget: 8000)\"\n    }\n  ],\n  \"warnings\": 2,\n  \"errors\": 0\n}\n</code></pre> <p>Examples:</p> <pre><code># Quick structural health check\nctx doctor\n\n# Machine-readable output for scripting\nctx doctor --json\n\n# Count warnings\nctx doctor --json | jq '.warnings'\n\n# Check for errors only\nctx doctor --json | jq '[.results[] | select(.status == \"error\")]'\n</code></pre>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#when-to-use-what","level":4,"title":"When to Use What","text":"Tool When <code>ctx status</code> Quick glance at files, tokens, and drift <code>ctx doctor</code> Thorough structural checkup (hooks, config, events too) <code>/ctx-doctor</code> Agent-driven diagnosis with event log pattern analysis <p><code>ctx status</code> tells you what's there. <code>ctx doctor</code> tells you what's wrong. <code>/ctx-doctor</code> tells you why it's wrong and what to do about it.</p>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/doctor/#what-it-does-not-do","level":4,"title":"What It Does Not Do","text":"<ul> <li>No event pattern analysis: that's the <code>/ctx-doctor</code> skill's job</li> <li>No auto-fixing: reports findings, doesn't modify anything</li> <li>No external service checks: doesn't verify webhook endpoint availability</li> </ul> <p>See also: Troubleshooting | <code>ctx hook event</code> | <code>/ctx-doctor</code> skill | Detecting and Fixing Drift</p>","path":["CLI","Diagnostics","Doctor"],"tags":[]},{"location":"cli/event/","level":1,"title":"Event","text":"","path":["Event"],"tags":[]},{"location":"cli/event/#ctx-hook-event","level":3,"title":"<code>ctx hook event</code>","text":"<p>Query the local hook event log. Requires <code>event_log: true</code> in <code>.ctxrc</code>. Reads events from <code>.context/state/events.jsonl</code> and outputs them in a human-readable table or raw JSONL format.</p> <p>All filter flags combine with AND logic.</p> <pre><code>ctx hook event [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--hook</code> Filter by hook name <code>--session</code> Filter by session ID <code>--event</code> Filter by event type (<code>relay</code>, <code>nudge</code>) <code>--last</code> Show last N events (default: 50) <code>--json</code> Output raw JSONL (for piping to <code>jq</code>) <code>--all</code> Include rotated log file <p>Examples:</p> <pre><code>ctx hook event                                        # recent events\nctx hook event --hook check-context-size --last 10    # one hook, last 10\nctx hook event --json | jq '.hook'                    # pipe to jq\nctx hook event --session abc123                       # filter by session\n</code></pre>","path":["Event"],"tags":[]},{"location":"cli/guide/","level":1,"title":"Guide","text":"","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/guide/#ctx-guide","level":2,"title":"<code>ctx guide</code>","text":"<p>Quick-reference cheat sheet for common <code>ctx</code> commands and skills.</p> <pre><code>ctx guide [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--skills</code> Show available skills <code>--commands</code> Show available CLI commands <p>Example:</p> <pre><code># Show the full cheat sheet\nctx guide\n\n# Skills only\nctx guide --skills\n\n# Commands only\nctx guide --commands\n</code></pre> <p>Works without initialization (no <code>.context/</code> required). Useful for a printable one-pager when onboarding to a project.</p>","path":["CLI","Getting Started","Guide"],"tags":[]},{"location":"cli/handover/","level":1,"title":"ctx handover","text":"","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover","level":2,"title":"<code>ctx handover</code>","text":"<p>Writes the per-session handover under <code>.context/handovers/<TS>-<slug>.md</code>: a former-agent-to-next-agent note created at session end by <code>/ctx-wrap-up</code> and read at session start by <code>/ctx-remember</code>. When <code>.context/kb/</code> exists, the writer additionally folds postdated closeouts into the handover's <code>## Folded Closeouts</code> section and archives them.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#ctx-handover-write-title","level":3,"title":"<code>ctx handover write <title></code>","text":"<pre><code>ctx handover write \"Cursor Hooks deep dive\" \\\n  --summary \"Drafted topic-page; minted EV-018..EV-024; cold-reader passed.\" \\\n  --next \"Re-ingest the v1.1 release notes URL once you have it.\"\n</code></pre> <p>Required flags:</p> Flag Description <code>--summary</code> What happened this session (past tense). Placeholder values (<code>TBD</code>, <code>see chat</code>, <code>n/a</code>) are rejected. <code>--next</code> What the next agent should do FIRST (future tense, specific). Same placeholder rejection. <p>Optional flags:</p> Flag Description <code>--highlights</code> Notable artifacts produced this session. <code>--open-questions</code> Things that remain undecided. <code>--commit</code> Override resolved git HEAD for the Provenance line (CI replay; honors <code>CTX_TASK_COMMIT</code>). <code>--no-fold</code> Skip closeout consumption (mid-session checkpoint). <p>Writes: <code>.context/handovers/<TS>-<slug>.md</code> with frontmatter (<code>sha</code>, <code>branch</code>, <code>generated-at</code>, <code>title</code>) and body sections (<code>## Summary</code>, <code>## Next Session</code>, optionally <code>## Highlights</code>, <code>## Open Questions</code>, <code>## Folded Closeouts</code>). The <code><TS>-<slug>.md</code> filename is timestamped so multiple concurrent agent runs never overwrite one another's handover.</p> <p>Side effect (when <code>--no-fold</code> is absent and <code>.context/kb/</code> exists): closeouts that postdate the latest handover are folded into the new handover and physically archived under <code>.context/archive/closeouts/</code>.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#how-to-trigger","level":3,"title":"How to Trigger","text":"<p>In ordinary sessions you do not invoke <code>ctx handover write</code> directly. The user-facing trigger is <code>/ctx-wrap-up</code>:</p> <pre><code>/ctx-wrap-up \"session title\"\n</code></pre> <p><code>/ctx-wrap-up</code> owns session-end and always delegates to <code>/ctx-handover</code> as its final step. Direct invocation of <code>/ctx-handover</code> is reserved for two cases:</p> <ul> <li><code>--no-fold</code> mid-session checkpoint.</li> <li>Recovery, when a prior session aborted before wrap-up.</li> </ul> <p>See <code>/ctx-wrap-up</code> and <code>/ctx-handover</code>.</p>","path":["ctx handover"],"tags":[]},{"location":"cli/handover/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Session Lifecycle</li> <li>Recipe: Recover an Aborted Session</li> <li>Skill: <code>/ctx-wrap-up</code></li> <li>Skill: <code>/ctx-handover</code></li> <li>Skill: <code>/ctx-remember</code></li> </ul>","path":["ctx handover"],"tags":[]},{"location":"cli/hook/","level":1,"title":"Hook","text":"","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#ctx-hook","level":3,"title":"<code>ctx hook</code>","text":"<p>Manage hook-related settings: messages, notifications, pause/resume, and event log.</p> <pre><code>ctx hook <subcommand> [flags]\n</code></pre>","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#subcommands","level":2,"title":"Subcommands","text":"Subcommand Description <code>ctx hook message list</code> Show all hook messages with override status <code>ctx hook message show <h> <v></code> Print the effective message template <code>ctx hook message edit <h> <v></code> Copy default to <code>.context/</code> for editing <code>ctx hook message reset <h> <v></code> Delete user override, revert to default <code>ctx hook notify [message]</code> Send a webhook notification <code>ctx hook notify setup</code> Configure and encrypt webhook URL <code>ctx hook notify test</code> Send a test notification <code>ctx hook pause</code> Pause all context hooks for this session <code>ctx hook resume</code> Resume paused context hooks <code>ctx hook event</code> Query the local hook event log","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hook/#examples","level":2,"title":"Examples","text":"<pre><code># View and manage hook messages\nctx hook message list\nctx hook message show qa-reminder gate\nctx hook message edit qa-reminder gate\n\n# Webhook notifications\nctx hook notify setup\nctx hook notify --event loop \"Loop completed\"\n\n# Pause/resume hooks\nctx hook pause\nctx hook resume\n\n# Browse event log\nctx hook event --last 20\nctx hook event --hook qa-reminder --json\n</code></pre> <p>See also: Customizing Hook Messages | Webhook Notifications | Pausing Context Hooks | System Hooks Audit</p>","path":["CLI","Runtime","Hook"],"tags":[]},{"location":"cli/hub/","level":1,"title":"Hub","text":"","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub","level":2,"title":"<code>ctx hub</code>","text":"<p>Operator commands for a <code>ctx</code> Hub: the gRPC server that fans out decisions, learnings, conventions, and tasks across projects. Use <code>ctx hub</code> to start and stop the server, inspect cluster state, add or remove peers at runtime, and hand off leadership before maintenance.</p> <p>Who Needs This Page</p> <p>You only need <code>ctx hub</code> if you are running a hub server or cluster. For client-side operations (register, subscribe, sync, publish, listen), see <code>ctx connect</code>. For the mental model behind the hub as a whole, read the <code>ctx</code> Hub overview.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-start","level":3,"title":"<code>ctx hub start</code>","text":"<p>Start the hub gRPC server.</p> <p>Examples:</p> <pre><code>ctx hub start                           # Foreground, default port 9900\nctx hub start --port 8080               # Custom port\nctx hub start --data-dir /srv/ctx-hub   # Custom data directory\n</code></pre> <p>On first run, generates an admin token and prints it to stdout. Save this token; it's required for <code>ctx connection register</code> in client projects. Subsequent runs reuse the stored token from <code><data-dir>/admin.token</code>.</p> <p>Default data directory: <code>~/.ctx/hub-data/</code></p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#daemon-mode","level":4,"title":"Daemon Mode","text":"<p>Run the hub as a detached background process:</p> <pre><code>ctx hub start --daemon          # Fork to background\nctx hub stop                    # Graceful shutdown\n</code></pre> <p>The daemon writes a PID file to <code><data-dir>/hub.pid</code>. Stop the daemon with <code>ctx hub stop</code> (see below).</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#cluster-mode","level":4,"title":"Cluster Mode","text":"<p>For high availability, run multiple hubs with Raft-based leader election:</p> <pre><code>ctx hub start --port 9900 \\\n  --peers host2:9901,host3:9901\n</code></pre> <p>Raft is used only for leader election. Data replication uses sequence-based gRPC sync on the append-only JSONL log; there is no multi-node consensus on writes. See the HA cluster recipe for the full setup and the Raft-lite durability caveat.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#flags","level":4,"title":"Flags","text":"Flag Description Default <code>--port</code> Hub listen port <code>9900</code> <code>--data-dir</code> Hub data directory <code>~/.ctx/hub-data/</code> <code>--daemon</code> Run the hub server in the background <code>false</code> <code>--peers</code> Comma-separated peer addresses for cluster mode (none)","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#validation","level":4,"title":"Validation","text":"<p>The hub validates every published entry before accepting it:</p> <ul> <li>Type must be one of <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code></li> <li>ID and Origin are required and non-empty</li> <li>Content size capped at 1 MB (text-only)</li> <li>Duplicate project registration is rejected (one token per project)</li> </ul>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stop","level":3,"title":"<code>ctx hub stop</code>","text":"<p>Stop a running hub daemon.</p> <p>Examples:</p> <pre><code>ctx hub stop                            # Stop using default data dir\nctx hub stop --data-dir /srv/ctx-hub    # Custom data directory\n</code></pre> <p>Sends <code>SIGTERM</code> to the PID recorded in <code><data-dir>/hub.pid</code>, waits for in-flight RPCs to drain, and removes the PID file. Safe to rerun: if no daemon is running, returns a \"no running hub\" error without side effects.</p>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-status","level":3,"title":"<code>ctx hub status</code>","text":"<p>Show cluster status: role, peers, sync state, entry count, and uptime.</p> <p>Examples:</p> <pre><code>ctx hub status\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-peer","level":3,"title":"<code>ctx hub peer</code>","text":"<p>Add or remove peers from the cluster at runtime. Useful for scaling up or replacing a decommissioned node without restarting the leader.</p> <p>Examples:</p> <pre><code>ctx hub peer add host2:9901\nctx hub peer remove host2:9901\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#ctx-hub-stepdown","level":3,"title":"<code>ctx hub stepdown</code>","text":"<p>Transfer leadership to another node gracefully. Triggers a new election among the remaining followers before the current leader steps down. Use before taking the leader offline for maintenance.</p> <p>Examples:</p> <pre><code>ctx hub stepdown\n</code></pre>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/hub/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx connect</code>: client-side commands   (register, subscribe, sync, publish, listen)</li> <li><code>ctx</code> Hub overview: mental   model and user stories</li> <li><code>ctx</code> Hub: Getting Started</li> <li>Hub operations: production   deployment, backup, monitoring</li> <li>Hub failure modes</li> <li>Hub security model</li> </ul>","path":["CLI","Integrations","Hub"],"tags":[]},{"location":"cli/init-status/","level":1,"title":"Init and Status","text":"","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-init","level":3,"title":"<code>ctx init</code>","text":"<p>Initialize a new <code>.context/</code> directory with template files.</p> <pre><code>ctx init [flags]\n</code></pre> <p>Git is required</p> <p><code>ctx init</code> (and every non-administrative <code>ctx</code> subcommand) refuses to operate without a <code>.git/</code> working tree at the project root. <code>ctx</code> already needed git to work properly; that requirement is now enforced rather than assumed.</p> <p>Handovers and closeouts stamp the current commit into their frontmatter, and the editorial pipeline pins in-repo evidence to a short SHA (none of which works without a repo). </p> <p>Run <code>git init</code> first if the project does not already have one. </p> <p>There is no <code>--allow-no-git</code> escape hatch. </p> <p>Flags:</p> Flag Short Description <code>--force</code> <code>-f</code> Overwrite existing context files <code>--minimal</code> <code>-m</code> Only create essential files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>CONSTITUTION.md</code>) <code>--merge</code> Auto-merge <code>ctx</code> content into existing <code>CLAUDE.md</code> <p>Creates:</p> <ul> <li><code>.context/</code> directory with all template files</li> <li><code>.context/kb/</code> (with <code>index.md</code> and <code>topics/</code>) and   <code>.context/ingest/</code> (with <code>KB-RULES.md</code>, mode prompts,   <code>OPERATOR.md</code>, <code>PROMPT.md</code>, <code>closeouts/</code>, <code>schemas/</code>) and   <code>.context/handovers/</code>: the editorial-pipeline scaffolding   (Phase KB). Embedded   templates are copied; existing files are preserved.</li> <li><code>.claude/settings.local.json</code> with pre-approved <code>ctx</code> permissions</li> <li><code>CLAUDE.md</code> with bootstrap instructions (or merges into existing)</li> </ul> <p>Claude Code hooks and skills are provided by the <code>ctx</code> plugin (see Integrations).</p> <p>Example:</p> <pre><code># Standard init\nctx init\n\n# Minimal setup (just core files)\nctx init --minimal\n\n# Force overwrite existing\nctx init --reset\n\n# Merge into existing files\nctx init --merge\n</code></pre> <p>After <code>ctx init</code> succeeds, the final output includes a hint showing the exact <code>eval \"$(ctx activate)\"</code> line to bind the new directory for your shell. Every other <code>ctx</code> command requires that binding (or an equivalent direct <code>CTX_DIR=/abs/path/.context</code> export) before it will run.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-activate","level":3,"title":"<code>ctx activate</code>","text":"<p>Emit a shell-native <code>export CTX_DIR=...</code> line for the target <code>.context/</code> directory. <code>ctx</code> does not search the filesystem during day-to-day commands: each one needs <code>CTX_DIR</code> set before it runs. <code>activate</code> is the convenience that figures out the path for you so you can bind it with one line.</p> <pre><code># Walk up from CWD, emit if exactly one candidate visible.\neval \"$(ctx activate)\"\n</code></pre> <p>Flags:</p> Flag Description <code>--shell</code> Shell dialect override. POSIX-family (<code>bash</code>, <code>zsh</code>, <code>sh</code>) all share one syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from <code>$SHELL</code>. <p>Resolution:</p> Candidate count from CWD Behavior Zero Error. Use <code>ctx init</code> to create one, or <code>cd</code> closer to the project root. One Emit <code>export CTX_DIR=<path></code> for that candidate. Two or more Refuse. List every candidate. Re-run from a more specific cwd. <p><code>activate</code> is args-free under the single-source-anchor model; the explicit-path mode was removed because hub-client / hub-server scenarios store at <code>~/.ctx/hub-data/</code> and never read <code>.context/</code>, so they activate from the project root like everyone else. Direct binding without a project-local scan is still available via <code>export CTX_DIR=/abs/path/.context</code> or the inline form.</p> <p>If the parent shell already has <code>CTX_DIR</code> set to a different value, the output gains a leading <code># ctx: replacing stale CTX_DIR=...</code> comment so the user sees the change in <code>eval</code> output before the replacement takes effect.</p> <p>See also: Activating a Context Directory for the full recipe including direnv setup and CI patterns.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-deactivate","level":3,"title":"<code>ctx deactivate</code>","text":"<p>Emit a shell-native <code>unset CTX_DIR</code> line. Pairs with <code>activate</code>.</p> <pre><code>eval \"$(ctx deactivate)\"\n</code></pre> <p>Flags:</p> Flag Description <code>--shell</code> Shell dialect override. POSIX-family (<code>bash</code>, <code>zsh</code>, <code>sh</code>) all share one <code>unset</code> syntax today; the flag exists for future fish/nushell/powershell support. Auto-detected from <code>$SHELL</code>. <p><code>deactivate</code> does not touch the filesystem, doesn't require a declared context directory, and never fails under normal operation; unsetting an already-unset variable is a no-op across supported shells.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-status","level":3,"title":"<code>ctx status</code>","text":"<p>Show the current context summary.</p> <pre><code>ctx status [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--json</code> Output as JSON <code>--verbose</code> <code>-v</code> Include file contents summary <p>Output:</p> <ul> <li>Context directory path</li> <li>Total files and token estimate</li> <li>Status of each file (loaded, empty, missing)</li> <li>Recent activity (modification times)</li> <li>Drift warnings if any</li> </ul> <p>Example:</p> <pre><code>ctx status\nctx status --json\nctx status --verbose\n</code></pre>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-agent","level":3,"title":"<code>ctx agent</code>","text":"<p>Print an AI-ready context packet optimized for LLM consumption.</p> <pre><code>ctx agent [flags]\n</code></pre> <p>Flags:</p> Flag Default Description <code>--budget</code> 8000 Token budget: controls content selection and prioritization <code>--format</code> md Output format: <code>md</code> or <code>json</code> <code>--cooldown</code> 10m Suppress repeated output within this duration (requires <code>--session</code>) <code>--session</code> (none) Session ID for cooldown isolation (e.g., <code>$PPID</code>) <code>--include-hub</code> false Include hub entries from <code>.context/hub/</code> <p>How budget works:</p> <p>The budget controls how much context is included. Entries are selected in priority tiers:</p> <ol> <li>Constitution: always included in full (inviolable rules)</li> <li>Tasks: all active tasks, up to 40% of budget</li> <li>Conventions: all conventions, up to 20% of budget</li> <li>Decisions: scored by recency and relevance to active tasks</li> <li>Learnings: scored by recency and relevance to active tasks</li> <li>Steering: applicable steering file bodies,    scored by their <code>inclusion</code> mode and description match    against the active prompt</li> <li>Skill: named skill content (from <code>--skill</code>)</li> <li>Hub: entries from <code>.context/hub/</code> (with <code>--include-hub</code>,    see <code>ctx connect</code>)</li> </ol> <p>Decisions and learnings are ranked by a combined score (how recent + how relevant to your current tasks). High-scoring entries are included with their full body. Entries that don't fit get title-only summaries in an \"Also Noted\" section. Superseded entries are excluded.</p> <p>Output Sections:</p> Section Source Selection Read These Files all <code>.context/</code> Non-empty files in priority order Constitution <code>CONSTITUTION.md</code> All rules (never truncated) Current Tasks <code>TASKS.md</code> All unchecked tasks (budget-capped) Key Conventions <code>CONVENTIONS.md</code> All items (budget-capped) Recent Decisions <code>DECISIONS.md</code> Full body, scored by relevance Key Learnings <code>LEARNINGS.md</code> Full body, scored by relevance Also Noted overflow Title-only summaries <p>Example:</p> <pre><code># Default (8000 tokens, markdown)\nctx agent\n\n# Smaller packet for tight context windows\nctx agent --budget 4000\n\n# JSON format for programmatic use\nctx agent --format json\n\n# Pipe to file\nctx agent --budget 4000 > context.md\n\n# With cooldown (hooks/automation: requires --session)\nctx agent --session $PPID\n</code></pre> <p>Use case: Copy-paste into AI chat, pipe to system prompt, or use in hooks.</p>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/init-status/#ctx-load","level":3,"title":"<code>ctx load</code>","text":"<p>Load and display assembled context as AI would see it.</p> <pre><code>ctx load [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--budget <tokens></code> Token budget for assembly (default: 8000) <code>--raw</code> Output raw file contents without assembly <p>Example:</p> <pre><code>ctx load\nctx load --budget 16000\nctx load --raw\n</code></pre>","path":["CLI","Getting Started","Init and Status"],"tags":[]},{"location":"cli/journal/","level":1,"title":"Journal","text":"","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal","level":3,"title":"<code>ctx journal</code>","text":"<p>Browse and search AI session history from Claude Code and other tools.</p> <pre><code>ctx journal <subcommand>\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source","level":4,"title":"<code>ctx journal source</code>","text":"<p>List all parsed sessions.</p> <pre><code>ctx journal source [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--limit</code> <code>-n</code> Maximum sessions to display (default: 20) <code>--project</code> <code>-p</code> Filter by project name <code>--tool</code> <code>-t</code> Filter by tool (e.g., <code>claude-code</code>) <code>--all-projects</code> Include sessions from all projects <p>Sessions are sorted by date (newest first) and display slug, project, start time, duration, turn count, and token usage.</p> <p>Example:</p> <pre><code>ctx journal source\nctx journal source --limit 5\nctx journal source --project ctx\nctx journal source --tool claude-code\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-source-show","level":4,"title":"<code>ctx journal source --show</code>","text":"<p>Show details of a specific session.</p> <pre><code>ctx journal source --show [session-id] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--latest</code> Show the most recent session <code>--full</code> Show full message content <code>--all-projects</code> Search across all projects <p>The session ID can be a full UUID, partial match, or session slug name.</p> <p>Example:</p> <pre><code>ctx journal source --show abc123\nctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show --latest\nctx journal source --show --latest --full\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-import","level":4,"title":"<code>ctx journal import</code>","text":"<p>Import sessions to editable journal files in <code>.context/journal/</code>.</p> <pre><code>ctx journal import [session-id] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Import all sessions (only new files by default) <code>--all-projects</code> Import from all projects <code>--regenerate</code> Re-import existing files (preserves YAML frontmatter by default) <code>--keep-frontmatter</code> Preserve enriched YAML frontmatter during regeneration (default: true) <code>--yes</code>, <code>-y</code> Skip confirmation prompt <code>--dry-run</code> Show what would be imported without writing files <p>Safe by default: <code>--all</code> only imports new sessions. Existing files are skipped. Use <code>--regenerate</code> to re-import existing files (conversation content is regenerated, YAML frontmatter from enrichment is preserved by default). Use <code>--keep-frontmatter=false</code> to discard enriched frontmatter during regeneration.</p> <p>Locked entries (via <code>ctx journal lock</code>) are always skipped, regardless of flags.</p> <p>Single-session import (<code>ctx journal import <id></code>) always writes without prompting, since you are explicitly targeting one session.</p> <p>The <code>journal/</code> directory should be gitignored (like <code>sessions/</code>) since it contains raw conversation data.</p> <p>Example:</p> <pre><code>ctx journal import abc123                 # Import one session\nctx journal import --all                  # Import only new sessions\nctx journal import --all --dry-run        # Preview what would be imported\nctx journal import --all --regenerate     # Re-import existing (prompts)\nctx journal import --all --regenerate -y  # Re-import without prompting\nctx journal import --all --regenerate --keep-frontmatter=false -y  # Discard frontmatter\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-lock","level":4,"title":"<code>ctx journal lock</code>","text":"<p>Protect journal entries from being overwritten by <code>import --regenerate</code> or modified by enrichment skills (<code>/ctx-journal-enrich</code>, <code>/ctx-journal-enrich-all</code>).</p> <pre><code>ctx journal lock <pattern> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Lock all journal entries <p>The pattern matches filenames by slug, date, or short ID. Locking a multi-part entry locks all parts. The lock is recorded in <code>.context/journal/.state.json</code> and a <code>locked: true</code> line is added to the file's YAML frontmatter for visibility.</p> <p>Example:</p> <pre><code>ctx journal lock abc12345\nctx journal lock 2026-01-21-session-abc12345.md\nctx journal lock --all\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-unlock","level":4,"title":"<code>ctx journal unlock</code>","text":"<p>Remove lock protection from journal entries.</p> <pre><code>ctx journal unlock <pattern> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--all</code> Unlock all journal entries <p>Example:</p> <pre><code>ctx journal unlock abc12345\nctx journal unlock --all\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-sync","level":4,"title":"<code>ctx journal sync</code>","text":"<p>Sync lock state from journal frontmatter to <code>.state.json</code>.</p> <pre><code>ctx journal sync\n</code></pre> <p>Scans all journal markdowns and updates <code>.state.json</code> to match each file's frontmatter. Files with <code>locked: true</code> in frontmatter are marked locked in state; files without a <code>locked:</code> line have their lock cleared.</p> <p>This is the inverse of <code>ctx journal lock</code>: instead of state driving frontmatter, frontmatter drives state. Useful after batch enrichment where you add <code>locked: true</code> to frontmatter manually.</p> <p>Example:</p> <pre><code># After enriching entries and adding locked: true to frontmatter\nctx journal sync\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal_1","level":3,"title":"<code>ctx journal</code>","text":"<p>Analyze and synthesize imported session files.</p> <pre><code>ctx journal <subcommand>\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-site","level":4,"title":"<code>ctx journal site</code>","text":"<p>Generate a static site from journal entries in <code>.context/journal/</code>.</p> <pre><code>ctx journal site [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--output</code> <code>-o</code> Output directory (default: .context/journal-site) <code>--build</code> Run zensical build after generating <code>--serve</code> Run zensical serve after generating <p>Creates a <code>zensical</code>-compatible site structure with an index page listing all sessions by date, and individual pages for each journal entry.</p> <p>Requires <code>zensical</code> to be installed for <code>--build</code> or <code>--serve</code>:</p> <pre><code>pipx install zensical\n</code></pre> <p>Example:</p> <pre><code>ctx journal site                    # Generate in .context/journal-site/\nctx journal site --output ~/public  # Custom output directory\nctx journal site --build            # Generate and build HTML\nctx journal site --serve            # Generate and serve locally\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-obsidian","level":4,"title":"<code>ctx journal obsidian</code>","text":"<p>Generate an Obsidian vault from journal entries in <code>.context/journal/</code>.</p> <pre><code>ctx journal obsidian [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--output</code> <code>-o</code> Output directory (default: .context/journal-obsidian) <p>Creates an Obsidian-compatible vault with:</p> <ul> <li>Wikilinks (<code>[[target|display]]</code>) for all internal navigation</li> <li>MOC pages (Map of Content) for topics, key files, and session types</li> <li>Related sessions footer linking entries that share topics</li> <li>Transformed frontmatter (<code>topics</code> → <code>tags</code> for Obsidian integration)</li> <li>Minimal <code>.obsidian/</code> config enforcing wikilink mode</li> </ul> <p>No external dependencies are required: Open the output directory as an Obsidian  vault directly.</p> <p>Example:</p> <pre><code>ctx journal obsidian                        # Generate in .context/journal-obsidian/\nctx journal obsidian --output ~/vaults/ctx  # Custom output directory\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-check","level":4,"title":"<code>ctx journal schema check</code>","text":"<p>Validate JSONL session files against the embedded schema and report drift.</p> <pre><code>ctx journal schema check [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--dir</code> Directory to scan for JSONL files <code>--all-projects</code> Scan all Claude Code project directories <code>--quiet</code> <code>-q</code> Exit code only (0 = clean, 1 = drift) <p>Scans JSONL files for unknown fields, missing required fields, unknown record types, and unknown content block types. When drift is found, writes a Markdown report to <code>.context/reports/schema-drift.md</code>. When drift resolves, the report is automatically deleted.</p> <p>Designed for interactive use, CI pipelines, and nightly cron jobs.</p> <p>Example:</p> <pre><code>ctx journal schema check                    # Current project\nctx journal schema check --all-projects     # All projects\nctx journal schema check --quiet            # Exit code only\nctx journal schema check --dir /path/to     # Custom directory\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-journal-schema-dump","level":4,"title":"<code>ctx journal schema dump</code>","text":"<p>Print the embedded JSONL schema definition.</p> <pre><code>ctx journal schema dump\n</code></pre> <p>Shows all known record types with their required and optional fields, and all recognized content block types with their parse status. Useful for inspecting what the schema validator expects.</p> <p>Example:</p> <pre><code>ctx journal schema dump\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/journal/#ctx-serve","level":3,"title":"<code>ctx serve</code>","text":"<p>Serve any zensical directory locally. This is a serve-only command: It does not generate or regenerate site content.</p> <pre><code>ctx serve [directory]\n</code></pre> <p>If no directory is specified, defaults to the journal site (<code>.context/journal-site</code>).</p> <p>Requires <code>zensical</code> to be installed:</p> <pre><code>pipx install zensical\n</code></pre> <p><code>ctx serve</code> vs. <code>ctx journal site --serve</code></p> <p><code>ctx journal site --serve</code> generates the journal site then serves it: an all-in-one command. <code>ctx serve</code> only serves an existing directory, and works with any zensical site (journal, docs, etc.).</p> <p>Example:</p> <pre><code>ctx serve                        # Serve journal site (no regeneration)\nctx serve .context/journal-site  # Same, explicit path\nctx serve ./site                 # Serve the docs site\n</code></pre>","path":["CLI","Sessions","Journal"],"tags":[]},{"location":"cli/kb/","level":1,"title":"ctx kb","text":"","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb","level":2,"title":"<code>ctx kb</code>","text":"<p>Knowledge-base editorial pipeline (Phase KB). Manages the <code>.context/kb/</code> knowledge base via mode-aware skills and a small set of supporting CLI commands. The editorial constitution lives at <code>.context/ingest/KB-RULES.md</code> (laid down by <code>ctx init</code>).</p> <pre><code>ctx kb [subcommand]\n</code></pre> Subcommand Type Purpose <code>ctx kb topic new \"<name>\"</code> CLI (real) Sole writer of topic-page scaffolds. Creates <code>.context/kb/topics/<slug>/index.md</code> from the embedded template. Refuses when the topic exists. <code>ctx kb note \"<text>\"</code> CLI (real) Appends a one-liner to <code>.context/ingest/findings.md</code>. Never touches a topic page. <code>ctx kb reindex</code> CLI (real) Refreshes the <code>CTX:KB:TOPICS</code> managed block in <code>.context/kb/index.md</code>. <code>ctx kb ingest <folder\\|paths></code> Skill-driven Mode-aware editorial pass. CLI form refuses on empty input and points at the <code>/ctx-kb-ingest</code> skill. <code>ctx kb ask \"<question>\"</code> Skill-driven Q&A grounded in the kb. CLI form refuses on empty input and points at the <code>/ctx-kb-ask</code> skill. <code>ctx kb site-review</code> Skill-driven Mechanical structural audit. Points at <code>/ctx-kb-site-review</code>. <code>ctx kb ground</code> Skill-driven Read-only freshness audit over tracked sources listed in <code>grounding-sources.md</code> (URLs, in-tree paths, MCP resources). Refuses when the file is empty. <p>Skill-driven vs real CLI</p> <p>The mode skills (<code>ingest</code>, <code>ask</code>, <code>site-review</code>, <code>ground</code>) do the editorial work themselves: the agent reads <code>.context/ingest/30-INGEST.md</code> (etc.) and executes the pass per the pass-mode contract. The CLI form for those subcommands validates input and prints the canonical skill invocation. The real CLI commands (<code>topic new</code>, <code>note</code>, <code>reindex</code>) own concrete state changes.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-topic-new-name","level":3,"title":"<code>ctx kb topic new \"<name>\"</code>","text":"<p>Scaffolds a folder-shaped topic at <code>.context/kb/topics/<slug>/index.md</code> from the embedded template.</p> <p>Slug: lowercase + kebab-case. Slashes are preserved for vendor-namespaced topology (e.g. <code>cursor/hooks</code>, <code>cursor/skills</code>, <code>cursor/rules</code> under a shared <code>cursor/</code> folder).</p> <p>Refuses when the topic folder already exists. Use the existing folder instead; the editorial pass extends pages, it doesn't reset them.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-note-text","level":3,"title":"<code>ctx kb note \"<text>\"</code>","text":"<p>Appends a timestamped one-liner to <code>.context/ingest/findings.md</code>. Use for parking findings the next ingest pass should absorb.</p> <pre><code>ctx kb note \"follow-up: chase the v1.2 release notes for the SIGTERM change\"\n</code></pre>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#ctx-kb-reindex","level":3,"title":"<code>ctx kb reindex</code>","text":"<p>Refreshes the <code>CTX:KB:TOPICS</code> managed block inside <code>.context/kb/index.md</code> so the kb landing page enumerates current topic folders. Run after <code>ctx kb topic new</code> to update the landing.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#skill-driven-subcommands","level":3,"title":"Skill-Driven Subcommands","text":"<p><code>ingest</code>, <code>ask</code>, <code>site-review</code>, <code>ground</code> exist as CLI surfaces so the editorial workflow is drivable from outside Claude Code (via the fallback <code>PROMPT.md</code> auto-router). In Claude Code, prefer the skills:</p> <pre><code>/ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n/ctx-kb-ask \"does the kb say hooks fire async?\"\n/ctx-kb-site-review\n/ctx-kb-ground\n</code></pre> <p>See the Build a Knowledge Base recipe for the full workflow.</p>","path":["ctx kb"],"tags":[]},{"location":"cli/kb/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Typical KB Session</li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["ctx kb"],"tags":[]},{"location":"cli/loop/","level":1,"title":"Loop","text":"","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/loop/#ctx-loop","level":2,"title":"<code>ctx loop</code>","text":"<p>Generate a shell script for running an autonomous loop.</p> <p>An autonomous loop continuously runs an AI assistant with the same prompt until a completion signal is detected, enabling iterative development where the AI builds on its previous work.</p> <pre><code>ctx loop [flags]\n</code></pre> <p>Flags:</p> Flag Short Description Default <code>--tool <tool></code> <code>-t</code> AI tool: <code>claude</code>, <code>aider</code>, or <code>generic</code> <code>claude</code> <code>--prompt <file></code> <code>-p</code> Prompt file to use <code>.context/loop.md</code> <code>--max-iterations <n></code> <code>-n</code> Maximum iterations (0 = unlimited) <code>0</code> <code>--completion <signal></code> <code>-c</code> Completion signal to detect <code>SYSTEM_CONVERGED</code> <code>--output <file></code> <code>-o</code> Output script filename <code>loop.sh</code> <p>Examples:</p> <pre><code># Generate loop.sh for Claude Code\nctx loop\n\n# Generate for Aider with custom prompt\nctx loop --tool aider --prompt TASKS.md\n\n# Limit to 10 iterations\nctx loop --max-iterations 10\n\n# Output to custom file\nctx loop -o my-loop.sh\n</code></pre> <p>Running the generated loop:</p> <pre><code>ctx loop\nchmod +x loop.sh\n./loop.sh\n</code></pre> <p>See also: Autonomous Loops for the full workflow.</p>","path":["CLI","Integrations","Loop"],"tags":[]},{"location":"cli/mcp/","level":1,"title":"MCP Server","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp","level":2,"title":"<code>ctx mcp</code>","text":"<p>Run <code>ctx</code> as a Model Context Protocol (MCP) server. MCP is a standard protocol that lets AI tools discover and consume context from external sources via JSON-RPC 2.0 over stdin/stdout.</p> <p>This makes <code>ctx</code> accessible to any MCP-compatible AI tool without custom hooks or integrations:</p> <ul> <li>Claude Desktop</li> <li>Cursor</li> <li>Windsurf</li> <li>VS Code Copilot</li> <li>Any tool supporting MCP</li> </ul>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-mcp-serve","level":3,"title":"<code>ctx mcp serve</code>","text":"<p>Start the MCP server. This command reads JSON-RPC 2.0 requests from stdin and writes responses to stdout. It is intended to be launched by MCP clients (Claude Desktop, Cursor, VS Code Copilot), not run directly from a shell. See Configuration below for how each host launches it.</p> <p>Flags: None. The server uses the declared context directory from <code>CTX_DIR</code>. As with every other <code>ctx</code> command, that variable must be set: the server does not walk the filesystem.</p> <p>Examples:</p> <pre><code># Normal invocation (by an MCP client via stdio transport)\nctx mcp serve\n\n# Pin a context directory for a specific workspace\nCTX_DIR=/path/to/project/.context ctx mcp serve\n\n# Verify the binary starts without a client attached (Ctrl-C to exit)\nctx mcp serve < /dev/null\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#configuration","level":2,"title":"Configuration","text":"","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#claude-desktop","level":3,"title":"Claude Desktop","text":"<p>Add to <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>:</p> <pre><code>{\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#cursor","level":3,"title":"Cursor","text":"<p>Add to <code>.cursor/mcp.json</code> in your project:</p> <pre><code>{\n  \"mcpServers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#vs-code-copilot","level":3,"title":"VS Code (Copilot)","text":"<p>Add to <code>.vscode/mcp.json</code>:</p> <pre><code>{\n  \"servers\": {\n    \"ctx\": {\n      \"command\": \"ctx\",\n      \"args\": [\"mcp\", \"serve\"]\n    }\n  }\n}\n</code></pre>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resources","level":2,"title":"Resources","text":"<p>Resources expose context files as read-only content. Each resource has a URI, name, and returns Markdown text.</p> URI Name Description <code>ctx://context/constitution</code> constitution Hard rules that must never be violated <code>ctx://context/tasks</code> tasks Current work items and their status <code>ctx://context/conventions</code> conventions Code patterns and standards <code>ctx://context/architecture</code> architecture System architecture documentation <code>ctx://context/decisions</code> decisions Architectural decisions with rationale <code>ctx://context/learnings</code> learnings Gotchas, tips, and lessons learned <code>ctx://context/glossary</code> glossary Project-specific terminology <code>ctx://context/agent</code> agent All files assembled in priority read order <p>The <code>agent</code> resource assembles all non-empty context files into a single Markdown document, ordered by the configured read priority.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#resource-subscriptions","level":3,"title":"Resource Subscriptions","text":"<p>Clients can subscribe to resource changes via <code>resources/subscribe</code>. The server polls for file mtime changes (default: 5 seconds) and emits <code>notifications/resources/updated</code> when a subscribed file changes on disk.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#tools","level":2,"title":"Tools","text":"<p>Tools expose <code>ctx</code> commands as callable operations. Each tool accepts JSON arguments and returns text results.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_status","level":3,"title":"<code>ctx_status</code>","text":"<p>Show context health: file count, token estimate, and per-file summary.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_add","level":3,"title":"<code>ctx_add</code>","text":"<p>Add a task, decision, learning, or convention to the context.</p> Argument Type Required Description <code>type</code> string Yes Entry type: task, decision, learning, convention <code>content</code> string Yes Title or main content <code>priority</code> string No Priority level (tasks only): high, medium, low <code>context</code> string Conditional Context field (decisions and learnings) <code>rationale</code> string Conditional Rationale (decisions only) <code>consequence</code> string Conditional Consequence (decisions only) <code>lesson</code> string Conditional Lesson learned (learnings only) <code>application</code> string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_complete","level":3,"title":"<code>ctx_complete</code>","text":"<p>Mark a task as done by number or text match.</p> Argument Type Required Description <code>query</code> string Yes Task number (e.g. \"1\") or search text","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_drift","level":3,"title":"<code>ctx_drift</code>","text":"<p>Detect stale or invalid context. Returns violations, warnings, and passed checks.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_journal_source","level":3,"title":"<code>ctx_journal_source</code>","text":"<p>Query recent AI session history (summaries, decisions, topics).</p> Argument Type Required Description <code>limit</code> number No Max sessions to return (default: 5) <code>since</code> string No ISO date filter: sessions after this date (YYYY-MM-DD) <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_watch_update","level":3,"title":"<code>ctx_watch_update</code>","text":"<p>Apply a structured context update to <code>.context/</code> files. Supports task, decision, learning, convention, and complete entry types. Human confirmation is required before calling.</p> Argument Type Required Description <code>type</code> string Yes Entry type: task, decision, learning, convention, complete <code>content</code> string Yes Main content <code>context</code> string Conditional Context background (decisions/learnings) <code>rationale</code> string Conditional Rationale (decisions only) <code>consequence</code> string Conditional Consequence (decisions only) <code>lesson</code> string Conditional Lesson learned (learnings only) <code>application</code> string Conditional How to apply (learnings only)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_compact","level":3,"title":"<code>ctx_compact</code>","text":"<p>Move completed tasks to the archive section and remove empty sections from context files. Human confirmation required.</p> Argument Type Required Description <code>archive</code> boolean No Also write tasks to <code>.context/archive/</code> (default: false)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_next","level":3,"title":"<code>ctx_next</code>","text":"<p>Suggest the next pending task based on priority and position.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_checktaskcompletion","level":3,"title":"<code>ctx_checktaskcompletion</code>","text":"<p>Advisory check: after a write operation, detect if any pending tasks were silently completed. Returns nudge text if a match is found.</p> Argument Type Required Description <code>recent_action</code> string No Brief description of what was just done <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_sessionevent","level":3,"title":"<code>ctx_sessionevent</code>","text":"<p>Signal a session lifecycle event. Type <code>end</code> triggers the session-end persistence ceremony - human confirmation required.</p> Argument Type Required Description <code>type</code> string Yes Event type: start, end <code>caller</code> string No Caller identifier (cursor, windsurf, vscode, claude-desktop)","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_steering_get","level":3,"title":"<code>ctx_steering_get</code>","text":"<p>Retrieve applicable steering files for a prompt. Without a prompt, returns always-included files only.</p> Argument Type Required Description <code>prompt</code> string No Prompt text to match against steering file descriptions <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_search","level":3,"title":"<code>ctx_search</code>","text":"<p>Search across <code>.context/</code> files for a query string. Returns matching lines with file paths and line numbers.</p> Argument Type Required Description <code>query</code> string Yes Search string to match against <p>Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_start","level":3,"title":"<code>ctx_session_start</code>","text":"<p>Execute session-start hooks and return aggregated context from hook outputs.</p> <p>Arguments: None.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_session_end","level":3,"title":"<code>ctx_session_end</code>","text":"<p>Execute session-end hooks with an optional summary. Returns aggregated context from hook outputs.</p> Argument Type Required Description <code>summary</code> string No Session summary passed to hook scripts","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx_remind","level":3,"title":"<code>ctx_remind</code>","text":"<p>List pending session-scoped reminders.</p> <p>Arguments: None. Read-only.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#prompts","level":2,"title":"Prompts","text":"<p>Prompts provide pre-built templates for common workflows. Clients can list available prompts via <code>prompts/list</code> and retrieve a specific prompt via <code>prompts/get</code>.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-session-start","level":3,"title":"<code>ctx-session-start</code>","text":"<p>Load full context at the beginning of a session. Returns all context files assembled in priority read order with session orientation instructions.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-decision-add","level":3,"title":"<code>ctx-decision-add</code>","text":"<p>Format an architectural decision entry with all required fields.</p> Argument Type Required Description <code>content</code> string Yes Decision title <code>context</code> string Yes Background context <code>rationale</code> string Yes Why this decision was made <code>consequence</code> string Yes Expected consequence","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-learning-add","level":3,"title":"<code>ctx-learning-add</code>","text":"<p>Format a learning entry with all required fields.</p> Argument Type Required Description <code>content</code> string Yes Learning title <code>context</code> string Yes Background context <code>lesson</code> string Yes The lesson learned <code>application</code> string Yes How to apply this lesson","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-reflect","level":3,"title":"<code>ctx-reflect</code>","text":"<p>Guide end-of-session reflection. Returns a structured review prompt covering progress assessment and context update recommendations.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/mcp/#ctx-checkpoint","level":3,"title":"<code>ctx-checkpoint</code>","text":"<p>Report session statistics: tool calls made, entries added, and pending updates queued during the current session.</p>","path":["CLI","Integrations","MCP Server"],"tags":[]},{"location":"cli/memory/","level":1,"title":"Memory","text":"","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory","level":2,"title":"<code>ctx memory</code>","text":"<p>Bridge Claude Code's auto memory (MEMORY.md) into <code>.context/</code>.</p> <p>Claude Code maintains per-project auto memory at <code>~/.claude/projects/<slug>/memory/MEMORY.md</code>. This command group discovers that file, mirrors it into <code>.context/memory/mirror.md</code> (git-tracked), and detects drift.</p> <pre><code>ctx memory <subcommand>\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-sync","level":3,"title":"<code>ctx memory sync</code>","text":"<p>Copy MEMORY.md to <code>.context/memory/mirror.md</code>. Archives the previous mirror before overwriting.</p> <pre><code>ctx memory sync [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--dry-run</code> Show what would happen without writing <p>Exit codes:</p> Code Meaning 0 Synced successfully 1 MEMORY.md not found (auto memory inactive) <p>Examples:</p> <pre><code>ctx memory sync\n# Archived previous mirror to mirror-2026-03-05-143022.md\n# Synced MEMORY.md -> .context/memory/mirror.md\n#   Source: ~/.claude/projects/-home-user-project/memory/MEMORY.md\n#   Lines: 47 (was 32)\n#   New content: 15 lines since last sync\n\nctx memory sync --dry-run\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-status","level":3,"title":"<code>ctx memory status</code>","text":"<p>Show drift, timestamps, line counts, and archive count.</p> <pre><code>ctx memory status\n</code></pre> <p>Exit codes:</p> Code Meaning 0 No drift 1 MEMORY.md not found 2 Drift detected (MEMORY.md changed since sync) <p>Examples:</p> <pre><code>ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines (modified since last sync)\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-diff","level":3,"title":"<code>ctx memory diff</code>","text":"<p>Show what changed in MEMORY.md since last sync.</p> <pre><code>ctx memory diff\n</code></pre> <p>Examples:</p> <pre><code>ctx memory diff\n# --- .context/memory/mirror.md (mirror)\n# +++ ~/.claude/projects/.../memory/MEMORY.md (source)\n# +- new learning: memory bridge works\n</code></pre> <p>No output when files are identical.</p>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-publish","level":3,"title":"<code>ctx memory publish</code>","text":"<p>Push curated <code>.context/</code> content into MEMORY.md so the agent sees it natively.</p> <pre><code>ctx memory publish [flags]\n</code></pre> <p>Content is selected in priority order: pending tasks, recent decisions (7 days), key conventions, recent learnings (7 days). Wrapped in <code><!-- ctx:published --></code> markers. Claude-owned content outside the markers is preserved.</p> <p>Flags:</p> Flag Description Default <code>--budget</code> Line budget for published content <code>80</code> <code>--dry-run</code> Show what would be published <p>Examples:</p> <pre><code>ctx memory publish --dry-run\n# Publishing .context/ -> MEMORY.md...\n#   Budget: 80 lines\n#   Published block:\n#     5 pending tasks (from TASKS.md)\n#     3 recent decisions (from DECISIONS.md)\n#     5 key conventions (from CONVENTIONS.md)\n#   Total: 42 lines (within 80-line budget)\n# Dry run - no files written.\n\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter budget\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-unpublish","level":3,"title":"<code>ctx memory unpublish</code>","text":"<p>Remove the ctx-managed marker block from MEMORY.md, preserving Claude-owned content.</p> <p>Examples:</p> <pre><code>ctx memory unpublish\n</code></pre> <p>Hook integration: The <code>check-memory-drift</code> hook runs on every prompt and nudges the agent when MEMORY.md has changed since last sync. The nudge fires once per session. See Memory Bridge.</p>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/memory/#ctx-memory-import","level":3,"title":"<code>ctx memory import</code>","text":"<p>Classify and promote entries from MEMORY.md into structured <code>.context/</code> files.</p> <pre><code>ctx memory import [flags]\n</code></pre> <p>Each entry is classified by keyword heuristics:</p> Keywords Target <code>always use</code>, <code>prefer</code>, <code>never use</code>, <code>standard</code> CONVENTIONS.md <code>decided</code>, <code>chose</code>, <code>trade-off</code>, <code>approach</code> DECISIONS.md <code>gotcha</code>, <code>learned</code>, <code>watch out</code>, <code>bug</code>, <code>caveat</code> LEARNINGS.md <code>todo</code>, <code>need to</code>, <code>follow up</code> TASKS.md Everything else Skipped <p>Deduplication prevents re-importing the same entry across runs.</p> <p>Flags:</p> Flag Description <code>--dry-run</code> Show classification plan without writing <p>Examples:</p> <pre><code>ctx memory import --dry-run\n# Scanning MEMORY.md for new entries...\n#   Found 6 entries\n#\n#   -> \"always use ctx from PATH\"\n#      Classified: CONVENTIONS.md (keywords: always use)\n#\n#   -> \"decided to use heuristic classification over LLM-based\"\n#      Classified: DECISIONS.md (keywords: decided)\n#\n# Dry run - would import: 4 entries\n# Skipped: 2 entries (session notes/unclassified)\n\nctx memory import    # Actually write entries to .context/ files\n</code></pre>","path":["CLI","Context","Memory"],"tags":[]},{"location":"cli/message/","level":1,"title":"Message","text":"","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message","level":3,"title":"<code>ctx hook message</code>","text":"<p>Manage hook message templates.</p> <p>Hook messages control the text hooks emit. The hook logic (when to fire, counting, state tracking) is universal; the messages are opinions that can be customized per-project.</p> <pre><code>ctx hook message <subcommand>\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-list","level":3,"title":"<code>ctx hook message list</code>","text":"<p>Show all hook messages with category and override status.</p> <pre><code>ctx hook message list [--json]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output in JSON format <p>Example:</p> <pre><code>ctx hook message list\nctx hook message list --json | jq '.[] | select(.override)'\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-show","level":3,"title":"<code>ctx hook message show</code>","text":"<p>Print the effective message template for a hook/variant pair. Shows the user override if present, otherwise the embedded default.</p> <pre><code>ctx hook message show <hook> <variant>\n</code></pre> <p>Example:</p> <pre><code>ctx hook message show qa-reminder gate\nctx hook message show check-context-size checkpoint\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-edit","level":3,"title":"<code>ctx hook message edit</code>","text":"<p>Copy the embedded default template for <code><hook> <variant></code> to <code>.context/hooks/messages/<hook>/<variant>.txt</code> so you can edit it directly. The override takes effect the next time the hook fires.</p> <pre><code>ctx hook message edit <hook> <variant>\n</code></pre> <p>If an override already exists, the command fails and directs you to edit it in place or reset it first.</p> <p>Example:</p> <pre><code>ctx hook message edit qa-reminder gate\n# Edit .context/hooks/messages/qa-reminder/gate.txt in your editor\n</code></pre>","path":["Message"],"tags":[]},{"location":"cli/message/#ctx-hook-message-reset","level":3,"title":"<code>ctx hook message reset</code>","text":"<p>Delete a user override and revert to the embedded default. Silent no-op if no override exists.</p> <pre><code>ctx hook message reset <hook> <variant>\n</code></pre> <p>Example:</p> <pre><code>ctx hook message reset qa-reminder gate\n</code></pre> <p>See Customizing hook messages for the full workflow.</p>","path":["Message"],"tags":[]},{"location":"cli/notify/","level":1,"title":"Notify","text":"","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify","level":2,"title":"<code>ctx hook notify</code>","text":"<p>Send fire-and-forget webhook notifications from skills, loops, and hooks.</p> <pre><code>ctx hook notify --event <name> [--session-id <id>] \"message\"\n</code></pre> <p>Flags:</p> Flag Short Description <code>--event</code> <code>-e</code> Event name (required) <code>--session-id</code> <code>-s</code> Session ID (optional) <p>Behavior:</p> <ul> <li>No webhook configured: silent no-op (exit 0)</li> <li>Webhook set but event not in <code>events</code> list: silent no-op (exit 0)</li> <li>Webhook set and event matches: fire-and-forget HTTP POST</li> <li>HTTP errors silently ignored (no retry)</li> </ul> <p>Examples:</p> <pre><code>ctx hook notify --event loop \"Loop completed after 5 iterations\"\nctx hook notify -e nudge -s session-abc \"Context checkpoint at prompt #20\"\n</code></pre>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-setup","level":3,"title":"<code>ctx hook notify setup</code>","text":"<p>Configure the webhook URL interactively. The URL is encrypted with AES-256-GCM using the encryption key and stored in <code>.context/.notify.enc</code>.</p> <p>Examples:</p> <pre><code>ctx hook notify setup\n</code></pre> <p>The encrypted file is safe to commit. The key (<code>~/.ctx/.ctx.key</code>) lives outside the project and is never committed.</p>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/notify/#ctx-hook-notify-test","level":3,"title":"<code>ctx hook notify test</code>","text":"<p>Send a test notification and report the HTTP response status.</p> <p>Examples:</p> <pre><code>ctx hook notify test\n</code></pre> <p>Payload format (JSON POST):</p> <pre><code>{\n  \"event\": \"loop\",\n  \"message\": \"Loop completed after 5 iterations\",\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> Field Type Description <code>event</code> string Event name from <code>--event</code> flag <code>message</code> string Notification message <code>session_id</code> string Session ID (omitted if empty) <code>timestamp</code> string UTC RFC3339 timestamp <code>project</code> string Project directory name <p>See also: Webhook Notifications recipe.</p>","path":["CLI","Integrations","Notify"],"tags":[]},{"location":"cli/pad/","level":1,"title":"Scratchpad","text":"","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad","level":2,"title":"<code>ctx pad</code>","text":"<p>Encrypted scratchpad for sensitive one-liners that travel with the project.</p> <p>When invoked without a subcommand, lists all entries.</p> <pre><code>ctx pad\nctx pad <subcommand>\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-add","level":3,"title":"<code>ctx pad add</code>","text":"<p>Append a new entry to the scratchpad.</p> <pre><code>ctx pad add <text>\nctx pad add <label> --file <path>\n</code></pre> <p>Flags:</p> Flag Short Description <code>--file</code> <code>-f</code> Ingest a file as a blob entry (max 64 KB) <p>Examples:</p> <pre><code>ctx pad add \"DATABASE_URL=postgres://user:pass@host/db\"\nctx pad add \"deploy config\" --file ./deploy.yaml\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-show","level":3,"title":"<code>ctx pad show</code>","text":"<p>Output the raw text of an entry by number. For blob entries, prints decoded file content (or writes to disk with <code>--out</code>).</p> <pre><code>ctx pad show <n>\nctx pad show <n> --out <path>\n</code></pre> <p>Arguments:</p> <ul> <li><code>n</code>: 1-based entry number</li> </ul> <p>Flags:</p> Flag Description <code>--out</code> Write decoded blob content to a file (blobs only) <p>Examples:</p> <pre><code>ctx pad show 3\nctx pad show 2 --out ./recovered.yaml\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-rm","level":3,"title":"<code>ctx pad rm</code>","text":"<p>Remove one or more entries by stable ID. Supports individual IDs and ranges.</p> <pre><code>ctx pad rm <id> [id...]\n</code></pre> <p>Arguments:</p> <ul> <li><code>id</code>: One or more entry IDs (e.g., <code>3</code>, <code>1 4</code>, <code>3-5</code>)</li> </ul> <p>Examples:</p> <pre><code>ctx pad rm 2\nctx pad rm 1 4\nctx pad rm 3-5\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-normalize","level":3,"title":"<code>ctx pad normalize</code>","text":"<p>Reassign entry IDs as a contiguous sequence 1..N, closing any gaps left by deletions.</p> <p>Examples:</p> <pre><code>ctx pad normalize\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-edit","level":3,"title":"<code>ctx pad edit</code>","text":"<p>Replace, append to, or prepend to an entry.</p> <pre><code>ctx pad edit <n> [text]\n</code></pre> <p>Arguments:</p> <ul> <li><code>n</code>: 1-based entry number</li> <li><code>text</code>: Replacement text (mutually exclusive with   <code>--append</code>/<code>--prepend</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--append</code> Append text to the end of the entry <code>--prepend</code> Prepend text to the beginning of entry <code>--file</code> Replace blob file content (preserves label) <code>--label</code> Replace blob label (preserves content) <p>Examples:</p> <pre><code>ctx pad edit 2 \"new text\"\nctx pad edit 2 --append \" suffix\"\nctx pad edit 2 --prepend \"prefix \"\nctx pad edit 1 --file ./v2.yaml\nctx pad edit 1 --label \"new name\"\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-mv","level":3,"title":"<code>ctx pad mv</code>","text":"<p>Move an entry from one position to another.</p> <pre><code>ctx pad mv <from> <to>\n</code></pre> <p>Arguments:</p> <ul> <li><code>from</code>: Source position (1-based)</li> <li><code>to</code>: Destination position (1-based)</li> </ul> <p>Examples:</p> <pre><code>ctx pad mv 3 1      # promote entry 3 to the top\nctx pad mv 1 5      # bury entry 1 to position 5\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-resolve","level":3,"title":"<code>ctx pad resolve</code>","text":"<p>Show both sides of a merge conflict in the encrypted scratchpad.</p> <p>Examples:</p> <pre><code>ctx pad resolve\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-import","level":3,"title":"<code>ctx pad import</code>","text":"<p>Bulk-import lines from a file into the scratchpad. Each non-empty line becomes a separate entry. All entries are written in a single encrypt/write cycle.</p> <p>With <code>--blob</code>, import all first-level files from a directory as blob entries. Each file becomes a blob with the filename as its label. Subdirectories and non-regular files are skipped.</p> <pre><code>ctx pad import <file>\nctx pad import -              # read from stdin\nctx pad import --blob <dir>   # import directory files as blobs\n</code></pre> <p>Arguments:</p> <ul> <li><code>file</code>: Path to a text file, <code>-</code> for stdin, or a directory   (with <code>--blob</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--blob</code> Import first-level files from a directory as blobs <p>Examples:</p> <pre><code>ctx pad import notes.txt\ngrep TODO *.go | ctx pad import -\nctx pad import --blob ./ideas/\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-export","level":3,"title":"<code>ctx pad export</code>","text":"<p>Export all blob entries from the scratchpad to a directory as files. Each blob's label becomes the filename. Non-blob entries are skipped.</p> <pre><code>ctx pad export [dir]\n</code></pre> <p>Arguments:</p> <ul> <li><code>dir</code>: Target directory (default: current directory)</li> </ul> <p>Flags:</p> Flag Short Description <code>--force</code> <code>-f</code> Overwrite existing files instead of timestamping <code>--dry-run</code> Print what would be exported without writing <p>When a file already exists, a unix timestamp is prepended to avoid collisions (e.g., <code>1739836200-label</code>). Use <code>--force</code> to overwrite instead.</p> <p>Examples:</p> <pre><code>ctx pad export ./ideas\nctx pad export --dry-run\nctx pad export --force ./backup\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pad/#ctx-pad-merge","level":3,"title":"<code>ctx pad merge</code>","text":"<p>Merge entries from one or more scratchpad files into the current pad. Each input file is auto-detected as encrypted or plaintext. Entries are deduplicated by exact content.</p> <pre><code>ctx pad merge FILE...\n</code></pre> <p>Arguments:</p> <ul> <li><code>FILE...</code>: One or more scratchpad files to merge (encrypted   or plaintext)</li> </ul> <p>Flags:</p> Flag Short Description <code>--key</code> <code>-k</code> Path to key file for decrypting input files <code>--dry-run</code> Print what would be merged without writing <p>Examples:</p> <pre><code>ctx pad merge worktree/.context/scratchpad.enc\nctx pad merge notes.md backup.enc\nctx pad merge --key /path/to/other.key foreign.enc\nctx pad merge --dry-run pad-a.enc pad-b.md\n</code></pre>","path":["CLI","Sessions","Scratchpad"],"tags":[]},{"location":"cli/pause/","level":1,"title":"Pause","text":"","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/pause/#ctx-hook-pause","level":2,"title":"<code>ctx hook pause</code>","text":"<p>Pause all context nudge and reminder hooks for the current session. Security hooks (dangerous command blocking) and housekeeping hooks still fire.</p> <pre><code>ctx hook pause [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--session-id</code> Session ID (overrides stdin) <p>Example:</p> <pre><code># Pause hooks for a quick investigation\nctx hook pause\n\n# Resume when ready\nctx hook resume\n</code></pre> <p>See also:</p> <ul> <li><code>ctx hook resume</code>: the matching resume command</li> <li>Pausing Context Hooks recipe</li> </ul>","path":["CLI","Sessions","Pause"],"tags":[]},{"location":"cli/prune/","level":1,"title":"Prune","text":"","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/prune/#ctx-prune","level":3,"title":"<code>ctx prune</code>","text":"<p>Remove per-session state files from <code>.context/state/</code> that are older than the specified age. Session state files are identified by UUID suffixes (<code>context-check-<session-id></code>, <code>heartbeat-<session-id></code>, and similar). Global files without session IDs (<code>events.jsonl</code>, <code>memory-import.json</code>, and other non-per-session markers) are always preserved.</p> <pre><code>ctx prune [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--days</code> Prune files older than this many days (default: 7) <code>--dry-run</code> Show what would be pruned without deleting <p>Examples:</p> <pre><code>ctx prune                 # Prune files older than 7 days\nctx prune --days 3        # Prune files older than 3 days\nctx prune --dry-run       # Preview without deleting\n</code></pre> <p>See State maintenance for the recommended cadence and automation recipe.</p>","path":["CLI","Runtime","Prune"],"tags":[]},{"location":"cli/remind/","level":1,"title":"Remind","text":"","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind","level":2,"title":"<code>ctx remind</code>","text":"<p>Session-scoped reminders that surface at session start. Reminders are stored verbatim and relayed verbatim: no summarization, no categories.</p> <p>When invoked with a text argument and no subcommand, adds a reminder.</p> <pre><code>ctx remind \"text\"\nctx remind <subcommand>\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-add","level":3,"title":"<code>ctx remind add</code>","text":"<p>Add a reminder. This is the default action: <code>ctx remind \"text\"</code> and <code>ctx remind add \"text\"</code> are equivalent.</p> <pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind add \"check CI after the deploy\" --after 2026-02-25\n</code></pre> <p>Arguments:</p> <ul> <li><code>text</code>: The reminder message (verbatim)</li> </ul> <p>Flags:</p> Flag Short Description <code>--after</code> <code>-a</code> Don't surface until this date (YYYY-MM-DD) <p>Examples:</p> <pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind \"check CI after the deploy\" --after 2026-02-25\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-list","level":3,"title":"<code>ctx remind list</code>","text":"<p>List all pending reminders. Date-gated reminders that aren't yet due are annotated with <code>(after DATE, not yet due)</code>.</p> <p>Examples:</p> <pre><code>ctx remind list\nctx remind ls            # alias\n</code></pre> <p>Aliases: <code>ls</code></p>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-dismiss","level":3,"title":"<code>ctx remind dismiss</code>","text":"<p>Remove one or more reminders by ID, or remove all with <code>--all</code>. Supports individual IDs and ranges.</p> <pre><code>ctx remind dismiss <id> [id...]\nctx remind dismiss --all\n</code></pre> <p>Arguments:</p> <ul> <li><code>id</code>: One or more reminder IDs (e.g., <code>3</code>, <code>3 5-7</code>)</li> </ul> <p>Flags:</p> Flag Description <code>--all</code> Dismiss all reminders <p>Aliases: <code>rm</code></p> <p>Examples:</p> <pre><code>ctx remind dismiss 3\nctx remind dismiss 3 5-7\nctx remind dismiss --all\n</code></pre>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/remind/#ctx-remind-normalize","level":3,"title":"<code>ctx remind normalize</code>","text":"<p>Reassign reminder IDs as a contiguous sequence 1..N, closing any gaps left by dismissals.</p> <p>Examples:</p> <pre><code>ctx remind normalize\n</code></pre> <p>See also: Session Reminders recipe.</p>","path":["CLI","Sessions","Remind"],"tags":[]},{"location":"cli/resume/","level":1,"title":"Resume","text":"","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/resume/#ctx-hook-resume","level":2,"title":"<code>ctx hook resume</code>","text":"<p>Resume context hooks after a pause. Silent no-op if not paused.</p> <pre><code>ctx hook resume [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--session-id</code> Session ID (overrides stdin) <p>Example:</p> <pre><code>ctx hook resume\n</code></pre> <p>See also:</p> <ul> <li><code>ctx hook pause</code>: the matching pause command</li> <li>Pausing Context Hooks recipe</li> </ul>","path":["CLI","Sessions","Resume"],"tags":[]},{"location":"cli/serve/","level":1,"title":"Serve","text":"","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#ctx-serve","level":2,"title":"<code>ctx serve</code>","text":"<p>Serve a static site locally via zensical.</p> <p>With no argument, serves the journal site at <code>.context/journal-site</code>. With a directory argument, serves that directory if it contains a <code>zensical.toml</code>.</p> <pre><code>ctx serve                             # Serve .context/journal-site\nctx serve ./my-site                   # Serve a specific directory\nctx serve ./docs                      # Serve any zensical site\n</code></pre> <p>This Command Does NOT Start a Hub</p> <p><code>ctx serve</code> is purely for static-site serving. To run a <code>ctx</code> Hub for cross-project knowledge sharing, use <code>ctx hub start</code>. That command lives in its own group because the hub is a gRPC server, not a static site.</p> <p>Requires zensical to be installed:</p> <pre><code>pipx install zensical\n</code></pre>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#arguments","level":3,"title":"Arguments","text":"Argument Description <code>[directory]</code> Directory containing a <code>zensical.toml</code> to serve <p>When omitted, serves <code>.context/journal-site</code> by default, the directory produced by <code>ctx journal site</code>.</p> <p>Examples:</p> <pre><code>ctx serve                         # Default: serve .context/journal-site\nctx serve ./my-site               # Serve a specific directory\nctx serve ./docs                  # Serve any zensical site\n</code></pre>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/serve/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx journal</code>: generate the journal site   that <code>ctx serve</code> displays.</li> <li><code>ctx hub start</code>: for running a <code>ctx</code> Hub   server, not a static site.</li> <li>Browsing and enriching past sessions:   the recipe that combines <code>ctx journal</code> and <code>ctx serve</code>.</li> </ul>","path":["CLI","Integrations","Serve"],"tags":[]},{"location":"cli/setup/","level":1,"title":"Setup","text":"","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/setup/#ctx-setup","level":2,"title":"<code>ctx setup</code>","text":"<p>Generate AI tool integration configuration.</p> <pre><code>ctx setup <tool> [flags]\n</code></pre> <p>Flags:</p> Flag Short Description <code>--write</code> <code>-w</code> Write the generated config to disk (e.g. <code>.github/copilot-instructions.md</code>) <p>Supported tools:</p> Tool Description <code>claude-code</code> Redirects to plugin install instructions <code>cursor</code> Cursor IDE <code>kiro</code> Kiro IDE <code>cline</code> Cline (VS Code extension) <code>aider</code> Aider CLI <code>copilot</code> GitHub Copilot <code>opencode</code> OpenCode (terminal-first AI coding agent) <code>windsurf</code> Windsurf IDE <p>Claude Code Uses the Plugin System</p> <p>Claude Code integration is now provided via the <code>ctx</code> plugin. Running <code>ctx setup claude-code</code> prints plugin install instructions.</p> <p>Examples:</p> <pre><code># Print hook instructions to stdout\nctx setup cursor\nctx setup aider\n\n# Generate and write .github/copilot-instructions.md\nctx setup copilot --write\n\n# Generate MCP config and sync steering files\nctx setup kiro --write\nctx setup cursor --write\nctx setup cline --write\n\n# Generate OpenCode plugin, skills, AGENTS.md, and global MCP config\nctx setup opencode --write\n</code></pre>","path":["CLI","Integrations","Setup"],"tags":[]},{"location":"cli/site/","level":1,"title":"Site","text":"","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site","level":2,"title":"<code>ctx site</code>","text":"<p>Site management commands for the ctx.ist static site.</p> <pre><code>ctx site <subcommand>\n</code></pre>","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/site/#ctx-site-feed","level":3,"title":"<code>ctx site feed</code>","text":"<p>Generate an Atom 1.0 feed from finalized blog posts in <code>docs/blog/</code>.</p> <pre><code>ctx site feed [flags]\n</code></pre> <p>Scans <code>docs/blog/</code> for files matching <code>YYYY-MM-DD-*.md</code>, parses YAML frontmatter, and generates a valid Atom feed. Only posts with <code>reviewed_and_finalized: true</code> are included. Summaries are extracted from the first paragraph after the heading.</p> <p>Flags:</p> Flag Short Type Default Description <code>--out</code> <code>-o</code> string <code>site/feed.xml</code> Output path <code>--base-url</code> string <code>https://ctx.ist</code> Base URL for entry links <p>Output:</p> <pre><code>Generated site/feed.xml (21 entries)\n\nSkipped:\n  2026-02-25-the-homework-problem.md: not finalized\n\nWarnings:\n  2026-02-09-defense-in-depth.md: no summary paragraph found\n</code></pre> <p>Three buckets: included (count), skipped (with reason), warnings (included but degraded). <code>exit 0</code> always: warnings inform but do not block.</p> <p>Frontmatter requirements:</p> Field Required Feed mapping <code>title</code> Yes <code><title></code> <code>date</code> Yes <code><updated></code> <code>reviewed_and_finalized</code> Yes Draft gate (must be <code>true</code>) <code>author</code> No <code><author><name></code> <code>topics</code> No <code><category term=\"\"></code> <p>Examples:</p> <pre><code>ctx site feed                                # Generate site/feed.xml\nctx site feed --out /tmp/feed.xml            # Custom output path\nctx site feed --base-url https://example.com # Custom base URL\nmake site-feed                               # Makefile shortcut\nmake site                                    # Builds site + feed\n</code></pre>","path":["CLI","Integrations","Site"],"tags":[]},{"location":"cli/skill/","level":1,"title":"Skill","text":"","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill","level":2,"title":"<code>ctx skill</code>","text":"<p>Manage reusable instruction bundles that can be installed into <code>.context/skills/</code>.</p> <p>A skill is a directory containing a <code>SKILL.md</code> file with YAML frontmatter (<code>name</code>, <code>description</code>) and a Markdown instruction body. Skills are loaded by the agent context packet when <code>--skill <name></code> is passed to <code>ctx agent</code>.</p> <pre><code>ctx skill <subcommand>\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-install","level":3,"title":"<code>ctx skill install</code>","text":"<p>Install a skill from a source directory.</p> <pre><code>ctx skill install <source>\n</code></pre> <p>Arguments:</p> <ul> <li><code>source</code>: Path to a directory containing <code>SKILL.md</code></li> </ul> <p>Examples:</p> <pre><code>ctx skill install ./my-skills/code-review\n# Installed code-review → .context/skills/code-review\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-list","level":3,"title":"<code>ctx skill list</code>","text":"<p>List all installed skills.</p> <p>Examples:</p> <pre><code>ctx skill list\n</code></pre>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/skill/#ctx-skill-remove","level":3,"title":"<code>ctx skill remove</code>","text":"<p>Remove an installed skill.</p> <p>Arguments:</p> <ul> <li><code>name</code>: Skill name to remove</li> </ul> <p>Examples:</p> <pre><code>ctx skill remove code-review\n</code></pre> <p>See also: Building Project Skills recipe.</p>","path":["CLI","Integrations","Skill"],"tags":[]},{"location":"cli/steering/","level":1,"title":"Steering","text":"","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering","level":2,"title":"<code>ctx steering</code>","text":"<p>Manage steering files: persistent behavioral rules for AI coding assistants.</p> <p>A steering file is a small Markdown document with YAML frontmatter that tells the AI how to behave in a specific context. <code>ctx steering</code> keeps those files in <code>.context/steering/</code>, decides which ones apply for a given prompt, and syncs them out to each AI tool's native format (Claude Code, Cursor, Kiro, Cline).</p> <pre><code>ctx steering <subcommand>\n</code></pre> <p>Steering vs Decisions vs Conventions</p> <p>The three look similar on disk but serve different purposes:</p> <ul> <li>Decisions record what was chosen and why.   Consumed mostly by humans (and by the agent via   <code>ctx agent</code>).</li> <li>Conventions describe how the codebase is written.   Consumed as reference material.</li> <li>Steering tells the AI how to behave when asked   about X. Consumed by the AI tool's prompt injection   layer, conditionally on prompt match.</li> </ul> <p>If you find yourself writing \"the AI should always do X\", that belongs in steering, not decisions.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#anatomy-of-a-steering-file","level":3,"title":"Anatomy of a Steering File","text":"<pre><code>---\nname: security\ndescription: Security rules for all code changes\ninclusion: always    # always | auto | manual\ntools: []            # empty = all tools\npriority: 10         # lower = injected first\n---\n\n# Security rules\n\n- Validate all user input at system boundaries.\n- Never log secrets, tokens, or credentials.\n- Prefer constant-time comparison for tokens.\n</code></pre> <p>Inclusion modes:</p> Mode When it's included <code>always</code> Every prompt, unconditionally <code>auto</code> When the prompt matches the <code>description</code> keywords <code>manual</code> Only when the user names it explicitly <p>Priority: lower numbers inject first, so high-priority rules appear at the top of the prompt. Default is <code>50</code>.</p> <p>Tools: an empty list means all configured tools receive the file; list specific tool names to scope it.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-init","level":3,"title":"<code>ctx steering init</code>","text":"<p>Create a starter set of steering files in <code>.context/steering/</code> to use as a scaffolding baseline.</p> <p>Examples:</p> <pre><code>ctx steering init\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-add","level":3,"title":"<code>ctx steering add</code>","text":"<p>Create a new steering file with default frontmatter.</p> <pre><code>ctx steering add <name>\n</code></pre> <p>Arguments:</p> <ul> <li><code>name</code>: Steering file name (without <code>.md</code> extension)</li> </ul> <p>Examples:</p> <pre><code>ctx steering add security\n# Created .context/steering/security.md\n</code></pre> <p>The generated file uses <code>inclusion: manual</code> and <code>priority: 50</code> by default. Edit the frontmatter to change behavior.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-list","level":3,"title":"<code>ctx steering list</code>","text":"<p>List all steering files with their inclusion mode, priority, and tool scoping.</p> <p>Examples:</p> <pre><code>ctx steering list\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-preview","level":3,"title":"<code>ctx steering preview</code>","text":"<p>Preview which steering files would be included for a given prompt. Useful for validating <code>auto</code>-inclusion descriptions against realistic prompts.</p> <pre><code>ctx steering preview [prompt]\n</code></pre> <p>Examples:</p> <pre><code>ctx steering preview \"create a REST API endpoint\"\n# Steering files matching prompt \"create a REST API endpoint\":\n#   api-standards        inclusion=auto     priority=20  tools=all\n#   security             inclusion=always   priority=10  tools=all\n</code></pre>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-steering-sync","level":3,"title":"<code>ctx steering sync</code>","text":"<p>Sync steering files to tool-native formats for tools that have a built-in rules primitive. Not every tool needs this; Claude Code and Codex use a different delivery mechanism (see below).</p> <p>Examples:</p> <pre><code>ctx steering sync\n</code></pre> <p>Which tools are sync targets?</p> Tool Sync target Mechanism Cursor <code>.cursor/rules/</code> Cursor reads the directory natively Cline <code>.clinerules/</code> Cline reads the directory natively Kiro <code>.kiro/steering/</code> Kiro reads the directory natively Claude Code (no-op) Delivered via hook + MCP (see next section) Codex (no-op) Same as Claude Code <p>For the three native-rules tools, <code>ctx steering sync</code> writes each matching steering file to the appropriate directory with tool-specific frontmatter transforms. Unchanged files are skipped (idempotent).</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#how-claude-code-and-codex-consume-steering","level":3,"title":"How Claude Code and Codex Consume Steering","text":"<p>Claude Code has no native \"steering files\" primitive, so <code>ctx steering sync</code> skips it entirely. Instead, steering reaches Claude through two non-sync channels, both activated by <code>ctx setup claude-code</code> (which installs the plugin):</p> <p>1. Automatic injection via the <code>PreToolUse</code> hook. The Claude Code plugin wires a <code>PreToolUse</code> hook that runs <code>ctx agent --budget 8000</code> before each tool call. <code>ctx agent</code> loads <code>.context/steering/</code> and calls <code>steering.Filter</code> with an empty prompt, so only files with <code>inclusion: always</code> match. Those files are included as Tier 6 of the context packet. The packet is printed on stdout, which Claude Code injects as additional context. This fires on every tool call; no user action.</p> <p>2. On-demand MCP tool call (<code>ctx_steering_get</code>). The <code>ctx</code> plugin ships a <code>.mcp.json</code> file that automatically registers the <code>ctx</code> MCP server (<code>ctx mcp serve</code>) with Claude Code on plugin install. Once registered, Claude can invoke the <code>ctx_steering_get</code> tool mid-task to fetch matching steering files for a specific prompt. This is the only path that resolves <code>inclusion: auto</code> and <code>inclusion: manual</code> matches for Claude Code; Claude passes the prompt to the MCP tool, which runs the keyword match against each file's description.</p> <p>Verify the MCP server is registered:</p> <pre><code>claude mcp list\n</code></pre> <p>Expected line: <code>ctx: ctx mcp serve - ✓ Connected</code>. If it's missing, reinstall the plugin from Claude Code (<code>/plugin</code> → find <code>ctx</code> → uninstall → install again); older plugin versions shipped without the <code>.mcp.json</code> file.</p> <p>Prefer <code>inclusion: always</code> for Claude Code</p> <p>Because the PreToolUse hook passes an empty prompt to <code>ctx agent</code>, only <code>always</code> files fire automatically. <code>auto</code> files require Claude to call the <code>ctx_steering_get</code> MCP tool on its own; <code>manual</code> files require an explicit user invocation. For rules that should reliably fire on every Claude Code session, use <code>inclusion: always</code>. Reserve <code>auto</code>/<code>manual</code> for situational libraries where the opt-in cost is acceptable and you understand Claude may not pull them in without prompting.</p> <p>The foundation files scaffolded by <code>ctx init</code> already default to <code>inclusion: always</code> for this reason.</p> <p>Practical implications:</p> <ul> <li>Running <code>ctx steering sync</code> before starting a Claude   session does nothing for Claude's benefit. Skip it.</li> <li><code>ctx steering preview</code> still works for validating your   descriptions; it doesn't depend on sync.</li> <li>If Claude Code is your only tool, the <code>ctx steering</code>   commands you care about are <code>add</code>, <code>list</code>, <code>preview</code>,   <code>init</code> (never <code>sync</code>).</li> <li>If you use both Claude Code and (say) Cursor,   <code>ctx steering sync</code> covers Cursor (where <code>auto</code> and   <code>manual</code> work natively) while the hook+MCP pipeline   covers Claude Code. For rules you need to fire   automatically on both, use <code>inclusion: always</code>.</li> </ul>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#ctx-agent-integration","level":3,"title":"<code>ctx agent</code> Integration","text":"<p>When <code>ctx agent</code> builds a context packet, steering files are loaded as Tier 6 of the budget-aware assembly (see <code>ctx agent</code>). Files with <code>inclusion: always</code> are always included; <code>auto</code> files are scored against the current prompt and included in priority order until the tier budget is exhausted.</p>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/steering/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx setup</code>: configure which tools receive   steering syncs</li> <li><code>ctx trigger</code>: lifecycle scripts (a different   hooking concept, see below)</li> <li>Building steering files recipe:   walkthrough from first file to synced output</li> </ul>","path":["CLI","Integrations","Steering"],"tags":[]},{"location":"cli/sysinfo/","level":1,"title":"Sysinfo","text":"","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/sysinfo/#ctx-sysinfo","level":3,"title":"<code>ctx sysinfo</code>","text":"<p>Display a snapshot of system resources (memory, swap, disk, load) with threshold-based alert severities. Mirrors what the <code>check-resource</code> hook plumbing monitors in the background, but this command prints the full report at any severity level, not only at DANGER.</p> <pre><code>ctx sysinfo [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--json</code> Output in JSON format <p>Alert thresholds:</p> Resource WARNING DANGER Memory ≥ 75% ≥ 90% Swap ≥ 50% ≥ 75% Disk ≥ 85% ≥ 95% Load ≥ 1.0x CPUs ≥ 1.5x CPUs <p>Examples:</p> <pre><code>ctx sysinfo                  # Human-readable table\nctx sysinfo --json           # Structured output\n</code></pre>","path":["CLI","Diagnostics","Sysinfo"],"tags":[]},{"location":"cli/system/","level":1,"title":"System","text":"","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system","level":3,"title":"<code>ctx system</code>","text":"<p>Hidden parent command that hosts Claude Code hook plumbing and a small set of session-lifecycle plumbing subcommands used by skills and editor integrations. The parent is registered without a visible group in <code>ctx --help</code>; run <code>ctx system --help</code> to see its subcommands.</p> <pre><code>ctx system <subcommand>\n</code></pre> <p>Commands Previously under <code>ctx system</code></p> <p>Several user-facing maintenance commands used to live under <code>ctx system</code> and were promoted to top-level:</p> <ul> <li><code>ctx system events</code> → <code>ctx hook event</code></li> <li><code>ctx system message</code> → <code>ctx hook message</code></li> <li><code>ctx system prune</code> → <code>ctx prune</code></li> <li><code>ctx system resources</code> → <code>ctx sysinfo</code></li> <li><code>ctx system stats</code> → <code>ctx usage</code></li> </ul> <p><code>ctx system bootstrap</code> remains under <code>ctx system</code> as a hidden, agent-only command. Update any scripts or personal docs that reference the old paths.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#plumbing-subcommands","level":2,"title":"Plumbing Subcommands","text":"<p>These are not hook handlers; they're called by skills and editor integrations during the session lifecycle. Safe to run manually.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-journal","level":4,"title":"<code>ctx system mark-journal</code>","text":"<p>Update processing state for a journal entry. Records the current date in <code>.context/journal/.state.json</code>. Used by journal skills to record pipeline progress.</p> <pre><code>ctx system mark-journal <filename> <stage>\n</code></pre> <p>Stages: <code>exported</code>, <code>enriched</code>, <code>normalized</code>, <code>fences_verified</code></p> Flag Description <code>--check</code> Check if stage is set (exit 1 if not) <p>Example:</p> <pre><code>ctx system mark-journal 2026-01-21-session-abc12345.md enriched\nctx system mark-journal 2026-01-21-session-abc12345.md normalized\nctx system mark-journal --check 2026-01-21-session-abc12345.md fences_verified\n</code></pre>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-mark-wrapped-up","level":4,"title":"<code>ctx system mark-wrapped-up</code>","text":"<p>Suppress context checkpoint nudges after a wrap-up ceremony. Writes a marker file that <code>check-context-size</code> checks before emitting checkpoint boxes. The marker expires after 2 hours.</p> <p>Called automatically by <code>/ctx-wrap-up</code> after persisting context (not intended for direct use).</p> <pre><code>ctx system mark-wrapped-up\n</code></pre> <p>No flags, no arguments. Idempotent: running it again updates the marker timestamp.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-pause-ctx-system-resume","level":4,"title":"<code>ctx system pause</code> / <code>ctx system resume</code>","text":"<p>Session-scoped hook suppression. <code>ctx system pause</code> writes a marker file that causes hook plumbing to no-op for the current session; <code>ctx system resume</code> removes it. These are the hook-plumbing counterparts to the <code>ctx hook pause</code> / <code>ctx hook resume</code> commands (which call them internally).</p> <p>Read the session ID from stdin JSON (same as hooks) or pass <code>--session-id</code>.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#ctx-system-session-event","level":4,"title":"<code>ctx system session-event</code>","text":"<p>Records a session lifecycle event (start or end) to the event log. Called by editor integrations when a workspace is opened or closed.</p> <pre><code>ctx system session-event --type start --caller vscode\nctx system session-event --type end --caller vscode\n</code></pre>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/system/#hook-subcommands","level":2,"title":"Hook Subcommands","text":"<p>Hidden Claude Code hook handlers implementing the hook contract: read JSON from stdin, perform logic, emit output on stdout, exit 0. Block commands output JSON with a <code>decision</code> field.</p> <p>UserPromptSubmit hooks: <code>context-load-gate</code>, <code>check-context-size</code>, <code>check-persistence</code>, <code>check-ceremony</code>, <code>check-journal</code>, <code>check-version</code>, <code>check-resource</code>, <code>check-knowledge</code>, <code>check-map-staleness</code>, <code>check-memory-drift</code>, <code>check-reminder</code>, <code>check-freshness</code>, <code>check-hub-sync</code>, <code>check-skill-discovery</code>, <code>heartbeat</code>.</p> <p>PreToolUse hooks: <code>block-non-path-ctx</code>, <code>block-dangerous-command</code>, <code>qa-reminder</code>, <code>specs-nudge</code>.</p> <p>PostToolUse hooks: <code>post-commit</code>, <code>check-task-completion</code>.</p> <p>See AI Tools for registration details and the Claude Code plugin integration.</p>","path":["CLI","Runtime","System"],"tags":[]},{"location":"cli/trace/","level":1,"title":"Commit Context Tracing","text":"","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace","level":3,"title":"<code>ctx trace</code>","text":"<p>Show the context behind git commits. Links commits back to the decisions, tasks, learnings, and sessions that motivated them.</p> <p><code>git log</code> shows what changed, <code>git blame</code> shows who, and <code>ctx trace</code> shows why.</p> <pre><code>ctx trace [commit] [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--last N</code> Show context for last N commits <code>--json</code> Output as JSON for scripting <p>Examples:</p> <pre><code># Show context for a specific commit\nctx trace abc123\n\n# Show context for last 10 commits\nctx trace --last 10\n\n# JSON output\nctx trace abc123 --json\n</code></pre> <p>Output:</p> <pre><code>Commit: abc123 \"Fix auth token expiry\"\nDate:   2026-03-14 10:00:00 -0700\nContext:\n  [Decision] #12: Use short-lived tokens with server-side refresh\n    Date: 2026-03-10\n\n  [Task] #8: Implement token rotation for compliance\n    Status: completed\n</code></pre> <p>When listing recent commits with <code>--last</code>:</p> <pre><code>abc123  Fix auth token expiry         decision:12, task:8\ndef456  Add rate limiting             decision:15, learning:7\n789abc  Update dependencies           (none)\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-file","level":3,"title":"<code>ctx trace file</code>","text":"<p>Show the context trail for a file. Combines <code>git log</code> with context resolution.</p> <pre><code>ctx trace file <path[:line-range]> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--last N</code> Maximum commits to show (default: 20) <p>Examples:</p> <pre><code># Show context trail for a file\nctx trace file src/auth.go\n\n# Show context for specific line range\nctx trace file src/auth.go:42-60\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-tag","level":3,"title":"<code>ctx trace tag</code>","text":"<p>Manually tag a commit with context. For commits made without the hook, or to add extra context after the fact.</p> <p>Tags are stored in <code>.context/trace/overrides.jsonl</code> since git trailers cannot be added to existing commits without rewriting history.</p> <pre><code>ctx trace tag <commit> --note \"<text>\"\n</code></pre> <p>Examples:</p> <pre><code>ctx trace tag HEAD --note \"Hotfix for production outage\"\nctx trace tag abc123 --note \"Part of Q1 compliance initiative\"\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#ctx-trace-hook","level":3,"title":"<code>ctx trace hook</code>","text":"<p>Enable or disable the prepare-commit-msg hook for automatic context tracing. When enabled, commits automatically receive a <code>ctx-context</code> trailer with references to relevant decisions, tasks, learnings, and sessions.</p> <pre><code>ctx trace hook <enable|disable>\n</code></pre> <p>Prerequisites: <code>ctx</code> must be on your <code>$PATH</code>. If you installed via <code>go install</code>, ensure <code>$GOPATH/bin</code> (or <code>$HOME/go/bin</code>) is in your shell's <code>$PATH</code>.</p> <p>What the hook does:</p> <ol> <li>Before each commit, collects context from three sources:</li> <li>Pending context accumulated during work (<code>ctx add</code>, <code>ctx task complete</code>)</li> <li>Staged file changes to <code>.context/</code> files</li> <li>Working state (in-progress tasks, active AI session)</li> <li>Injects a <code>ctx-context</code> trailer into the commit message</li> <li>After commit, records the mapping in <code>.context/trace/history.jsonl</code></li> </ol> <p>Examples:</p> <pre><code># Install the hook\nctx trace hook enable\n\n# Remove the hook\nctx trace hook disable\n</code></pre> <p>Resulting commit message:</p> <pre><code>Fix auth token expiry handling\n\nRefactored token refresh logic to handle edge case\nwhere refresh token expires during request.\n\nctx-context: decision:12, task:8, session:abc123\n</code></pre>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#reference-types","level":3,"title":"Reference Types","text":"<p>The <code>ctx-context</code> trailer supports these reference types:</p> Prefix Points to Example <code>decision:<n></code> Entry #n in DECISIONS.md <code>decision:12</code> <code>learning:<n></code> Entry #n in LEARNINGS.md <code>learning:5</code> <code>task:<n></code> Task #n in TASKS.md <code>task:8</code> <code>convention:<n></code> Entry #n in CONVENTIONS.md <code>convention:3</code> <code>session:<id></code> AI session by ID <code>session:abc123</code> <code>\"<text>\"</code> Free-form context note <code>\"Performance fix for P1 incident\"</code>","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trace/#storage","level":3,"title":"Storage","text":"<p>Context trace data is stored in the <code>.context/</code> directory:</p> File Purpose Lifecycle <code>state/pending-context.jsonl</code> Accumulates refs during work Truncated after each commit <code>trace/history.jsonl</code> Permanent commit-to-context map Append-only, never truncated <code>trace/overrides.jsonl</code> Manual tags for existing commits Append-only","path":["CLI","Diagnostics","Commit Context Tracing"],"tags":[]},{"location":"cli/trigger/","level":1,"title":"Trigger","text":"","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger","level":2,"title":"<code>ctx trigger</code>","text":"<p>Manage lifecycle triggers: executable scripts that fire at specific events during an AI session. Triggers can block tool calls, inject context, and automate reactions: any side effect you want at session boundaries, tool boundaries, or file-save events.</p> <pre><code>ctx trigger <subcommand>\n</code></pre> <p>Triggers Execute Arbitrary Scripts</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks: only enable scripts you've read and understand. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#where-triggers-live","level":3,"title":"Where Triggers Live","text":"<p>Triggers live in <code>.context/hooks/<trigger-type>/</code> as executable scripts. The on-disk directory name is still <code>hooks/</code> for historical reasons even though the command is <code>ctx trigger</code>. Each script:</p> <ul> <li>Reads a JSON payload from stdin.</li> <li>Returns a JSON payload on stdout.</li> <li>Returns a non-zero exit code to block or error.</li> </ul> <pre><code>.context/\n└── hooks/\n    ├── session-start/\n    │   └── inject-context.sh\n    ├── pre-tool-use/\n    │   └── block-legacy.sh\n    └── post-tool-use/\n        └── record-edit.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#trigger-types","level":3,"title":"Trigger Types","text":"Type Fires when <code>session-start</code> An AI session begins <code>session-end</code> An AI session ends <code>pre-tool-use</code> Before an AI tool call is executed <code>post-tool-use</code> After an AI tool call returns <code>file-save</code> When a file is saved <code>context-add</code> When a context entry is added","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#input-and-output-contract","level":3,"title":"Input and Output Contract","text":"<p>Each trigger receives a JSON object on stdin with the event details. Minimal contract (fields vary by trigger type):</p> <pre><code>{\n  \"type\": \"pre-tool-use\",\n  \"tool\": \"write_file\",\n  \"path\": \"src/auth.go\",\n  \"session_id\": \"abc123-...\"\n}\n</code></pre> <p>The trigger may write a JSON object to stdout to influence behavior. Example for a blocking <code>pre-tool-use</code> trigger:</p> <pre><code>{\n  \"action\": \"block\",\n  \"message\": \"Editing src/auth.go requires approval from #security\"\n}\n</code></pre> <p>For non-blocking event loggers, simply read stdin and exit 0 without writing to stdout.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-add","level":3,"title":"<code>ctx trigger add</code>","text":"<p>Create a new trigger script with a template. The generated file has a bash shebang, a stdin reader using <code>jq</code>, and a basic JSON output structure.</p> <pre><code>ctx trigger add <trigger-type> <name>\n</code></pre> <p>Arguments:</p> <ul> <li><code>trigger-type</code>: One of <code>session-start</code>, <code>session-end</code>,   <code>pre-tool-use</code>, <code>post-tool-use</code>, <code>file-save</code>, <code>context-add</code></li> <li><code>name</code>: Script name (without <code>.sh</code> extension)</li> </ul> <p>Examples:</p> <pre><code>ctx trigger add session-start inject-context\n# Created .context/hooks/session-start/inject-context.sh\n\nctx trigger add pre-tool-use block-legacy\n# Created .context/hooks/pre-tool-use/block-legacy.sh\n</code></pre> <p>The generated script is not executable by default. Enable it with <code>ctx trigger enable</code> after reviewing the contents.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-list","level":3,"title":"<code>ctx trigger list</code>","text":"<p>List all discovered triggers, grouped by trigger type, with their enabled/disabled status.</p> <p>Examples:</p> <pre><code>ctx trigger list\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-test","level":3,"title":"<code>ctx trigger test</code>","text":"<p>Run all enabled triggers of a given type against a mock payload. Use <code>--tool</code> and <code>--path</code> to customize the mock input for tool-related events.</p> <pre><code>ctx trigger test <trigger-type> [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--tool</code> Tool name to put in mock input <code>--path</code> File path to put in mock input <p>Examples:</p> <pre><code>ctx trigger test session-start\nctx trigger test pre-tool-use --tool write_file --path src/main.go\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-enable","level":3,"title":"<code>ctx trigger enable</code>","text":"<p>Enable a trigger by setting its executable permission bit. Searches every trigger-type directory for a script matching <code><name></code>.</p> <pre><code>ctx trigger enable <name>\n</code></pre> <p>Examples:</p> <pre><code>ctx trigger enable inject-context\n# Enabled .context/hooks/session-start/inject-context.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#ctx-trigger-disable","level":3,"title":"<code>ctx trigger disable</code>","text":"<p>Disable a trigger by clearing its executable permission bit. Searches every trigger-type directory for a script matching <code><name></code>.</p> <pre><code>ctx trigger disable <name>\n</code></pre> <p>Examples:</p> <pre><code>ctx trigger disable inject-context\n# Disabled .context/hooks/session-start/inject-context.sh\n</code></pre>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#three-hooking-concepts-in-ctx-dont-confuse-them","level":3,"title":"Three Hooking Concepts in <code>ctx</code> (Don't Confuse Them)","text":"<p>This is a common source of confusion. <code>ctx</code> has three distinct hook-like layers, and they serve different purposes:</p> Layer Owned by Where it runs Configured via <code>ctx trigger</code> You <code>.context/hooks/<type>/*.sh</code> <code>ctx trigger add/enable</code> <code>ctx system</code> hooks <code>ctx</code> itself built-in, called by <code>ctx</code>'s own lifecycle internal (see <code>ctx system --help</code>) Claude Code hooks Claude Code <code>.claude/settings.local.json</code> edit JSON, or <code>/ctx-sanitize-permissions</code> <p>Use <code>ctx trigger</code> when you want project-specific automation that your AI tool will run at lifecycle events. Use Claude Code hooks for tool-specific integrations that don't need to be portable across tools. <code>ctx system</code> hooks are not something you author; they're the internal nudge machinery that ships with ctx.</p>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/trigger/#see-also","level":3,"title":"See Also","text":"<ul> <li><code>ctx steering</code>: persistent AI behavioral   rules (a different concept; rules vs scripts)</li> <li>Authoring triggers recipe: a   full walkthrough with security guidance</li> </ul>","path":["CLI","Integrations","Trigger"],"tags":[]},{"location":"cli/usage/","level":1,"title":"Usage","text":"","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/usage/#ctx-usage","level":3,"title":"<code>ctx usage</code>","text":"<p>Display per-session token usage statistics from the local stats JSONL files written by the <code>heartbeat</code> hook. By default, shows the last 20 entries across all sessions. Use <code>--follow</code> to stream new entries as they arrive (like <code>tail -f</code>).</p> <pre><code>ctx usage [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>-f</code>, <code>--follow</code> Stream new entries as they arrive <code>-s</code>, <code>--session</code> Filter by session ID (prefix match) <code>-n</code>, <code>--last</code> Show last N entries (default: 20) <code>-j</code>, <code>--json</code> Output raw JSONL <p>Examples:</p> <pre><code>ctx usage                     # Last 20 entries across all sessions\nctx usage --follow            # Live stream (like tail -f)\nctx usage --session abc123    # Filter to one session\nctx usage --last 100 --json   # Last 100 as raw JSONL\n</code></pre>","path":["CLI","Diagnostics","Usage"],"tags":[]},{"location":"cli/watch/","level":1,"title":"Watch","text":"","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/watch/#ctx-watch","level":2,"title":"<code>ctx watch</code>","text":"<p>Watch for AI output and auto-apply context updates.</p> <p>Parses <code><context-update></code> XML commands from AI output and applies them to context files.</p> <pre><code>ctx watch [flags]\n</code></pre> <p>Flags:</p> Flag Description <code>--log <file></code> Log file to watch (default: stdin) <code>--dry-run</code> Preview updates without applying <p>Examples:</p> <pre><code># Watch stdin\nai-tool | ctx watch\n\n# Watch a log file\nctx watch --log /path/to/ai-output.log\n\n# Preview without applying\nctx watch --dry-run\n</code></pre>","path":["CLI","Context","Watch"],"tags":[]},{"location":"cli/why/","level":1,"title":"Why","text":"","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"cli/why/#ctx-why","level":2,"title":"<code>ctx why</code>","text":"<p>Read <code>ctx</code>'s philosophy documents directly in the terminal.</p> <pre><code>ctx why [DOCUMENT]\n</code></pre> <p>Documents:</p> Name Description <code>manifesto</code> The <code>ctx</code> Manifesto: creation, not code <code>about</code> About <code>ctx</code>: what it is and why it exists <code>invariants</code> Design invariants: properties that must hold <p>Examples:</p> <pre><code># Interactive numbered menu\nctx why\n\n# Show a specific document\nctx why manifesto\nctx why about\nctx why invariants\n\n# Pipe to a pager\nctx why manifesto | less\n</code></pre>","path":["CLI","Getting Started","Why"],"tags":[]},{"location":"home/","level":1,"title":"Home","text":"<ul> <li><code>ctx</code> is not a prompt.</li> <li><code>ctx</code> is version-controlled cognitive state.</li> </ul> <p><code>ctx</code> is the persistence layer for human-AI reasoning.</p> <p>Deterministic. Git-native. Human-readable. Local-first.</p> <p>Start here.</p> <p>Learn what <code>ctx</code> does, set it up, and run your first session.</p> <p>Pre-1.0: Moving Fast</p> <p><code>ctx</code> is under active development. This website tracks the development branch, not the latest release:</p> <p>Some features described here may not exist in the binary you have installed.</p> <p>Expect rough edges.</p> <p>If something is missing or broken, open an issue.</p>","path":["Home"],"tags":[]},{"location":"home/#introduction","level":2,"title":"Introduction","text":"","path":["Home"],"tags":[]},{"location":"home/#about","level":3,"title":"About","text":"<p>What <code>ctx</code> is, how it works, and why persistent context changes how you work with AI.</p>","path":["Home"],"tags":[]},{"location":"home/#is-it-right-for-me","level":3,"title":"Is It Right for Me?","text":"<p>Good fit, not-so-good fit, and a 5-minute trial to find out for yourself.</p>","path":["Home"],"tags":[]},{"location":"home/#faq","level":3,"title":"FAQ","text":"<p>Quick answers to the questions newcomers ask most about <code>ctx</code>, files, tooling, and trade-offs.</p>","path":["Home"],"tags":[]},{"location":"home/#get-started","level":2,"title":"Get Started","text":"","path":["Home"],"tags":[]},{"location":"home/#getting-started","level":3,"title":"Getting Started","text":"<p>Install the binary, set up the plugin, and verify it works.</p>","path":["Home"],"tags":[]},{"location":"home/#your-first-session","level":3,"title":"Your First Session","text":"<p>Step-by-step walkthrough from <code>ctx init</code> to verified recall.</p>","path":["Home"],"tags":[]},{"location":"home/#common-workflows","level":3,"title":"Common Workflows","text":"<p>Day-to-day commands for tracking context, checking health, and browsing history.</p>","path":["Home"],"tags":[]},{"location":"home/#concepts","level":2,"title":"Concepts","text":"","path":["Home"],"tags":[]},{"location":"home/#context-files","level":3,"title":"Context Files","text":"<p>What each <code>.context/</code> file does. What's their purpose. How do we best leverage them.</p>","path":["Home"],"tags":[]},{"location":"home/#configuration","level":3,"title":"Configuration","text":"<p>Flexible configuration: <code>.ctxrc</code>, environment variables, and CLI flags.</p>","path":["Home"],"tags":[]},{"location":"home/#hub","level":3,"title":"Hub","text":"<p>A fan-out channel for decisions, learnings, conventions, and tasks that need to cross project boundaries, without replicating everything else.</p>","path":["Home"],"tags":[]},{"location":"home/#working-with-ai","level":2,"title":"Working with AI","text":"","path":["Home"],"tags":[]},{"location":"home/#prompting-guide","level":3,"title":"Prompting Guide","text":"<p>Effective prompts for AI sessions with <code>ctx</code>.</p>","path":["Home"],"tags":[]},{"location":"home/#keeping-ai-honest","level":3,"title":"Keeping AI Honest","text":"<p>AI agents confabulate: they invent history, claim familiarity with decisions never made, and sometimes declare tasks complete when they aren't. Tools and habits to push back.</p>","path":["Home"],"tags":[]},{"location":"home/#my-ai-keeps-making-the-same-mistakes","level":3,"title":"My AI Keeps Making the Same Mistakes","text":"<p>Stop rediscovering the same bugs and dead-ends across sessions.</p>","path":["Home"],"tags":[]},{"location":"home/#joining-a-project","level":3,"title":"Joining a Project","text":"<p>You inherited a <code>.context/</code> directory. Get oriented fast: priority order, what to read first, how to ramp up.</p>","path":["Home"],"tags":[]},{"location":"home/#customization","level":2,"title":"Customization","text":"","path":["Home"],"tags":[]},{"location":"home/#steering-files","level":3,"title":"Steering Files","text":"<p>Tell the assistant how to behave when a specific kind of prompt arrives.</p>","path":["Home"],"tags":[]},{"location":"home/#lifecycle-triggers","level":3,"title":"Lifecycle Triggers","text":"<p>Make things happen at session boundaries: block dangerous tool calls, inject standup notes, log file saves.</p>","path":["Home"],"tags":[]},{"location":"home/#community","level":2,"title":"Community","text":"","path":["Home"],"tags":[]},{"location":"home/#ctx","level":3,"title":"#<code>ctx</code>","text":"<p>We are the builders who care about durable context. Join the community. Hang out in IRC. Star <code>ctx</code> on GitHub.</p>","path":["Home"],"tags":[]},{"location":"home/#contributing","level":3,"title":"Contributing","text":"<p>Development setup, project layout, and pull request process.</p>","path":["Home"],"tags":[]},{"location":"home/about/","level":1,"title":"About","text":"<p>\"Creation, not code; Context, not prompts; Verification, not vibes.\"</p> <p>Read the <code>ctx</code> Manifesto →</p> <p>\"Without durable context, intelligence resets; with <code>ctx</code>, creation compounds.\"</p> <p>Without persistent memory, every session starts at zero; <code>ctx</code> makes sessions cumulative.</p> <p>Join the <code>ctx</code> Community →</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#what-is-ctx","level":2,"title":"What Is <code>ctx</code>?","text":"<p><code>ctx</code> (Context) is a file-based system that enables AI coding assistants to persist project knowledge across sessions. It lives in a <code>.context/</code> directory in your repo.</p> <ul> <li>A session is interactive.</li> <li><code>ctx</code> enables cognitive continuity.</li> <li>Cognitive continuity enables durable, symbiotic-like human-AI workflows.</li> </ul> <p>Context Files</p> <p>Context files let AI tools remember decisions, conventions, and learnings:</p> <p>Context files are explicit and versionable contracts  between you and your agents.</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#why-do-i-keep-re-explaining-my-codebase","level":2,"title":"Why Do I Keep Re-Explaining My Codebase?!?!","text":"<p>You open a new AI session. The first thing you do is re-explain your project.</p> <p>Again.</p> <p>The architecture, the database choice, the naming conventions, the thing you tried last week that didn't work. You have said all of this before (maybe yesterday, maybe an hour ago), but the AI doesn't know that.</p> <ul> <li>You explain the same architecture every session;</li> <li>The AI suggests an approach you already rejected, again;</li> <li>A decision you made three sessions ago gets relitigated from scratch;</li> <li>You spend more time setting context than building features.</li> </ul> <p>This isn't AI failure: It is staleness. </p> <p>Without persistent memory, every session starts at zero.</p>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#day-and-night","level":3,"title":"Day and Night","text":"<p><code>ctx</code> gives your AI a memory that persists across sessions:</p> Without <code>ctx</code>With <code>ctx</code> <pre><code>Session 12: Monday morning\n\nAI:  \"I'll scaffold a MySQL schema for this.\"\nYou: \"We use PostgreSQL.\"\n\nAI:  \"For auth, session-based would be simplest.\"\nYou: \"We decided on JWT.\"\n\nAI:  \"Endpoints in camelCase?\"\nYou: \"snake_case! Like the rest of the API!!!!!!\"\n\nYou: *scrolls up to copy an existing endpoint*\n\n20 minutes spent restoring decisions that already exist.\n</code></pre> <pre><code>Session 12: Monday morning\n\nYou:  \"Let's continue the auth implementation.\"\nAI:   \"Per DECISIONS.md (2026-02-12): JWT, not sessions: \n       The project uses PostgreSQL with JWT\n       authentication. The API follows snake_case conventions.\n       Last session you completed the login endpoint: the\n       registration endpoint is next on TASKS.md. Want me to\n       start there?\"\n\n0 minutes re-explaining. Straight to building.\n</code></pre>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/about/#how-ctx-solves-this","level":3,"title":"How <code>ctx</code> Solves This","text":"<p><code>ctx</code> creates a <code>.context/</code> directory in your project that stores structured knowledge files:</p> File What It Remembers <code>TASKS.md</code> What you're working on and what's next <code>DECISIONS.md</code> Architectural choices and why you made them <code>LEARNINGS.md</code> Gotchas, bugs, things that didn't work <code>CONVENTIONS.md</code> Naming patterns, code style, project rules <code>CONSTITUTION.md</code> Hard rules the AI must never violate <p>These files can version with your code in <code>git</code>: </p> <ul> <li>They load automatically at the session start    (via hooks in Claude Code, or manually with <code>ctx agent</code> for   other tools). </li> <li>The AI reads them, cites them, and builds on them, instead   of asking you to start over. <ul> <li>And when it acts, it can point to the exact file and line that      justifies the choice.</li> </ul> </li> </ul> <p>Every decision you record, every lesson you capture, makes the next session smarter.</p> <p><code>ctx</code> accumulates.</p> <p>Connect with <code>ctx</code></p> <ul> <li>Join the Community →: ask questions, share workflows, and help shape what comes next</li> <li>Read the Blog →: real-world patterns, ponderings, and lessons learned from building <code>ctx</code> using <code>ctx</code></li> </ul> <p>Ready to Get Started?</p> <ul> <li>Getting Started →: full installation and setup</li> <li>Your First Session →: step-by-step walkthrough from <code>ctx init</code> to verified recall</li> </ul>","path":["Home","Introduction","About"],"tags":[]},{"location":"home/common-workflows/","level":1,"title":"Common Workflows","text":"<p>The commands below cover what you'll use most often: </p> <ul> <li>recording context, </li> <li>checking health, </li> <li>browsing history, </li> <li>and running loops.</li> </ul> <p>Each section is a self-contained snippet you can copy into your terminal.</p> <p>For deeper, step-by-step guides, see Recipes.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#track-context","level":2,"title":"Track Context","text":"<p>Prefer Skills over Raw Commands</p> <p>When working with an AI agent, use <code>/ctx-task-add</code>, <code>/ctx-decision-add</code>, or <code>/ctx-learning-add</code> instead of raw <code>ctx add</code> commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <pre><code># Add a task\nctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Record a decision (full ADR fields required)\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Note a learning\nctx learning add \"Mock functions must be hoisted in Jest\" \\\n  --context \"Tests failed with undefined mock errors\" \\\n  --lesson \"Jest hoists mock calls to top of file\" \\\n  --application \"Place jest.mock() before imports\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Mark task complete\nctx task complete \"user auth\"\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#leave-a-reminder-for-next-session","level":2,"title":"Leave a Reminder for Next Session","text":"<p>Drop a note that surfaces automatically at the start of your next session:</p> <pre><code># Leave a reminder\nctx remind \"refactor the swagger definitions\"\n\n# Date-gated: don't surface until a specific date\nctx remind \"check CI after the deploy\" --after 2026-02-25\n\n# List pending reminders\nctx remind list\n\n# Dismiss reminders by ID (supports ranges)\nctx remind dismiss 1\nctx remind dismiss 3 5-7\n</code></pre> <p>Reminders are relayed verbatim at session start by the <code>check-reminders</code> hook and repeat every session until you dismiss them.</p> <p>See Session Reminders for the full recipe.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#check-context-health","level":2,"title":"Check Context Health","text":"<pre><code># Detect stale paths, missing files, potential secrets\nctx drift\n\n# See full context summary\nctx status\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#browse-session-history","level":2,"title":"Browse Session History","text":"<p>List and search past AI sessions from the terminal:</p> <pre><code>ctx journal source --limit 5\n</code></pre>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#journal-site","level":3,"title":"Journal Site","text":"<p>Import session transcripts to a browsable static site with search, navigation, and topic indices.</p> <p>The <code>ctx journal</code> command requires zensical (Python >= 3.10).</p> <p><code>zensical</code> is a Python-based static site generator from the Material for MkDocs team.</p> <p>(why zensical?).</p> <p>If you don't have it on your system, install <code>zensical</code> once with pipx:</p> <pre><code># One-time setup\npipx install zensical\n</code></pre> <p>Avoid <code>pip install zensical</code></p> <p><code>pip install</code> often fails: For example, on macOS, system Python installs a non-functional stub (<code>zensical</code> requires <code>Python >= 3.10</code>), and Homebrew Python blocks system-wide installs (<code>PEP 668</code>).</p> <p><code>pipx</code> creates an isolated environment with the correct Python version automatically.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#import-and-serve","level":3,"title":"Import and Serve","text":"<p>Then, import and serve:</p> <pre><code># Import all sessions to .context/journal/ (only new files)\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n</code></pre> <p>Open http://localhost:8000 to browse.</p> <p>To update after new sessions, run the same two commands again.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#safe-by-default","level":3,"title":"Safe by Default","text":"<p><code>ctx journal import --all</code> is safe by default:</p> <ul> <li>It only imports new sessions and skips existing files.</li> <li>Locked entries (via <code>ctx journal lock</code>) are always skipped by   both import and enrichment skills.</li> <li>If you add <code>locked: true</code> to frontmatter during enrichment, run   <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>.</li> </ul>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#re-importing-existing-files","level":3,"title":"Re-Importing Existing Files","text":"<p>Here is how you regenerate existing files.</p> <p>Backup your <code>.context</code> folder before regeneration, as this is a potentially destructive action.</p> <p>To re-import journal files, you need to explicitly opt-in using the <code>--regenerate</code> flag:</p> Flag combination Frontmatter Body <code>--regenerate</code> Preserved Overwritten from source <code>--regenerate --keep-frontmatter=false</code> Overwritten Overwritten <p>Regeneration Overwrites Body Edits</p> <p><code>--regenerate</code> preserves your YAML frontmatter (tags, summary, enrichment metadata) but it replaces the Markdown body with a fresh import.</p> <p>Any manual edits you made to the transcript will be lost.</p> <p>Lock entries you want to protect first: <code>ctx journal lock <session-id></code>.</p> <p>See Session Journal for the full pipeline including normalization and enrichment.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#scratchpad","level":2,"title":"Scratchpad","text":"<p>Store short, sensitive one-liners in an encrypted scratchpad that travels with the project:</p> <pre><code># Write a note\nctx pad set db-password \"postgres://user:pass@localhost/mydb\"\n\n# Read it back\nctx pad get db-password\n\n# List all keys\nctx pad list\n</code></pre> <p>The scratchpad is encrypted with a key stored at <code>~/.ctx/.ctx.key</code> (outside the project, never committed).</p> <p>See Scratchpad for details.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#run-an-autonomous-loop","level":2,"title":"Run an Autonomous Loop","text":"<p>Generate a script that iterates an AI agent until a completion signal is detected:</p> <pre><code>ctx loop\nchmod +x loop.sh\n./loop.sh\n</code></pre> <p>See Autonomous Loops for configuration and advanced usage.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#trace-commit-context","level":2,"title":"Trace Commit Context","text":"<p>Link your git commits back to the decisions, tasks, and learnings that motivated them. Enable the hook once:</p> <pre><code># Install the git hook (one-time setup)\nctx trace hook enable\n</code></pre> <p>From now on, every <code>git commit</code> automatically gets a <code>ctx-context</code> trailer linking it to relevant context. No extra steps needed; just use <code>ctx add</code>, <code>ctx task complete</code>, and commit as usual.</p> <pre><code># Later: why was this commit made?\nctx trace abc123\n\n# Recent commits with their context\nctx trace --last 10\n\n# Context trail for a specific file\nctx trace file src/auth.go\n\n# Manually tag a commit after the fact\nctx trace tag HEAD --note \"Hotfix for production outage\"\n</code></pre> <p>To stop: <code>ctx trace hook disable</code>.</p> <p>See CLI Reference: trace for details.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#agent-session-start","level":2,"title":"Agent Session Start","text":"<p>The first thing an AI agent should do at session start is discover where context lives:</p> <pre><code>ctx system bootstrap\n</code></pre> <p>This prints the resolved context directory, the files in it, and the operating rules. The <code>CLAUDE.md</code> template instructs the agent to run this automatically. See CLI Reference: bootstrap.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#the-two-skills-you-should-always-use","level":2,"title":"The Two Skills You Should Always Use","text":"<p>Using <code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end are the highest-value skills in the entire catalog:</p> <pre><code># session begins:\n/ctx-remember\n... do work ...\n# before closing the session:\n/ctx-wrap-up\n</code></pre> <p>Let's provide some context, because this is important:</p> <p>Although the agent will eventually discover your context through <code>CLAUDE.md → AGENT_PLAYBOOK.md</code>, <code>/ctx-remember</code> hydrates the full context up front (tasks, decisions, recent sessions) so the agent starts informed rather than piecing things together over several turns.</p> <p><code>/ctx-wrap-up</code> is the other half: A structured review that captures learnings, decisions, and tasks before you close the window.</p> <p>Hooks like <code>check-persistence</code> remind you (the user) mid-session that context hasn't been saved in a while, but they don't trigger persistence automatically: You still have to act. Also, a <code>CTRL+C</code> can end things at any moment with no reliable \"before session end\" event. </p> <p>In short, <code>/ctx-wrap-up</code> is the deliberate checkpoint that makes  sure nothing slips through. And <code>/ctx-remember</code> it its mirror skill to be used at session start.</p> <p>See Session Ceremonies for the full workflow.</p>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-commands-vs-ai-skills","level":2,"title":"CLI Commands vs. AI Skills","text":"<p>Most <code>ctx</code> operations come in two flavors: a CLI command you run in your terminal and an AI skill (slash command) you invoke inside your coding assistant.</p> <p>Commands and skills are not interchangeable: Each has a distinct role.</p> <code>ctx</code> CLI command <code>ctx</code> AI skill Runs where Your terminal Inside the AI assistant Speed Fast (milliseconds) Slower (LLM round-trip) Cost Free Consumes tokens and context Analysis Deterministic heuristics Semantic / judgment-based Best for Quick checks, scripting, CI Deep analysis, generation, workflow orchestration","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#paired-commands","level":3,"title":"Paired Commands","text":"<p>These have both a CLI and a skill counterpart. Use the CLI for quick, deterministic checks; use the skill when you need the agent's judgment.</p> CLI Skill When to prefer the skill <code>ctx drift</code> <code>/ctx-drift</code> Semantic analysis: catches meaning drift the CLI misses <code>ctx status</code> <code>/ctx-status</code> Interpreted summary with recommendations <code>ctx task add</code> <code>/ctx-task-add</code> Agent decomposes vague goals into concrete tasks <code>ctx decision add</code> <code>/ctx-decision-add</code> Agent drafts rationale and consequences from discussion <code>ctx learning add</code> <code>/ctx-learning-add</code> Agent extracts the lesson from a debugging session <code>ctx convention add</code> <code>/ctx-convention-add</code> Agent observes a repeated pattern and codifies it <code>ctx task archive</code> <code>/ctx-archive</code> Agent reviews which tasks are truly done <code>ctx pad</code> <code>/ctx-pad</code> Agent reads/writes scratchpad entries in conversation flow <code>ctx journal</code> <code>/ctx-history</code> Agent searches session history with semantic understanding <code>ctx agent</code> <code>/ctx-agent</code> Agent loads and acts on the context packet <code>ctx loop</code> <code>/ctx-loop</code> Agent tailors the loop script to your project <code>ctx doctor</code> <code>/ctx-doctor</code> Agent adds semantic analysis to structural checks <code>ctx hook pause</code> <code>/ctx-pause</code> Agent pauses hooks with session-aware reasoning <code>ctx hook resume</code> <code>/ctx-resume</code> Agent resumes hooks after a pause <code>ctx remind</code> <code>/ctx-remind</code> Agent manages reminders in conversation flow","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#ai-only-skills","level":3,"title":"AI-Only Skills","text":"<p>These have no CLI equivalent. They require the agent's reasoning.</p> Skill Purpose <code>/ctx-remember</code> Load context and present structured readback at session start <code>/ctx-wrap-up</code> End-of-session ceremony: persist learnings, decisions, tasks <code>/ctx-next</code> Suggest 1-3 concrete next actions from context <code>/ctx-commit</code> Commit with integrated context capture <code>/ctx-reflect</code> Pause and assess session progress <code>/ctx-consolidate</code> Merge overlapping learnings or decisions <code>/ctx-prompt-audit</code> Analyze prompting patterns for improvement <code>/ctx-plan</code> Stress-test an existing plan through adversarial interview <code>/ctx-plan-import</code> Import Claude Code plan files into project specs <code>/ctx-implement</code> Execute a plan step-by-step with verification <code>/ctx-worktree</code> Manage parallel agent worktrees <code>/ctx-journal-enrich</code> Add metadata, tags, and summaries to journal entries <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich <code>/ctx-blog</code> Generate a blog post (zensical-flavored Markdown) <code>/ctx-blog-changelog</code> Generate themed blog post from commits between releases <code>/ctx-architecture</code> Build and maintain architecture maps (ARCHITECTURE.md, DETAILED_DESIGN.md)","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/common-workflows/#cli-only-commands","level":3,"title":"CLI-Only Commands","text":"<p>These are infrastructure: used in scripts, CI, or one-time setup.</p> Command Purpose <code>ctx init</code> Initialize <code>.context/</code> directory <code>ctx load</code> Output assembled context for piping <code>ctx task complete</code> Mark a task done by substring match <code>ctx sync</code> Reconcile context with codebase state <code>ctx compact</code> Consolidate and clean up context files <code>ctx trace</code> Show context behind git commits <code>ctx trace hook</code> Enable/disable commit context tracing hook <code>ctx setup</code> Generate AI tool integration config <code>ctx watch</code> Watch AI output and auto-apply context updates <code>ctx serve</code> Serve any zensical directory (default: journal) <code>ctx permission snapshot</code> Save settings as a golden image <code>ctx permission restore</code> Restore settings from golden image <code>ctx journal site</code> Generate browsable journal from exports <code>ctx hook notify setup</code> Configure webhook notifications <code>ctx decision</code> List and filter decisions <code>ctx learning</code> List and filter learnings <code>ctx task</code> List tasks, manage archival and snapshots <code>ctx why</code> Read the philosophy behind <code>ctx</code> <code>ctx guide</code> Quick-reference cheat sheet <code>ctx site</code> Site management commands <code>ctx config</code> Manage runtime configuration profiles <code>ctx system</code> System diagnostics and hook commands <code>ctx completion</code> Generate shell autocompletion scripts <p>Rule of Thumb</p> <p>Quick check? Use the CLI. </p> <p>Need judgment? Use the skill.</p> <p>When in doubt, start with the CLI: It's free and instant.</p> <p>Escalate to the skill when heuristics aren't enough.</p> <p>Next Up: Context Files →: what each <code>.context/</code> file does and how to use it</p> <p>See Also:</p> <ul> <li>Recipes: targeted how-to guides for specific tasks</li> <li>Knowledge Capture: patterns for recording decisions, learnings, and conventions</li> <li>Context Health: keeping your <code>.context/</code> accurate and drift-free</li> <li>Session Archaeology: digging into past sessions</li> <li>Task Management: tracking and completing work items</li> </ul>","path":["Home","Get Started","Common Workflows"],"tags":[]},{"location":"home/community/","level":1,"title":"#ctx","text":"<p>Open source is better together.</p> <p>We are the builders who care about durable context, verifiable decisions,  and human-AI workflows that compound over time.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#help-ctx-change-how-ai-remembers","level":2,"title":"Help <code>ctx</code> Change How AI Remembers","text":"<p>If you like the idea, a star helps <code>ctx</code> reach engineers who run into context drift every day:</p> <p> Star <code>ctx</code> on GitHub ⭐</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#ctx-you","level":2,"title":"<code>ctx</code> ♥️ You","text":"<p>Join the community to ask questions, share feedback, and connect with other users:</p> <ul> <li> Discord join the <code>ctx</code> Discord:   Real-time discussion, field notes, and early ideas.</li> <li> Read the <code>ctx</code> Source on GitHub:   Issues, discussions, and contributions.</li> </ul>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#want-to-contribute","level":2,"title":"Want to Contribute?","text":"<p>Early adopters shape the conventions.</p> <p><code>ctx</code> is free and open source software.</p> <p>Contributions are always welcome and appreciated.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/community/#code-of-conduct","level":2,"title":"Code of Conduct","text":"<p>Clear context requires respectful collaboration.  </p> <p><code>ctx</code> follows the Contributor Covenant.</p>","path":["Home","Community","#ctx"],"tags":[]},{"location":"home/configuration/","level":1,"title":"Configuration","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#configuration","level":2,"title":"Configuration","text":"<p><code>ctx</code> uses three layers of configuration. Each layer overrides the one below it:</p> <ol> <li>CLI flags: Per-invocation overrides (highest priority)</li> <li>Environment variables: Shell or CI/CD overrides</li> <li>The <code>.ctxrc</code> file: Project-level defaults (YAML)</li> <li>Built-in defaults: Hardcoded fallbacks (lowest priority)</li> </ol> <p>All settings are optional: If nothing is configured, <code>ctx</code> works out of the box with sensible defaults.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#the-ctxrc-file","level":2,"title":"The <code>.ctxrc</code> File","text":"<p>The <code>.ctxrc</code> file is an optional YAML file placed in the project root (next to your <code>.context/</code> directory). It lets you set project-level defaults that apply to every <code>ctx</code> command.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#location","level":3,"title":"Location","text":"<pre><code>my-project/\n├── .ctxrc              ← configuration file\n├── .context/\n│   ├── TASKS.md\n│   ├── DECISIONS.md\n│   └── ...\n└── src/\n</code></pre> <p><code>ctx</code> reads <code>.ctxrc</code> from the project root (i.e. the parent of <code>CTX_DIR</code>, or <code>dirname(CTX_DIR)/.ctxrc</code>). It does not walk up from CWD. That means whichever project you've activated via <code>eval \"$(ctx activate)\"</code> (or by exporting <code>CTX_DIR</code> directly), its paired <code>.ctxrc</code> is what governs the invocation. There is no global or user-level config file: configuration is always per-project.</p> <p>Contributors: Dev Configuration Profile</p> <p>The <code>ctx</code> repo ships two <code>.ctxrc</code> source profiles (<code>.ctxrc.base</code> and <code>.ctxrc.dev</code>). The working copy is gitignored and swapped between them via <code>ctx config switch dev</code> / <code>ctx config switch base</code>. See Contributing: Configuration Profiles.</p> <p>Using a Different <code>.context</code> Directory</p> <p>You point <code>ctx</code> at a <code>.context/</code> directory by setting the <code>CTX_DIR</code> environment variable, not through <code>.ctxrc</code>. <code>ctx</code> does not search the filesystem. Use <code>eval \"$(ctx activate)\"</code> to bind <code>CTX_DIR</code> for your shell. <code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename.</p> <p>See Environment Variables below for details.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#full-reference","level":3,"title":"Full Reference","text":"<p>A commented <code>.ctxrc</code> showing all options and their defaults:</p> <pre><code># .ctxrc: ctx runtime configuration\n# https://ctx.ist/configuration/\n#\n# All settings are optional. Missing values use defaults.\n# Priority: CLI flags > environment variables > .ctxrc > defaults\n#\n# token_budget: 8000\n# auto_archive: true\n# archive_after_days: 7\n# scratchpad_encrypt: true\n# event_log: false\n# entry_count_learnings: 30\n# entry_count_decisions: 20\n# convention_line_count: 200\n# injection_token_warn: 15000\n# context_window: 200000      # auto-detected for Claude Code; override for other tools\n# billing_token_warn: 0       # one-shot warning at this token count (0 = disabled)\n#\n# stale_age_days: 30      # days before drift flags a context file as stale (0 = disabled)\n# key_rotation_days: 90\n# task_nudge_interval: 5   # Edit/Write calls between task completion nudges\n#\n# notify:               # requires: ctx hook notify setup\n#   events:             # required: no events sent unless listed\n#     - loop\n#     - nudge\n#     - relay\n#\n# tool: \"\"              # Active AI tool: claude, cursor, cline, kiro, codex\n#\n# steering:             # Steering layer configuration\n#   dir: .context/steering\n#   default_inclusion: manual\n#   default_tools: []\n#\n# hooks:                # Hook system configuration\n#   dir: .context/hooks\n#   timeout: 10\n#   enabled: true\n#\n# provenance_required:  # Relax provenance flags for ctx add\n#   session_id: true    # Require --session-id (default: true)\n#   branch: true        # Require --branch (default: true)\n#   commit: true        # Require --commit (default: true)\n#\n# priority_order:\n#   - CONSTITUTION.md\n#   - TASKS.md\n#   - CONVENTIONS.md\n#   - ARCHITECTURE.md\n#   - DECISIONS.md\n#   - LEARNINGS.md\n#   - GLOSSARY.md\n#   - AGENT_PLAYBOOK.md\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#option-reference","level":3,"title":"Option Reference","text":"Option Type Default Description <code>token_budget</code> <code>int</code> <code>8000</code> Default token budget for <code>ctx agent</code> and <code>ctx load</code> <code>auto_archive</code> <code>bool</code> <code>true</code> Auto-archive completed tasks during <code>ctx compact</code> <code>archive_after_days</code> <code>int</code> <code>7</code> Days before completed tasks are archived <code>scratchpad_encrypt</code> <code>bool</code> <code>true</code> Encrypt scratchpad with AES-256-GCM <code>event_log</code> <code>bool</code> <code>false</code> Enable local hook event logging to <code>.context/state/events.jsonl</code> <code>entry_count_learnings</code> <code>int</code> <code>30</code> Drift warning when <code>LEARNINGS.md</code> exceeds this entry count (0 = disable) <code>entry_count_decisions</code> <code>int</code> <code>20</code> Drift warning when <code>DECISIONS.md</code> exceeds this entry count (0 = disable) <code>convention_line_count</code> <code>int</code> <code>200</code> Drift warning when <code>CONVENTIONS.md</code> exceeds this line count (0 = disable) <code>injection_token_warn</code> <code>int</code> <code>15000</code> Warn when auto-injected context exceeds this token count (0 = disable) <code>context_window</code> <code>int</code> <code>200000</code> Context window size in tokens. Auto-detected for Claude Code (200k/1M); override for other AI tools <code>billing_token_warn</code> <code>int</code> <code>0</code> (off) One-shot warning when session tokens exceed this threshold (0 = disabled). For plans where tokens beyond an included allowance cost extra <code>stale_age_days</code> <code>int</code> <code>30</code> Days before <code>ctx drift</code> flags a context file as stale (0 = disable) <code>key_rotation_days</code> <code>int</code> <code>90</code> Days before encryption key rotation nudge <code>task_nudge_interval</code> <code>int</code> <code>5</code> Edit/Write calls between task completion nudges <code>notify.events</code> <code>[]string</code> (all) Event filter for webhook notifications (empty = all) <code>priority_order</code> <code>[]string</code> (see below) Custom file loading priority for context assembly <code>tool</code> <code>string</code> (empty) Active AI tool identifier (<code>claude</code>, <code>cursor</code>, <code>cline</code>, <code>kiro</code>, <code>codex</code>). Used by steering sync and hook dispatch <code>steering.dir</code> <code>string</code> <code>.context/steering</code> Steering files directory <code>steering.default_inclusion</code> <code>string</code> <code>manual</code> Default inclusion mode for new steering files (<code>always</code>, <code>auto</code>, <code>manual</code>) <code>steering.default_tools</code> <code>[]string</code> (all) Default tool filter for new steering files (empty = all tools) <code>hooks.dir</code> <code>string</code> <code>.context/hooks</code> Hook scripts directory <code>hooks.timeout</code> <code>int</code> <code>10</code> Per-hook execution timeout in seconds <code>hooks.enabled</code> <code>bool</code> <code>true</code> Whether hook execution is enabled <code>provenance_required.session_id</code> <code>bool</code> <code>true</code> Require <code>--session-id</code> on <code>ctx add</code> for tasks, decisions, learnings <code>provenance_required.branch</code> <code>bool</code> <code>true</code> Require <code>--branch</code> on <code>ctx add</code> for tasks, decisions, learnings <code>provenance_required.commit</code> <code>bool</code> <code>true</code> Require <code>--commit</code> on <code>ctx add</code> for tasks, decisions, learnings <p>Default priority order (used when <code>priority_order</code> is not set):</p> <ol> <li><code>CONSTITUTION.md</code></li> <li><code>TASKS.md</code></li> <li><code>CONVENTIONS.md</code></li> <li><code>ARCHITECTURE.md</code></li> <li><code>DECISIONS.md</code></li> <li><code>LEARNINGS.md</code></li> <li><code>GLOSSARY.md</code></li> <li><code>AGENT_PLAYBOOK.md</code></li> </ol> <p>See Context Files for the rationale behind this ordering.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#environment-variables","level":2,"title":"Environment Variables","text":"<p>Environment variables override <code>.ctxrc</code> values but are overridden by CLI flags.</p> Variable Description Equivalent <code>.ctxrc</code> key <code>CTX_DIR</code> Declare the context directory path (required, no fallback) (none) <code>CTX_TOKEN_BUDGET</code> Override the default token budget <code>token_budget</code>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples","level":3,"title":"Examples","text":"<pre><code># Use a shared context directory\nCTX_DIR=/shared/team-context ctx status\n\n# Increase token budget for a single run\nCTX_TOKEN_BUDGET=16000 ctx agent\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#cli-global-flags","level":2,"title":"CLI Global Flags","text":"<p>CLI flags have the highest priority and override both environment variables and <code>.ctxrc</code> settings. These flags are available on every <code>ctx</code> command.</p> Flag Description <code>--tool <name></code> Override active AI tool identifier (e.g. <code>kiro</code>, <code>cursor</code>) <code>--version</code> Show version and exit <code>--help</code> Show command help and exit","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_1","level":3,"title":"Examples","text":"<pre><code># Point to a different context directory inline:\nCTX_DIR=/path/to/project/.context ctx status\n</code></pre>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#priority-order","level":2,"title":"Priority Order","text":"<p>When the same setting is configured in multiple layers, the highest-priority layer wins:</p> <pre><code>CLI flags  >  Environment variables  >  .ctxrc  >  Built-in defaults\n(highest)                                          (lowest)\n</code></pre> <p>The context directory itself is resolved differently: it lives outside this priority chain. <code>CTX_DIR</code> (env) must be declared; <code>.ctxrc</code> does not carry a fallback for it, and there is no built-in default. See Activating a Context Directory.</p> <p>Example resolution for <code>token_budget</code>:</p> Layer Value Wins? <code>CTX_TOKEN_BUDGET</code> <code>4000</code> Yes <code>.ctxrc</code> <code>8000</code> No Default <code>8000</code> No","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#examples_2","level":2,"title":"Examples","text":"","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#external-context-directory","level":3,"title":"External <code>.context</code> Directory","text":"<p>Store a project's context outside the project tree (useful when a repo is read-only, or when you want to keep notes adjacent rather than checked in). Declare the path via <code>CTX_DIR</code>:</p> <pre><code>export CTX_DIR=/home/you/ctx-stores/my-project/.context\n</code></pre> <p>One <code>.context/</code> per project</p> <p>The parent of the context directory is the project root by contract: <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook all read the codebase from <code>filepath.Dir(ContextDir())</code>. Pointing two projects at the same <code>.context/</code> directory will collide their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use <code>ctx hub</code>, not a shared <code>.context/</code>.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-token-budget","level":3,"title":"Custom Token Budget","text":"<p>Increase the token budget for projects with large context:</p> <pre><code># .ctxrc\ntoken_budget: 16000\n</code></pre> <p>This affects the default budget for <code>ctx agent</code> and <code>ctx load</code>. You can still override per-invocation with <code>ctx agent --budget 4000</code>.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#disabled-scratchpad-encryption","level":3,"title":"Disabled Scratchpad Encryption","text":"<p>Turn off encryption for the scratchpad (useful in ephemeral environments where key management is unnecessary):</p> <pre><code># .ctxrc\nscratchpad_encrypt: false\n</code></pre> <p>Unencrypted Scratchpads Store Secrets in Plaintext</p> <p>Only disable encryption if you understand the security implications.</p> <p>The scratchpad may contain sensitive data such as API keys, database URLs, or deployment credentials.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#custom-priority-order","level":3,"title":"Custom Priority Order","text":"<p>Reorder context files to prioritize architecture over conventions:</p> <pre><code># .ctxrc\npriority_order:\n  - CONSTITUTION.md\n  - TASKS.md\n  - ARCHITECTURE.md\n  - DECISIONS.md\n  - CONVENTIONS.md\n  - LEARNINGS.md\n  - GLOSSARY.md\n  - AGENT_PLAYBOOK.md\n</code></pre> <p>Files not listed in <code>priority_order</code> receive the lowest priority (100). The order affects <code>ctx agent</code>, <code>ctx load</code>, and drift's file-priority calculations.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#billing-token-threshold","level":3,"title":"Billing Token Threshold","text":"<p>Get a one-shot warning when your session crosses a token threshold where extra charges begin (e.g., Claude Pro includes 200k tokens; beyond that costs extra):</p> <pre><code># .ctxrc\nbilling_token_warn: 180000   # warn before hitting the 200k paid boundary\n</code></pre> <p>The warning fires once per session the first time token usage exceeds the threshold. Set to <code>0</code> (or omit) to disable.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#adjusted-drift-thresholds","level":3,"title":"Adjusted Drift Thresholds","text":"<p>Raise or lower the entry-count thresholds that trigger drift warnings:</p> <pre><code># .ctxrc\nentry_count_learnings: 50   # warn above 50 learnings (default: 30)\nentry_count_decisions: 10   # warn above 10 decisions (default: 20)\nconvention_line_count: 300  # warn above 300 lines (default: 200)\n</code></pre> <p>Set any threshold to <code>0</code> to disable that specific check.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"<p>Get notified when loops complete, hooks fire, or agents reach milestones:</p> <pre><code># Configure the webhook URL (encrypted, safe to commit)\nctx hook notify setup\n\n# Test delivery\nctx hook notify test\n</code></pre> <p>Filter which events reach your webhook:</p> <pre><code># .ctxrc\nnotify:\n  events:\n    - loop      # loop completion/max-iteration\n    - nudge     # VERBATIM relay hooks fired\n    # - relay   # all hook output (verbose, for debugging)\n    # - heartbeat  # every-prompt session-alive signal\n</code></pre> <p>Notifications are opt-in: No events are sent unless explicitly listed.</p> <p>See Webhook Notifications for a step-by-step recipe.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#hook-message-overrides","level":2,"title":"Hook Message Overrides","text":"<p>Hook messages control what text hooks emit when they fire. Each message can be overridden per-project by placing a text file at the matching path under <code>.context/</code>:</p> <pre><code>.context/hooks/messages/{hook}/{variant}.txt\n</code></pre> <p>The override takes priority over the embedded default compiled into the <code>ctx</code> binary. An empty file silences the message while preserving the hook's logic (counting, state tracking, cooldowns).</p> <p>Use <code>ctx hook message</code> to discover and manage overrides:</p> <pre><code>ctx hook message list                      # see all messages\nctx hook message show qa-reminder gate     # view the current template\nctx hook message edit qa-reminder gate     # copy default for editing\nctx hook message reset qa-reminder gate    # revert to default\n</code></pre> <p>See Customizing Hook Messages for detailed examples including Python, JavaScript, and silence configurations.</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/configuration/#agent-bootstrapping","level":2,"title":"Agent Bootstrapping","text":"<p>AI agents need to know the resolved context directory at session start. The <code>ctx system bootstrap</code> command prints the context path, file list, and operating rules in both text and JSON formats:</p> <pre><code>ctx system bootstrap          # text output for agents\nctx system bootstrap -q       # just the context directory path\nctx system bootstrap --json   # structured output for automation\n</code></pre> <p>The <code>CLAUDE.md</code> template instructs the agent to run this as its first action. Every nudge (context checkpoint, persistence reminder, etc.) also includes a <code>Context: <dir></code> footer that re-anchors the agent to the correct directory throughout the session.</p> <p>This replaces the previous approach of hardcoding <code>.context/</code> paths in agent instructions. </p> <p>See CLI Reference: bootstrap for full details.</p> <p>See also: CLI Reference | Context Files | Scratchpad</p>","path":["Home","Concepts","Configuration"],"tags":[]},{"location":"home/context-files/","level":1,"title":"Context Files","text":"","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#context","level":2,"title":"<code>.context/</code>","text":"<p>Each context file in <code>.context/</code> serves a specific purpose. </p> <p>Files are designed to be human-readable, AI-parseable, and token-efficient.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#file-overview","level":2,"title":"File Overview","text":"<p>The core context files live directly under <code>.context/</code>. They are the substrate <code>ctx</code> reads in priority order when assembling the agent context packet:</p> File Purpose Priority <code>CONSTITUTION.md</code> Hard rules that must NEVER be violated 1 (highest) <code>TASKS.md</code> Current and planned work 2 <code>CONVENTIONS.md</code> Project patterns and standards 3 <code>ARCHITECTURE.md</code> System overview and components 4 <code>DECISIONS.md</code> Architectural decisions with rationale 5 <code>LEARNINGS.md</code> Lessons learned, gotchas, tips 6 <code>GLOSSARY.md</code> Domain terms and abbreviations 7 <code>AGENT_PLAYBOOK.md</code> Instructions for AI tools 8 (lowest) <p>Two subdirectories under <code>.context/</code> are implementation details that are user-editable but not part of the priority read order:</p> <ul> <li><code>.context/templates/</code>: format templates for <code>ctx decision add</code>   and <code>ctx learning add</code>. See templates below.</li> <li><code>.context/steering/</code>: behavioral rules with YAML frontmatter   that get synced into each AI tool's native config. See   steering below, and the full   Steering files page for the design and workflow.</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#outside-context","level":3,"title":"Outside <code>.context/</code>","text":"<p>Two other moving parts are often confused with context files but are not under <code>.context/</code>:</p> <ul> <li>Skills live in <code>.claude/skills/</code> (project-local) or are provided   by the installed <code>ctx</code> plugin. A typical project doesn't see the   plugin's skills at all; they ride with the plugin and are owned by   its update cycle. See <code>ctx skill</code> and   Skills reference.</li> <li>Hooks: Claude Code <code>PreToolUse</code>/<code>PostToolUse</code>/   <code>UserPromptSubmit</code> entries configured in <code>.claude/settings.json</code> or   shipped by a plugin. The <code>ctx</code> plugin registers its own hooks   automatically; a typical project does not author hooks by hand,   and any local edits to plugin-owned hook files will be overridden   on the next plugin update. If you need to customize behavior, edit   your own project settings, not the plugin's files. See   Hook sequence diagrams.</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#read-order-rationale","level":2,"title":"Read Order Rationale","text":"<p>The priority order follows a logical progression for AI tools:</p> <ol> <li><code>CONSTITUTION.md</code>: Inviolable rules first. The AI tool must know what it    cannot do before attempting anything.</li> <li><code>TASKS.md</code>: Current work items. What the AI tool should focus on.</li> <li><code>CONVENTIONS.md</code>: How to write code. Patterns and standards to follow    when implementing tasks.</li> <li><code>ARCHITECTURE.md</code>: System structure. Understanding of components and    boundaries before making changes.</li> <li><code>DECISIONS.md</code>: Historical context. Why things are the way they are,    to avoid re-debating settled decisions.</li> <li><code>LEARNINGS.md</code>: Gotchas and tips. Lessons from past work that inform    the current implementation.</li> <li><code>GLOSSARY.md</code>: Reference material. Domain terms and abbreviations for    lookup as needed.</li> <li><code>AGENT_PLAYBOOK.md</code>: Meta instructions last. How to use this context    system itself. Loaded last because the agent should understand the    content (rules, tasks, patterns) before the operating manual.</li> </ol>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#constitutionmd","level":2,"title":"<code>CONSTITUTION.md</code>","text":"<p>Purpose: Define hard invariants: Rules that must NEVER be violated,  regardless of the task.</p> <p>AI tools read this first and should refuse tasks that violate these rules.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure","level":3,"title":"Structure","text":"<pre><code># Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these, the task \nis wrong.\n\n## Security Invariants\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never store customer/user data in context files\n* [ ] Never disable security linters without documented exception\n\n## Quality Invariants\n\n* [ ] All code must pass tests before commit\n* [ ] No `any` types in TypeScript without documented reason\n* [ ] No TODO comments in main branch (*move to `TASKS.md`*)\n\n## Process Invariants\n\n* [ ] All architectural changes require a decision record\n* [ ] Breaking changes require version bump\n* [ ] Generated files are never committed\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines","level":3,"title":"Guidelines","text":"<ul> <li>Keep rules minimal and absolute</li> <li>Each rule should be enforceable (can verify compliance)</li> <li>Use checkbox format for clarity</li> <li>Never compromise on these rules</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tasksmd","level":2,"title":"<code>TASKS.md</code>","text":"<p>Purpose: Track current work, planned work, and blockers.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_1","level":3,"title":"Structure","text":"<p>Tasks are organized by Phase: logical groupings that preserve order and enable replay. </p> <p>Tasks stay in their Phase permanently; status is tracked via checkboxes and  inline tags.</p> <pre><code># Tasks\n\n## Phase 1: Initial Setup\n\n* [x] Set up project structure\n* [x] Configure linting and formatting\n* [ ] Add CI/CD pipeline `#in-progress`\n\n## Phase 2: Core Features\n\n* [ ] Implement user authentication `#priority:high`\n* [ ] Add API rate limiting `#priority:medium`\n  * Blocked by: Need to finalize auth first\n\n## Backlog\n\n* [ ] Performance optimization `#priority:low`\n* [ ] Add metrics dashboard `#priority:deferred`\n</code></pre> <p>Key principles:</p> <ul> <li>Tasks never move between sections: mark as <code>[x]</code> or <code>[-]</code> in place</li> <li>Use <code>#in-progress</code> inline tag to indicate current work</li> <li>Phase headers provide structure and replay order</li> <li>Backlog section for unscheduled work</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#tags","level":3,"title":"Tags","text":"<p>Use inline backtick-wrapped tags for metadata:</p> Tag Values Purpose <code>#priority</code> <code>high</code>, <code>medium</code>, <code>low</code> Task urgency <code>#area</code> <code>core</code>, <code>cli</code>, <code>docs</code>, <code>tests</code> Codebase area <code>#estimate</code> <code>1h</code>, <code>4h</code>, <code>1d</code> Time estimate (optional) <code>#in-progress</code> (none) Currently being worked on <p>Lifecycle tags (for session correlation):</p> Tag Format When to add <code>#added</code> <code>YYYY-MM-DD-HHMMSS</code> Auto-added by <code>ctx task add</code> <code>#started</code> <code>YYYY-MM-DD-HHMMSS</code> When beginning work on the task <p>These timestamps help correlate tasks with session files and track which session started vs completed work.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-markers","level":3,"title":"Status Markers","text":"Marker Meaning <code>[ ]</code> Pending <code>[x]</code> Completed <code>[-]</code> Skipped (include reason)","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_1","level":3,"title":"Guidelines","text":"<ul> <li>Never delete tasks; mark as <code>[x]</code> completed or <code>[-]</code> skipped</li> <li>Never move tasks between sections; use inline tags for status</li> <li>Use <code>ctx task archive</code> periodically to move completed tasks to archive</li> <li>Mark current work with <code>#in-progress</code> inline tag</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#decisionsmd","level":2,"title":"<code>DECISIONS.md</code>","text":"<p>Purpose: Record architectural decisions with rationale so they don't get re-debated.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_2","level":3,"title":"Structure","text":"<pre><code># Decisions\n\n## [YYYY-MM-DD] Decision Title\n\n**Status**: Accepted | Superseded | Deprecated\n\n**Context**: What situation prompted this decision?\n\n**Decision**: What was decided?\n\n**Rationale**: Why was this the right choice?\n\n**Consequence**: What are the implications?\n\n**Alternatives Considered**:\n* Alternative A: Why rejected\n* Alternative B: Why rejected\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example","level":3,"title":"Example","text":"<pre><code>## [2025-01-15] Use TypeScript Strict Mode\n\n**Status**: Accepted\n\n**Context**: Starting a new project, need to choose the type-checking level.\n\n**Decision**: Enable TypeScript strict mode with all strict flags.\n\n**Rationale**: Catches more bugs at compile time. Team has experience\nwith strict mode. Upfront cost pays off in reduced runtime errors.\n\n**Consequence**: More verbose type annotations required. Some\nthird-party libraries need type assertions.\n\n**Alternatives Considered**:\n- Basic TypeScript: Rejected because it misses null checks\n- JavaScript with JSDoc: Rejected because tooling support is weaker\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#status-values","level":3,"title":"Status Values","text":"Status Meaning Accepted Current, active decision Superseded Replaced by newer decision (link to it) Deprecated No longer relevant","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#learningsmd","level":2,"title":"<code>LEARNINGS.md</code>","text":"<p>Purpose: Capture lessons learned, gotchas, and tips that shouldn't be forgotten.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_3","level":3,"title":"Structure","text":"<pre><code># Learnings\n\n## Category Name\n\n### Learning Title\n\n**Discovered**: YYYY-MM-DD\n\n**Context**: When/how was this learned?\n\n**Lesson**: What's the takeaway?\n\n**Application**: How should this inform future work?\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#example_1","level":3,"title":"Example","text":"<pre><code>## Testing\n\n### Vitest Mocks Must Be Hoisted\n\n**Discovered**: 2025-01-15\n\n**Context**: Tests were failing intermittently when mocking fs module.\n\n**Lesson**: Vitest requires `vi.mock()` calls to be hoisted to the\ntop of the file. Dynamic mocks need `vi.doMock()` instead.\n\n**Application**: Always use `vi.mock()` at file top. Use `vi.doMock()`\nonly when mock needs runtime values.\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#categories","level":3,"title":"Categories","text":"<p>Organize learnings by topic:</p> <ul> <li>Testing</li> <li>Build & Deploy</li> <li>Performance</li> <li>Security</li> <li>Third-Party Libraries</li> <li>Git and Workflow</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#conventionsmd","level":2,"title":"<code>CONVENTIONS.md</code>","text":"<p>Purpose: Document project patterns, naming conventions, and standards.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_4","level":3,"title":"Structure","text":"<pre><code># Conventions\n\n## Naming\n\n* **Files**: kebab-case for all source files\n* **Components**: PascalCase for React components\n* **Functions**: camelCase, verb-first (getUser, parseConfig)\n* **Constants**: SCREAMING_SNAKE_CASE\n\n## Patterns\n\n### Pattern Name\n\n**When to use**: Situation description\n\n**Implementation**:\n// in triple backticks\n// Example code\n\n**Why**: Rationale for this pattern\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_2","level":3,"title":"Guidelines","text":"<ul> <li>Include concrete examples</li> <li>Explain the \"why\" not just the \"what\"</li> <li>Keep patterns minimal: Only document what's non-obvious</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#architecturemd","level":2,"title":"<code>ARCHITECTURE.md</code>","text":"<p>Purpose: Provide system overview and component relationships.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_5","level":3,"title":"Structure","text":"<pre><code># Architecture\n\n## Overview\n\nBrief description of what the system does and how it's organized.\n\n## Components\n\n### Component Name\n\n**Responsibility**: What this component does\n\n**Dependencies**: What it depends on\n\n**Dependents**: What depends on it\n\n**Key Files**:\n* path/to/file.ts: Description\n\n## Data Flow\n\nDescription or diagram of how data moves through the system.\n\n## Boundaries\n\nWhat's in scope vs out of scope for this codebase.\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_3","level":3,"title":"Guidelines","text":"<ul> <li>Keep diagrams simple (Mermaid works well)</li> <li>Focus on boundaries and interfaces</li> <li>Update when major structural changes occur</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#glossarymd","level":2,"title":"<code>GLOSSARY.md</code>","text":"<p>Purpose: Define domain terms, abbreviations, and project vocabulary.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#structure_6","level":3,"title":"Structure","text":"<pre><code># Glossary\n\n## Domain Terms\n\n### Term Name\n\n**Definition**: What it means in this project's context\n\n**Not to be confused with**: Similar terms that mean different things\n\n**Example**: How it's used\n\n## Abbreviations\n\n| Abbrev | Expansion                     | Context                |\n|--------|-------------------------------|------------------------|\n| ADR    | Architectural Decision Record | Decision documentation |\n| SUT    | System Under Test             | Testing                |\n</code></pre>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#guidelines_4","level":3,"title":"Guidelines","text":"<ul> <li>Define project-specific meanings</li> <li>Clarify potentially ambiguous terms</li> <li>Include abbreviations used in code or docs</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#agent_playbookmd","level":2,"title":"<code>AGENT_PLAYBOOK.md</code>","text":"<p>Purpose: Explicit instructions for how AI tools should read, apply,  and update context.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#key-sections","level":3,"title":"Key Sections","text":"<p>Read Order: Priority order for loading context files</p> <p>When to Update: Events that trigger context updates</p> <p>How to Avoid Hallucinating Memory: Critical rules:</p> <ol> <li>Never assume: If not in files, you don't know it</li> <li>Never invent history: Don't claim \"we discussed\" without evidence</li> <li>Verify before referencing: Search files before citing</li> <li>When uncertain, say so</li> <li>Trust files over intuition</li> </ol> <p>Context Update Commands: Format for automated updates via <code>ctx watch</code>:</p> <pre><code><context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"complete\">user auth</context-update>\n<context-update type=\"learning\"\n  context=\"Debugging hooks\"\n  lesson=\"Hooks receive JSON via stdin\"\n  application=\"Parse JSON stdin with the host language\"\n>Hook Input Format</context-update>\n<context-update type=\"decision\"\n  context=\"Need a caching layer\"\n  rationale=\"Redis is fast and team has experience\"\n  consequence=\"Must provision Redis infrastructure\"\n>Use Redis for caching</context-update>\n</code></pre> <p>See Integrations  for full documentation.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#templates","level":2,"title":"<code>templates/</code>","text":"<p>Location: <code>.context/templates/</code>. Status: implementation detail, user-editable.</p> <p>Purpose: Format templates for <code>ctx decision add</code> and <code>ctx learning add</code>. These control the structure of new entries appended to DECISIONS.md and LEARNINGS.md.</p> <p><code>ctx init</code> deploys two starter templates:</p> <ul> <li><code>decision.md</code>: sections Context, Rationale, Consequence</li> <li><code>learning.md</code>: sections Context, Lesson, Application</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing","level":3,"title":"Customizing","text":"<p>Edit the templates directly. Changes take effect immediately on the next <code>ctx add</code> command. For example, to add a \"References\" section to all new decisions, edit <code>.context/templates/decision.md</code>.</p> <p>Templates are committed to git, so customizations are shared with the team.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#steering","level":2,"title":"<code>steering/</code>","text":"<p>Location: <code>.context/steering/</code>. Status: implementation detail, user-editable.</p> <p>Purpose: Behavioral rules with YAML frontmatter that tell an AI assistant how to behave when a specific kind of prompt arrives. Unlike the core context files (which describe what the project is), steering files describe what to do and ride alongside the prompt through the AI tool's native rule pipeline (Claude Code, Cursor, Kiro, Cline). <code>ctx</code> matches steering files to prompts and syncs them out to each tool's config.</p> <p><code>ctx init</code> scaffolds four foundation files:</p> <ul> <li><code>product.md</code>: who this project serves and why</li> <li><code>tech.md</code>: the technology stack and its constraints</li> <li><code>structure.md</code>: how the code is organized</li> <li><code>workflow.md</code>: how work moves through the system</li> </ul> <p>Each file carries YAML frontmatter describing when it applies (always, matching prompts, or manually referenced) and what tool scope it covers. The foundation files use <code>inclusion: always</code> by default so every session picks them up.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#customizing_1","level":3,"title":"Customizing","text":"<p>Edit the files directly. Add your own steering files with <code>ctx steering add</code>, preview the match set with <code>ctx steering preview</code>, and run <code>ctx steering sync</code> to push them into each AI tool's config after changes. Steering files are committed to git, so they're shared with the team.</p> <p>For the design rationale, the full inclusion/priority model, and the end-to-end sync workflow, see the dedicated Steering files page.</p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#parsing-rules","level":2,"title":"Parsing Rules","text":"<p>All context files follow these conventions:</p> <ol> <li>Headers define structure: <code>#</code> for title, <code>##</code> for sections, <code>###</code> for     items</li> <li>Bold keys for fields: <code>**Key**:</code> followed by value</li> <li>Code blocks are literal: Never parse code block content as structure</li> <li>Lists are ordered: Items appear in priority/chronological order</li> <li>Tags are inline: Backtick-wrapped tags like <code>#priority:high</code></li> </ol>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Refactoring with Intent:   how persistent context prevents drift during refactoring sessions</li> </ul>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/context-files/#token-efficiency","level":2,"title":"Token Efficiency","text":"<p>Keep context files concise:</p> <ul> <li>Use abbreviations in tags, not prose;</li> <li>Omit obvious words (\"The,\" \"This\");</li> <li>Prefer bullet points over paragraphs;</li> <li>Keep examples minimal but illustrative;</li> <li>Archive old completed items periodically.</li> </ul> <p>Next Up: Prompting Guide →:    effective prompts for AI sessions with <code>ctx</code></p>","path":["Home","Concepts","Context Files"],"tags":[]},{"location":"home/contributing/","level":1,"title":"Contributing","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#development-setup","level":2,"title":"Development Setup","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#prerequisites","level":3,"title":"Prerequisites","text":"<ul> <li>Go (version defined in <code>go.mod</code>)</li> <li>Claude Code</li> <li>Git</li> <li>GNU Make</li> <li>Zensical</li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#1-fork-or-clone-the-repository","level":3,"title":"1. Fork (or Clone) the Repository","text":"<pre><code># Fork on GitHub, then:\ngit clone https://github.com/<you>/ctx.git\ncd ctx\n\n# Or, if you have push access:\ngit clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#2-build-and-install-the-binary","level":3,"title":"2. Build and Install the Binary","text":"<pre><code>make build\nsudo make install\n</code></pre> <p>This compiles the <code>ctx</code> binary and places it in <code>/usr/local/bin/</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#3-install-the-plugin-from-your-local-clone","level":3,"title":"3. Install the Plugin from Your Local Clone","text":"<p>The repository ships a Claude Code plugin under <code>internal/assets/claude/</code>. Point Claude Code at your local copy so that skills and hooks reflect your working tree: no reinstall needed after edits:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter the absolute path to the root of your clone,    e.g. <code>~/WORKSPACE/ctx</code>    (this is where <code>.claude-plugin/marketplace.json</code> lives: it points    Claude Code to the actual plugin in <code>internal/assets/claude</code>);</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code>.</li> </ol> <p>Claude Code Caches Plugin Files</p> <p>Even though the marketplace points at a directory on disk, Claude Code caches skills and hooks. After editing files under <code>internal/assets/claude/</code>, clear the cache and restart:</p> <pre><code>make plugin-reload   # then restart Claude Code\n</code></pre> <p>See Skill or Hook Changes for details.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#4-verify","level":3,"title":"4. Verify","text":"<pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n</code></pre> <p>You should see the <code>ctx</code> plugin listed, sourced from your local path.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#project-layout","level":2,"title":"Project Layout","text":"<pre><code>ctx/\n├── cmd/ctx/            # CLI entry point\n├── internal/\n│   ├── assets/claude/  # ← Claude Code plugin (skills, hooks)\n│   ├── bootstrap/      # Project initialization templates\n│   ├── claude/         # Claude Code integration helpers\n│   ├── cli/            # Command implementations\n│   ├── config/         # Configuration loading\n│   ├── context/        # Core context logic\n│   ├── crypto/         # Scratchpad encryption\n│   ├── drift/          # Drift detection\n│   ├── index/          # Context file indexing\n│   ├── journal/        # Journal site generation\n│   ├── memory/         # Memory bridge (discover, mirror, import, publish)\n│   ├── notify/         # Webhook notifications\n│   ├── rc/             # .ctxrc parsing\n│   ├── journal/        # Session history, parsers, and state\n│   ├── sysinfo/        # System resource monitoring\n│   ├── task/           # Task management\n│   └── validation/     # Input validation\n├── .claude/\n│   └── skills/         # Dev-only skills (not distributed)\n├── assets/             # Static assets (banners, logos)\n├── docs/               # Documentation site source\n├── editors/            # Editor extensions (VS Code)\n├── examples/           # Example configurations\n├── hack/               # Build scripts\n├── specs/              # Feature specifications\n└── .context/           # ctx's own context (dogfooding)\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skills-two-directories-one-rule","level":3,"title":"Skills: Two Directories, One Rule","text":"Directory What lives here Distributed to users? <code>internal/assets/claude/skills/</code> The 39 <code>ctx-*</code> skills that ship with the plugin Yes <code>.claude/skills/</code> Dev-only skills (release, QA, backup, etc.) No <p><code>internal/assets/claude/skills/</code> is the single source of truth for user-facing skills. If you are adding or modifying a <code>ctx-*</code> skill, edit it there.</p> <p><code>.claude/skills/</code> holds skills that only make sense inside this repository (release automation, QA checks, backup scripts). These are never distributed to users.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#dev-only-skills-reference","level":4,"title":"Dev-Only Skills Reference","text":"Skill When to use <code>/_ctx-absorb</code> Merge deltas from a parallel worktree or separate checkout <code>/_ctx-audit</code> Detect code-level drift after YOLO sprints or before releases <code>/_ctx-qa</code> Run QA checks before committing <code>/_ctx-release</code> Run the full release process <code>/_ctx-release-notes</code> Generate release notes for <code>dist/RELEASE_NOTES.md</code> <code>/_ctx-alignment-audit</code> Audit doc claims against agent instructions <code>/_ctx-update-docs</code> Check docs/code consistency after changes <code>/_ctx-command-audit</code> Audit CLI surface after renames, moves, or deletions <p>Six skills previously in this list have been promoted to bundled plugin skills and are now available to all <code>ctx</code> users: <code>/ctx-brainstorm</code>, <code>/ctx-link-check</code>, <code>/ctx-permission-sanitize</code>, <code>/ctx-skill-create</code>, <code>/ctx-spec</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#how-to-add-things","level":2,"title":"How to Add Things","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-cli-command","level":3,"title":"Adding a New CLI Command","text":"<ol> <li>Create a package under <code>internal/cli/<name>/</code> with <code>doc.go</code>, <code>cmd.go</code>,    and <code>run.go</code>;</li> <li>Implement <code>Cmd() *cobra.Command</code> as the entry point;</li> <li>Add <code>Use*</code> and <code>DescKey*</code> constants in <code>internal/config/embed/cmd/<name>.go</code>;</li> <li>Add command descriptions in <code>internal/assets/commands/commands.yaml</code>;</li> <li>Add examples in <code>internal/assets/commands/examples.yaml</code>;</li> <li>Add flag descriptions in <code>internal/assets/commands/flags.yaml</code>;</li> <li>Register the command in <code>internal/bootstrap/group.go</code> (add import +    entry in the appropriate group function);</li> <li>Create an output package at <code>internal/write/<name>/</code> for all    user-facing output (see Package Taxonomy);</li> <li>Create error constructors at <code>internal/err/<name>/</code> for    domain-specific errors;</li> <li>Add tests in the same package (<code><name>_test.go</code>);</li> <li>Add a doc page at <code>docs/cli/<name>.md</code> and update     <code>docs/cli/index.md</code>;</li> <li>Add the page to <code>zensical.toml</code> nav.</li> </ol> <p>Pattern to follow: <code>internal/cli/pad/pad.go</code> (parent with subcommands) or <code>internal/cli/drift/</code> (single command).</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#package-taxonomy","level":3,"title":"Package Taxonomy","text":"<p><code>ctx</code> separates concerns into a strict package taxonomy. Knowing where things go prevents code review friction and keeps the AST lint tests happy.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#output-internalwrite","level":4,"title":"Output: <code>internal/write/</code>","text":"<p>Every CLI command's user-facing output lives in its own sub-package under <code>internal/write/<domain>/</code>. Output functions accept <code>*cobra.Command</code> and call <code>cmd.Println(...)</code>, never <code>fmt.Print*</code> directly. All text strings are loaded from YAML via <code>desc.Text(text.DescKey*)</code>, never inline.</p> <pre><code>internal/write/add/add.go       # output for ctx add\ninternal/write/stat/stat.go     # output for ctx usage\ninternal/write/resource/        # output for ctx sysinfo\n</code></pre> <p>Exception: <code>write/rc/</code> writes to <code>os.Stderr</code> because rc loads before cobra is initialized.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#errors-internalerr","level":4,"title":"Errors: <code>internal/err/</code>","text":"<p>Domain-specific error constructors live under <code>internal/err/<domain>/</code>. Each package mirrors the write structure. Constructor functions return <code>error</code> and load messages from YAML via <code>desc.Text(text.DescKey*)</code>.</p> <p>Identity sentinels (matched at the call site with <code>errors.Is</code>) are declared as <code>entity.Sentinel</code> consts:</p> <pre><code>const ErrMissingFoo = entity.Sentinel(text.DescKeyErrPkgMissingFoo)\n</code></pre> <p><code>entity.Sentinel</code> is a typed string whose <code>Error()</code> resolves the key through <code>desc.Text</code> at call time, so the user-facing text stays in <code>commands/text/errors.yaml</code> and the sentinel value itself remains pure identity. Never declare sentinels as <code>var ErrX = errors.New(...)</code> with a hardcoded English string — that bypasses localization and materializes the string before the embedded YAML lookup is populated.</p> <p>When a sentinel needs to carry fields (a path, a name), use a typed struct in <code>internal/err/<domain>/</code> instead. See <code>internal/err/context.NotFoundError</code> for the canonical pattern with <code>Error()</code>, <code>Is(target error) bool</code>, and an <code>errors.As</code> consumer contract.</p> <pre><code>internal/err/add/add.go         # errors for ctx add\ninternal/err/config/config.go   # errors for configuration\ninternal/err/cli/cli.go         # errors for CLI argument validation\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#config-constants-internalconfig","level":4,"title":"Config Constants: <code>internal/config/</code>","text":"<p>Pure-constant leaf packages with zero internal dependencies (stdlib only). Over 60 sub-packages, organized by domain. See <code>internal/config/README.md</code> for the full decision tree.</p> What you're adding Where it goes File names, extensions, paths <code>config/file/</code>, <code>config/dir/</code> Regex patterns <code>config/regex/</code> CLI flag names (<code>--flag-name</code>) <code>config/flag/flag.go</code> Flag description YAML keys <code>config/embed/flag/<cmd>.go</code> Command Use/DescKey strings <code>config/embed/cmd/<cmd>.go</code> User-facing text YAML keys <code>config/embed/text/<domain>.go</code> Time durations, thresholds <code>config/<domain>/</code>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#the-assets-pipeline","level":4,"title":"The Assets Pipeline","text":"<p>User-facing text flows through a three-level chain:</p> <ol> <li>Go constant (<code>config/embed/text/</code>) defines a string key:    <code>DescKeyWriteAddedTo = \"write.added-to\"</code></li> <li>Call site resolves it: <code>desc.Text(text.DescKeyWriteAddedTo)</code></li> <li>YAML (<code>internal/assets/commands/text/write.yaml</code>) holds the    actual text: <code>write.added-to: { short: \"Added to %s\" }</code></li> </ol> <p>The same pattern applies to command descriptions (<code>commands.yaml</code>), flag descriptions (<code>flags.yaml</code>), and examples (<code>examples.yaml</code>). The <code>TestDescKeyYAMLLinkage</code> test verifies every constant resolves to a non-empty YAML value.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-new-session-parser","level":3,"title":"Adding a New Session Parser","text":"<p>The journal system uses a <code>SessionParser</code> interface. To add support for a new AI tool (e.g. Aider, Cursor):</p> <ol> <li>Create <code>internal/journal/parser/<tool>.go</code>;</li> <li>Implement parsing logic that returns <code>[]*Session</code>;</li> <li>Register the parser in <code>FindSessions()</code> / <code>FindSessionsForCWD()</code>;</li> <li>Use <code>config.Tool*</code> constants for the tool identifier;</li> <li>Add test fixtures and parser tests.</li> </ol> <p>Pattern to follow: the Claude Code JSONL parser in <code>internal/journal/parser/</code>.</p> <p>Multilingual Session Headers</p> <p>The Markdown parser recognizes session header prefixes configured via <code>session_prefixes</code> in <code>.ctxrc</code> (default: <code>Session:</code>). To support a new language, users add a prefix to their <code>.ctxrc</code> - no code change needed. New parser implementations can use <code>rc.SessionPrefixes()</code> if they also need prefix-based header detection.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#adding-a-bundled-skill","level":3,"title":"Adding a Bundled Skill","text":"<ol> <li>Create <code>internal/assets/claude/skills/<skill-name>/SKILL.md</code>;</li> <li>Follow the skill format: trigger, negative triggers, steps, quality gate;</li> <li>Run <code>make plugin-reload</code> and restart Claude Code to test;</li> <li>Add a <code>Skill</code> entry to <code>.claude-plugin/plugin.json</code> if user-invocable;</li> <li>Document in <code>docs/reference/skills.md</code>.</li> </ol> <p>Pattern to follow: any skill in <code>internal/assets/claude/skills/ctx-status/</code>.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#test-expectations","level":3,"title":"Test Expectations","text":"<ul> <li>Unit tests: colocated with source (<code>foo.go</code> → <code>foo_test.go</code>);</li> <li>Test helpers: use <code>t.Helper()</code> so failures point to callers;</li> <li>HOME isolation: use <code>t.TempDir()</code> + <code>t.Setenv(\"HOME\", ...)</code> for   tests that touch <code>~/.claude/</code> or <code>~/.ctx/</code>;</li> <li>rc.Reset(): call after <code>os.Chdir</code> in tests that change working   directory (rc caches on first access);</li> <li>No network: all tests run offline, use fixtures.</li> </ul> <p>Run <code>make test</code> before submitting. Target: no failures, no skips.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#day-to-day-workflow","level":2,"title":"Day-to-Day Workflow","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#go-code-changes","level":3,"title":"Go Code Changes","text":"<p>After modifying Go source files, rebuild and reinstall:</p> <pre><code>make build && sudo make install\n</code></pre> <p>The <code>ctx</code> binary is statically compiled. There is no hot reload. You must rebuild for Go changes to take effect.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#skill-or-hook-changes","level":3,"title":"Skill or Hook Changes","text":"<p>Edit files under <code>internal/assets/claude/skills/</code> or <code>internal/assets/claude/hooks/</code>.</p> <p>Claude Code caches plugin files, so edits aren't picked up automatically.</p> <p>Clear the cache and restart:</p> <pre><code>make plugin-reload   # nukes ~/.claude/plugins/cache/activememory-ctx/\n# then restart Claude Code\n</code></pre> <p>The plugin will be re-installed from your local marketplace on startup. No version bump is needed during development.</p> <p>Version Bumps Are for Releases, Not Iteration</p> <p>Only bump <code>VERSION</code>, <code>plugin.json</code>, and <code>marketplace.json</code> when cutting a release. During development, <code>make plugin-reload</code> is all you need.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"<p>The repo ships two <code>.ctxrc</code> source profiles. The working copy (<code>.ctxrc</code>) is gitignored and swapped between them:</p> File Purpose <code>.ctxrc.base</code> Golden baseline: all defaults, no logging <code>.ctxrc.dev</code> Dev profile: notify events enabled, verbose logging <code>.ctxrc</code> Working copy (gitignored: copied from one of the above) <p>Use <code>ctx</code> commands to switch:</p> <pre><code>ctx config switch dev      # switch to dev profile\nctx config switch base     # switch to base profile\nctx config status          # show which profile is active\n</code></pre> <p>After cloning, run <code>ctx config switch dev</code> to get started with full logging.</p> <p>See Configuration for the full <code>.ctxrc</code> option reference.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#backups","level":3,"title":"Backups","text":"<p><code>ctx</code> does not ship a backup command. File-level backup is an OS / infrastructure concern; <code>ctx hub</code> handles the cross-machine knowledge persistence that matters most. For everything else, see Backup Strategy: rsync, Time Machine, Borg, or whichever tool already handles the rest of your files.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-tests","level":3,"title":"Running Tests","text":"<pre><code>make test   # fast: all tests\nmake audit  # full: fmt + vet + lint + drift + docs + test\nmake smoke  # build + run basic commands end-to-end\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#running-the-docs-site-locally","level":3,"title":"Running the Docs Site Locally","text":"<pre><code>make site-setup  # one-time: install zensical via pipx\nmake site-serve  # serve at localhost\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#submitting-changes","level":2,"title":"Submitting Changes","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#before-you-start","level":3,"title":"Before You Start","text":"<ol> <li>Check existing issues to avoid duplicating effort;</li> <li>For large changes, open an issue first to discuss the approach;</li> <li>Read the specs in <code>specs/</code> for design context.</li> </ol>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#pull-request-process","level":3,"title":"Pull Request Process","text":"<p>Respect the maintainers' time and energy: Keep your pull requests isolated and strive to minimze code changes.</p> <p>If you Pull Request solves more than one distinct issues, it's better to create separate pull requests instead of sending them in one large bundle.</p> <ol> <li>Create a feature branch: <code>git checkout -b feature/my-feature</code>;</li> <li>Make your changes;</li> <li>Run <code>make audit</code> to catch issues early;</li> <li>Commit with a clear message;</li> <li>Push and open a pull request.</li> </ol> <p>Audit Your Code Before Submitting</p> <p>Run <code>make audit</code> before submitting:</p> <p><code>make audit</code> covers formatting, vetting, linting, drift checks,  doc consistency, and tests in one pass.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#commit-messages","level":3,"title":"Commit Messages","text":"<p>Following conventional commits is recommended but not required:</p> <p>Types: <code>feat</code>, <code>fix</code>, <code>docs</code>, <code>test</code>, <code>refactor</code>, <code>chore</code></p> <p>Examples:</p> <ul> <li><code>feat(cli): add ctx export command</code></li> <li><code>fix(drift): handle missing files gracefully</code></li> <li><code>docs: update installation instructions</code></li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-style","level":3,"title":"Code Style","text":"<ul> <li>Follow Go conventions (<code>gofmt</code>, <code>go vet</code>);</li> <li>Keep functions focused and small;</li> <li>Add tests for new functionality;</li> <li>Handle errors explicitly; use descriptive names (<code>readErr</code>,   <code>writeErr</code>) not repeated <code>err</code>;</li> <li>No magic strings: all repeated literals go in <code>internal/config/</code>;</li> <li>Output goes through <code>internal/write/</code> packages, not <code>fmt.Print*</code>;</li> <li>Errors go through <code>internal/err/</code> constructors, not inline   <code>fmt.Errorf</code>;</li> <li>See Package Taxonomy and   <code>.context/CONVENTIONS.md</code> for the full reference.</li> </ul>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#code-of-conduct","level":2,"title":"Code of Conduct","text":"<p>A clear context requires respectful collaboration.</p> <p><code>ctx</code> follows the Contributor Covenant.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#boring-legal-stuff","level":2,"title":"Boring Legal Stuff","text":"","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#developer-certificate-of-origin-dco","level":3,"title":"Developer Certificate of Origin (DCO)","text":"<p>By contributing, you agree to the Developer Certificate of Origin.</p> <p>All commits must be signed off:</p> <pre><code>git commit -s -m \"feat: add new feature\"\n</code></pre>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/contributing/#license","level":3,"title":"License","text":"<p>Contributions are licensed under the Apache 2.0 License.</p>","path":["Home","Community","Contributing"],"tags":[]},{"location":"home/faq/","level":1,"title":"FAQ","text":"","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-markdown","level":2,"title":"Why Markdown?","text":"<p>Markdown is human-readable, version-controllable, and tool-agnostic. Every AI model can parse it natively. Every developer can read it in a terminal, a browser, or a code review. There's no schema to learn, no binary format to decode, no vendor lock-in. You can inspect your context with <code>cat</code>, diff it with <code>git diff</code>, and review it in a PR.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-ctx-work-offline","level":2,"title":"Does <code>ctx</code> Work Offline?","text":"<p>Yes. <code>ctx</code> is completely local. It reads and writes files on disk, generates context packets from local state, and requires no network access. The only feature that touches the network is the optional webhook notifications hook, which you have to explicitly configure.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-gets-committed-to-git","level":2,"title":"What Gets Committed to Git?","text":"<p>The <code>.context/</code> directory: yes, commit it. That's the whole point. Team members and AI agents read the same context files.</p> <p>What not to commit:</p> <ul> <li><code>.ctx.key</code>: your encryption key. Stored at <code>~/.ctx/.ctx.key</code>,   never in the repo. <code>ctx init</code> handles this automatically.</li> <li><code>journal/</code> and <code>logs/</code>: generated data, potentially large.   <code>ctx init</code> adds these to <code>.gitignore</code>.</li> <li><code>scratchpad.enc</code>: your choice. It's encrypted, so it's safe to   commit if you want shared scratchpad state. See   Scratchpad for details.</li> </ul>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#how-big-should-my-token-budget-be","level":2,"title":"How Big Should My Token Budget Be?","text":"<p>The default is 8000 tokens, which works well for most projects. Configure it via <code>.ctxrc</code> or the <code>CTX_TOKEN_BUDGET</code> environment variable:</p> <pre><code># In .ctxrc\ntoken_budget = 12000\n\n# Or as an environment variable\nexport CTX_TOKEN_BUDGET=12000\n\n# Or per-invocation\nctx agent --budget 4000\n</code></pre> <p>Higher budgets include more context but cost more tokens per request. Lower budgets force sharper prioritization: <code>ctx</code> drops lower-priority content first, so CONSTITUTION and TASKS always make the cut.</p> <p>See Configuration for all available settings.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#why-not-a-database","level":2,"title":"Why Not a Database?","text":"<p>Files are inspectable, diffable, and reviewable in pull requests. You can <code>grep</code> them, <code>cat</code> them, pipe them through <code>jq</code> or <code>awk</code>. They work with every version control system and every text editor.</p> <p>A database would add a dependency, require migrations, and make context opaque. The design bet is that context should be as visible and portable as the code it describes.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#does-it-work-with-tools-other-than-claude-code","level":2,"title":"Does It Work with Tools Other than Claude Code?","text":"<p>Yes. <code>ctx agent</code> outputs a context packet that any AI tool can consume: paste it into ChatGPT, Cursor, Copilot, Aider, or anything else that accepts text input.</p> <p>Claude Code gets first-class integration via the <code>ctx</code> plugin (hooks, skills, automatic context loading). VS Code Copilot Chat has a dedicated <code>ctx</code> extension. Other tools integrate via generated instruction files or manual pasting.</p> <p>See Integrations for tool-specific setup, including the multi-tool recipe.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#can-i-use-ctx-on-an-existing-project","level":2,"title":"Can I Use <code>ctx</code> on an Existing Project?","text":"<p>Yes. Run <code>ctx init</code> in any repo and it creates <code>.context/</code> with template files. Start recording decisions, tasks, and conventions as you work. Context grows naturally; you don't need to backfill everything on day one.</p> <p>See Getting Started for the full setup flow, or Joining a <code>ctx</code> Project if someone else already initialized it.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#what-happens-when-context-files-get-too-big","level":2,"title":"What Happens When Context Files Get Too Big?","text":"<p>Token budgeting handles this automatically. <code>ctx agent</code> prioritizes content by file priority (CONSTITUTION first, GLOSSARY last) and trims lower-priority entries when the budget is tight.</p> <p>For manual maintenance, <code>ctx compact</code> archives completed tasks and old entries, keeping active context lean. You can also run <code>ctx task archive</code> to move completed tasks out of TASKS.md.</p> <p>The goal is to keep context files focused on current state. Historical entries belong in git history or the archive.</p>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/faq/#is-context-meant-to-be-shared","level":2,"title":"Is <code>.context/</code> Meant to Be Shared?","text":"<p>Yes. Commit it to your repo. Every team member and every AI agent reads the same files. That's the mechanism for shared memory: decisions made in one session are visible in the next, regardless of who (or what) starts it.</p> <p>The only per-user state is the encryption key (<code>~/.ctx/.ctx.key</code>) and the optional scratchpad. Everything else is team-shared by design.</p> <p>Related:</p> <ul> <li>Getting Started - installation and first setup</li> <li>Configuration - <code>.ctxrc</code>, environment variables, and defaults</li> <li>Context Files - what each file does and how to use it</li> </ul>","path":["Home","Introduction","FAQ"],"tags":[]},{"location":"home/first-session/","level":1,"title":"Your First Session","text":"<p>Here's what a complete first session looks like, from initialization to the moment your AI cites your project context back to you.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-1-initialize-your-project","level":2,"title":"Step 1: Initialize Your Project","text":"<p>Run <code>ctx init</code> in your project root:</p> <pre><code>cd your-project\nctx init\n</code></pre> <p>Sample output:</p> <pre><code>Context initialized in .context/\n\n  ✓ CONSTITUTION.md\n  ✓ TASKS.md\n  ✓ DECISIONS.md\n  ✓ LEARNINGS.md\n  ✓ CONVENTIONS.md\n  ✓ ARCHITECTURE.md\n  ✓ GLOSSARY.md\n  ✓ AGENT_PLAYBOOK.md\n\nSetting up encryption key...\n  ✓ ~/.ctx/.ctx.key\n\nClaude Code plugin (hooks + skills):\n  Install: claude /plugin marketplace add ActiveMemory/ctx\n  Then:    claude /plugin install ctx@activememory-ctx\n\nNext steps:\n  1. Edit .context/TASKS.md to add your current tasks\n  2. Run 'ctx status' to see context summary\n  3. Run 'ctx agent' to get AI-ready context packet\n</code></pre> <p>This created your <code>.context/</code> directory with template files. </p> <p>For Claude Code, install the <code>ctx</code> plugin to get automatic hooks and skills.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-2-activate-the-project","level":2,"title":"Step 2: Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory the rest of these commands should use:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the next steps fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. For more options (multiple <code>.context/</code> directories, scripts, CI), see Activating a Context Directory.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-3-populate-your-context","level":2,"title":"Step 3: Populate Your Context","text":"<p>Add a task and a decision: These are the entries your AI will remember:</p> <pre><code>ctx task add \"Implement user authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to TASKS.md\n\nctx decision add \"Use PostgreSQL for primary database\" \\\n  --context \"Need a reliable database for production\" \\\n  --rationale \"PostgreSQL offers ACID compliance and JSON support\" \\\n  --consequence \"Team needs PostgreSQL training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Output: ✓ Added to DECISIONS.md\n</code></pre> <p>These entries are what the AI will recall in future sessions. You don't need to populate everything now: Context grows naturally as you work.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-4-check-your-context","level":2,"title":"Step 4: Check Your Context","text":"<pre><code>ctx status\n</code></pre> <p>Sample output:</p> <pre><code>Context Status\n====================\n\nContext Directory: .context/\nTotal Files: 8\nToken Estimate: 1,247 tokens\n\nFiles:\n  ✓ CONSTITUTION.md (loaded)\n  ✓ TASKS.md (1 items)\n  ✓ DECISIONS.md (1 items)\n  ○ LEARNINGS.md (empty)\n  ✓ CONVENTIONS.md (loaded)\n  ✓ ARCHITECTURE.md (loaded)\n  ✓ GLOSSARY.md (loaded)\n  ✓ AGENT_PLAYBOOK.md (loaded)\n\nRecent Activity:\n  - TASKS.md modified 2 minutes ago\n  - DECISIONS.md modified 1 minute ago\n</code></pre> <p>Notice the token estimate: This is how much context your AI will load.</p> <p>The <code>○</code> next to <code>LEARNINGS.md</code> means it's still empty; it will fill in as you capture lessons during development.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-5-start-an-ai-session","level":2,"title":"Step 5: Start an AI Session","text":"<p>With Claude Code (and the <code>ctx</code> plugin), start every session with:</p> <pre><code>/ctx-remember\n</code></pre> <p>This loads your context and presents a structured  readback so you can confirm the agent knows what is going on. Context also loads automatically via hooks, but the explicit ceremony gives you a readback to verify.</p> <p>Steering Files Fire Automatically</p> <p>If you edited the four foundation files scaffolded by <code>ctx init</code> (<code>.context/steering/product.md</code>, <code>tech.md</code>, <code>structure.md</code>, <code>workflow.md</code>), their <code>inclusion: always</code> rules are prepended to every tool call via the plugin's <code>PreToolUse</code> hook, with no <code>/ctx-remember</code> needed, no MCP call. Edit a file, save, and the next tool call in Claude Code picks it up. See Steering files for details on the inclusion modes.</p> <p>Using VS Code?</p> <p>With VS Code Copilot Chat (and the <code>ctx</code> extension),  type <code>@ctx /agent</code> in chat to load your context packet, or <code>@ctx /status</code> to check your project context. Run <code>ctx setup copilot --write</code> once to generate <code>.github/copilot-instructions.md</code> for automatic context loading.</p> <p>If you are not using Claude Code, generate a  context packet for your AI tool:</p> <pre><code>ctx agent --budget 8000\n</code></pre> <p>Sample output:</p> <pre><code># Context Packet\nGenerated: 2026-02-14T15:30:45Z | Budget: 8000 tokens | Used: ~2450\n\n## Read These Files (in order)\n1. .context/CONSTITUTION.md\n2. .context/TASKS.md\n3. .context/CONVENTIONS.md\n...\n\n## Current Tasks\n- [ ] Implement user authentication\n- [ ] Add rate limiting to API endpoints\n\n## Key Conventions\n- Use gofmt for formatting\n- Path construction uses filepath.Join\n\n## Recent Decisions\n## [2026-02-14-120000] Use PostgreSQL for the primary database\n\n**Context**: Evaluated PostgreSQL, MySQL, and SQLite...\n**Rationale**: PostgreSQL offers better JSON support...\n\n## Key Learnings\n## [2026-02-14-100000] Connection pool sizing matters\n\n**Context**: Hit connection limits under load...\n**Lesson**: Default pool size of 10 is too low for concurrent requests...\n\n## Also Noted\n- Use JWT for session management\n- Always validate input at API boundary\n</code></pre> <p>Paste this output into your AI tool's system prompt or conversation start.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#step-6-verify-it-works","level":2,"title":"Step 6: Verify It Works","text":"<p>Ask your AI: \"What are our current tasks?\"</p> <p>A working setup produces a response like:</p> <pre><code>Based on the project context, you have one active task:\n\n- **Implement user authentication** (pending)\n\nThere's also a recent architectural decision to **use PostgreSQL for\nthe primary database**, chosen for its ACID compliance and JSON support.\n\nWant me to start on the authentication task?\n</code></pre> <p>That's the success moment:</p> <p>The AI is citing your exact context entries from Step 2, not hallucinating or asking you to re-explain.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-gets-created","level":2,"title":"What Gets Created","text":"<pre><code>.context/\n├── CONSTITUTION.md     # Hard rules: NEVER violate these\n├── TASKS.md            # Current and planned work\n├── CONVENTIONS.md      # Project patterns and standards\n├── ARCHITECTURE.md     # System overview\n├── DECISIONS.md        # Architectural decisions with rationale\n├── LEARNINGS.md        # Lessons learned, gotchas, tips\n├── GLOSSARY.md         # Domain terms and abbreviations\n└── AGENT_PLAYBOOK.md   # How AI tools should use this\n</code></pre> <p>Claude Code integration (hooks + skills) is provided by the <code>ctx</code> plugin: See Integrations/Claude Code.</p> <p>VS Code Copilot Chat integration is provided by the <code>ctx</code> extension: See Integrations/VS Code.</p> <p>See Context Files for detailed documentation of each file.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/first-session/#what-to-gitignore","level":2,"title":"What to <code>.gitignore</code>","text":"<p>Rule of Thumb</p> <ul> <li>If it's knowledge (decisions, tasks, learnings,   conventions), commit it.</li> <li>If it's generated output, raw session data, or a secret, <code>.gitignore</code> it.</li> </ul> <p>Commit your <code>.context/</code> knowledge files: that's the whole point.</p> <p>You should <code>.gitignore</code> the generated and sensitive paths:</p> <pre><code># Journal data (large, potentially sensitive)\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Hook logs (machine-specific)\n.context/logs/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n</code></pre> <p><code>ctx init</code> Patches Your .Gitignore for You</p> <p><code>ctx init</code> automatically adds these entries to your <code>.gitignore</code>.</p> <p>Review the additions with <code>cat .gitignore</code> after init.</p> <p>See also:</p> <ul> <li>Security Considerations</li> <li>Scratchpad Encryption</li> <li>Session Journal</li> </ul> <p>Next Up: Common Workflows →:  day-to-day commands for tracking context, checking health, and browsing history.</p>","path":["Home","Get Started","Your First Session"],"tags":[]},{"location":"home/getting-started/","level":1,"title":"Getting Started","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"<p><code>ctx</code> does not require <code>git</code>, but using version control with your <code>.context/</code> directory is strongly recommended:</p> <p>AI sessions occasionally modify or overwrite context files inadvertently. With <code>git</code>, the AI can check history and restore lost content: Without it, the data is gone.</p> <p>Also, several <code>ctx</code> features (journal changelog, blog generation) also use <code>git</code> history directly.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#installation","level":2,"title":"Installation","text":"<p>Every setup starts with the <code>ctx</code> binary: the CLI tool itself.</p> <p>If you use Claude Code, you also install the <code>ctx</code> plugin, which adds hooks (context autoloading, persistence nudges) and 25+ <code>/ctx-*</code> skills. For other AI tools, <code>ctx</code> integrates via generated instruction files or manual context pasting: see Integrations for tool-specific setup.</p> <p>Pick one of the options below to install the binary. Claude Code users should also follow the plugin steps included in each option.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-1-build-from-source-recommended","level":3,"title":"Option 1: Build from Source (Recommended)","text":"<p>Requires Go (version defined in  <code>go.mod</code>) and Claude Code.</p> <pre><code>git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\nmake build\nsudo make install\n</code></pre> <p>Install the Claude Code plugin from your local clone:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter the path to the root of your clone,    e.g. <code>~/WORKSPACE/ctx</code>    (this is where <code>.claude-plugin/marketplace.json</code> lives: It points    Claude Code to the actual plugin in <code>internal/assets/claude</code>)</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>This points Claude Code at the plugin source on disk. Changes you make to hooks or skills take effect immediately: No reinstall is needed.</p> <p>Local Installs Need Manual Enablement</p> <p>Unlike marketplace installs, local plugin installs are not auto-enabled globally. The plugin will only work in projects that explicitly enable it. Run <code>ctx init</code> in each project (it auto-enables the plugin), or add the entry to <code>~/.claude/settings.json</code> manually:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Verify:</p> <pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed\n</code></pre> <p>Use the Source, Luke</p> <p>Building from source gives you the latest features and bug fixes.</p> <p>Since <code>ctx</code> is predominantly a developer tool, this is the recommended approach: </p> <p>You get the freshest code, can inspect what you are installing, and the plugin stays in sync with the binary.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#option-2-binary-download-marketplace","level":3,"title":"Option 2: Binary Download + Marketplace","text":"<p>Pre-built binaries are available from the releases page.</p> Linux (x86_64)Linux (ARM64)macOS (Apple Silicon)macOS (Intel)Windows <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64\nchmod +x ctx-0.8.1-linux-amd64\nsudo mv ctx-0.8.1-linux-amd64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-arm64\nchmod +x ctx-0.8.1-linux-arm64\nsudo mv ctx-0.8.1-linux-arm64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-arm64\nchmod +x ctx-0.8.1-darwin-arm64\nsudo mv ctx-0.8.1-darwin-arm64 /usr/local/bin/ctx\n</code></pre> <pre><code>curl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-darwin-amd64\nchmod +x ctx-0.8.1-darwin-amd64\nsudo mv ctx-0.8.1-darwin-amd64 /usr/local/bin/ctx\n</code></pre> <p>Download <code>ctx-0.8.1-windows-amd64.exe</code> from the releases page and add it to your <code>PATH</code>.</p> <p>Claude Code users: install the plugin from the marketplace:</p> <ol> <li>Launch <code>claude</code>;</li> <li>Type <code>/plugin</code> and press Enter;</li> <li>Select Marketplaces → Add Marketplace;</li> <li>Enter <code>ActiveMemory/ctx</code>;</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code>.</li> </ol> <p>Other tool users: see Integrations for tool-specific setup (Cursor, Copilot, Aider, Windsurf, etc.).</p> <p>Verify the Plugin Is Enabled</p> <p>After installing, confirm the plugin is enabled globally. Check <code>~/.claude/settings.json</code> for an <code>enabledPlugins</code> entry. If missing, run <code>ctx init</code> in your project (it auto-enables the plugin), or add it manually:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Verify:</p> <pre><code>ctx --version       # binary is in PATH\nclaude /plugin list # plugin is installed (Claude Code only)\n</code></pre>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#verifying-checksums","level":4,"title":"Verifying Checksums","text":"<p>Each binary has a corresponding <code>.sha256</code> checksum file. To verify your download:</p> <pre><code># Download the checksum file\ncurl -LO https://github.com/ActiveMemory/ctx/releases/download/v0.8.1/ctx-0.8.1-linux-amd64.sha256\n\n# Verify the binary\nsha256sum -c ctx-0.8.1-linux-amd64.sha256\n</code></pre> <p>On macOS, use <code>shasum -a 256 -c</code> instead of <code>sha256sum -c</code>.</p> Plugin Details <p>After installation (either option) you get:</p> <ul> <li>Context autoloading: <code>ctx agent</code> runs on every tool use (with cooldown)</li> <li>Persistence nudges: reminders to capture learnings and decisions</li> <li>Post-commit hooks: nudge context capture after <code>git commit</code></li> <li>Context size monitoring: alerts as sessions grow large</li> <li>Project skills: <code>/ctx-status</code>, <code>/ctx-task-add</code>, <code>/ctx-history</code>, and more</li> </ul> <p>See Integrations for the full hook and skill reference.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#quick-start","level":2,"title":"Quick Start","text":"","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#1-initialize-context","level":3,"title":"1. Initialize Context","text":"<pre><code>cd your-project\nctx init\n</code></pre> <p>This creates a <code>.context/</code> directory with template files and an encryption key at <code>~/.ctx/</code> for the encrypted scratchpad. For Claude Code, install the <code>ctx</code> plugin for automatic hooks and skills.</p> <p><code>ctx init</code> also scaffolds four foundation steering files in <code>.context/steering/</code>: <code>product.md</code>, <code>tech.md</code>, <code>structure.md</code>, <code>workflow.md</code>. They are placeholders until you customize them (see the next step); skipping that step has consequences, so it is broken out as its own numbered beat rather than buried here.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#2-customize-your-steering-files","level":3,"title":"2. Customize Your Steering Files","text":"<p>Steering files are behavioral rules prepended to every AI prompt: the layer that tells your AI how to act on this specific project. They are distinct from decisions (what was chosen) and conventions (how the codebase is written); see <code>ctx</code> for Steering Files for the full model.</p> <p><code>ctx init</code> scaffolded four foundation files; open each and fill it in:</p> File What to fill in <code>product.md</code> What the project is, who uses it, what's out of scope <code>tech.md</code> Languages, frameworks, runtime, hard constraints <code>structure.md</code> Directory layout, where new files go, naming rules <code>workflow.md</code> Branch strategy, commit conventions, pre-commit checks <p>Each scaffolded file ships with a tombstone marker line (<code><!-- remove this after you edit the steering file !--></code>). As long as the marker is present, the file is silently skipped on every load path: the agent context packet, MCP <code>ctx_steering_get</code>, and native-tool sync (Cursor / Cline / Kiro). The skip is deliberate: injecting unfilled placeholders into AI prompts is worse than no steering at all, because the AI tries to follow \"Describe the product...\" as if it were a rule.</p> <p>Replace each file's body with real content, then delete the tombstone line. When the line is gone, the file becomes active on the next AI tool call.</p> <p>Don't want steering at all? Pass <code>--no-steering-init</code> to <code>ctx init</code> to skip the scaffold entirely. Existing edits are never clobbered by re-running <code>ctx init</code>.</p> <p>Inclusion modes (<code>always</code> / <code>auto</code> / <code>manual</code>), priority, and tool scoping are covered in Writing Steering Files and <code>ctx steering</code>.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#3-activate-the-project","level":3,"title":"3. Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory the rest of these commands should use:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the next steps fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. For more options (multiple <code>.context/</code> directories, scripts, CI), see Activating a Context Directory.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#4-check-status","level":3,"title":"4. Check Status","text":"<pre><code>ctx status\n</code></pre> <p>Shows context summary: files present, token estimate, and recent activity.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5-start-using-with-ai","level":3,"title":"5. Start Using with AI","text":"<p>With Claude Code (and the <code>ctx</code> plugin installed), context loads automatically via hooks.</p> <p>With VS Code Copilot Chat, install the <code>ctx</code> extension and use <code>@ctx /status</code>, <code>@ctx /agent</code>, and other slash commands directly in chat. Run <code>ctx setup copilot --write</code> to generate <code>.github/copilot-instructions.md</code> for automatic context loading.</p> <p>For other tools, paste the output of:</p> <pre><code>ctx agent --budget 8000\n</code></pre>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#5b-set-up-for-your-ai-tool","level":3,"title":"5B. Set Up for Your AI Tool","text":"<p>If you use an MCP-compatible tool, generate the integration config with <code>ctx setup</code>:</p> KiroCursorCline <pre><code>ctx setup kiro --write\n# Creates .kiro/settings/mcp.json and syncs steering files\n</code></pre> <pre><code>ctx setup cursor --write\n# Creates .cursor/mcp.json and syncs steering files\n</code></pre> <pre><code>ctx setup cline --write\n# Creates .vscode/mcp.json and syncs steering files\n</code></pre> <p>This registers the <code>ctx</code> MCP server and syncs any steering files into the tool's native format. Re-run after adding or changing steering files.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#6-verify-it-works","level":3,"title":"6. Verify It Works","text":"<p>Ask your AI: \"Do you remember?\"</p> <p>It should cite specific context: current tasks, recent decisions, or previous session topics.</p>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/getting-started/#7-set-up-companion-tools-highly-recommended","level":3,"title":"7. Set Up Companion Tools (Highly Recommended)","text":"<p><code>ctx</code> works on its own, but two companion MCP servers unlock significantly better agent behavior. The investment is small and the benefits compound over sessions:</p> <ul> <li> Gemini Search grounded web search with citations. Skills like <code>/ctx-code-review</code>   and <code>/ctx-explain</code> use it for up-to-date documentation lookups instead   of relying on training data. </li> <li> <p>GitNexus: code   knowledge graph with symbol resolution, blast radius analysis, and   domain clustering. Skills like <code>/ctx-refactor</code> and <code>/ctx-code-review</code>   use it for impact analysis and dependency awareness.</p> </li> </ul> <pre><code># Index your project for GitNexus (run once, then after major changes)\nnpx gitnexus analyze\n</code></pre> <p>Both are optional MCP servers: if they are not connected, skills degrade gracefully to built-in capabilities. See Companion Tools for setup details and verification.</p> <p>Next Up:</p> <ul> <li>Your First Session →: a step-by-step walkthrough   from <code>ctx init</code> to verified recall</li> <li>Common Workflows →: day-to-day commands for   tracking context, checking health, and browsing history</li> </ul>","path":["Home","Get Started","Getting Started"],"tags":[]},{"location":"home/hub/","level":1,"title":"Hub","text":"","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#sharing-is-caring","level":2,"title":"Sharing Is Caring","text":"<p><code>ctx</code> projects are normally independent: each project has its own <code>.context/</code> directory, its own decisions, its own learnings, its own journal. That's the right default, since most work is project-local, and mixing context across projects tends to dilute more than it helps.</p> <p>But sometimes a decision or a learning should cross project boundaries. A convention you codified in one project deserves to be visible in another. A gotcha you discovered debugging service A is the same gotcha waiting for you in service B. The <code>ctx</code> Hub is the feature that makes those specific entries travel, without replicating everything else.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#what-the-hub-actually-is","level":2,"title":"What the Hub Actually Is","text":"<p>In one paragraph: the <code>ctx</code> Hub is a fan-out channel for four specific kinds of structured entries: <code>decision</code>, <code>learning</code>, <code>convention</code>, and <code>task</code>. You publish an entry with <code>ctx add --share</code> in one project, and it appears in <code>.context/hub/</code> for every other project subscribed to that type. When you run <code>ctx agent --include-hub</code>, those shared entries become part of your next agent context packet.</p> <p>That is the entire feature. The Hub does not:</p> <ul> <li>Share your session journal (<code>.context/journal/</code>). That stays   local to each project.</li> <li>Share your scratchpad (<code>.context/pad</code>). Encrypted notes never   leave the machine that created them.</li> <li>Share your <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, or   <code>CONVENTIONS.md</code> wholesale. Only entries you explicitly   <code>--share</code> cross the boundary.</li> <li>Provide user identity or attribution. The Hub identifies   projects, not people.</li> </ul> <p>If you want \"my agent in project B sees everything my agent did in project A,\" that's not the Hub. Local session density stays local.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#who-its-for","level":2,"title":"Who It's For","text":"<p>Two shapes, same mechanics, different trust models.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"<p>One developer, many projects. You want a learning from project A to show up when you open project B a week later. You want a convention you codified in your dotfiles project to be visible everywhere else on your workstation. Run a Hub on localhost, register each project, done.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#small-trusted-team","level":3,"title":"Small Trusted Team","text":"<p>A few teammates on a LAN or a hub.ctx-like self-hosted server. You want team conventions to propagate without a wiki. You want lessons from one on-call engineer's 3 AM incident to reach everyone else's agent on the next session. Same mechanics as the personal case, plus TLS in front and a short security runbook.</p> <p>The Hub is not a multi-tenant public service. It assumes everyone holding a client token is friendly. Don't stand up <code>hub.example.com</code> for untrusted participants.</p>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/hub/#going-further","level":2,"title":"Going Further","text":"<ul> <li>First-time setup: Hub: Getting Started,   a five-minute walkthrough on localhost.</li> <li>Mental model and user stories: Hub Overview,   what flows, what doesn't, and when not to use it.</li> <li>Team / LAN deployment: Multi-machine setup.</li> <li>Redundancy: HA cluster.</li> <li>Operating a Hub: Hub Operations   and Hub Failure Modes.</li> <li>Security posture: Hub Security Model.</li> <li>Command reference: <code>ctx serve</code>,   <code>ctx connect</code>,   <code>ctx hub</code>.</li> </ul>","path":["Home","Concepts","Hub"],"tags":[]},{"location":"home/is-ctx-right/","level":1,"title":"Is It Right for Me?","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#good-fit","level":2,"title":"Good Fit","text":"<p><code>ctx</code> shines when context matters more than code.</p> <p>If any of these sound like your project, it's worth trying:</p> <ul> <li>Multi-session AI work: You use AI across many sessions on the same   codebase, and re-explaining is slowing you down.</li> <li>Architectural decisions that matter: Your project has non-obvious   choices (database, auth strategy, API design) that the AI keeps   second-guessing.</li> <li>\"Why\" matters as much as \"what\": you need the AI to understand   rationale, not just current code</li> <li>Team handoffs: Multiple people (or multiple AI tools) work on the   same project and need shared context.</li> <li>AI-assisted development across tools: Uou switch between Claude Code,   Cursor, Copilot, or other tools and want context to follow the project,   not the tool.</li> <li>Long-lived projects: Anything you'll work on for weeks or months,   where accumulated knowledge has compounding value.</li> </ul>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#may-not-be-the-right-fit","level":2,"title":"May Not Be the Right Fit","text":"<p><code>ctx</code> adds overhead that isn't worth it for every project. Be honest about when to skip it:</p> <ul> <li>One-off scripts: If the project is a single file you'll finish today,   there's nothing to remember.</li> <li>RAG-only workflows: If retrieval from an external knowledge base already   gives the agent everything it needs for each session, adding <code>ctx</code> may be   unnecessary. RAG retrieves information; <code>ctx</code> defines the project's   working memory: They are complementary.</li> <li>No AI involvement: <code>ctx</code> is designed for human-AI workflows; without   an AI consumer, the files are just documentation.</li> <li>Enterprise-managed context platforms: If your organization provides   centralized context services, <code>ctx</code> may duplicate that layer.</li> </ul> <p>For a deeper technical comparison with RAG, prompt management tools, and agent frameworks, see <code>ctx</code> and Similar Tools.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#project-size-guide","level":2,"title":"Project Size Guide","text":"","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#solo-developer-single-repo","level":3,"title":"Solo Developer, Single Repo","text":"<p>This is <code>ctx</code>'s sweet spot. </p> <p>You get the most value here: one person, one project, decisions, and learnings  accumulating over time. Setup takes 5 minutes and the <code>.context/</code> directory directory stays small, and every session gets faster.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#small-team-one-or-two-repos","level":3,"title":"Small Team, One or Two Repos","text":"<p>Works well. </p> <p>Context files commit to git, so the whole team shares the same decisions and conventions. Each person's AI starts with the team's decisions already loaded. Merge conflicts on <code>.context/</code> files are rare and easy to resolve (they are just Markdown).</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#multiple-repos-or-larger-teams","level":3,"title":"Multiple Repos or Larger Teams","text":"<p><code>ctx</code> operates per repository.</p> <p>Each repo has its own <code>.context/</code> directory with its own decisions, tasks, and learnings. This matches the way code, ownership, and history already work in <code>git</code>.</p> <p>There is no built-in cross-repo context layer.</p> <p>For organizations that need centralized, organization-wide knowledge, <code>ctx</code> complements a platform solution by providing durable, project-local working memory for AI sessions.</p>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/is-ctx-right/#5-minute-trial","level":2,"title":"5-Minute Trial","text":"<p>Zero commitment. Try it, and delete <code>.context/</code> if it's not for you.</p> <p>Using Claude Code?</p> <p>Install the <code>ctx</code> plugin from the Marketplace for Claude-native hooks,  skills, and automatic context loading:</p> <ol> <li>Type <code>/plugin</code> and press Enter</li> <li>Select Marketplaces → Add Marketplace</li> <li>Enter <code>ActiveMemory/ctx</code></li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>You'll still need the <code>ctx</code> binary for the CLI: See Getting Started for install options.</p> <pre><code># 1. Initialize\ncd your-project\nctx init\n\n# 2. Activate the project (bind CTX_DIR for this shell).\n#    Required: ctx does not walk the filesystem to find .context/.\neval \"$(ctx activate)\"\n\n# 3. Add one real decision from your project\nctx decision add \"Your actual architectural choice\" \\\n  --context \"What prompted this decision\" \\\n  --rationale \"Why you chose this approach\" \\\n  --consequence \"What changes as a result\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# 4. Check what the AI will see\nctx status\n\n# 5. Start an AI session and ask: \"Do you remember?\"\n</code></pre> <p>If the AI cites your decision back to you, it's working.</p> <p>Want to remove it later? One command:</p> <pre><code>rm -rf .context/\n</code></pre> <p>No dependencies to uninstall. No configuration to revert. Just files.</p> <p>Ready to try it out?</p> <ul> <li>Join the Community→: Open Source is better together.</li> <li>Getting Started →: Full installation and setup.</li> <li><code>ctx</code> and Similar Tools →: Detailed comparison   with other approaches.</li> </ul>","path":["Home","Introduction","Is It Right for Me?"],"tags":[]},{"location":"home/joining-a-project/","level":1,"title":"Joining a Project","text":"<p>You've joined a team or inherited a project, and there's a <code>.context/</code> directory in the repo. Good news: someone already set up persistent context. This page gets you oriented fast.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#what-to-read-first","level":2,"title":"What to Read First","text":"<p>The files in <code>.context/</code> have a deliberate priority order. Read them top-down:</p> <ol> <li>CONSTITUTION.md: Hard rules. Read this before you touch anything.    These are inviolable constraints the team has agreed on.</li> <li>TASKS.md: Current and planned work. Shows what's in progress,    what's pending, and what's blocked.</li> <li>CONVENTIONS.md: How the team writes code. Naming patterns,    file organization, preferred idioms.</li> <li>ARCHITECTURE.md: System overview. Components, boundaries, data flow.</li> <li>DECISIONS.md: Why things are the way they are. Saves you from    re-proposing something the team already evaluated and rejected.</li> <li>LEARNINGS.md: Gotchas, tips, and hard-won lessons. The stuff    that doesn't fit anywhere else but will save you hours.</li> </ol> <p>See Context Files for detailed documentation of each file's structure and purpose.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#activate-the-project","level":2,"title":"Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory to read from:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the commands in the rest of this guide fail with <code>Error: no context directory specified</code>. Direnv users can wire it into <code>.envrc</code> and forget about it. See Activating a Context Directory for more options (multiple <code>.context/</code> directories, scripts, CI).</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#checking-context-health","level":2,"title":"Checking Context Health","text":"<p>Before you start working, check whether the context is current:</p> <pre><code>ctx status\n</code></pre> <p>This shows file counts, token estimates, and recent activity. If files haven't been touched in weeks, the context may be stale.</p> <pre><code>ctx drift\n</code></pre> <p>This compares context files against recent code changes and flags potential drift: decisions that no longer match the codebase, conventions that have shifted, or tasks that look outdated.</p> <p>If things are stale, mention it to the team. Don't silently fix it yourself on day one.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#starting-your-first-session","level":2,"title":"Starting Your First Session","text":"<p>Generate a context packet to prime your AI:</p> <pre><code>ctx agent --budget 8000\n</code></pre> <p>This outputs a token-budgeted summary of the project context, ordered by priority. With Claude Code and the <code>ctx</code> plugin, context loads automatically via hooks. You can also use the <code>/ctx-remember</code> skill to get a structured readback of what the AI knows.</p> <p>The readback is your verification step: if the AI can cite specific tasks and decisions, the context is working.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#adding-context","level":2,"title":"Adding Context","text":"<p>As you work, you'll discover things worth recording. Use the CLI:</p> <pre><code># Record a decision you made or learned about\nctx decision add \"Use connection pooling for DB access\" \\\n  --rationale \"Reduces connection overhead under load\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Capture a gotcha you hit\nctx learning add \"Redis timeout defaults to 5s\" \\\n  --context \"Hit timeouts during bulk operations\" \\\n  --application \"Set explicit timeout for batch jobs\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add a convention you noticed the team follows\nctx convention add \"All API handlers return structured errors\"\n</code></pre> <p>You can also just tell the AI: \"Record this as a learning\" or \"Add this decision to context.\" With the <code>ctx</code> plugin, context-update commands handle the file writes.</p> <p>See the Knowledge Capture recipe for the full workflow.</p>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#session-etiquette","level":2,"title":"Session Etiquette","text":"<p>A few norms for working in a ctx-managed project:</p> <ul> <li>Respect existing conventions. If <code>CONVENTIONS.md</code> says   \"use <code>filepath.Join</code>,\" use <code>filepath.Join</code>. If you disagree, propose   a change, don't silently diverge.</li> <li>Don't restructure context files without asking. The file layout   and section structure are shared state. Reorganizing them affects   every team member and every AI session.</li> <li>Mark tasks done when complete. Check the box (<code>[x]</code>) in place.   Don't move tasks between sections or delete them.</li> <li>Add context as you go. Decisions, learnings, and conventions   you discover are valuable to the next person (or the next session).</li> </ul>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/joining-a-project/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"<p>Ignoring CONSTITUTION.md. The constitution exists for a reason. If a task conflicts with a constitution rule, the task is wrong. Raise it with the team instead of working around the constraint.</p> <p>Deleting tasks. Never delete a task from TASKS.md. Mark it <code>[x]</code> (done) or <code>[-]</code> (skipped with a reason). The history matters for session replay and audit.</p> <p>Bypassing hooks. If the project uses <code>ctx</code> hooks (pre-commit nudges, context autoloading), don't disable them. They exist to keep context fresh. If a hook is noisy or broken, fix it or file a task.</p> <p>Over-contributing on day one. Read first, then contribute. Adding a dozen learnings before you understand the project's norms creates noise, not signal.</p> <p>Related:</p> <ul> <li>Getting Started: installation and setup from scratch</li> <li>Context Files: detailed file reference</li> <li>Knowledge Capture: recording decisions,    learnings, and conventions</li> <li>Session Lifecycle: how a typical AI    session flows with <code>ctx</code></li> </ul>","path":["Home","Working with AI","Joining a Project"],"tags":[]},{"location":"home/keeping-ai-honest/","level":1,"title":"Keeping AI Honest","text":"","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-problem","level":2,"title":"The Problem","text":"<p>AI agents confabulate. They invent history that never happened, claim familiarity with decisions that were never made, and sometimes declare a task complete when it is not. This is not malice - it is the default behavior of a system optimizing for plausible-sounding responses.</p> <p>When your AI says \"we decided to use Redis for caching last week,\" can you verify that? When it says \"the auth module is complete,\" can you confirm it? Without grounded, persistent context, the answer is no. You are trusting vibes.</p> <p><code>ctx</code> replaces vibes with verifiable artifacts.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#grounded-memory","level":2,"title":"Grounded Memory","text":"<p>Every entry in <code>ctx</code> context files has a timestamp and structured fields. When the AI cites a decision, you can check it.</p> <pre><code>## [2026-01-28-143022] Use Event Sourcing for Audit Trail\n\n**Status**: Accepted\n\n**Context**: Compliance requires full mutation history.\n\n**Decision**: Event sourcing for the audit subsystem only.\n\n**Rationale**: Append-only log meets compliance requirements\nwithout imposing event sourcing on the entire domain model.\n</code></pre> <p>The timestamp <code>2026-01-28-143022</code> is not decoration. It is a verifiable anchor. If the AI references this decision, you can open DECISIONS.md, find the entry, and confirm it says what the AI claims. If the entry does not exist, the AI is hallucinating - and you know immediately.</p> <p>This is grounded memory: claims that trace back to artifacts you control and can audit.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#constitutionmd-hard-guardrails","level":2,"title":"<code>CONSTITUTION.md</code>: Hard Guardrails","text":"<p>CONSTITUTION.md defines rules the AI must treat as inviolable. These are not suggestions or best practices - they are constraints that override task requirements.</p> <pre><code># Constitution\n\nThese rules are INVIOLABLE. If a task requires violating these,\nthe task is wrong.\n\n* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] All public API changes require a decision record\n* [ ] Never delete context files without explicit user approval\n</code></pre> <p>The AI reads these at session start, before anything else. A well- integrated agent will refuse a task that conflicts with a constitutional rule, citing the specific rule it would violate.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-agent-playbooks-anti-hallucination-rules","level":2,"title":"The Agent Playbook's Anti-Hallucination Rules","text":"<p>The AGENT_PLAYBOOK.md file includes a section called \"How to Avoid Hallucinating Memory\" with five explicit rules:</p> <ol> <li>Never assume. If it is not in the context files, you do not    know it.</li> <li>Never invent history. Do not claim \"we discussed\" something    without a file reference.</li> <li>Verify before referencing. Search files before citing them.</li> <li>When uncertain, say so. \"I don't see a decision on this\" is    always better than a fabricated one.</li> <li>Trust files over intuition. If the files say PostgreSQL but    your training data suggests MySQL, the files win.</li> </ol> <p>These rules create a behavioral contract. The AI is not left to guess how confident it should be - it has explicit instructions to ground every claim in the context directory.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#drift-detection","level":2,"title":"Drift Detection","text":"<p>Context files can go stale. You rename a package, delete a module, or finish a sprint, and suddenly ARCHITECTURE.md references paths that no longer exist. Stale context is almost as dangerous as no context: the AI treats outdated information as current truth.</p> <p><code>ctx drift</code> detects this divergence:</p> <pre><code>ctx drift\n</code></pre> <p>It scans context files for references to files, paths, and symbols that no longer exist in the codebase. Stale references get flagged so you can update or remove them before they mislead the next session.</p> <p>Regular drift checks - weekly, or after major refactors - keep your context files honest the same way tests keep your code honest.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#the-verification-loop","level":2,"title":"The Verification Loop","text":"<p>The <code>/ctx-commit</code> skill includes a built-in verification step: before staging, it maps claims to evidence and runs self-audit questions to surface gaps. This catches inconsistencies at the point where they matter most: right before code is committed.</p> <p>This closes the loop. You write context. The AI reads context. The verification step confirms that context still matches reality. When it does not, you fix it - and the next session starts from truth, not from drift.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#trust-through-structure","level":2,"title":"Trust through Structure","text":"<p>The common thread across all of these mechanisms is structure over prose. Timestamps make claims verifiable. Constitutional rules make boundaries explicit. Drift detection makes staleness visible. The playbook makes behavioral expectations concrete.</p> <p>You do not need to trust the AI. You need to trust the system -- and verify when it matters.</p>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/keeping-ai-honest/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Detecting and Fixing Drift: the   full workflow for keeping context files accurate</li> <li>Invariants: the properties   that must hold for any valid <code>ctx</code> implementation</li> <li>Agent Security: threat model and   mitigations for AI agents operating with persistent context</li> </ul>","path":["Home","Working with AI","Keeping AI Honest"],"tags":[]},{"location":"home/opencode/","level":1,"title":"ctx for OpenCode","text":"","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#the-problem","level":2,"title":"The Problem","text":"<p>Every OpenCode session starts from zero. You re-explain your architecture, the AI repeats mistakes it made yesterday, and decisions get rediscovered instead of remembered.</p> <p>Without <code>ctx</code>:</p> <pre><code>> \"Add the validation middleware we discussed\"\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n</code></pre> <p>With <code>ctx</code>:</p> <pre><code>> \"Add the validation middleware we discussed\"\n\nYes. From the Jan 15 session. You decided on Zod schemas at the\nroute level (DECISIONS.md #12), and the pattern is in\nCONVENTIONS.md. I'll follow the existing middleware in\nsrc/middleware/auth.ts as a reference.\n</code></pre> <p>That's the whole pitch: your AI remembers.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#setup-one-command","level":2,"title":"Setup (One Command)","text":"<p>Install the <code>ctx</code> binary first (installation docs), then run from your project root:</p> <pre><code>ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>This does three things:</p> <ol> <li><code>ctx setup opencode --write</code>: generates the project-local OpenCode plugin,    skills, and <code>AGENTS.md</code>, then merges the <code>ctx</code> MCP server into OpenCode's    global config (<code>~/.config/opencode/opencode.json</code> or    <code>$OPENCODE_HOME/opencode.json</code>). This writes outside the project root    because non-interactive shells (like MCP subprocesses) cannot discover    project-local config; the same reason the Copilot CLI integration    writes to <code>~/.copilot/mcp-config.json</code>.</li> <li><code>ctx init</code>: creates the <code>.context/</code> directory with template files.</li> <li><code>eval \"$(ctx activate)\"</code>: binds <code>CTX_DIR</code> for your shell.</li> </ol>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose <code>.opencode/plugins/ctx.ts</code> Lifecycle plugin (hooks into <code>ctx system</code> commands) <code>~/.config/opencode/opencode.json</code> Global MCP server registration (or <code>$OPENCODE_HOME/opencode.json</code>) <code>AGENTS.md</code> Agent instructions (OpenCode reads this natively) <code>.opencode/skills/ctx-*/SKILL.md</code> Slash command skills <p>The plugin is a single file with no runtime dependencies; no <code>bun install</code> or <code>npm install</code> needed. OpenCode loads it automatically on launch.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"<p>The plugin wires OpenCode lifecycle events to <code>ctx</code>. You don't need to do anything; it just works.</p> Event What fires What it does New session <code>session.created</code> Warms <code>ctx</code> state in the background (bootstrap + agent packet) so MCP queries are fast on first use Agent idle <code>session.idle</code> Runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI) After <code>git commit</code> <code>tool.execute.after</code> Runs <code>ctx system post-commit</code> to capture context state After file edit <code>tool.execute.after</code> Runs <code>ctx system check-task-completion</code> to detect silent task completions Every shell call <code>shell.env</code> Injects <code>CTX_DIR</code> so all <code>ctx</code> commands in the agent's shell resolve to the right project Context compaction <code>experimental.session.compacting</code> Pushes <code>ctx system bootstrap</code> output into the compaction context so the agent retains breadcrumbs to re-read context files post-compaction <p>The compaction hook matters most. When OpenCode compresses your context window to free up tokens, the plugin makes sure the compressed summary includes a pointer back to your <code>.context/</code> directory and its file inventory, so the agent can re-read tasks, decisions, and learnings on demand, even though the original messages are gone.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#how-compaction-works","level":3,"title":"How Compaction Works","text":"<p>When your conversation exceeds the context window, OpenCode runs a compaction pass (you can trigger one manually with <code>/compact</code>). The compaction agent summarizes older messages and drops the originals. Without <code>ctx</code>, all accumulated knowledge disappears. With <code>ctx</code>, the plugin intercepts the <code>experimental.session.compacting</code> event and appends <code>ctx system bootstrap</code> output (context directory path and file inventory) into the compaction context. The result: the compressed summary retains the breadcrumbs the agent needs to re-read tasks, decisions, learnings, and conventions on demand, even though the original messages that loaded them are gone.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#what-is-not-included","level":3,"title":"What Is Not Included","text":"<p>Note: dangerous-command blocking is Claude Code-specific and is not part of the OpenCode integration. OpenCode's execution model (explicit user approval for every shell command) makes a pre-execution blocklist unnecessary.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#slash-commands","level":2,"title":"Slash Commands","text":"<p>Four skills are available as slash commands:</p> Command When to use <code>/ctx-agent</code> Load full context packet. Use at session start or when context feels stale. <code>/ctx-remember</code> \"Do you remember?\"; reads tasks, decisions, learnings, and recent journal entries. Returns a structured readback. <code>/ctx-status</code> Context summary at a glance: file count, token estimate, recent activity. <code>/ctx-wrap-up</code> End-of-session ceremony. Captures learnings, decisions, conventions, and outstanding tasks to <code>.context/</code> files. <p>You don't need to use these often. The plugin handles most context loading automatically. These are for when you want explicit control.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#mcp-tools","level":2,"title":"MCP Tools","text":"<p>The <code>ctx</code> MCP server exposes tools directly to the agent. These let the AI read and write your context files without shell commands:</p> Tool Purpose <code>ctx_add</code> Add a task, decision, learning, or convention <code>ctx_complete</code> Mark a task done by number or text match <code>ctx_search</code> Full-text search across all <code>.context/</code> files <code>ctx_next</code> Suggest the next pending task by priority <code>ctx_drift</code> Detect stale context: dead paths, missing files <code>ctx_compact</code> Archive completed tasks, clean empty sections <code>ctx_remind</code> List pending session-scoped reminders <code>ctx_status</code> Context health: file count, token estimate <code>ctx_steering_get</code> Retrieve steering files applicable to the current prompt <code>ctx_journal_source</code> Query recent AI session history <code>ctx_sessionevent</code> Signal session start/end lifecycle events <code>ctx_watch_update</code> Apply structured updates to <code>.context/</code> files <code>ctx_checktaskcompletion</code> After a write, detect silently completed tasks <p>You don't invoke these yourself. The agent uses them as needed.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"<p>If you re-run <code>ctx setup opencode --write</code> (e.g., after updating <code>ctx</code>), the plugin and skills are rewritten in place. Restart OpenCode to pick up the refreshed plugin. OpenCode only loads plugins at launch, not mid-session.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix <code>opencode mcp list</code> shows <code>ctx ✗ failed MCP error -32000: Connection closed</code> <code>CTX_DIR</code> not resolving in the MCP subprocess Re-run <code>ctx setup opencode --write</code> to regenerate the sh-wrapper that sets <code>CTX_DIR</code> Plugin installed but no hooks fire Flat-file vs. subdirectory discovery mismatch (OpenCode requires <code>.opencode/plugins/<name>.ts</code>, not a subfolder) Verify the plugin is at <code>.opencode/plugins/ctx.ts</code>. Check with <code>opencode --print-logs --log-level DEBUG</code> <code>ctx agent</code> Markdown leaking into the TUI BunShell command missing <code>.nothrow().quiet()</code> Update to the latest plugin: <code>ctx setup opencode --write</code> and restart","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#verify-it-works","level":2,"title":"Verify It Works","text":"<p>Start a new OpenCode session and ask:</p> <pre><code>Do you remember?\n</code></pre> <p>The AI should cite specific context: current tasks, recent decisions, or previous session topics. If it says \"I don't have memory\" or \"Let me check,\" something went wrong; check that the plugin installed correctly and <code>.context/</code> has files in it.</p>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/opencode/#whats-next","level":2,"title":"What's Next","text":"<ul> <li>Your First Session: step-by-step walkthrough from   <code>ctx init</code> to verified recall.</li> <li>Common Workflows: day-to-day commands for   tracking context, checking health, and browsing history.</li> <li>Context Files: what lives in <code>.context/</code> and how   each file is used.</li> </ul>","path":["Home","Get Started","ctx for OpenCode"],"tags":[]},{"location":"home/prompting-guide/","level":1,"title":"Prompting Guide","text":"<p>New to <code>ctx</code>?</p> <p>This guide references context files like <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>LEARNINGS.md</code>:</p> <p>These are plain Markdown files that <code>ctx</code> maintains in your project's <code>.context/</code> directory.</p> <p>If terms like \"context packet\" or \"session ceremony\" are unfamiliar,</p> <ul> <li>start with the <code>ctx</code> Manifesto for the why,</li> <li>About for the big picture,</li> <li>then Getting Started to set up your first   project.</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#literature-matters","level":2,"title":"Literature Matters","text":"<p>This guide is about crafting effective prompts for working with  AI assistants in <code>ctx</code>-enabled projects, but the guidelines given here apply to other AI systems, too.</p> <p>The right prompt triggers the right behavior. </p> <p>This guide documents prompts that reliably produce good results.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#tldr","level":2,"title":"TL;DR","text":"Goal Prompt Load context \"Do you remember?\" Resume work \"What's the current state?\" What's next <code>/ctx-next</code> Debug \"Why doesn't X work?\" Validate \"Is this consistent with our decisions?\" Impact analysis \"What would break if we...\" Reflect <code>/ctx-reflect</code> Wrap up <code>/ctx-wrap-up</code> Persist \"Add this as a learning\" Explore \"How does X work in this codebase?\" Sanity check \"Is this the right approach?\" Completeness \"What am I missing?\" One more thing \"What's the single smartest addition?\" Set tone \"Push back if my assumptions are wrong.\" Constrain scope \"Only change files in X. Nothing else.\" Course correct \"Stop. That's not what I meant.\" Check health \"Run <code>ctx drift</code>\" Commit <code>/ctx-commit</code>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#session-start","level":2,"title":"Session Start","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#do-you-remember","level":3,"title":"\"do you remember?\"","text":"<p>Triggers the AI to silently read <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, and check recent history via <code>ctx journal</code> before responding with a  structured readback:</p> <ol> <li>Last session: most recent session topic and date</li> <li>Active work: pending or in-progress tasks</li> <li>Recent context: 1-2 recent decisions or learnings</li> <li>Next step: offer to continue or ask what to focus on</li> </ol> <p>Use this at the start of every important session.</p> <pre><code>Do you remember what we were working on?\n</code></pre> <p>This question implies prior context exists. The AI checks files rather than admitting ignorance. The expected response cites specific context (session names, task counts, decisions), not vague summaries.</p> <p>If the AI instead narrates its discovery process (\"Let me check if there are files...\"), it has not loaded <code>CLAUDE.md</code> or <code>AGENT_PLAYBOOK.md</code> properly.</p> <p>For a detailed case study on making agents actually follow this protocol (including the failure modes, the timing problem, and the hook design that solved it) see The Dog Ate My Homework.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#whats-the-current-state","level":3,"title":"\"What's the Current State?\"","text":"<p>Prompts reading of <code>TASKS.md</code>, recent sessions, and status overview.</p> <p>Use this when resuming work after a break.</p> <p>Variants:</p> <ul> <li>\"Where did we leave off?\"</li> <li>\"What's in progress?\"</li> <li>\"Show me the open tasks.\"</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#during-work","level":2,"title":"During Work","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-doesnt-x-work","level":3,"title":"\"Why Doesn't X Work?\"","text":"<p>This triggers root cause analysis rather than surface-level fixes.</p> <p>Use this when something fails unexpectedly.</p> <p>Framing as \"why\" encourages investigation before action. The AI will trace  through code, check configurations, and identify the actual cause.</p> <p>Real Example</p> <p>\"Why can't I run /ctx-reflect?\" led to discovering missing permissions in <code>settings.local.json</code> bootstrapping.</p> <p>This was a fix that benefited all users of <code>ctx</code>.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-consistent-with-our-decisions","level":3,"title":"\"Is This Consistent with Our Decisions?\"","text":"<p>This prompts checking <code>DECISIONS.md</code> before implementing.</p> <p>Use this before making architectural choices.</p> <p>Variants:</p> <ul> <li>\"Check if we've decided on this before\"</li> <li>\"Does this align with our conventions?\"</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-would-break-if-we","level":3,"title":"\"What Would Break If We...\"","text":"<p>This triggers defensive thinking and impact analysis.</p> <p>Use this before making significant changes.</p> <pre><code>What would break if we change the Settings struct?\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#before-you-start-read-x","level":3,"title":"\"Before You Start, Read X\"","text":"<p>This ensures specific context is loaded before work begins.</p> <p>Use this when you know the relevant context exists in a specific file.</p> <pre><code>Before you start, check ctx journal source for the auth discussion session\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-control","level":3,"title":"Scope Control","text":"<p>Constrain the AI to prevent sprawl. These are some of the most useful prompts in day-to-day work.</p> <pre><code>Only change files in internal/cli/add/. Nothing else.\n</code></pre> <pre><code>No new files. Modify the existing implementation.\n</code></pre> <pre><code>Keep the public API unchanged. Internal refactor only.\n</code></pre> <p>Use these when the AI tends to \"helpfully\" modify adjacent code, add documentation you didn't ask for, or create new abstractions.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#course-correction","level":3,"title":"Course Correction","text":"<p>Steer the AI when it goes off-track: Don't wait for it to finish a wrong approach.</p> <pre><code>Stop! That's not what I meant. Let me clarify.\n</code></pre> <pre><code>Let's step back. Explain what you're about to do before changing anything.\n</code></pre> <pre><code>Undo that last change and try a different approach.\n</code></pre> <p>These work because they interrupt momentum.</p> <p>Without explicit course correction, the AI tends to commit harder to a wrong path rather than reconsidering.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#failure-modes","level":3,"title":"Failure Modes","text":"<p>When the AI misbehaves, match the symptom to the recovery prompt:</p> Symptom Recovery prompt Hand-waves (\"should work now\") \"Show evidence: file/line refs, command output, or test name.\" Creates unnecessary files \"No new files. Modify the existing implementation.\" Expands scope unprompted \"Stop after the smallest working change. Ask before expanding scope.\" Narrates instead of acting \"Skip the explanation. Make the change and show the diff.\" Repeats a failed approach \"That didn't work last time. Try a different approach.\" Claims completion without proof \"Run the test. Show me the output.\" <p>These are recovery handles, not rules to paste into <code>CLAUDE.md</code>.</p> <p>Use them in the moment when you see the behavior.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reflection-and-persistence","level":2,"title":"Reflection and Persistence","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-did-we-learn","level":3,"title":"\"What Did We Learn?\"","text":"<p>This prompts reflection on the session and often triggers adding learnings to <code>LEARNINGS.md</code>.</p> <p>Use this after completing a task or debugging session.</p> <p>This is an explicit reflection prompt. The AI will summarize insights and often offer to persist them.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#add-this-as-a-learningdecision","level":3,"title":"\"Add This as a Learning/decision\"","text":"<p>This is an explicit persistence request.</p> <p>Use this when you have discovered something worth remembering.</p> <pre><code>Add this as a learning: \"JSON marshal escapes angle brackets by default\"\n\n# or simply.\nAdd this as a learning.\n# and let the AI autonomously infer and summarize.\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#save-context-before-we-end","level":3,"title":"\"Save Context Before We End\"","text":"<p>This triggers context persistence before the session closes.</p> <p>Use it at the end of the session or before switching topics.</p> <p>Variants:</p> <ul> <li>\"Let's persist what we did\"</li> <li>\"Update the context files\"</li> <li><code>/ctx-wrap-up</code>:the recommended end-of-session ceremony   (see Session Ceremonies)</li> <li><code>/ctx-reflect</code>: mid-session reflection checkpoint</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#exploration-and-research","level":2,"title":"Exploration and Research","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-the-codebase-for-x","level":3,"title":"\"Explore the Codebase for X\"","text":"<p>This triggers thorough codebase search rather than guessing.</p> <p>Use this when you need to understand how something works.</p> <p>This works because \"Explore\" signals that investigation is needed,  not immediate action.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#how-does-x-work-in-this-codebase","level":3,"title":"\"How Does X Work in This Codebase?\"","text":"<p>This prompts reading actual code rather than explaining general concepts.</p> <p>Use this to understand the existing implementation.</p> <pre><code>How does session saving work in this codebase?\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#find-all-places-where-x","level":3,"title":"\"Find All Places Where X\"","text":"<p>This triggers a comprehensive search across the codebase.</p> <p>Use this before refactoring or understanding the impact.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#meta-and-process","level":2,"title":"Meta and Process","text":"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-should-we-document-from-this","level":3,"title":"\"What Should We Document from This?\"","text":"<p>This prompts identifying learnings, decisions, and conventions worth persisting.</p> <p>Use this after complex discussions or implementations.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#is-this-the-right-approach","level":3,"title":"\"Is This the Right Approach?\"","text":"<p>This invites the AI to challenge the current direction.</p> <p>Use this when you want a sanity check.</p> <p>This works because it allows AI to disagree.</p> <p>AIs often default to agreeing; this prompt signals you want an honest assessment.</p> <p>Stronger variant: \"Push back if my assumptions are wrong.\" This sets the tone for the entire session: The AI will flag questionable choices proactively instead of waiting to be asked.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#what-am-i-missing","level":3,"title":"\"What Am I Missing?\"","text":"<p>This prompts thinking about edge cases, overlooked requirements, or unconsidered approaches.</p> <p>Use this before finalizing a design or implementation.</p> <p>Forward-looking variant: \"What's the single smartest addition you could make to this at this point?\" Use this after you think you're done: It surfaces improvements you wouldn't have thought to ask for. The constraint to one thing prevents feature sprawl.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#cli-commands-as-prompts","level":2,"title":"CLI Commands as Prompts","text":"<p>Asking the AI to run <code>ctx</code> commands is itself a prompt. These load context or trigger specific behaviors:</p> Command What it does \"Run <code>ctx status</code>\" Shows context summary, file presence, staleness \"Run <code>ctx agent</code>\" Loads token-budgeted context packet \"Run <code>ctx drift</code>\" Detects dead paths, stale files, missing context","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ctx-skills","level":3,"title":"<code>ctx</code> Skills","text":"<p>The <code>SKILS.md</code> Standard</p> <p>Skills are formalized prompts stored as <code>SKILL.md</code> files.</p> <p>The <code>/slash-command</code> syntax below is Claude Code specific. </p> <p>Other agents can use the same skill files, but invocation may differ. </p> <p>Use <code>ctx</code> skills  by name:</p> Skill When to use <code>/ctx-status</code> Quick context summary <code>/ctx-agent</code> Load full context packet <code>/ctx-remember</code> Recall project context and structured readback <code>/ctx-wrap-up</code> End-of-session context persistence <code>/ctx-history</code> Browse session history for past discussions <code>/ctx-reflect</code> Structured reflection checkpoint <code>/ctx-next</code> Suggest what to work on next <code>/ctx-commit</code> Commit with context persistence <code>/ctx-drift</code> Detect and fix context drift <code>/ctx-implement</code> Execute a plan step-by-step with verification <code>/ctx-loop</code> Generate autonomous loop script <code>/ctx-pad</code> Manage encrypted scratchpad <code>/ctx-archive</code> Archive completed tasks <code>/check-links</code> Audit docs for dead links <p>Ceremony vs. Workflow Skills</p> <p>Most skills work conversationally: \"what should we work on?\" triggers <code>/ctx-next</code>, \"save that as a learning\" triggers <code>/ctx-learning-add</code>. Natural language is the recommended approach.</p> <p>Two skills are the exception: <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> are ceremony skills for session boundaries: Invoke them as explicit slash commands: conversational triggers risk partial execution. See Session Ceremonies.</p> <p>Skills combine a prompt, tool permissions, and domain knowledge into a single invocation.</p> <p>Skills beyond Claude Code</p> <p>The <code>/slash-command</code> syntax above is Claude Code native, but the underlying <code>SKILL.md</code> files are a standard Markdown format that any agent can consume. If you use a different coding agent, consult its documentation for how to load skill files as prompt templates.</p> <p>See Integrations for setup details.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#anti-patterns","level":2,"title":"Anti-Patterns","text":"<p>Based on our <code>ctx</code> development experience (i.e., \"sipping our own champagne\") so far, here are some prompts that tend to produce poor results:</p> Prompt Problem Better Alternative \"Fix this\" Too vague, may patch symptoms \"Why is this failing?\" \"Make it work\" Encourages quick hacks \"What's the right way to solve this?\" \"Just do it\" Skips planning \"Plan this, then implement\" \"You should remember\" Confrontational \"Do you remember?\" \"Obviously...\" Discourages questions State the requirement directly \"Idiomatic X\" Triggers language priors \"Follow project conventions\" \"Implement everything\" No phasing, sprawl risk Break into tasks, implement one at a time \"You should know this\" Assumes context is loaded \"Before you start, read X\"","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#reliability-checklist","level":2,"title":"Reliability Checklist","text":"<p>Before sending a non-trivial prompt, check these four elements. This is the guide's DNA in one screenful.</p> <ol> <li>Goal in one sentence: What does \"done\" look like?</li> <li>Files to read: What existing code or context should the AI    review before acting?</li> <li>Verification command: How will you prove it worked?    (test name, CLI command, expected output)</li> <li>Scope boundary: What should the AI not touch?</li> </ol> <p>A prompt that covers all four is almost always good enough.</p> <p>A prompt missing <code>#3</code> is how you get \"should work now\" without evidence.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#safety-invariants","level":2,"title":"Safety Invariants","text":"<p>These Are Invariants: Not Suggestions</p> <p>A prompting guide earns its trust by being honest about risk.</p> <p>These four rules mentioned below don't change with model versions, agent frameworks, or project size.</p> <p>Build them into your workflow once and stop thinking about them.</p> <p>Tool-using agents can read files, run commands, and modify your codebase. That power makes them useful. It also creates a trust boundary you should be aware of.</p> <p>These invariants apply regardless of which agent or model you use.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#treat-the-repository-text-as-untrusted-input","level":3,"title":"Treat the Repository Text as \"Untrusted Input\"","text":"<p>Issue descriptions, PR comments, commit messages, documentation, and even code comments can contain text that looks like instructions. An agent that reads a GitHub issue and then runs a command found inside it is executing untrusted input.</p> <p>The rule: Before running any command the agent found in repo text (issues, docs, comments), restate the command explicitly and confirm it does what you expect. Don't let the agent copy-paste from untrusted sources into a shell.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#ask-before-destructive-operations","level":3,"title":"Ask Before Destructive Operations","text":"<p><code>git push --force</code>, <code>rm -rf</code>, <code>DROP TABLE</code>, <code>docker system prune</code>: these are irreversible or hard to reverse. A good agent should pause before running them, but don't rely on that.</p> <p>The rule: For any operation that deletes data, overwrites history, or affects shared infrastructure, require explicit confirmation. If the agent runs something destructive without asking, that's a course-correction moment: \"Stop. Never run destructive commands without asking first.\"</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#scope-the-blast-radius","level":3,"title":"Scope the Blast Radius","text":"<p>An agent told to \"fix the tests\" might modify test fixtures, change assertions, or delete tests that inconveniently fail. An agent told to \"deploy\" might push to production. Broad mandates create broad risk.</p> <p>The rule: Constrain scope before starting work. The Reliability Checklist's scope boundary (<code>#4</code>) is your primary safety lever. When in doubt, err on the side of a tighter boundary.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#secrets-never-belong-in-context","level":3,"title":"Secrets Never Belong in Context","text":"<p><code>LEARNINGS.md</code>, <code>DECISIONS.md</code>, and session transcripts are plain-text files that may be committed to version control.</p> <p>Don't persist API keys, passwords, tokens, or credentials in context files.</p> <p>The rule: If the agent encounters a secret during work, it should use it transiently (environment variable, an alias to the secret instead of the actual secret, etc.) and never write it to a context file. </p> <p>Any Secret Seen IS Exposed</p> <p>If you see a secret in a context file, remove it immediately and  rotate the credential.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#explore-plan-implement","level":2,"title":"Explore → Plan → Implement","text":"<p>For non-trivial work, name the phase you want:</p> <pre><code>Explore src/auth and summarize the current flow.\nThen propose a plan. After I approve, implement with tests.\n</code></pre> <p>This prevents the AI from jumping straight to code. </p> <p>The three phases map to different modes of thinking:</p> <ul> <li>Explore: read, search, understand: no changes</li> <li>Plan: propose approach, trade-offs, scope: no changes</li> <li>Implement: write code, run tests, verify: changes</li> </ul> <p>Small fixes skip straight to implement. Complex or uncertain work benefits from all three.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#prompts-by-task-type","level":2,"title":"Prompts by Task Type","text":"<p>Different tasks need different prompt structures. The pattern: symptom + location + verification.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#bugfix","level":3,"title":"Bugfix","text":"<pre><code>Users report search returns empty results for queries with hyphens.\nReproduce in src/search/. Write a failing test for \"foo-bar\",\nfix the root cause, run: go test ./internal/search/...\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#refactor","level":3,"title":"Refactor","text":"<pre><code>Inspect src/auth/ and list duplication hotspots.\nPropose a refactor plan scoped to one module.\nAfter approval, remove duplication without changing behavior.\nAdd a test if coverage is missing. Run: make audit\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#research","level":3,"title":"Research","text":"<pre><code>Explore the request flow around src/api/.\nSummarize likely bottlenecks with evidence.\nPropose 2-3 hypotheses. Do not implement yet.\n</code></pre>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#docs","level":3,"title":"Docs","text":"<pre><code>Update docs/cli-reference.md to reflect the new --format flag.\nConfirm the flag exists in the code and the example works.\n</code></pre> <p>Notice each prompt includes what to verify and how. Without that, you get a \"should work now\" instead of evidence.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#writing-tasks-as-prompts","level":2,"title":"Writing Tasks as Prompts","text":"<p>Tasks in <code>TASKS.md</code> are indirect prompts to the AI. How you write them shapes how the AI approaches the work.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-motivation-not-just-the-goal","level":3,"title":"State the Motivation, Not Just the Goal","text":"<p>Tell the AI why you are building something, not just what.</p> <p>Bad: \"Build a calendar view.\"</p> <p>Good: \"Build a calendar view. The motivation is that all notes and tasks we build later should be viewable here.\"</p> <p>The second version lets the AI anticipate downstream requirements:</p> <p>It will design the calendar's data model to be compatible with future features: Without you having to spell out every integration point. Motivation turns a one-off task into a directional task.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#state-the-deliverable-not-just-steps","level":3,"title":"State the Deliverable, Not Just Steps","text":"<p>Bad task (implementation-focused): <pre><code>- [ ] T1.1.0: Parser system\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n</code></pre></p> <p>The AI may complete all subtasks but miss the actual goal. What does \"Parser system\" deliver to the user?</p> <p>Good task (deliverable-focused): <pre><code>- [ ] T1.1.0: Parser CLI command\n  **Deliverable**: `ctx journal source` command that shows parsed sessions\n  - [ ] Define data structures\n  - [ ] Implement line parser\n  - [ ] Implement session grouper\n</code></pre></p> <p>Now the AI knows the subtasks serve a specific user-facing deliverable.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#use-acceptance-criteria","level":3,"title":"Use Acceptance Criteria","text":"<p>For complex tasks, add explicit \"done when\" criteria:</p> <pre><code>- [ ] T2.0: Authentication system\n  **Done when**:\n  - [ ] User can register with email\n  - [ ] User can log in and get a token\n  - [ ] Protected routes reject unauthenticated requests\n</code></pre> <p>This prevents premature \"task complete\" when only the implementation details are done, but the feature doesn't actually work.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#subtasks-parent-task","level":3,"title":"Subtasks ≠ Parent Task","text":"<p>Completing all subtasks does not mean the parent task is complete.</p> <p>The parent task describes what the user gets.</p> <p>Subtasks describe how to build it.</p> <p>Always re-read the parent task description before marking it complete. Verify the stated deliverable exists and works.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#why-do-these-approaches-work","level":2,"title":"Why Do These Approaches Work?","text":"<p>The patterns in this guide aren't invented here: They are practitioner translations of well-established, peer-reviewed research, most of which predate the current AI (hype) wave.</p> <p>The underlying ideas come from decades of work in machine learning, cognitive science, and numerical optimization. For a concrete case study showing how these principles play out when an agent decides whether to follow instructions (attention competition, optimization toward least-resistance paths, and observable compliance as a design goal) see The Dog Ate My Homework.</p> <p>Phased work (\"Explore → Plan → Implement\") applies chain-of-thought reasoning: Decomposing a problem into sequential steps before acting. Forcing intermediate reasoning steps measurably improves output quality in language models, just as it does in human problem-solving. Wei et al., Chain-of-Thought Prompting Elicits Reasoning in Large Language Models (2022).</p> <p>Root-cause prompts (\"Why doesn't X work?\") use step-back abstraction: Retreating to a higher-level question before diving into specifics. This mirrors how experienced engineers debug: they ask \"what should happen?\" before asking \"what went wrong?\" Zheng et al., Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models (2023).</p> <p>Exploring alternatives (\"Propose 2-3 approaches\") leverages self-consistency: Generating multiple independent reasoning paths and selecting the most coherent result. The idea traces back to ensemble methods in ML: A committee of diverse solutions outperforms any single one. Wang et al., Self-Consistency Improves Chain of Thought Reasoning in Language Models (2022).</p> <p>Impact analysis (\"What would break if we...\") is a form of tree-structured exploration: Branching into multiple consequence paths before committing. This is the same principle behind game-tree search (minimax, MCTS) that has powered decision-making systems since the 1950s. Yao et al., Tree of Thoughts: Deliberate Problem Solving with Large Language Models (2023).</p> <p>Motivation prompting (\"Build X because Y\") works through goal conditioning: Providing the objective function alongside the task. In optimization terms, you are giving the gradient direction, not just the loss. The model can make locally coherent decisions that serve the global objective because it knows what \"better\" means.</p> <p>Scope constraints (\"Only change files in X\") apply constrained optimization: Bounding the search space to prevent divergence. This is the same principle behind regularization in ML: Without boundaries, powerful optimizers find solutions that technically satisfy the objective but are practically useless.</p> <p>CLI commands as prompts (\"Run <code>ctx status</code>\") interleave reasoning with acting: The model thinks, acts on external tools, observes results, then thinks again. Grounding reasoning in real tool output reduces hallucination because the model can't ignore evidence it just retrieved. Yao et al., ReAct: Synergizing Reasoning and Acting in Language Models (2022).</p> <p>Task decomposition (\"Prompts by Task Type\") applies least-to-most prompting: Breaking a complex problem into subproblems and solving them sequentially, each building on the last. This is the research version of \"plan, then implement one slice.\" Zhou et al., Least-to-Most Prompting Enables Complex Reasoning in Large Language Models (2022).</p> <p>Explicit planning (\"Explore → Plan → Implement\") is directly supported by plan-and-solve prompting, which addresses missing-step failures in zero-shot reasoning by extracting a plan before executing. The phased structure prevents the model from jumping to code before understanding the problem. Wang et al., Plan-and-Solve Prompting: Improving Zero-Shot Chain-of-Thought Reasoning by Large Language Models (2023).</p> <p>Session reflection (\"What did we learn?\", <code>/ctx-reflect</code>) is a form of verbal reinforcement learning: Improving future performance by persisting linguistic feedback as memory rather than updating weights. This is exactly what <code>LEARNINGS.md</code> and <code>DECISIONS.md</code> provide: a durable feedback signal across sessions. Shinn et al., Reflexion: Language Agents with Verbal Reinforcement Learning (2023).</p> <p>These aren't prompting \"hacks\" that you will find in the \"1000 AI Prompts for the Curious\" listicles: They are applications of foundational principles:</p> <ul> <li>Decomposition,</li> <li>Abstraction,</li> <li>Ensemble Reasoning,</li> <li>Search,</li> <li>and Constrained Optimization.</li> </ul> <p>They work because language models are, at their core, optimization systems navigating probabilistic landscapes.</p>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>The Attention Budget:   Why your AI forgets what you just told it, and how token budgets shape   context strategy</li> <li>The Dog Ate My Homework:   A case study in making agents follow instructions: attention timing,   delegation decay, and observable compliance as a design goal</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/prompting-guide/#contributing","level":2,"title":"Contributing","text":"<p>Found a prompt that works well? Open an issue or PR with:</p> <ol> <li>The prompt text;</li> <li>What behavior it triggers;</li> <li>When to use it;</li> <li>Why it works (optional but helpful).</li> </ol> <p>Dive Deeper:</p> <ul> <li>Recipes: targeted how-to guides for specific tasks</li> <li>CLI Reference: all commands and flags</li> <li>Integrations: setup for Claude Code, Cursor, Aider</li> </ul>","path":["Home","Working with AI","Prompting Guide"],"tags":[]},{"location":"home/repeated-mistakes/","level":1,"title":"My AI Keeps Making the Same Mistakes","text":"","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-problem","level":2,"title":"The Problem","text":"<p>You found a bug last Tuesday. You debugged it, understood the root cause, and moved on. Today, a new session hits the exact same bug. The AI rediscovers it from scratch, burning twenty minutes on something you already solved.</p> <p>Worse: you spent an hour last week evaluating two database migration strategies, picked one, documented why in a comment somewhere, and now the AI is cheerfully suggesting the approach you rejected. Again.</p> <p>This is not a model problem. It is a memory problem. Without persistent context, every session starts with amnesia.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#how-ctx-stops-the-loop","level":2,"title":"How <code>ctx</code> Stops the Loop","text":"<p><code>ctx</code> gives your AI three files that directly prevent repeated mistakes, each targeting a different failure mode.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#decisionsmd-stop-relitigating-settled-choices","level":3,"title":"<code>DECISIONS.md</code>: Stop Relitigating Settled Choices","text":"<p>When you make an architectural decision, record it with rationale and rejected alternatives. The AI reads this at session start and treats it as settled.</p> <pre><code>## [2026-02-12] Use JWT for Authentication\n\n**Status**: Accepted\n\n**Context**: Need stateless auth for the API layer.\n\n**Decision**: JWT with short-lived access tokens and refresh rotation.\n\n**Rationale**: Stateless, scales horizontally, team has prior experience.\n\n**Alternatives Considered**:\n- Session-based auth: Rejected. Requires sticky sessions or shared store.\n- API keys only: Rejected. No user identity, no expiry rotation.\n</code></pre> <p>Next session, when the AI considers auth, it reads this entry and builds on the decision instead of re-debating it. If someone asks \"why not sessions?\", the rationale is already there.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#learningsmd-capture-gotchas-once","level":3,"title":"<code>LEARNINGS.md</code>: Capture Gotchas Once","text":"<p>Learnings are the bugs, quirks, and non-obvious behaviors that cost you time the first time around. Write them down so they cost you zero time the second time.</p> <pre><code>## Build\n\n### CGO Required for SQLite on Alpine\n\n**Discovered**: 2026-01-20\n\n**Context**: Docker build failed silently with \"no such table\" at runtime.\n\n**Lesson**: The go-sqlite3 driver requires CGO_ENABLED=1 and gcc\ninstalled in the build stage. Alpine needs apk add build-base.\n\n**Application**: Always use the golang:alpine image with build-base\nfor SQLite builds. Never set CGO_ENABLED=0.\n</code></pre> <p>Without this entry, the next session that touches the Dockerfile will hit the same wall. With it, the AI knows before it starts.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#constitutionmd-draw-hard-lines","level":3,"title":"<code>CONSTITUTION.md</code>: Draw Hard Lines","text":"<p>Some mistakes are not about forgetting - they are about boundaries the AI should never cross. CONSTITUTION.md sets inviolable rules.</p> <pre><code>* [ ] Never commit secrets, tokens, API keys, or credentials\n* [ ] Never disable security linters without a documented exception\n* [ ] All database migrations must be reversible\n</code></pre> <p>The AI reads these as absolute constraints. It does not weigh them against convenience. It refuses tasks that would violate them.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#the-accumulation-effect","level":2,"title":"The Accumulation Effect","text":"<p>Each of these files grows over time. Session one captures two decisions. Session five adds a tricky learning about timezone handling. Session twelve records a convention about error message formatting.</p> <p>By session twenty, your AI has a knowledge base that no single person carries in their head. New team members - human or AI - inherit it instantly.</p> <p>The key insight: you are not just coding. You are building a knowledge layer that makes every future session faster.</p> <p><code>ctx</code> files version with your code in git. They survive branch switches, team changes, and model upgrades. The context outlives any single session.</p>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#getting-started","level":2,"title":"Getting Started","text":"<p>Capture your first decision or learning right now:</p> <pre><code>ctx decision add \"Use PostgreSQL\" \\\n  --context \"Need a relational database for the project\" \\\n  --rationale \"Team expertise, JSONB support, mature ecosystem\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\nctx learning add \"Vitest mock hoisting\" \\\n  --context \"Tests failing intermittently\" \\\n  --lesson \"vi.mock() must be at file top level\" \\\n  --application \"Use vi.doMock() for dynamic mocks\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/repeated-mistakes/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Knowledge Capture: the full workflow   for persisting decisions, learnings, and conventions</li> <li>Context Files Reference: structure and format for   every file in <code>.context/</code></li> <li>About <code>ctx</code>: the bigger picture - why persistent context   changes how you work with AI</li> </ul>","path":["Home","Working with AI","My AI Keeps Making the Same Mistakes"],"tags":[]},{"location":"home/steering/","level":1,"title":"Steering Files","text":"","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#steering-files","level":2,"title":"Steering Files","text":"<p><code>ctx</code> projects talk to AI assistants through several layers (context files, decisions, conventions, the agent context packet) but none of those can tell the assistant how to behave when a specific kind of prompt arrives. That's what steering files are for.</p> <p>A steering file is a small Markdown document with YAML frontmatter that says: \"when the user asks about X, prepend these rules to the prompt.\" <code>ctx</code> manages those files in <code>.context/steering/</code>, decides which ones match each prompt, and syncs them out to each AI tool's native config (Claude Code, Cursor, Kiro, Cline) so the rules actually land in the prompt pipeline.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#not-the-same-as-decisions-or-conventions","level":2,"title":"Not the Same as Decisions or Conventions","text":"<p>The three look similar on disk but serve different purposes:</p> Kind Purpose Decisions (<code>DECISIONS.md</code>) What was chosen and why Conventions (<code>CONVENTIONS.md</code>) How the codebase is written Steering (<code>.context/steering/*.md</code>) How the AI should behave on matching prompts <p>If you find yourself writing \"the AI should always do X when asked about Y,\" that belongs in steering, not decisions.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#your-first-steering-files","level":2,"title":"Your First Steering Files","text":"<p><code>ctx init</code> scaffolds four foundation steering files in <code>.context/steering/</code> so you start with something to edit rather than an empty directory:</p> File What to fill in <code>product.md</code> What the project is, who it's for, what's out of scope <code>tech.md</code> Languages, frameworks, runtime, hard constraints <code>structure.md</code> Directory layout, where new files go, naming rules <code>workflow.md</code> Branch strategy, commit conventions, pre-commit checks <p>Each file starts with an inline HTML comment explaining the three inclusion modes, priority semantics, and tool scoping. The comment is invisible in rendered Markdown but visible when you open the file to edit it; it's self-documenting scaffolding, not forever guidance. Delete the comment once you've customized the file.</p> <p>Default settings for foundation files:</p> <ul> <li><code>inclusion: always</code>: fires on every AI tool call</li> <li><code>priority: 10</code>: injected near the top of the prompt</li> <li><code>tools: []</code>: applies to every configured AI tool</li> </ul> <p>You should open each of these files and replace the placeholder content with your project's actual rules. Re-running <code>ctx init</code> is safe: existing files are left alone, so your edits survive. Use <code>ctx init --no-steering-init</code> to opt out of the scaffold entirely.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#inclusion-modes","level":2,"title":"Inclusion Modes","text":"<p>Each steering file declares an inclusion mode in its frontmatter:</p> Mode When the file is included <code>always</code> Every prompt, unconditionally <code>auto</code> When the prompt keywords match the file's description <code>manual</code> Only when the user explicitly names the file <p>Which mode to pick depends on the AI tool you use, because the two tool families consume steering very differently.</p> <p>Claude Code and Codex: prefer <code>inclusion: always</code> for rules that must fire reliably. These tools have two delivery channels:</p> <ol> <li>The plugin's <code>PreToolUse</code> hook runs <code>ctx agent</code>    with an empty prompt, so only <code>always</code> files match    and get injected automatically on every tool call.</li> <li>The <code>ctx_steering_get</code> MCP tool, registered    automatically when the <code>ctx</code> plugin is installed. Claude    can call this tool mid-task to fetch <code>auto</code> or    <code>manual</code> files matching a specific prompt. Verify    with <code>claude mcp list</code>; look for <code>ctx: ✓ Connected</code>.</li> </ol> <p>Use <code>always</code> for invariants and anything that must fire every session. Use <code>auto</code> for situational rules where \"Claude fetches this when the prompt is relevant\" is the right behavior; those still land, just on Claude's judgment. Use <code>manual</code> for reference libraries you'll name explicitly.</p> <p>Cursor, Cline, Kiro: <code>auto</code> is the natural default. These tools read <code>.cursor/rules/</code>, <code>.clinerules/</code>, or <code>.kiro/steering/</code> natively and resolve the description match on their own, so <code>auto</code> files fire when the prompt matches. <code>manual</code> files load on explicit invocation. <code>always</code> still works but consumes context budget on every turn.</p> <p>Mixed setups: if a rule must fire on Claude Code, pick <code>always</code>, even if it's overkill for your Cursor setup. The context budget cost is small; the alternative (silently not firing) is worse.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-families-of-ai-tools-two-delivery-paths","level":2,"title":"Two Families of AI Tools, Two Delivery Paths","text":"<p>Not every AI tool consumes steering the same way. <code>ctx</code> handles two tool families differently, and it's worth knowing which family your editor is in before you wonder why a rule isn't firing.</p> <p>Native-rules tools (Cursor, Cline, Kiro) have a built-in rules primitive. They read a specific directory (<code>.cursor/rules/</code>, <code>.clinerules/</code>, <code>.kiro/steering/</code>) and apply the rules they find there. <code>ctx</code> handles these via <code>ctx steering sync</code>, which exports your files into the tool-native format. Run <code>sync</code> whenever you edit a steering file.</p> <p>Hook + MCP tools (Claude Code, Codex) have no native rules primitive, so <code>ctx steering sync</code> is a no-op for them. Instead, <code>ctx</code> delivers steering through two non-sync channels:</p> <ol> <li>Automatic injection via a <code>PreToolUse</code> hook. The    <code>ctx setup claude-code</code> plugin wires a hook that runs    <code>ctx agent --budget 8000</code> before each tool call.    <code>ctx agent</code> loads your steering files, filters them by    the active prompt, and includes matching bodies in the    context packet it prints. Claude Code feeds that output    back into its context. Every tool call, automatically.</li> <li>On-demand via the <code>ctx_steering_get</code> MCP tool. The    <code>ctx</code> MCP server exposes a tool Claude can call mid-task    to fetch matching steering files for a specific prompt.    Claude decides when to call it; it's not automatic.</li> </ol> <p>Both channels activate when you run <code>ctx setup claude-code --write</code>. After that, steering just works for Claude Code.</p> <p>Practical takeaway:</p> <ul> <li>Using Cursor/Cline/Kiro only? Run <code>ctx steering sync</code>   after edits.</li> <li>Using Claude Code or Codex only? Never run <code>sync</code>; the   hook+MCP pipeline handles it.</li> <li>Using both? Run <code>sync</code> for the native-rules tools; the   hook+MCP pipeline covers Claude Code automatically.</li> </ul>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#two-shapes-of-automation-rules-and-scripts","level":2,"title":"Two Shapes of Automation: Rules and Scripts","text":"<p>Steering is one of two hook-like layers <code>ctx</code> provides for customizing AI behavior. They're complementary:</p> <ul> <li>Steering: persistent rules that get prepended to   prompts. Declarative, text-only, scored by match.</li> <li>Triggers: executable shell scripts   that fire at lifecycle events. Imperative, runs arbitrary   code, gated by exit codes.</li> </ul> <p>Pick steering when you want \"always remind the AI of X.\" Pick triggers when you want \"do Y when event Z happens.\" They can coexist; many projects use both.</p>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/steering/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Writing Steering Files:   a six-step walkthrough: scaffold, write the rule, preview   matches, list, get-rules-in-front-of-the-AI (two paths   depending on tool family), verify.</li> <li><code>ctx steering</code> reference: full   command, flag, and frontmatter reference; includes the   per-tool delivery-mechanism table and a dedicated section   on how Claude Code and Codex consume steering.</li> <li><code>ctx setup</code>: configure which AI   tools receive steering. For Cursor/Cline/Kiro this is   about sync targets; for Claude Code/Codex it installs   the plugin that wires the <code>PreToolUse</code> hook and MCP   server.</li> <li>Lifecycle Triggers: the imperative   companion to steering files.</li> </ul>","path":["Home","Customization","Steering Files"],"tags":[]},{"location":"home/triggers/","level":1,"title":"Lifecycle Triggers","text":"","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#lifecycle-triggers","level":2,"title":"Lifecycle Triggers","text":"<p>Some things can't be expressed as a rule you want the AI to follow. Sometimes you want something to happen: block a dangerous tool call, inject today's standup notes into the next session, log every file save to a journal. That's what triggers are for.</p> <p>A trigger is an executable shell script that <code>ctx</code> runs at a specific lifecycle event: the start of a session, before a tool call, when a file is saved, and so on. Triggers read a JSON payload from stdin, do whatever they need, and write a JSON response on stdout. They can allow, block, or inject context into the pipeline depending on the event type.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#trigger-types","level":2,"title":"Trigger Types","text":"Type Fires when Use case <code>session-start</code> A new AI session begins Inject rotating context, standup notes <code>session-end</code> An AI session ends Persist summaries, send notifications <code>pre-tool-use</code> Before a tool call executes Block, gate, or audit <code>post-tool-use</code> After a tool call completes Log, react, post-process <code>file-save</code> A file is saved Lint on save, update indices <code>context-add</code> A new entry is added to <code>.context/</code> Cross-link, notify, enrich","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-are-arbitrary-code-treat-them-like-pre-commit-hooks","level":2,"title":"Triggers Are Arbitrary Code: Treat Them like Pre-Commit Hooks","text":"<p>Only Enable Scripts You've Read and Understand</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. A malicious or buggy trigger can block tool calls, corrupt context files, or exfiltrate data.</p> <p><code>ctx trigger add</code> intentionally creates new scripts disabled (no executable bit). You must <code>ctx trigger enable <name></code> after reviewing the contents. That's not a suggestion; it's the security model.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#three-hook-like-layers-in-ctx","level":2,"title":"Three Hook-like Layers in <code>ctx</code>","text":"<p>Triggers are one of three distinct hook-like concepts in ctx. The names are similar but the owners and use cases are not:</p> Layer Owned by Where they live When to use <code>ctx trigger</code> You <code>.context/hooks/<type>/*.sh</code> Project-specific automation, any AI tool <code>ctx system</code> hooks <code>ctx</code> itself built-in, wired into tool configs Built-in nudges (you don't author these) Claude Code hooks Claude Code <code>.claude/settings.local.json</code> Claude-Code-only tool-specific integration <p>This page is about the first category. The other two run automatically and are invisible to you.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#triggers-vs-steering-same-problem-different-shape","level":2,"title":"Triggers vs Steering: Same Problem, Different Shape","text":"<p>Triggers are the imperative counterpart to steering files. Steering expresses persistent rules the AI reads before each prompt; triggers express side effects that run on lifecycle events. They're complementary, not competing:</p> <ul> <li>Want the AI to remember something? → Steering.</li> <li>Want a script to run when something happens? → Trigger.</li> </ul> <p>Most projects use both.</p>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/triggers/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Authoring Lifecycle Triggers:   walkthrough with security guidance: scaffold, test,   enable, iterate.</li> <li><code>ctx trigger</code> reference: command   reference, trigger type table, input/output contract.</li> <li>Steering files: the declarative   counterpart to triggers.</li> </ul>","path":["Home","Customization","Lifecycle Triggers"],"tags":[]},{"location":"home/vscode/","level":1,"title":"ctx for VS Code","text":"","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#the-problem","level":2,"title":"The Problem","text":"<p>Every Copilot Chat session in VS Code starts from zero. You re-explain what you were doing, the AI repeats yesterday's mistakes, and decisions you spent an hour reasoning through last week get rediscovered instead of remembered.</p> <p>Without <code>ctx</code>:</p> <pre><code>@workspace add the validation middleware we discussed\n\nI don't have context about previous discussions. Could you describe\nwhat validation middleware you're referring to?\n</code></pre> <p>With <code>ctx</code>:</p> <pre><code>@ctx Do you remember?\n\nLast session (2026-05-09): you decided on Zod schemas at the route level\n(DECISIONS.md #12). Pattern lives in CONVENTIONS.md. Open task: wire\nthe auth middleware into the new /admin routes (TASKS.md, in-progress).\nThe reference implementation is src/middleware/auth.ts.\n</code></pre> <p>That's the whole pitch: your AI remembers, right inside the IDE you already work in.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#setup","level":2,"title":"Setup","text":"<p>Install the extension and the <code>ctx</code> binary, then <code>ctx init</code> your project:</p> <ol> <li>Install the extension from the    VS Code Marketplace    (publisher: <code>activememory</code>, display name: <code>ctx</code>: Persistent Context    for AI). Or build from source (see    editors/vscode/README.md).</li> <li>Install the <code>ctx</code> CLI if you haven't already    (installation docs). If you skip    this step, the extension will auto-download the right binary for    your platform on first use (see Auto-Bootstrap    below).</li> <li>From your project root, run:</li> </ol> <pre><code>ctx init && eval \"$(ctx activate)\"\n</code></pre> <ol> <li>Open Copilot Chat in VS Code and type <code>@ctx /init</code> to verify    the extension can reach the CLI.</li> </ol>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-gets-created","level":3,"title":"What Gets Created","text":"File Purpose <code>.context/</code> Project-local context directory (created by <code>ctx init</code>) <code>.github/copilot-instructions.md</code> Repository instructions Copilot reads natively; regenerated automatically whenever <code>.context/</code> files change <p>The extension itself lives in VS Code's extension storage. No project files are added beyond <code>.context/</code> and the Copilot instructions.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#how-you-use-it","level":2,"title":"How You Use It","text":"<p>Type <code>@ctx</code> in the Copilot Chat view to invoke the chat participant. Then either:</p> <ul> <li>Use a slash command: <code>@ctx /status</code>, <code>@ctx /wrapup</code>, etc. There   are 45 commands; the most common ones live in the Slash Commands   table below.</li> <li>Use natural language: <code>@ctx what should I work on?</code> routes to   <code>/next</code>; <code>@ctx time to wrap up</code> routes to <code>/wrapup</code>. See   Natural Language.</li> </ul> <p>The extension shows context-aware follow-up suggestions after each command. For example, after <code>/init</code> you'll see buttons for \"Show status\" or \"Generate copilot integration.\"</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#what-happens-automatically","level":2,"title":"What Happens Automatically","text":"<p>The extension registers several VS Code event handlers that mirror Claude Code's hook system. These run in the background; no user action needed.</p> Trigger What fires File save Task-completion check on non-<code>.context/</code> files Git commit Notification prompting to add a Decision, Learning, run <code>/verify</code>, or Skip <code>.context/</code> file change Refreshes pending reminders and regenerates <code>.github/copilot-instructions.md</code> Dependency file change When <code>go.mod</code>, <code>package.json</code>, etc. change, prompts to refresh the dependency map (<code>/map</code>) Every 5 minutes Updates the reminder status-bar item and writes a heartbeat timestamp Extension activate Fires <code>ctx system session-event --type start</code> Extension deactivate Fires <code>ctx system session-event --type end</code>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#status-bar","level":3,"title":"Status Bar","text":"<p>A <code>$(bell) ctx</code> indicator appears in the status bar when you have pending reminders. It refreshes every 5 minutes and hides itself when nothing is due.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#slash-commands","level":2,"title":"Slash Commands","text":"<p>The extension surfaces 45 commands across six categories. The most commonly used:</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#core-context","level":3,"title":"Core Context","text":"Command When to use <code>/init</code> Initialize a <code>.context/</code> directory with template files <code>/status</code> Token estimate, file count, what's recent <code>/agent</code> Print AI-ready context packet <code>/drift</code> Detect stale paths, missing files, dead references <code>/recall</code> Browse and search prior AI session history <code>/add</code> Add a task, decision, learning, or convention","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#session-lifecycle","level":3,"title":"Session Lifecycle","text":"Command When to use <code>/wrapup</code> End-of-session ceremony: status, drift, journal audit <code>/remember</code> Structured readback (trigger: \"Do you remember?\") from tasks, decisions, learnings, recent journal <code>/reflect</code> Surface items worth persisting as decisions or learnings <code>/pause</code> / <code>/resume</code> Save and restore session state for later","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#discovery-planning","level":3,"title":"Discovery & Planning","text":"Command When to use <code>/brainstorm</code> Browse and develop ideas from <code>ideas/</code> <code>/spec</code> List or scaffold feature specs from templates <code>/verify</code> Run verification (doctor + drift) <code>/map</code> Show dependency map (go.mod, package.json) <p>Full list (with maintenance, audit, metadata, and system commands) is in editors/vscode/README.md.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#natural-language","level":2,"title":"Natural Language","text":"<p>Plain English after <code>@ctx</code> is routed to the right command:</p> <ul> <li>\"What should I work on next?\" → <code>/next</code></li> <li>\"Time to wrap up\" → <code>/wrapup</code></li> <li>\"Show me the status\" → <code>/status</code></li> <li>\"Add a decision\" → <code>/add</code></li> <li>\"Check for drift\" → <code>/drift</code></li> </ul> <p>If the phrase doesn't match a known pattern, the extension surfaces a short menu of likely matches.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#auto-bootstrap","level":2,"title":"Auto-Bootstrap","text":"<p>If the <code>ctx</code> CLI isn't on PATH (or at a path configured via <code>ctx.executablePath</code>), the extension auto-downloads the right binary:</p> <ol> <li>Detects OS and architecture (darwin / linux / windows, amd64 / arm64).</li> <li>Fetches the latest release from    GitHub Releases.</li> <li>Downloads and verifies the matching binary.</li> <li>Caches it in VS Code's global storage directory.</li> </ol> <p>Subsequent sessions reuse the cached binary. To pin a specific version, set <code>ctx.executablePath</code> in your VS Code settings.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li>VS Code 1.93+</li> <li>GitHub Copilot Chat extension</li> <li><code>ctx</code> CLI on PATH, or let the extension auto-download it</li> </ul>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#configuration","level":2,"title":"Configuration","text":"Setting Default Description <code>ctx.executablePath</code> <code>ctx</code> Path to the <code>ctx</code> CLI binary. Set this if <code>ctx</code> isn't on PATH and you don't want auto-download.","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#refreshing-the-integration","level":2,"title":"Refreshing the Integration","text":"<p>The extension updates through the VS Code Marketplace like any other extension; install new versions via the Extensions view. Updates to the <code>ctx</code> CLI are independent: bump it via your package manager, or let the auto-bootstrap fetch the latest release.</p> <p>Unlike the OpenCode integration, there is no <code>ctx setup</code> step for VS Code. The extension carries its own runtime; <code>ctx</code>'s role is only to provide the CLI it shells out to.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#troubleshooting","level":2,"title":"Troubleshooting","text":"Symptom Cause Fix <code>@ctx</code> participant doesn't appear in Copilot Chat Copilot Chat not installed or not signed in Install GitHub Copilot Chat and ensure you're signed in to a Copilot-eligible account <code>@ctx /status</code> says <code>ctx</code> not found CLI not on PATH and auto-download disabled Either add <code>ctx</code> to PATH (<code>brew install activememory/tap/ctx</code> or download from Releases), or unset <code>ctx.executablePath</code> to let the extension auto-download Status-bar reminder never updates Heartbeat suppressed or <code>.context/</code> doesn't exist Run <code>ctx init</code> from your project root; reload VS Code if the indicator still doesn't appear within 5 minutes Commands run but nothing is captured to <code>.context/</code> Workspace folder missing or <code>.context/</code> outside the open folder Make sure your project root (the one with <code>.context/</code>) is the workspace root, not a subdirectory of it","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#verify-it-works","level":2,"title":"Verify It Works","text":"<p>Open Copilot Chat and ask:</p> <pre><code>@ctx Do you remember?\n</code></pre> <p>You should see a structured readback citing specific tasks, decisions, and recent session topics. If you instead see \"I don't have memory\" or \"Let me check,\" something went wrong: confirm the CLI is reachable (<code>@ctx /system doctor</code>) and <code>.context/</code> has files in it.</p>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"home/vscode/#whats-next","level":2,"title":"What's Next","text":"<ul> <li>Your First Session: step-by-step walkthrough from   <code>ctx init</code> to verified recall.</li> <li>Common Workflows: day-to-day commands for   tracking context, checking health, and browsing history.</li> <li>Context Files: what lives in <code>.context/</code> and how   each file is used.</li> <li>Setup across AI Tools: wiring <code>ctx</code>   for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf   alongside VS Code.</li> </ul>","path":["Home","Get Started","ctx for VS Code"],"tags":[]},{"location":"operations/","level":1,"title":"Operations","text":"<p>Guides for installing, upgrading, integrating, and running <code>ctx</code>. Split into three groups by audience.</p>","path":["Operations"],"tags":[]},{"location":"operations/#day-to-day","level":2,"title":"Day-to-Day","text":"<p>Everyday operation guides for anyone running <code>ctx</code> in a project or adopting it in a team.</p>","path":["Operations"],"tags":[]},{"location":"operations/#integration","level":3,"title":"Integration","text":"<p>Adopt <code>ctx</code> in an existing project: initialize context files, migrate from other tools, and onboard team members.</p>","path":["Operations"],"tags":[]},{"location":"operations/#upgrade","level":3,"title":"Upgrade","text":"<p>Upgrade between versions with step-by-step migration notes and breaking-change guidance.</p>","path":["Operations"],"tags":[]},{"location":"operations/#ai-tools","level":3,"title":"AI Tools","text":"<p>Configure <code>ctx</code> with Claude Code, Cursor, Aider, Copilot, Windsurf, and other AI coding tools.</p>","path":["Operations"],"tags":[]},{"location":"operations/#autonomous-loops","level":3,"title":"Autonomous Loops","text":"<p>Run an unattended AI agent that works through tasks overnight, with <code>ctx</code> providing persistent memory between iterations.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub","level":2,"title":"Hub","text":"<p>Operator guides for running a <code>ctx</code> Hub, the gRPC server that fans out structured entries across projects. If you're a client connecting to a Hub someone else runs, see <code>ctx connect</code> and the Hub recipes instead.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub-operations","level":3,"title":"Hub Operations","text":"<p>Data directory layout, daemon management, systemd unit, backup and restore, log rotation, monitoring, and upgrades.</p>","path":["Operations"],"tags":[]},{"location":"operations/#hub-failure-modes","level":3,"title":"Hub Failure Modes","text":"<p>What can go wrong in network, storage, cluster, auth, and clock layers, and what you should do about each one. Includes the short-list table oncall engineers will want bookmarked.</p>","path":["Operations"],"tags":[]},{"location":"operations/#maintainers","level":2,"title":"Maintainers","text":"<p>Runbooks for people shipping <code>ctx</code> itself.</p>","path":["Operations"],"tags":[]},{"location":"operations/#cutting-a-release","level":3,"title":"Cutting a Release","text":"<p>Step-by-step runbook for maintainers: bump version, generate release notes, run the release script, and verify the result.</p>","path":["Operations"],"tags":[]},{"location":"operations/#runbooks","level":2,"title":"Runbooks","text":"<p>Step-by-step procedures you run with your agent. Each runbook includes a prompt to paste into a Claude Code session and guidance on triaging the results.</p> Runbook Purpose When to run Release checklist Full pre-release sequence Before every release Plugin release Plugin-specific release steps Plugin changes ship Breaking migration Guide users across breaking changes Releases with renames Hub deployment Set up a <code>ctx</code> Hub end-to-end First-time hub setup New contributor Onboarding: clone to first session New contributors Codebase audit AST audits, magic strings, dead code, doc alignment Before release, quarterly Docs semantic audit Narrative gaps, weak pages, structural problems Before release, after adding pages Sanitize permissions Clean <code>.claude/settings.local.json</code> of over-broad grants After heavy permission granting Architecture exploration Systematic architecture docs across repos New codebase onboarding, reviews <p>Recommended cadence:</p> <ul> <li>Before every release: release checklist (which includes   codebase audit + docs semantic audit)</li> <li>Monthly: sanitize permissions</li> <li>Quarterly: full sweep of all audit runbooks</li> </ul>","path":["Operations"],"tags":[]},{"location":"operations/autonomous-loop/","level":1,"title":"Autonomous Loops","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#autonomous-ai-development","level":2,"title":"Autonomous AI Development","text":"<p>Iterate until done.</p> <p>An autonomous loop is an iterative AI development workflow where an agent  works on tasks until completion, without constant human intervention. </p> <p><code>ctx</code> provides the memory that makes this possible:</p> <ul> <li><code>ctx</code> provides the memory: persistent context that survives across iterations</li> <li>The loop provides the automation: continuous execution until done</li> </ul> <p>Together, they enable fully autonomous AI development where the agent remembers everything across iterations.</p> <p>Origin</p> <p>This pattern is inspired by Geoffrey Huntley's Ralph Wiggum technique.</p> <p>We use generic terminology here so the concepts remain clear regardless of trends.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#how-it-works","level":2,"title":"How It Works","text":"<pre><code>graph TD\n    A[Start Loop] --> B[Load .context/loop.md]\n    B --> C[AI reads .context/]\n    C --> D[AI picks task from TASKS.md]\n    D --> E[AI completes task]\n    E --> F[AI updates context files]\n    F --> G[AI commits changes]\n    G --> H{Check signals}\n    H -->|SYSTEM_CONVERGED| I[Done - all tasks complete]\n    H -->|SYSTEM_BLOCKED| J[Done - needs human input]\n    H -->|Continue| B</code></pre> <ol> <li>Loop reads <code>.context/loop.md</code> and invokes AI</li> <li>AI loads context from <code>.context/</code></li> <li>AI picks one task and completes it</li> <li>AI updates context files (mark task done, add learnings)</li> <li>AI commits changes</li> <li>Loop checks for completion signals</li> <li>Repeat until converged or blocked</li> </ol>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#quick-start-shell-while-loop-recommended","level":2,"title":"Quick Start: Shell While Loop (Recommended)","text":"<p>The best way to run an autonomous loop is a plain shell script that invokes your AI tool in a fresh process on each iteration. This is \"pure ralph\":</p> <p>The only state that carries between iterations is what lives in <code>.context/</code> and the git history. No context window bleed, no accumulated tokens, no hidden state.</p> <p>Create a <code>loop.sh</code>:</p> <pre><code>#!/bin/bash\n# loop.sh: an autonomous iteration loop\n\nPROMPT_FILE=\"${1:-.context/loop.md}\"\nMAX_ITERATIONS=\"${2:-10}\"\nOUTPUT_FILE=\"/tmp/loop_output.txt\"\n\nfor i in $(seq 1 $MAX_ITERATIONS); do\n  echo \"=== Iteration $i ===\"\n\n  # Invoke AI with prompt\n  cat \"$PROMPT_FILE\" | claude --print > \"$OUTPUT_FILE\" 2>&1\n\n  # Display output\n  cat \"$OUTPUT_FILE\"\n\n  # Check for completion signals\n  if grep -q \"SYSTEM_CONVERGED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop complete: All tasks done\"\n    break\n  fi\n\n  if grep -q \"SYSTEM_BLOCKED\" \"$OUTPUT_FILE\"; then\n    echo \"Loop blocked: Needs human input\"\n    break\n  fi\n\n  sleep 2\ndone\n</code></pre> <p>Make it executable and run:</p> <pre><code>chmod +x loop.sh\n./loop.sh\n</code></pre> <p>You can also generate this script with <code>ctx loop</code> (see CLI Reference).</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-do-we-use-a-shell-loop","level":3,"title":"Why Do We Use a Shell Loop?","text":"<p>Each iteration starts a fresh AI process with zero context window history. The agent knows only what it reads from <code>.context/</code> files: Exactly the information you chose to persist. </p> <p>This is the core loop principle: memory is explicit, not accidental.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#alternative-claude-codes-built-in-loop","level":2,"title":"Alternative: Claude Code's Built-in Loop","text":"<p>Claude Code has built-in loop support:</p> <pre><code># Start autonomous loop\n/loop\n\n# Cancel running loop\n/cancel-loop\n</code></pre> <p>This is convenient for quick iterations, but be aware of important caveats:</p> <p>This Loop Is Not Pure</p> <p>Claude Code's <code>/loop</code> runs all iterations within the same session. This means:</p> <ul> <li>State leaks between iterations: The context window accumulates   output from every previous iteration. The agent \"remembers\" things   it saw earlier (even if they were never persisted to <code>.context/</code>).</li> <li>Token budget degrades: Each iteration adds to the context window,   leaving less room for actual work in later iterations.</li> <li>Not ergonomic for long runs: Users report that the built-in loop   is less predictable for 10+ iteration runs compared to a shell loop.</li> </ul> <p>For short explorations (2-5 iterations) or interactive use, <code>/loop</code> works fine. For overnight unattended runs or anything where iteration independence matters, use the shell while loop instead.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#the-contextloopmd-file","level":2,"title":"The <code>.context/loop.md</code> File","text":"<p>The prompt file instructs the AI on how to work autonomously. Here's a template:</p> <pre><code># Autonomous Development Prompt\n\nYou are working on this project autonomously. Follow these steps:\n\n## 1. Load Context\n\nRead these files in order:\n\n1. `.context/CONSTITUTION.md`: NEVER violate these rules\n2. `.context/TASKS.md`: Find work to do\n3. `.context/CONVENTIONS.md`: Follow these patterns\n4. `.context/DECISIONS.md`: Understand past choices\n\n## 2. Pick One Task\n\nFrom `.context/TASKS.md`, select ONE task that is:\n\n- Not blocked\n- Highest priority available\n- Within your capabilities\n\n## 3. Complete the Task\n\n- Write code following conventions\n- Run tests if applicable\n- Keep changes focused and minimal\n\n## 4. Update Context\n\nAfter completing work:\n\n- Mark task complete in `TASKS.md`\n- Add any learnings to `LEARNINGS.md`\n- Add any decisions to `DECISIONS.md`\n\n## 5. Commit Changes\n\nCreate a focused commit with clear message.\n\n## 6. Signal Status\n\nEnd your response with exactly ONE of:\n\n- `SYSTEM_CONVERGED`: All tasks in TASKS.md are complete\n- `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n- (no signal): More work remains, continue to next iteration\n\n## Rules\n\n- ONE task per iteration\n- NEVER skip tests\n- NEVER violate CONSTITUTION.md\n- Commit after each task\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#completion-signals","level":2,"title":"Completion Signals","text":"<p>The loop watches for these signals in AI output:</p> Signal Meaning When to Use <code>SYSTEM_CONVERGED</code> All tasks complete No pending tasks in TASKS.md <code>SYSTEM_BLOCKED</code> Cannot proceed Needs clarification, access, or decision <code>BOOTSTRAP_COMPLETE</code> Initial setup done Project scaffolding finished","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-usage","level":3,"title":"Example Usage","text":"<p>converged state</p> <pre><code>I've completed all tasks in TASKS.md:\n- [x] Set up project structure\n- [x] Implement core API\n- [x] Add authentication\n- [x] Write tests\n\nNo pending tasks remain.\n\nSYSTEM_CONVERGED\n</code></pre> <p>blocked state</p> <pre><code>I cannot proceed with the \"Deploy to production\" task because:\n- Missing AWS credentials\n- Need confirmation on region selection\n\nPlease provide credentials and confirm deployment region.\n\nSYSTEM_BLOCKED\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#why-ctx-and-loops-work-well-together","level":2,"title":"Why <code>ctx</code> and  Loops Work Well Together","text":"Without <code>ctx</code> With <code>ctx</code> Each iteration starts fresh Each iteration has full history Decisions get re-made Decisions persist in <code>DECISIONS.md</code> Learnings are lost Learnings accumulate in <code>LEARNINGS.md</code> Tasks can be forgotten Tasks tracked in <code>TASKS.md</code>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#automatic-context-updates","level":3,"title":"Automatic Context Updates","text":"<p>During the loop, the AI should update context files:</p> <p>Mark task complete: <pre><code>ctx task complete \"implement user auth\"\n</code></pre></p> <p>Or emit an update command (parsed by <code>ctx watch</code>): <pre><code><context-update type=\"complete\">user auth</context-update>\n</code></pre></p> <p>Add learning: <pre><code>ctx learning add \"Rate limiting requires Redis connection\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p> <p>Or via update command: <pre><code><context-update type=\"learning\"\n  context=\"Implementing rate limiter\"\n  lesson=\"Rate limiting requires Redis connection\"\n  application=\"Ensure Redis is provisioned before enabling rate limits\"\n>Rate Limiting Redis Dependency</context-update>\n</code></pre></p> <p>Record decision: <pre><code>ctx decision add \"Use JWT tokens for API authentication\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#advanced-watch-mode","level":2,"title":"Advanced: Watch Mode","text":"<p>Run <code>ctx watch</code> alongside the loop to automatically process context updates:</p> <pre><code># Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n</code></pre> <p>The watch command processes context updates from the loop output in real time.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#project-setup","level":2,"title":"Project Setup","text":"<p>Initialize a project for autonomous loop operation, then activate it so the loop's <code>ctx</code> commands know which <code>.context/</code> to use:</p> <pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p>For unattended overnight runs, put the binding directly at the top of your loop script (<code>export CTX_DIR=/abs/path/.context</code>) so it survives without depending on a live shell. See Activating a Context Directory.</p> <p>The loop prompt template is deployed to <code>.context/loop.md</code> during initialization. It instructs the agent to:</p> <ul> <li>Work autonomously without asking clarifying questions;</li> <li>Follow one-task-per-iteration discipline;</li> <li>Use <code>SYSTEM_CONVERGED</code> / <code>SYSTEM_BLOCKED</code> signals;</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#example-project-structure","level":2,"title":"Example Project Structure","text":"<pre><code>my-project/\n├── .context/\n│   ├── CONSTITUTION.md\n│   ├── TASKS.md          # Work items for the loop\n│   ├── DECISIONS.md\n│   ├── LEARNINGS.md\n│   ├── CONVENTIONS.md\n│   └── sessions/         # Loop iteration history\n├── loop.sh               # Loop script (if not using Claude Code)\n└── src/                  # Your code\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#sample-tasksmd-for-autonomous-loops","level":3,"title":"Sample <code>TASKS.md</code> for Autonomous Loops","text":"<pre><code># Tasks\n\n## Phase 1: Setup\n\n- [x] Initialize project structure\n- [x] Set up testing framework\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Polish\n\n- [ ] Add rate limiting `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n</code></pre> <p>The loop will work through these systematically, marking each complete.</p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#loop-runs-forever","level":3,"title":"Loop Runs Forever","text":"<p>Cause: AI not emitting completion signals</p> <p>Fix: Ensure .context/loop.md explicitly instructs signaling: <pre><code>End EVERY response with one of:\n- SYSTEM_CONVERGED (if all tasks done)\n- SYSTEM_BLOCKED (if stuck)\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#context-not-persisting","level":3,"title":"Context Not Persisting","text":"<p>Cause: AI not updating context files</p> <p>Fix: Add explicit instructions to .context/loop.md: <pre><code>After completing a task, you MUST:\n1. Run: ctx task complete \"<task>\"\n2. Add learnings: ctx learning add \"...\" --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre></p>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#tasks-getting-repeated","level":3,"title":"Tasks Getting Repeated","text":"<p>Cause: Task not marked complete before next iteration</p> <p>Fix: Ensure commit happens after context update:</p> <pre><code>Order of operations:\n1. Complete coding work\n2. Update context files (*`ctx task complete`, `ctx add`*)\n3. Commit **ALL** changes including `.context/`\n4. Then signal status\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#ai-violating-constitution","level":3,"title":"AI Violating Constitution","text":"<p>Cause: Constitution not read first</p> <p>Fix: Make constitution check explicit in <code>.context/loop.md</code>:</p> <pre><code>BEFORE any work:\n1. Read .context/CONSTITUTION.md\n2. If task would violate ANY rule, emit SYSTEM_BLOCKED\n3. Explain which rule prevents the work\n</code></pre>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Building <code>ctx</code> Using <code>ctx</code>:    The dogfooding story: how autonomous loops built the tool that powers them</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/autonomous-loop/#resources","level":2,"title":"Resources","text":"<ul> <li>Geoffrey Huntley's Ralph Wiggum Technique:    The original inspiration</li> <li>Context CLI: Command reference</li> <li>Integrations: Tool-specific setup</li> </ul>","path":["Operations","Day-to-Day","Autonomous Loops"],"tags":[]},{"location":"operations/hub-failure-modes/","level":1,"title":"Hub Failure Modes","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#ctx-hub-failure-modes","level":1,"title":"<code>ctx</code> Hub: Failure Modes","text":"<p>What can go wrong, what the system does about it, and what you should do. Complementary to <code>ctx</code> Hub Operations.</p> <p>Design Posture</p> <p>The hub is best-effort knowledge sharing, not a durable ledger. Local <code>.context/</code> files are the source of truth for each project; the hub is a fan-out channel. This framing informs every failure-mode decision below.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#network","level":2,"title":"Network","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#client-loses-connection-mid-stream","level":3,"title":"Client Loses Connection Mid-Stream","text":"<p>What happens: <code>ctx connection listen</code> detects the EOF, waits with exponential backoff, and reconnects. On reconnect it passes its last-seen sequence; the hub replays everything newer.</p> <p>What you should do: nothing. If reconnects are looping, check firewall state on the hub and <code>ctx hub status</code> output.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-majority-side-reachable","level":3,"title":"Partition: Majority Side Reachable","text":"<p>What happens: clients routed to the majority side continue to publish and listen. The minority nodes step down to followers that cannot accept writes (Raft quorum lost).</p> <p>What you should do: let it heal. When the partition closes, followers catch up via sequence-based sync automatically.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#partition-split-brain-no-quorum","level":3,"title":"Partition: Split Brain (No Quorum)","text":"<p>What happens: no node holds a majority, so no leader is elected. All nodes become read-only. <code>ctx connection publish</code> and <code>ctx add --share</code> fail with a \"no leader\" error; local writes still succeed.</p> <p>What you should do: fix the network. If the partition is permanent (e.g., a data center is gone), bootstrap a new cluster from the survivors with <code>ctx hub peer remove</code> for the dead nodes.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#hub-unreachable-during-ctx-add-share","level":3,"title":"Hub Unreachable during <code>ctx add --share</code>","text":"<p>What happens: the local write succeeds; the share step prints a warning and exits non-zero on the share leg only. <code>--share</code> is best-effort; it never blocks local context updates.</p> <p>What you should do: run <code>ctx connection publish</code> later to backfill, or rely on another <code>--share</code> for the same entry ID. The hub deduplicates by entry ID.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#storage","level":2,"title":"Storage","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#disk-full-on-the-leader","level":3,"title":"Disk Full on the Leader","text":"<p>What happens: <code>entries.jsonl</code> append fails. The hub rejects writes with an error and stays up for read traffic. Clients retry; followers keep their in-sync status using whatever the leader already wrote.</p> <p>What you should do: free disk or grow the volume, then nothing else; the hub resumes accepting writes on the next append attempt.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#corrupt-entriesjsonl","level":3,"title":"Corrupt <code>entries.jsonl</code>","text":"<p>What happens: if the last line is a partial JSON write from a crash, the hub truncates it on startup and logs a warning. If any earlier line is malformed, the hub refuses to start.</p> <p>What you should do: inspect with <code>jq -c . <data-dir>/entries.jsonl > /dev/null</code> to find the bad line. Move the bad region to a <code>.quarantine</code> file, then start. Nothing is ever silently dropped.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#metajson-entriesjsonl-sequence-mismatch","level":3,"title":"<code>meta.json</code> / <code>entries.jsonl</code> Sequence Mismatch","text":"<p>What happens: the hub refuses to start. This usually means someone copied one file without the other.</p> <p>What you should do: restore both files from the same backup, or accept the higher sequence by regenerating <code>meta.json</code> from <code>entries.jsonl</code> (manual for now; file a bug).</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#cluster","level":2,"title":"Cluster","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-clean-shutdown","level":3,"title":"Leader Crash, Clean Shutdown","text":"<p>What happens: <code>ctx hub stop</code> triggers <code>stepdown</code> first, so a new leader is elected before the old one exits. In-flight writes drain. Clients reconnect to the new leader transparently.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#leader-crash-hard-fail-kill-9-power-loss","level":3,"title":"Leader Crash, Hard Fail (Kill -9, Power Loss)","text":"<p>What happens: Raft detects the missing heartbeat and elects a new leader within a few seconds. Writes the old leader accepted but had not yet replicated can be lost. See the Raft-lite warning in the cluster recipe.</p> <p>What you should do: if you need stronger durability, run <code>ctx connection listen</code> on a dedicated \"collector\" project that persists entries locally as a write-ahead backup.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#split-brain-after-rejoin","level":3,"title":"Split-Brain After Rejoin","text":"<p>What happens: Raft reconciles: the minority side's uncommitted writes are discarded, and the majority's log is authoritative.</p> <p>What you should do: nothing automatic. If you know the minority had important writes, grep for them in <code><data-dir>/entries.jsonl.rejected</code> (written by the reconciliation pass) and replay them with <code>ctx connection publish</code>.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#auth-and-tokens","level":2,"title":"Auth and Tokens","text":"","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#lost-admin-token","level":3,"title":"Lost Admin Token","text":"<p>What happens: you cannot register new projects.</p> <p>What you should do: retrieve it from <code><data-dir>/admin.token</code>. If that file is also gone, stop the hub and regenerate. Note that all existing client tokens keep working; only new registrations need the admin token.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-admin-token","level":3,"title":"Compromised Admin Token","text":"<p>What happens: anyone with the token can register new projects and publish. They cannot read existing entries without a client token for a project that subscribes.</p> <p>What you should do: rotate the admin token (regenerate <code><data-dir>/admin.token</code> and restart), revoke suspicious client registrations via <code>clients.json</code>, and audit <code>entries.jsonl</code> for unexpected origins.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-client-token","level":3,"title":"Compromised Client Token","text":"<p>What happens: the attacker can publish as that project and read anything that project is subscribed to. Because <code>Origin</code> is self-asserted on publish, the attacker can also publish entries tagged with any other project's name, so attribution in <code>entries.jsonl</code> cannot be trusted after a token compromise.</p> <p>What you should do: remove the client's entry from <code>clients.json</code>, restart the hub, and re-register the legitimate project with a fresh token. Audit <code>entries.jsonl</code> for entries published after the compromise timestamp and quarantine any that look suspicious; remember that <code>Origin</code> on those entries proves nothing.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#compromised-hub-host","level":3,"title":"Compromised Hub Host","text":"<p>What happens: <code><data-dir>/clients.json</code> stores client tokens verbatim (not hashed). Anyone with read access to that file has every client token in hand and can impersonate any registered project until each one is rotated.</p> <p>What you should do: treat it as a total hub compromise. Stop the hub, wipe <code><data-dir></code> (keep a forensic copy first), regenerate the admin token, and have every client re-register. See Security model for the mitigations that reduce the blast radius while the hashing follow-up is pending.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#clock-skew","level":2,"title":"Clock Skew","text":"<p>Hub entries carry a timestamp assigned by the publishing client. The hub does not rewrite timestamps. Clients with significant clock skew will publish entries that look out of order in the shared feed.</p> <p>What you should do: run NTP on all client machines. If you see entries dated in the future or far past, the publisher's clock is the culprit.</p>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#the-short-list","level":2,"title":"The Short List","text":"Symptom First thing to check Client can't reach hub Firewall, then <code>ctx hub status</code> \"No leader\" errors Cluster quorum; run <code>ctx hub status</code> on each peer Hub won't start after crash Last line of <code>entries.jsonl</code> Entries missing after restore Check <code>clients.json</code> sequence vs local <code>.sync-state.json</code> Duplicate entries in shared feed Client replayed after restore, safe (dedup by ID) Followers lagging Disk or network on the follower, not the leader","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub-failure-modes/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub Operations</li> <li><code>ctx</code> Hub security model</li> <li>HA cluster recipe</li> </ul>","path":["Operations","Hub","Hub Failure Modes"],"tags":[]},{"location":"operations/hub/","level":1,"title":"Hub Operations","text":"","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#ctx-hub-operations","level":1,"title":"<code>ctx</code> Hub: Operations","text":"<p>Running the <code>ctx</code> <code>ctx</code> Hub in production. This page is for operators: people running a hub for themselves or a team, not people writing to a hub someone else is running.</p> <p>If you have not read it yet, start with the <code>ctx</code> Hub overview. It explains what the hub is, the two user stories it supports (personal cross-project brain vs small trusted team), and what it does not do. A client-side tour is in Getting Started.</p> <p>Operator Cheat Sheet</p> <ul> <li>The hub fans out four entry types only: <code>decision</code>,   <code>learning</code>, <code>convention</code>, <code>task</code>. Journals, scratchpad,   and other local state are out of scope.</li> <li>Identity is per-project, not per-user. Attribution is   limited to <code>Origin</code>, which is self-asserted by the   publishing client.</li> <li>The data model is an append-only JSONL log plus two   small JSON sidecar files. Nothing is rewritten in place.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#data-directory-layout","level":2,"title":"Data Directory Layout","text":"<p>The hub stores everything under a single data directory (default <code>~/.ctx/hub-data/</code>, override with <code>--data-dir</code>).</p> <pre><code><data-dir>/\n  admin.token        # Initial admin token (chmod 600)\n  clients.json       # Registered client tokens and project names\n  meta.json          # Sequence counter, version, cluster metadata\n  entries.jsonl      # Append-only log (single source of truth)\n  hub.pid            # Daemon PID file (daemon mode only)\n  raft/              # Raft state (cluster mode only)\n    log.db\n    stable.db\n    snapshots/\n</code></pre> <p>Invariants:</p> <ul> <li><code>entries.jsonl</code> is append-only. Every line is a valid JSON   object. Corrupt lines are fatal at startup: fix or truncate   before restart.</li> <li><code>meta.json</code> is authoritative for the next sequence number. On   restart, the hub reads the last valid line of <code>entries.jsonl</code> and   refuses to start if the sequences disagree.</li> <li><code>clients.json</code> holds hashed client tokens; losing it invalidates   all client registrations.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#starting-and-stopping","level":2,"title":"Starting and Stopping","text":"ForegroundDaemon <pre><code>ctx hub start                    # Ctrl-C to stop\nctx hub start --port 8080        # Custom port\nctx hub start --data-dir /srv/ctx-hub\n</code></pre> <pre><code>ctx hub start --daemon           # Fork to background\nctx hub stop                      # Graceful shutdown\n</code></pre> <p><code>--stop</code> sends SIGTERM to the PID in <code>hub.pid</code>, waits for in-flight RPCs to drain, then exits. If the daemon is wedged, remove <code>hub.pid</code> and send <code>SIGKILL</code> manually. <code>entries.jsonl</code> is crash-safe, so you will not lose accepted writes.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#systemd-unit","level":2,"title":"Systemd Unit","text":"<p>For production single-node deployments, run the hub as a systemd service instead of <code>--daemon</code>:</p> <pre><code># /etc/systemd/system/ctx-hub.service\n[Unit]\nDescription=ctx `ctx` Hub\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nUser=ctx\nGroup=ctx\nExecStart=/usr/local/bin/ctx hub start --port 9900 \\\n    --data-dir /var/lib/ctx-hub\nRestart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nProtectSystem=strict\nProtectHome=true\nReadWritePaths=/var/lib/ctx-hub\nPrivateTmp=true\n\n[Install]\nWantedBy=multi-user.target\n</code></pre> <pre><code>sudo systemctl enable --now ctx-hub\nsudo journalctl -u ctx-hub -f\n</code></pre>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#backup-and-restore","level":2,"title":"Backup and Restore","text":"<p>Because <code>entries.jsonl</code> is append-only, backups are trivial:</p> <pre><code># Hot backup, safe while the hub is running.\ncp <data-dir>/entries.jsonl backups/entries-$(date +%F).jsonl\ncp <data-dir>/meta.json      backups/meta-$(date +%F).json\ncp <data-dir>/clients.json   backups/clients-$(date +%F).json\n</code></pre> <p>For a consistent snapshot across all three files, stop the hub, copy, then start again, or use a filesystem-level snapshot (LVM, ZFS, Btrfs).</p> <p>Restore:</p> <pre><code>ctx hub stop                           # Stop the hub\ncp backups/entries-2026-04-10.jsonl <data-dir>/entries.jsonl\ncp backups/meta-2026-04-10.json      <data-dir>/meta.json\ncp backups/clients-2026-04-10.json   <data-dir>/clients.json\nctx hub start --daemon\n</code></pre> <p>Clients that pushed sequences above the restored watermark will re-publish on the next <code>listen</code> reconnect, because the hub now reports a lower sequence than what clients have on disk. This is safe; the store deduplicates by entry ID.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#log-rotation","level":2,"title":"Log Rotation","text":"<p><code>entries.jsonl</code> grows unbounded. For long-lived hubs, rotate it offline:</p> <pre><code>ctx hub stop\nmv <data-dir>/entries.jsonl <data-dir>/entries-$(date +%F).jsonl.old\n# Replay the last N days into a fresh entries.jsonl if you want a\n# trimmed active log, or leave the old file in place as history.\nctx hub start --daemon\n</code></pre> <p>Do not truncate <code>entries.jsonl</code> while the hub is running. The hub holds an open file handle; an in-place truncation confuses the sequence counter and loses writes.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#monitoring","level":2,"title":"Monitoring","text":"<p>Liveness probe:</p> <pre><code>ctx hub status --exit-code\n</code></pre> <p>Exit code <code>0</code> means the node is healthy (leader or in-sync follower); non-zero means degraded. Wire this into your monitoring of choice.</p> <p>For cluster deployments, watch for:</p> <ul> <li>Role flaps: the leader changing more than once per hour   suggests network instability or disk contention.</li> <li>Replication lag: <code>ctx hub status</code> shows per-peer sequence   offsets. Sustained lag > 100 sequences on a follower is worth   investigating.</li> <li><code>entries.jsonl</code> growth rate: sudden spikes often indicate a   misbehaving <code>ctx connection listen</code> reconnect loop.</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#upgrading","level":2,"title":"Upgrading","text":"<p>The JSONL format is versioned in <code>meta.json</code>. <code>ctx</code> refuses to start against a newer store version than it understands; older store versions are upgraded in place at first start after an upgrade.</p> <p>Always back up <code><data-dir>/</code> before upgrading.</p>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/hub/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub failure modes</li> <li><code>ctx</code> Hub security model</li> <li><code>ctx serve</code> reference</li> <li><code>ctx hub</code> reference</li> </ul>","path":["Operations","Hub","Hub Operations"],"tags":[]},{"location":"operations/integrations/","level":1,"title":"AI Tools","text":"","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#ai-tools","level":2,"title":"AI Tools","text":"<p>Context works with any AI tool that can read files. This guide covers setup  for popular AI coding assistants.</p> <p>Activate the Project Before Running <code>ctx</code> Commands</p> <p>After <code>ctx init</code>, run:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>This tells <code>ctx</code> which <code>.context/</code> directory the rest of the commands on this page should use. If you skip it, you'll see <code>Error: no context directory specified</code>. The <code>ctx setup <tool></code> commands work without activation, but most others (<code>ctx agent</code>, <code>ctx add</code>, <code>ctx status</code>, <code>ctx watch</code>) need it. See Activating a Context Directory.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#claude-code-full-integration","level":2,"title":"Claude Code (Full Integration)","text":"<p>Claude Code has the deepest integration via the <code>ctx</code> plugin.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup","level":3,"title":"Setup","text":"<p>First, install <code>ctx</code> and initialize your project, then activate it for the current shell:</p> <pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p>Then, install the <code>ctx</code> plugin in Claude Code:</p> <pre><code># From the ctx repository\nclaude /plugin install ./internal/assets/claude\n\n# Or from the marketplace\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n</code></pre> <p>Ensure the Plugin Is Enabled</p> <p>Installing a plugin registers it, but local installs may not auto-enable it globally. Verify <code>~/.claude/settings.json</code> contains:</p> <pre><code>{ \"enabledPlugins\": { \"ctx@activememory-ctx\": true } }\n</code></pre> <p>Without this, the plugin's hooks and skills won't appear in other projects. Running <code>ctx init</code> auto-enables the plugin; use <code>--no-plugin-enable</code> to skip this step.</p> <p>This gives you:</p> Component Purpose <code>.context/</code> All context files <code>CLAUDE.md</code> Bootstrap instructions Plugin hooks Lifecycle automation Plugin skills Agent Skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works","level":3,"title":"How It Works","text":"<pre><code>graph TD\n    A[Session Start] --> B[Claude reads CLAUDE.md]\n    B --> C[PreToolUse hook runs]\n    C --> D[ctx agent loads context]\n    D --> E[Work happens]\n    E --> F[Session End]</code></pre> <ol> <li>Session start: Claude reads <code>CLAUDE.md</code>, which tells it to check <code>.context/</code></li> <li>First tool use: <code>PreToolUse</code> hook runs <code>ctx agent</code> and emits the context    packet (subsequent invocations within the cooldown window are silent)</li> <li>Next session: Claude reads context files and continues with context</li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#plugin-hooks","level":3,"title":"Plugin Hooks","text":"<p>The <code>ctx</code> plugin provides lifecycle hooks implemented as Go subcommands (<code>ctx system *</code>):</p> Hook Event Purpose <code>ctx system context-load-gate</code> PreToolUse (<code>.*</code>) Auto-inject context on first tool use <code>ctx system block-non-path-ctx</code> PreToolUse (<code>Bash</code>) Block <code>./ctx</code> or <code>go run</code>: force <code>$PATH</code> install <code>ctx system qa-reminder</code> PreToolUse (<code>Bash</code>) Remind agent to lint/test before committing <code>ctx system specs-nudge</code> PreToolUse (<code>EnterPlanMode</code>) Nudge agent to use project specs when planning <code>ctx system check-context-size</code> UserPromptSubmit Nudge context assessment as sessions grow <code>ctx system check-ceremonies</code> UserPromptSubmit Nudge /ctx-remember and /ctx-wrap-up adoption <code>ctx system check-persistence</code> UserPromptSubmit Remind to persist learnings/decisions <code>ctx system check-journal</code> UserPromptSubmit Remind to export/enrich journal entries <code>ctx system check-reminders</code> UserPromptSubmit Relay pending reminders at session start <code>ctx system check-version</code> UserPromptSubmit Warn when binary/plugin versions diverge <code>ctx system check-resources</code> UserPromptSubmit Warn when memory/swap/disk/load hit DANGER level <code>ctx system check-knowledge</code> UserPromptSubmit Nudge when knowledge files grow large <code>ctx system check-map-staleness</code> UserPromptSubmit Nudge when ARCHITECTURE.md is stale <code>ctx system heartbeat</code> UserPromptSubmit Session-alive signal with prompt count metadata <code>ctx system post-commit</code> PostToolUse (<code>Bash</code>) Nudge context capture and QA after git commits <p>A catch-all <code>PreToolUse</code> hook also runs <code>ctx agent</code> on every tool use (with cooldown) to autoload context.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#hook-configuration","level":3,"title":"Hook Configuration","text":"<p>The plugin's <code>hooks.json</code> wires everything automatically: no manual configuration in <code>settings.local.json</code> needed:</p> <pre><code>{\n  \"hooks\": {\n    \"PreToolUse\": [\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system context-load-gate\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system block-non-path-ctx\" }\n        ]\n      },\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system qa-reminder\" }\n        ]\n      },\n      {\n        \"matcher\": \"EnterPlanMode\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system specs-nudge\" }\n        ]\n      },\n      {\n        \"matcher\": \".*\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx agent --budget 4000 2>/dev/null || true\" }\n        ]\n      }\n    ],\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Bash\",\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system post-commit\" }\n        ]\n      }\n    ],\n    \"UserPromptSubmit\": [\n      {\n        \"hooks\": [\n          { \"type\": \"command\", \"command\": \"ctx system check-context-size\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-ceremonies\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-persistence\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-journal\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-reminders\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-version\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-resources\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-knowledge\" },\n          { \"type\": \"command\", \"command\": \"ctx system check-map-staleness\" },\n          { \"type\": \"command\", \"command\": \"ctx system heartbeat\" }\n        ]\n      }\n    ]\n  }\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#customizing-token-budget-and-cooldown","level":3,"title":"Customizing Token Budget and Cooldown","text":"<p>Edit the <code>PreToolUse</code> command to change the token budget or cooldown:</p> <pre><code>\"command\": \"ctx agent --budget 8000 --session $PPID >/dev/null || true\"\n\"command\": \"ctx agent --budget 4000 --cooldown 5m --session $PPID >/dev/null || true\"\n</code></pre> <p>The <code>--session $PPID</code> flag isolates the cooldown per session: <code>$PPID</code> resolves to the Claude Code process PID, so concurrent sessions don't interfere. The default cooldown is 10 minutes; use <code>--cooldown 0</code> to disable it.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#verifying-setup","level":3,"title":"Verifying Setup","text":"<ol> <li>Start a new Claude Code session;</li> <li>Ask: \"Do you remember?\"</li> <li>Claude should cite specific context:<ul> <li>Current tasks from <code>.context/TASKS.md</code>;</li> <li>Recent decisions or learnings;</li> <li>Recent session history from <code>ctx journal</code>.</li> </ul> </li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#local-plugin-development","level":3,"title":"Local Plugin Development","text":"<p>When developing <code>ctx</code> locally (adding skills, hooks, or changing plugin behavior), Claude Code caches the plugin by version. You must bump the version in both files and update the marketplace for changes to take effect:</p> <ol> <li>Bump version in both:</li> <li> <p><code>internal/assets/claude/.claude-plugin/plugin.json</code> (plugin manifest),    <code>.claude-plugin/marketplace.json</code> (marketplace listing*);</p> </li> <li> <p>Update the marketplace in Claude Code:</p> </li> <li>Open the Plugins UI (<code>/plugins</code> or Esc menu),</li> <li>Go to Marketplaces tab,</li> <li>Select the <code>activememory-ctx</code> Marketplace,</li> <li> <p>Choose Update marketplace;</p> </li> <li> <p>Start a new Claude Code session: skill changes aren't    reflected in existing sessions.</p> </li> </ol> <p>Both Version Files Must Match</p> <p>If you only bump <code>plugin.json</code> but not <code>marketplace.json</code> (or vice versa), Claude Code may not detect the update. Always bump both together.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#troubleshooting","level":3,"title":"Troubleshooting","text":"Issue Solution Context not loading Check <code>ctx</code> is in PATH: <code>which ctx</code> Hook errors Verify plugin is installed: <code>claude /plugin list</code> New skill not visible Bump version in both <code>plugin.json</code> files, update marketplace","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-load","level":3,"title":"Manual Context Load","text":"<p>If hooks aren't working, manually load context:</p> <pre><code># Get context packet\nctx agent --budget 4000\n\n# Or paste into conversation\ncat .context/TASKS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#agent-skills","level":3,"title":"Agent Skills","text":"<p>The <code>ctx</code> plugin ships Agent Skills following the agentskills.io specification.</p> <p>These are invoked in Claude Code with <code>/skill-name</code>.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-lifecycle-skills","level":4,"title":"Session Lifecycle Skills","text":"Skill Description <code>/ctx-remember</code> Recall project context at session start (ceremony) <code>/ctx-wrap-up</code> End-of-session context persistence (ceremony) <code>/ctx-status</code> Show context summary (tasks, decisions, learnings) <code>/ctx-agent</code> Get AI-optimized context packet <code>/ctx-next</code> Suggest 1-3 concrete next actions from context <code>/ctx-commit</code> Commit with integrated context capture <code>/ctx-reflect</code> Review session and suggest what to persist <code>/ctx-remind</code> Manage session-scoped reminders <code>/ctx-pause</code> Pause context hooks for this session <code>/ctx-resume</code> Resume context hooks after a pause","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-persistence-skills","level":4,"title":"Context Persistence Skills","text":"Skill Description <code>/ctx-task-add</code> Add a task to TASKS.md <code>/ctx-learning-add</code> Add a learning to LEARNINGS.md <code>/ctx-decision-add</code> Add a decision with context/rationale/consequence <code>/ctx-convention-add</code> Add a coding convention to CONVENTIONS.md <code>/ctx-archive</code> Archive completed tasks","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#scratchpad-skills","level":4,"title":"Scratchpad Skills","text":"Skill Description <code>/ctx-pad</code> Manage encrypted scratchpad entries","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-history-skills","level":4,"title":"Session History Skills","text":"Skill Description <code>/ctx-history</code> Browse AI session history <code>/ctx-journal-enrich</code> Enrich a journal entry with frontmatter/tags <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#blogging-skills","level":4,"title":"Blogging Skills","text":"<p>Blogging Is a Better Way of Creating Release Notes</p> <p>The blogging workflow can also double as generating release notes:</p> <p>AI reads your git commit history and creates a \"narrative\", which is essentially what a release note is for.</p> Skill Description <code>/ctx-blog</code> Generate blog post from recent activity <code>/ctx-blog-changelog</code> Generate blog post from commit range with theme","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#auditing-health-skills","level":4,"title":"Auditing & Health Skills","text":"Skill Description <code>/ctx-doctor</code> Troubleshoot <code>ctx</code> behavior with structural health checks <code>/ctx-drift</code> Detect and fix context drift (structural + semantic) <code>/ctx-consolidate</code> Merge redundant learnings or decisions into denser entries <code>/ctx-alignment-audit</code> Audit doc claims against playbook instructions <code>/ctx-prompt-audit</code> Analyze session logs for vague prompts <code>/check-links</code> Audit docs for dead internal and external links","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#planning-execution-skills","level":4,"title":"Planning & Execution Skills","text":"Skill Description <code>/ctx-loop</code> Generate a Ralph Loop iteration script <code>/ctx-implement</code> Execute a plan step-by-step with checks <code>/ctx-plan-import</code> Import Claude Code plan files into project specs <code>/ctx-worktree</code> Manage git worktrees for parallel agents <code>/ctx-architecture</code> Build and maintain architecture maps","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage-examples","level":4,"title":"Usage Examples","text":"<pre><code>/ctx-status\n/ctx-learning-add \"Token refresh requires explicit cache invalidation\"\n/ctx-journal-enrich twinkly-stirring-kettle\n</code></pre> <p>Skills support partial matching where applicable (e.g., session slugs).</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#cursor-ide","level":2,"title":"Cursor IDE","text":"<p>Cursor can use context files through its system prompt or by reading  files directly.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_1","level":3,"title":"Setup","text":"<pre><code># Generate Cursor configuration\nctx setup cursor\n\n# Initialize context\nctx init --minimal\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration","level":3,"title":"Configuration","text":"<p>Add to Cursor settings (<code>.cursor/settings.json</code>):</p> <pre><code>// split to multiple lines for readability\n{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and \n  .context/CONVENTIONS.md before responding. \n  Follow rules in .context/CONSTITUTION.md.\",\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage","level":3,"title":"Usage","text":"<ol> <li>Open your project in Cursor</li> <li>Context files are available in the file tree</li> <li>Reference them in prompts:     \"Check .context/DECISIONS.md for our approach to...\"</li> </ol>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-injection","level":3,"title":"Manual Context Injection","text":"<p>For more control, paste context directly:</p> <pre><code># Get AI-ready packet\nctx agent --budget 4000 | pbcopy  # macOS\nctx agent --budget 4000 | xclip  # Linux\n</code></pre> <p>Paste into Cursor's chat.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#aider","level":2,"title":"Aider","text":"<p>Aider works well with context files through its <code>--read</code> flag.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_2","level":3,"title":"Setup","text":"<pre><code># Generate Aider configuration\nctx setup aider\n\n# Initialize context\nctx init\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_1","level":3,"title":"Configuration","text":"<p>Create <code>.aider.conf.yml</code>:</p> <pre><code>read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_1","level":3,"title":"Usage","text":"<pre><code># Start Aider (reads context files automatically)\naider\n\n# Or specify files explicitly\naider --read .context/TASKS.md --read .context/CONVENTIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#with-watch-mode","level":3,"title":"With Watch Mode","text":"<p>Run <code>ctx watch</code> alongside Aider to capture context updates:</p> <pre><code># Terminal 1: Run Aider\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/aider.log\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#github-copilot","level":2,"title":"GitHub Copilot","text":"<p>GitHub Copilot integrates with <code>ctx</code> at three levels: an automated instructions file, a VS Code Chat extension, and manual patterns.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_3","level":3,"title":"Setup","text":"<pre><code># Initialize context\nctx init\n\n# Generate .github/copilot-instructions.md\nctx setup copilot --write\n</code></pre> <p>The <code>--write</code> flag creates <code>.github/copilot-instructions.md</code>, which Copilot reads automatically at the start of every session. This file contains your project's constitution rules, current tasks, conventions, and architecture: giving Copilot persistent context without manual copy-paste.</p> <p>Re-run <code>ctx setup copilot --write</code> after updating your <code>.context/</code> files to regenerate the instructions.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#vs-code-chat-extension-ctx","level":3,"title":"VS Code Chat Extension (<code>@ctx</code>)","text":"<p>The <code>ctx</code> VS Code extension adds a <code>@ctx</code> chat participant to GitHub Copilot Chat, giving you direct access to 45 context commands from within the editor, plus automatic hooks on file save / git commit / <code>.context/</code> changes / dependency-file edits, and a reminder status-bar indicator.</p> <p>Full guide: <code>ctx</code> for VS Code</p> <p>The home-page guide covers daily workflows, the full command list, natural-language routing, auto-bootstrap of the <code>ctx</code> CLI, troubleshooting, and \"Verify It Works.\" This subsection is the install-and-pointers overview; the dedicated page is the authoritative reference.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#installation","level":4,"title":"Installation","text":"<p>The extension ships to the VS Code Marketplace under publisher <code>activememory</code> (display name: <code>ctx</code>: Persistent Context for AI). Install via the Extensions view or <code>code --install-extension</code>.</p> <p>To build from source instead (requires Node.js 20+):</p> <pre><code>cd editors/vscode\nnpm ci\nnpm run build\nnpx @vscode/vsce package\ncode --install-extension ctx-context-<version>.vsix\n</code></pre> <p>Reload VS Code. Type <code>@ctx</code> in Copilot Chat to verify.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created","level":4,"title":"What Gets Created","text":"File Purpose <code>.context/</code> Project-local context directory (created by <code>ctx init</code>, not by the extension) <code>.github/copilot-instructions.md</code> Repository instructions Copilot reads natively; regenerated automatically when <code>.context/</code> files change <p>The extension itself lives in VS Code's extension storage; no project files beyond <code>.context/</code> and the Copilot instructions are added.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_1","level":4,"title":"How It Works","text":"<ul> <li>Chat participant: <code>@ctx</code> is registered with VS Code's Chat API; 45 slash commands route to dedicated handlers that shell out to the <code>ctx</code> CLI.</li> <li>Automatic hooks: file save → task-completion check; git commit → decision/learning prompt; <code>.context/</code> change → regenerate Copilot instructions; dependency-file change → <code>/map</code> prompt.</li> <li>Status-bar reminder: a <code>$(bell) ctx</code> indicator surfaces pending session reminders, refreshing every 5 minutes.</li> <li>Natural language: plain English after <code>@ctx</code> is routed to the nearest matching command.</li> <li>Auto-bootstrap: if the <code>ctx</code> CLI isn't on PATH, the extension downloads the correct platform binary from GitHub Releases and caches it.</li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_2","level":4,"title":"Configuration","text":"Setting Default Description <code>ctx.executablePath</code> <code>ctx</code> Path to the <code>ctx</code> binary. Set this if <code>ctx</code> is not in your <code>PATH</code>.","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#session-persistence","level":3,"title":"Session Persistence","text":"<p><code>ctx init</code> creates a <code>.context/sessions/</code> directory for storing session data from non-Claude tools. The Markdown session parser scans this directory during <code>ctx journal</code>, enabling session history for Copilot and other tools.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-patterns","level":3,"title":"Manual Patterns","text":"<p>These patterns work without the extension, using Copilot's built-in file awareness:</p> <p>Pattern 1: Keep context files open</p> <p>Open <code>.context/CONVENTIONS.md</code> in a split pane. Copilot will reference it.</p> <p>Pattern 2: Reference in comments</p> <pre><code>// See .context/CONVENTIONS.md for naming patterns\n// Following decision in .context/DECISIONS.md: Use PostgreSQL\n\nfunction getUserById(id: string) {\n  // Copilot now has context\n}\n</code></pre> <p>Pattern 3: Paste context into Copilot Chat</p> <pre><code>ctx agent --budget 2000\n</code></pre> <p>Paste output into Copilot Chat for context-aware responses.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#opencode","level":2,"title":"OpenCode","text":"<p>OpenCode is a terminal-first AI coding agent. <code>ctx</code> integrates via a thin lifecycle plugin, MCP server, and <code>AGENTS.md</code> instructions.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_4","level":3,"title":"Setup","text":"<pre><code># Generate OpenCode plugin, global MCP config, skills, and AGENTS.md\nctx setup opencode --write\n\n# Initialize context\nctx init\neval \"$(ctx activate)\"\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#what-gets-created_1","level":3,"title":"What Gets Created","text":"File Purpose <code>.opencode/plugins/ctx.ts</code> Lifecycle plugin (hooks to <code>ctx system</code>) <code>~/.config/opencode/opencode.json</code> Global MCP server registration (or <code>$OPENCODE_HOME/opencode.json</code>) <code>AGENTS.md</code> Agent instructions (read natively) <code>.opencode/skills/ctx-*/SKILL.md</code> <code>ctx</code> skills","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#how-it-works_2","level":3,"title":"How It Works","text":"<p>The plugin wires OpenCode lifecycle events to <code>ctx system</code>:</p> <ul> <li><code>session.created</code>: warms <code>ctx</code> state in the background (bootstrap + agent packet) so MCP queries are fast on first use.</li> <li><code>tool.execute.after</code> (shell, on <code>git commit</code>): runs <code>ctx system post-commit</code>.</li> <li><code>tool.execute.after</code> (edit/write): runs <code>ctx system check-task-completion</code>.</li> <li><code>session.idle</code>: runs persistence and task-completion checks (silent: output is buffered, not surfaced to the TUI).</li> <li><code>shell.env</code>: injects <code>CTX_DIR</code> into the agent's shell so <code>ctx</code> commands resolve to the right project.</li> <li><code>experimental.session.compacting</code>: pushes <code>ctx system bootstrap</code> output into the compaction context so the agent keeps breadcrumbs back to <code>.context/</code>.</li> </ul> <p>The plugin is a single file with no runtime dependencies; no <code>bun install</code> needed. OpenCode loads it automatically on launch.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-updates","level":3,"title":"Context Updates","text":"<pre><code># Get AI-optimized context packet\nctx agent\n\n# Check context health\nctx status\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#windsurf-ide","level":2,"title":"Windsurf IDE","text":"<p>Windsurf supports custom instructions and file-based context.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#setup_5","level":3,"title":"Setup","text":"<pre><code># Generate Windsurf configuration\nctx setup windsurf\n\n# Initialize context\nctx init\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#configuration_3","level":3,"title":"Configuration","text":"<p>Add to Windsurf settings:</p> <pre><code>// Split to multiple lines for readability\n{\n  \"ai.customInstructions\": \"Always read .context/CONSTITUTION.md first. \n  Check .context/TASKS.md for current work. \n  Follow patterns in .context/CONVENTIONS.md.\"\n}\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#usage_2","level":3,"title":"Usage","text":"<p>Context files appear in the file tree. Reference them when chatting:</p> <ul> <li>\"What's in our task list?\" → AI reads <code>.context/TASKS.md</code></li> <li>\"What convention do we use for naming?\" → AI reads <code>.context/CONVENTIONS.md</code></li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#generic-integration","level":2,"title":"Generic Integration","text":"<p>For any AI tool that can read files, use these patterns:</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#manual-context-loading","level":3,"title":"Manual Context Loading","text":"<pre><code># Get full context\nctx load\n\n# Get AI-optimized packet\nctx agent --budget 8000\n\n# Get specific file\ncat .context/TASKS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#system-prompt-template","level":3,"title":"System Prompt Template","text":"<pre><code>You are working on a project with persistent context in .context/\n\nBefore responding:\n1. Read .context/CONSTITUTION.md - NEVER violate these rules\n2. Check .context/TASKS.md for current work\n3. Follow .context/CONVENTIONS.md patterns\n4. Reference .context/DECISIONS.md for architectural choices\n\nWhen you learn something new, note it for .context/LEARNINGS.md\nWhen you make a decision, document it for .context/DECISIONS.md\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#automated-updates","level":3,"title":"Automated Updates","text":"<p>If your AI tool outputs to a log, use <code>ctx watch</code>:</p> <pre><code># Watch log file for context-update commands\nyour-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n</code></pre> <p>The AI can emit updates like:</p> <pre><code><context-update type=\"complete\">implement caching</context-update>\n<context-update type=\"learning\"\n  context=\"Implementing caching layer\"\n  lesson=\"Important thing learned today\"\n  application=\"Apply this insight going forward\"\n>Caching Insight</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#context-update-commands","level":2,"title":"Context Update Commands","text":"<p>The <code>ctx watch</code> command parses update commands from AI output. Use this format:</p> <pre><code><context-update type=\"TYPE\" [attributes]>Content</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#supported-types","level":3,"title":"Supported Types","text":"Type Target File Required Attributes <code>task</code> TASKS.md None <code>decision</code> DECISIONS.md <code>context</code>, <code>rationale</code>, <code>consequence</code> <code>learning</code> LEARNINGS.md <code>context</code>, <code>lesson</code>, <code>application</code> <code>convention</code> CONVENTIONS.md None <code>complete</code> TASKS.md None","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#simple-format-tasks-conventions-complete","level":3,"title":"Simple Format (Tasks, Conventions, Complete)","text":"<pre><code><context-update type=\"task\">Implement rate limiting</context-update>\n<context-update type=\"convention\">Use kebab-case for files</context-update>\n<context-update type=\"complete\">rate limiting</context-update>\n</code></pre>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#structured-format-learnings-decisions","level":3,"title":"Structured Format (Learnings, Decisions)","text":"<p>Learnings and decisions support structured attributes for better documentation:</p> <p>Learning with full structure:</p> <pre><code><context-update type=\"learning\"\n  context=\"Debugging Claude Code hooks\"\n  lesson=\"Hooks receive JSON via stdin, not environment variables\"\n  application=\"Parse JSON stdin with the host language (Go, Python, etc.): no jq needed\"\n>Hook Input Format</context-update>\n</code></pre> <p>Decision with full structure:</p> <pre><code><context-update type=\"decision\"\n  context=\"Need a caching layer for API responses\"\n  rationale=\"Redis is fast, well-supported, and team has experience\"\n  consequence=\"Must provision Redis infrastructure; team training on Redis patterns\"\n>Use Redis for caching</context-update>\n</code></pre> <p>Learnings require: <code>context</code>, <code>lesson</code>, <code>application</code> attributes. Decisions require: <code>context</code>, <code>rationale</code>, <code>consequence</code> attributes. Updates missing required attributes are rejected with an error.</p>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/integrations/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Skills That Fight the Platform:    Common pitfalls in skill design that work against the host tool</li> <li>The Anatomy of a Skill That Works:   What makes a skill reliable: the E/A/R framework and quality gates</li> </ul>","path":["Operations","Day-to-Day","AI Tools"],"tags":[]},{"location":"operations/migration/","level":1,"title":"Integration","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#adopting-ctx-in-existing-projects","level":2,"title":"Adopting <code>ctx</code> in Existing Projects","text":"<p>Claude Code User?</p> <p>You probably want the plugin instead of this page.</p> <p>Install <code>ctx</code> from the marketplace: (<code>/plugin</code> → search \"<code>ctx</code>\" → Install) and you're done: hooks, skills, and updates are handled for you.</p> <p>See Getting Started for the full walkthrough.</p> <p>This guide covers adopting <code>ctx</code> in existing projects regardless of which tools your team uses.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#quick-paths","level":2,"title":"Quick Paths","text":"You have... Command What happens Nothing (greenfield) <code>ctx init</code> Creates <code>.context/</code>, <code>CLAUDE.md</code>, permissions Existing <code>CLAUDE.md</code> <code>ctx init --merge</code> Backs up your file, inserts <code>ctx</code> block after the H1 Existing <code>CLAUDE.md</code> + <code>ctx</code> markers <code>ctx init --reset</code> Replaces the <code>ctx</code> block, leaves your content intact <code>.cursorrules</code> / <code>.aider.conf.yml</code> <code>ctx init</code> <code>ctx</code> ignores those files: they coexist cleanly Team repo, first adopter <code>ctx init --merge && git add .context/ CLAUDE.md</code> Initialize and commit for the team","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-claudemd","level":2,"title":"Existing <code>CLAUDE.md</code>","text":"<p>This is the most common scenario:</p> <p>You have a <code>CLAUDE.md</code> with project-specific instructions and don't want to  lose them.</p> <p>You Own <code>CLAUDE.md</code></p> <p>After initialization, <code>CLAUDE.md</code> is yours: edit it freely.</p> <p>Add project instructions, remove sections you don't need, reorganize as  you see fit.</p> <p>The only part <code>ctx</code> manages is the block between the <code><!-- ctx:context --></code> and <code><!-- ctx:end --></code> markers; everything outside those markers is yours to change at any time.</p> <p>If you remove the markers, nothing breaks: <code>ctx</code> simply treats the file  as having no <code>ctx</code> content and will offer to merge again on the next  <code>ctx init</code>.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-ctx-init-does","level":3,"title":"What <code>ctx init</code> Does","text":"<p>When <code>ctx init</code> detects an existing <code>CLAUDE.md</code>, it checks for <code>ctx</code> markers (<code><!-- ctx:context --></code> ... <code><!-- ctx:end --></code>):</p> State Default behavior With <code>--merge</code> With <code>--force</code> No <code>CLAUDE.md</code> Creates from template Creates from template Creates from template Exists, no <code>ctx</code> markers Prompts to merge Auto-merges (no prompt) Auto-merges (no prompt) Exists, has <code>ctx</code> markers Skips (already set up) Skips Replaces the <code>ctx</code> block only","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-merge-flag","level":3,"title":"The <code>--merge</code> Flag","text":"<p><code>--merge</code> auto-merges without prompting. The merge process:</p> <ol> <li>Backs up your existing <code>CLAUDE.md</code> to <code>CLAUDE.md.<timestamp>.bak</code>;</li> <li>Finds the H1 heading (e.g., <code># My Project</code>) in your file;</li> <li>Inserts the <code>ctx</code> block immediately after it;</li> <li>Preserves everything else untouched.</li> </ol> <p>Your content before and after the <code>ctx</code> block remains exactly as it was.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#before-after-example","level":3,"title":"Before / After Example","text":"<p>Before: your existing <code>CLAUDE.md</code>:</p> <pre><code># My Project\n\n## Build Commands\n\n-`npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n</code></pre> <p>After <code>ctx init --merge</code>:</p> <pre><code># My Project\n\n<!-- ctx:context -->\n<!-- DO NOT REMOVE: This marker indicates ctx-managed content -->\n\n## IMPORTANT: You Have Persistent Memory\n\nThis project uses Context (`ctx`) for context persistence across sessions.\n...\n\n<!-- ctx:end -->\n\n## Build Commands\n\n- `npm run build`: production build\n- `npm test`: run tests\n\n## Code Style\n\n- Use TypeScript strict mode\n- Prefer named exports\n</code></pre> <p>Your build commands and code style sections are untouched. The <code>ctx</code> block sits between markers and can be updated independently.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#the-force-flag","level":3,"title":"The <code>--force</code> Flag","text":"<p>If your <code>CLAUDE.md</code> already has <code>ctx</code> markers (from a previous <code>ctx init</code>), the default behavior is to skip it. Use <code>--force</code> to replace the <code>ctx</code> block with the latest template: This is useful after upgrading <code>ctx</code>:</p> <pre><code>ctx init --reset\n</code></pre> <p>This only replaces content between <code><!-- ctx:context --></code> and <code><!-- ctx:end --></code>. Your own content outside the markers is preserved. A timestamped backup is created before any changes.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#undoing-a-merge","level":3,"title":"Undoing a Merge","text":"<p>Every merge creates a backup:</p> <pre><code>$ ls CLAUDE.md*.bak\nCLAUDE.md.1738000000.bak\n</code></pre> <p>To restore:</p> <pre><code>cp CLAUDE.md.1738000000.bak CLAUDE.md\n</code></pre> <p>Or if you are using <code>git</code>, simply:</p> <pre><code>git checkout CLAUDE.md\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#existing-cursorrules-aider-copilot","level":2,"title":"Existing <code>.cursorrules</code> / Aider / Copilot","text":"<p><code>ctx</code> doesn't touch tool-specific config files. It creates its own files (<code>.context/</code>, <code>CLAUDE.md</code>) and coexists with whatever you already have.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-does-ctx-create","level":3,"title":"What Does <code>ctx</code> Create?","text":"<code>ctx</code> creates <code>ctx</code> does NOT touch <code>.context/</code> directory <code>.cursorrules</code> <code>CLAUDE.md</code> (or merges into) <code>.aider.conf.yml</code> <code>.claude/settings.local.json</code> (seeded by <code>ctx init</code>; the plugin manages hooks and skills) <code>.github/copilot-instructions.md</code> <code>.windsurfrules</code> Any other tool-specific config <p>Claude Code hooks and skills are provided by the <code>ctx</code> plugin, installed from the Claude Code marketplace (<code>/plugin</code> → search \"<code>ctx</code>\" → Install).</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#running-ctx-alongside-other-tools","level":3,"title":"Running <code>ctx</code> Alongside Other Tools","text":"<p>The <code>.context/</code> directory is the source of truth. Tool-specific configs point to it:</p> <ul> <li>Cursor: Reference <code>.context/</code> files in your system prompt   (see Cursor setup)</li> <li>Aider: Add <code>.context/</code> files to the <code>read:</code> list in <code>.aider.conf.yml</code>   (see Aider setup)</li> <li>Copilot: Keep <code>.context/</code> files open or reference them in comments   (see Copilot setup)</li> </ul> <p>You can generate a tool-specific configuration with:</p> <pre><code>ctx setup cursor    # Generate Cursor config snippet\nctx setup aider     # Generate .aider.conf.yml\nctx setup copilot   # Generate Copilot tips\nctx setup windsurf  # Generate Windsurf config\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#migrating-content-into-context","level":3,"title":"Migrating Content into <code>.context/</code>","text":"<p>If you have project knowledge scattered across <code>.cursorrules</code> or custom prompt files, consider migrating it:</p> <ol> <li>Rules / invariants → <code>.context/CONSTITUTION.md</code></li> <li>Code patterns → <code>.context/CONVENTIONS.md</code></li> <li>Architecture notes → <code>.context/ARCHITECTURE.md</code></li> <li>Known issues / tips → <code>.context/LEARNINGS.md</code></li> </ol> <p>You don't need to delete the originals: <code>ctx</code> and tool-specific files can coexist. But centralizing in <code>.context/</code> means every tool gets the same context.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#team-adoption","level":2,"title":"Team Adoption","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#context-is-designed-to-be-committed","level":3,"title":"<code>.context/</code> Is Designed to Be Committed","text":"<p>The context files (tasks, decisions, learnings, conventions, architecture) are meant to live in version control. However, some subdirectories are personal or sensitive and should not be committed.</p> <p><code>ctx init</code> automatically adds these <code>.gitignore</code> entries:</p> <pre><code># Journals contain full session transcripts: personal, potentially large\n.context/journal/\n.context/journal-site/\n.context/journal-obsidian/\n\n# Legacy encryption key path (copy to ~/.ctx/.ctx.key if needed)\n.context/.ctx.key\n\n# Runtime state and logs (ephemeral, machine-specific):\n.context/state/\n.context/logs/\n\n# Claude Code local settings (machine-specific)\n.claude/settings.local.json\n</code></pre> <p>With those in place, committing is straightforward:</p> <pre><code># One person initializes\nctx init --merge\n\n# Commit context files (journals and keys are already gitignored)\ngit add .context/ CLAUDE.md\ngit commit -m \"Add ctx context management\"\ngit push\n</code></pre> <p>Teammates pull and immediately have context. No per-developer setup needed.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#what-about-claude","level":3,"title":"What about <code>.claude/</code>?","text":"<p>The <code>.claude/</code> directory contains permissions that <code>ctx init</code> seeds. Hooks and skills are provided by the <code>ctx</code> plugin (not per-project files).</p> File Commit? Why <code>.claude/settings.local.json</code> No Machine-specific, accumulates session permissions <code>.claude/settings.golden.json</code> Yes Curated permission snapshot (via <code>ctx permission snapshot</code>)","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#merge-conflicts-in-context-files","level":3,"title":"Merge Conflicts in Context Files","text":"<p>Context files are plain Markdown. Resolve conflicts the same way you would for any other documentation file:</p> <pre><code># After a conflicting pull\ngit diff .context/TASKS.md    # See both sides\n# Edit to keep both sets of tasks, then:\ngit add .context/TASKS.md\ngit commit\n</code></pre> <p>Common conflict scenarios:</p> <ul> <li>TASKS.md: Two people added tasks: Keep both.</li> <li>DECISIONS.md: Same decision recorded differently: Unify the entry.</li> <li>LEARNINGS.md: Parallel discoveries: Keep both, remove duplicates.</li> </ul>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#gradual-adoption","level":3,"title":"Gradual Adoption","text":"<p>You don't need the whole team to switch at once:</p> <ol> <li>One person runs <code>ctx init --merge</code> and commits;</li> <li><code>CLAUDE.md</code> instructions work immediately for Claude Code users;</li> <li>Other tool users can adopt at their own pace using <code>ctx setup <tool></code>;</li> <li>Context files benefit everyone who reads them, even without tool integration.</li> </ol>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verifying-it-worked","level":2,"title":"Verifying It Worked","text":"","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#activate-the-project","level":3,"title":"Activate the Project","text":"<p>Tell <code>ctx</code> which <code>.context/</code> directory to use for the rest of the verification steps:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>You only need to run this once per terminal. If you skip it, the status check below fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#check-status","level":3,"title":"Check Status","text":"<pre><code>ctx status\n</code></pre> <p>You should see your context files listed with token counts and no warnings.</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#test-memory","level":3,"title":"Test Memory","text":"<p>Start a new AI session and ask: \"Do you remember?\"</p> <p>The AI should cite specific context:</p> <ul> <li>Current tasks from <code>.context/TASKS.md</code>;</li> <li>Recent decisions or learnings;</li> <li>Session history (if you've had prior sessions);</li> </ul> <p>If it responds with generic \"I don't have memory\", check that <code>ctx</code> is in your PATH (<code>which ctx</code>) and that hooks are configured (see Troubleshooting).</p>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#verify-the-merge","level":3,"title":"Verify the Merge","text":"<p>If you used <code>--merge</code>, check that your original content is intact:</p> <pre><code># Your original content should still be there\ncat CLAUDE.md\n\n# The ctx block should be between markers\ngrep -c \"ctx:context\" CLAUDE.md  # Should print 1\ngrep -c \"ctx:end\" CLAUDE.md      # Should print 1\n</code></pre>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/migration/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Getting Started: Full setup walkthrough</li> <li>Context Files: What each <code>.context/</code> file does</li> <li>Integrations: Per-tool setup (Claude Code, Cursor, Aider, Copilot)</li> <li>CLI Reference: All <code>ctx</code> commands and flags</li> </ul>","path":["Operations","Day-to-Day","Integration"],"tags":[]},{"location":"operations/release/","level":1,"title":"Cutting a Release","text":"<p>Full Release Checklist</p> <p>This page covers the mechanics of cutting a release (bump, tag, push). For the complete pre-release ceremony (audits, tests, verification, and post-release steps), see the Release Checklist runbook.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#prerequisites","level":2,"title":"Prerequisites","text":"<p>Before you can cut a release you need:</p> <ul> <li>Push access to <code>origin</code> (GitHub)</li> <li>GPG signing configured (<code>make gpg-test</code>)</li> <li>Go installed (version in <code>go.mod</code>)</li> <li>Zensical installed (<code>make site-setup</code>)</li> <li>A clean working tree (<code>git status</code> shows nothing to commit)</li> </ul>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#step-by-step","level":2,"title":"Step-by-Step","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#1-update-the-version-file","level":3,"title":"1. Update the VERSION File","text":"<pre><code>echo \"0.9.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.9.0\"\n</code></pre> <p>The VERSION file uses bare semver (<code>0.9.0</code>), no <code>v</code> prefix. The release script adds the <code>v</code> prefix for git tags.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#2-generate-release-notes","level":3,"title":"2. Generate Release Notes","text":"<p>In Claude Code:</p> <pre><code>/_ctx-release-notes\n</code></pre> <p>This analyzes commits since the last tag and writes <code>dist/RELEASE_NOTES.md</code>. The release script refuses to proceed without this file.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#3-verify-docs-and-commit-any-remaining-changes","level":3,"title":"3. Verify Docs and Commit Any Remaining Changes","text":"<pre><code>/ctx-link-check    # audit docs for dead links\nmake audit          # full check: fmt, vet, lint, style, test\ngit status          # must be clean\n</code></pre>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#4-run-the-release","level":3,"title":"4. Run the Release","text":"<pre><code>make release\n</code></pre> <p>Or, if you are in a Claude Code session:</p> <pre><code>/_ctx-release\n</code></pre> <p>The release script does everything in order:</p> Step What happens 1 Reads <code>VERSION</code>, verifies release notes exist 2 Verifies working tree is clean 3 Updates version in 4 config files (plugin.json, marketplace.json, VS Code package.json + lock) 4 Updates download URLs in 3 doc files (index.md, getting-started.md, integrations.md) 5 Adds new row to versions.md 6 Rebuilds the documentation site (<code>make site</code>) 7 Commits all version and docs updates 8 Runs <code>make test</code> and <code>make smoke</code> 9 Builds binaries for all 6 platforms via <code>hack/build-all.sh</code> 10 Creates a signed git tag (<code>v0.9.0</code>) 11 Pushes the tag to origin 12 Updates and pushes the <code>latest</code> tag","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#5-github-ci-takes-over","level":3,"title":"5. GitHub CI Takes Over","text":"<p>Pushing a <code>v*</code> tag triggers <code>.github/workflows/release.yml</code>:</p> <ol> <li>Checks out the tagged commit</li> <li>Runs the full test suite</li> <li>Builds binaries for all platforms</li> <li>Creates a GitHub Release with auto-generated notes</li> <li>Uploads binaries and SHA256 checksums</li> </ol>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#6-verify","level":3,"title":"6. Verify","text":"<ul> <li> GitHub Releases shows the new version</li> <li> All 6 binaries are attached (linux/darwin x amd64/arm64, windows x amd64)</li> <li> SHA256 files are attached</li> <li> Release notes look correct</li> </ul>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#what-gets-updated-automatically","level":2,"title":"What Gets Updated Automatically","text":"<p>The release script updates 8 files so you do not have to:</p> File What changes <code>internal/assets/claude/.claude-plugin/plugin.json</code> Plugin version <code>.claude-plugin/marketplace.json</code> Marketplace version (2 fields) <code>editors/vscode/package.json</code> VS Code extension version <code>editors/vscode/package-lock.json</code> VS Code lock version (2 fields) <code>docs/index.md</code> Download URLs <code>docs/home/getting-started.md</code> Download URLs <code>docs/operations/integrations.md</code> VSIX filename version <code>docs/reference/versions.md</code> New version row + latest pointer <p>The Go binary version is injected at build time via <code>-ldflags</code> from the VERSION file. No source file needs editing.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#build-targets-reference","level":2,"title":"Build Targets Reference","text":"Target What it does <code>make release</code> Full release (script + tag + push) <code>make build</code> Build binary for current platform <code>make build-all</code> Build all 6 platform binaries <code>make test</code> Unit tests <code>make smoke</code> Integration smoke tests <code>make audit</code> Full check (fmt + vet + lint + drift + docs + test) <code>make site</code> Rebuild documentation site","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#release-notes-not-found","level":3,"title":"\"Release Notes Not Found\"","text":"<pre><code>ERROR: dist/RELEASE_NOTES.md not found.\n</code></pre> <p>Run <code>/_ctx-release-notes</code> in Claude Code first, or write <code>dist/RELEASE_NOTES.md</code> manually.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#working-tree-is-not-clean","level":3,"title":"\"Working Tree Is Not Clean\"","text":"<pre><code>ERROR: Working tree is not clean.\n</code></pre> <p>Commit or stash all changes before running <code>make release</code>.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#tag-already-exists","level":3,"title":"\"Tag Already Exists\"","text":"<pre><code>ERROR: Tag v0.9.0 already exists.\n</code></pre> <p>You cannot release the same version twice. Either bump VERSION to a new version, or delete the old tag if the release was incomplete:</p> <pre><code>git tag -d v0.9.0\ngit push origin :refs/tags/v0.9.0\n</code></pre>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/release/#ci-build-fails-after-tag-push","level":3,"title":"CI Build Fails After Tag Push","text":"<p>The tag is already published. Fix the issue, bump to a patch version (e.g. <code>0.9.1</code>), and release again. Do not force-push tags that others may have already fetched.</p>","path":["Operations","Maintainers","Cutting a Release"],"tags":[]},{"location":"operations/upgrading/","level":1,"title":"Upgrade","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade","level":2,"title":"Upgrade","text":"<p>New versions of <code>ctx</code> may ship updated permissions, <code>CLAUDE.md</code> directives, or plugin hooks and skills.</p> <p>Claude Code User?</p> <p>The marketplace can update skills, hooks, and prompts independently: <code>/plugin</code> → select <code>ctx</code> → Update now (or enable auto-update).</p> <p>The <code>ctx</code> binary is separate: rebuild from source or download a new release when one is available, then run <code>ctx init --reset --merge</code>. Knowledge files are preserved automatically.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#tldr","level":2,"title":"TL:DR","text":"<pre><code># Plugin users (Claude Code)\n# /plugin → select ctx → Update now\n# Then update the binary and reinitialize:\nctx init --reset --merge\n\n# From-source / manual users\n# install new ctx binary, then:\nctx init --reset --merge\n# /plugin → select ctx → Update now   (if using Claude Code)\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-changes-between-versions","level":2,"title":"What Changes between Versions","text":"<p><code>ctx init</code> generates two categories of files:</p> Category Examples Changes between versions? Infrastructure <code>.claude/settings.local.json</code> (permissions), ctx-managed sections in <code>CLAUDE.md</code>, <code>ctx</code> plugin (hooks + skills) Yes Knowledge <code>.context/TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>, <code>ARCHITECTURE.md</code>, <code>GLOSSARY.md</code>, <code>CONSTITUTION.md</code>, <code>AGENT_PLAYBOOK.md</code> No: this is your data <p>Infrastructure is regenerated by <code>ctx init</code> and plugin updates. Knowledge files are yours and should never be overwritten.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#upgrade-steps","level":2,"title":"Upgrade Steps","text":"","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#1-install-the-new-version","level":3,"title":"1. Install the New Version","text":"<p>Build from source or download the binary:</p> <pre><code>cd /path/to/ctx-source\ngit pull\nmake build\nsudo make install\nctx --version   # verify\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#2-reinitialize","level":3,"title":"2. Reinitialize","text":"<pre><code>ctx init --reset --merge\n</code></pre> <ul> <li><code>--force</code> regenerates infrastructure files (permissions, ctx-managed   sections in <code>CLAUDE.md</code>).</li> <li><code>--merge</code> preserves your content outside <code>ctx</code> markers.</li> </ul> <p>Knowledge files (<code>.context/TASKS.md</code>, <code>DECISIONS.md</code>, etc.) are preserved automatically: <code>ctx init</code> only overwrites infrastructure, never your data.</p> <p>Encryption key: The encryption key lives at <code>~/.ctx/.ctx.key</code> (outside the project). Reinit does not affect it. If you have a legacy key at <code>.context/.ctx.key</code> or <code>~/.local/ctx/keys/</code>, copy it manually (see Syncing Scratchpad Notes).</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#3-update-the-ctx-plugin","level":3,"title":"3. Update the <code>ctx</code> Plugin","text":"<p>If you use Claude Code, update the plugin to get new hooks and skills:</p> <ol> <li>Open <code>/plugin</code> in Claude Code.</li> <li>Select <code>ctx</code>.</li> <li>Click Update now.</li> </ol> <p>Or enable auto-update so the plugin stays current without manual steps.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#4-review-custom-settings","level":3,"title":"4. Review Custom Settings","text":"<p>If you added custom permissions to <code>.claude/settings.local.json</code> beyond what <code>ctx init</code> provides, diff and merge:</p> <pre><code>diff .claude.bak/settings.local.json .claude/settings.local.json\n</code></pre> <p>Manually add back any custom entries that the new init dropped.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#5-verify","level":3,"title":"5. Verify","text":"<p>Activate the project first, otherwise <code>ctx status</code> and <code>ctx drift</code> will fail with <code>Error: no context directory specified</code>:</p> <pre><code>eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#6-clean-up","level":3,"title":"6. Clean Up","text":"<p>If you made manual backups, remove them once satisfied:</p> <pre><code>rm -rf .context.bak .claude.bak CLAUDE.md.bak\n</code></pre>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/upgrading/#what-if-i-skip-the-upgrade","level":2,"title":"What If I Skip the Upgrade?","text":"<p>The old binary still works with your existing <code>.context/</code> files. But you may miss:</p> <ul> <li>New plugin hooks that enforce better practices or catch mistakes;</li> <li>Updated skill prompts that produce better results;</li> <li>New <code>.gitignore</code> entries for directories added in newer versions;</li> <li>Bug fixes in the CLI itself.</li> </ul> <p>The plugin and the binary can be updated independently. You can update the plugin (for new hooks/skills) even if you stay on an older binary, and vice versa.</p> <p>Context files are plain Markdown: They never break between versions.</p> <p>The surrounding infrastructure is what evolves.</p>","path":["Operations","Day-to-Day","Upgrade"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/","level":1,"title":"Architecture Exploration","text":"","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#architecture-exploration","level":1,"title":"Architecture Exploration","text":"<p>Systematically build architecture documentation across one or more repositories using <code>ctx</code> skills. Each invocation does one unit of work; a simple loop drives the agent through all phases.</p> <p>When to use: When onboarding to a new codebase, performing architecture reviews, or building up <code>.context/</code> documentation across a workspace of repos.</p> <p>Prerequisites: <code>ctx</code> installed, repos cloned under a shared workspace directory (e.g., <code>~/WORKSPACE/</code>).</p> <p>Companion skills:</p> <ul> <li><code>/ctx-architecture</code>: structural baseline and principal analysis</li> <li><code>/ctx-architecture-enrich</code>: code intelligence enrichment via GitNexus</li> <li><code>/ctx-architecture-failure-analysis</code>: adversarial failure analysis</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#overview","level":2,"title":"Overview","text":"<p>The agent progresses through phases per repo, depth-first:</p> Phase Skill What it does <code>bootstrap</code> <code>ctx init</code> + <code>/ctx-architecture</code> Initialize context and build structural baseline <code>principal</code> <code>/ctx-architecture principal</code> Deep analysis: vision, bottlenecks, alternatives <code>enriched</code> <code>/ctx-architecture-enrich</code> Quantify with code intelligence (blast radius, flows) <code>frontier-N</code> <code>/ctx-architecture</code> (re-run) Explore unexplored areas found in convergence report <code>lens-*</code> <code>/ctx-architecture</code> with lens Focused exploration through conceptual lenses <p>Exploration stops when convergence >= 0.85, frontier runs plateau, or all lenses are exhausted.</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#setup","level":2,"title":"Setup","text":"<p>Create a tracking directory in your workspace root:</p> <pre><code>cd ~/WORKSPACE\nmkdir -p .arch-explorer\n</code></pre> <p>Create <code>.arch-explorer/manifest.json</code> listing your repos:</p> <pre><code>{\n  \"repos\": [\"ctx\", \"portal\", \"infra\"],\n  \"current_repo_index\": 0,\n  \"progress\": {}\n}\n</code></pre> <p>Create <code>.arch-explorer/run-log.md</code> (empty, the agent appends to it).</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#prompt","level":2,"title":"Prompt","text":"<p>Save this as <code>.arch-explorer/PROMPT.md</code> and invoke with your agent. The prompt is self-contained: the agent reads the manifest, picks the next unit of work, executes it, updates tracking, and stops.</p> <pre><code>You are an autonomous architecture exploration agent. Your job is to\nsystematically build and evolve architecture documentation across all\nrepositories in this workspace using `ctx` skills.\n\n## Execution Protocol\n\n### Step 1: Read State\n\nRead `.arch-explorer/manifest.json`. This tells you:\n- Which repos exist and their order\n- What has been done per repo (`progress` object)\n- Which repo to work on next (`current_repo_index`)\n\n### Step 2: Pick the Next Unit of Work\n\n**Strategy: depth-first, sequential.**\n\nFind the current repo (by `current_repo_index`). Determine its next\nphase from the progression below. If all phases are exhausted for this\nrepo (convergence score >= 0.85 or 3+ frontier runs with no new\nfindings), advance `current_repo_index` and pick the next repo.\n\n### Phase Progression (per repo)\n\nEach repo progresses through these phases in order:\n\n| Phase | Skill | Prerequisite |\n|-------|-------|-------------|\n| `bootstrap` | `ctx init` + `/ctx-architecture` | None |\n| `principal` | `/ctx-architecture principal` | bootstrap done |\n| `enriched` | `/ctx-architecture-enrich` | principal done, GitNexus indexed |\n| `frontier-N` | `/ctx-architecture` (re-run) | enriched done |\n\n**`bootstrap` is a single composite unit:** `ctx init` followed by\nstructural analysis. This is the ONLY phase that combines two actions.\nNo other phase may chain actions.\n\n**Frontier runs** are numbered: `frontier-1`, `frontier-2`, etc.\nEach frontier run reads CONVERGENCE-REPORT.md and picks unexplored\nareas. The skill handles this automatically.\n\nAfter the third frontier run OR when convergence >= 0.85, apply\n**conceptual lenses** (one per run):\n\n| Lens | Focus Areas |\n|------|-------------|\n| `security` | Auth flows, input validation, secrets, attack surfaces, trust boundaries |\n| `performance` | Hot paths, caching, concurrency, resource lifecycle, allocation patterns |\n| `stability` | Error handling, retries, graceful degradation, circuit breakers, timeouts |\n| `observability` | Logging, metrics, tracing, alerting, debugging affordances |\n| `data-integrity` | Storage, serialization, migrations, consistency, backup, recovery |\n\nFor lens runs, prepend the lens context as an explicit instruction to\nthe skill invocation:\n\n> \"Focus exploration on security: auth flows, input validation, secrets,\n> attack surfaces, trust boundaries.\"\n\nDo NOT wait for the skill to ask what to explore. Provide the lens\nfocus as input upfront.\n\n### Step 3: Do the Work\n\n1. `cd` into the sub-repo directory (`~/WORKSPACE/<repo-name>`, NOT\n   `~/WORKSPACE` itself).\n2. Verify `CTX_DIR` already points at THIS sub-repo's `.context/`:\n\n    ```bash\n    test \"$CTX_DIR\" = \"$PWD/.context\" || {\n      echo \"STOP: CTX_DIR=$CTX_DIR but this sub-repo needs $PWD/.context.\"\n      echo \"Re-launch the agent with CTX_DIR set to the sub-repo:\"\n      echo \"  cd $PWD && CTX_DIR=\\\"\\$PWD/.context\\\" claude --print 'Follow .arch-explorer/PROMPT.md' --allowedTools '*'\"\n      exit 1\n    }\n    ```\n\n    If it fails, STOP. The agent cannot change `CTX_DIR` for itself:\n    child shells and skill invocations inherit the parent Claude\n    process environment, which only the caller can control. Do not\n    proceed, do not run `ctx` commands, do not skip the check.\n3. If phase is `bootstrap`:\n    - Run `ctx init`, confirm `.context/` exists.\n    - Then run `/ctx-architecture` (structural baseline).\n4. If phase is `principal` or `frontier-*`:\n    - Run `/ctx-architecture` (add `principal` argument for principal phase).\n    - The skill will read existing artifacts and build on them.\n5. If phase is `enriched`:\n    - Verify GitNexus is connected: call `mcp__gitnexus__list_repos`.\n    - Success = non-empty list returned with no error.\n    - If GitNexus unavailable, log as `enriched-skipped` and advance\n      to `frontier-1`.\n    - Run `/ctx-architecture-enrich`.\n6. If phase is a lens run (`lens-security`, etc.):\n    - Run `/ctx-architecture` with lens focus prepended as instruction\n      (see lens table above for exact wording).\n\n### Step 4: Extract Results\n\nAfter the skill completes, gather:\n\n- **Convergence score**: from `map-tracking.json`, computed as:\n  average of all module `confidence` values (0.0-1.0). If\n  `map-tracking.json` is missing or has no confidence values,\n  record `null` and log a warning.\n- **Frontier count**: from CONVERGENCE-REPORT.md, count the number\n  of listed unexplored areas. If CONVERGENCE-REPORT.md is missing,\n  record `frontier_count: null` and log a warning. Treat missing\n  as \"exploration should continue\" (do not stall).\n- **Key findings**: 2-3 bullet points of what was discovered or\n  changed in this run (new modules mapped, danger zones found, etc.)\n- **New artifacts**: list any new files created in `.context/`\n\n### Step 5: Update Tracking\n\nUpdate `.arch-explorer/manifest.json`:\n\n```json\n{\n  \"progress\": {\n    \"ctx\": {\n      \"phases_completed\": [\"bootstrap\", \"principal\"],\n      \"current_phase\": \"enriched\",\n      \"lenses_explored\": [],\n      \"last_run\": \"2026-04-07T14:00:00Z\",\n      \"convergence_score\": 0.72,\n      \"frontier_count\": 3,\n      \"total_runs\": 2,\n      \"findings_summary\": \"14 modules mapped, 3 danger zones, 2 extension points\"\n    }\n  }\n}\n```\n\nAppend to `.arch-explorer/run-log.md`:\n\n```markdown\n## 2026-04-07T14:00:00Z / ctx / principal\n\n**Phase:** principal\n**Convergence:** 0.45 -> 0.72\n**Frontiers remaining:** 3\n**Key findings:**\n- Identified CLI dispatch as primary bottleneck (fan-out to 12 subsystems)\n- Security: context files readable by any process (no access control)\n- Strategic recommendation: extract context engine into library package\n\n**Artifacts updated:** ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md, map-tracking.json\n```\n\n### Step 6: Report and Stop\n\nPrint this exact format as the FINAL output of the invocation:\n\n```\n[arch-explorer] DONE\n  repo: ctx\n  phase: principal\n  convergence: 0.72\n  frontiers: 3\n  runs_on_repo: 3\n  next: ctx / enriched\n```\n\nThe `[arch-explorer] DONE` line is the terminal marker. After printing\nit, produce no further output. Execution is complete.\n\n## Rules\n\n1. **One unit per invocation.** The only composite unit is `bootstrap`\n   (init + structural). All other phases are exactly one skill run.\n2. **Additive only.** Never delete or overwrite existing artifacts.\n   The skills already handle incremental updates.\n3. **No duplicated work.** Read manifest before acting. If a phase is\n   already recorded as completed, skip it.\n4. **Log everything.** Every run gets a run-log entry, even failures\n   and skips.\n5. **Fail gracefully.** If a skill fails (missing GitNexus, broken repo,\n   etc.), log the failure with reason and advance to the next phase or\n   repo. Don't retry in the same invocation.\n6. **Respect `ctx` conventions.** Each repo gets its own `.context/`\n   directory. Never write architecture artifacts outside `.context/`.\n\n## Stopping Logic\n\nA repo is considered \"explored\" when ANY of these is true:\n- Convergence score >= 0.85 (from map-tracking.json)\n- 3+ frontier runs produced no new findings (frontier_count unchanged\n  across consecutive runs)\n- All 5 lenses have been applied\n- Convergence score is `null` after 3 attempts (artifacts aren't being\n  generated properly; log warning and move on)\n\nWhen a repo is explored, advance `current_repo_index` in the manifest.\n\n## When All Repos Are Done\n\nWhen every repo has reached its stopping condition, print:\n\n```\n[arch-explorer] ALL DONE\n  - ctx: 0.92 convergence, 8 runs, 5 lenses\n  - portal: 0.87 convergence, 6 runs, 3 lenses\n  ...\n```\n</code></pre>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#invocation","level":2,"title":"Invocation","text":"<p>The caller MUST set <code>CTX_DIR</code> to the sub-repo the agent will work on. The agent verifies this at Step 3.2 and stops if it does not match. The wrapper reads the manifest to pick the current sub-repo, then launches <code>claude</code> with <code>CTX_DIR</code> pinned to that sub-repo's <code>.context/</code>.</p> <p>Single run (safest for quota):</p> <pre><code>cd ~/WORKSPACE\nREPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\nCTX_DIR=\"$PWD/$REPO/.context\" \\\n  claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n</code></pre> <p>Batch of N runs:</p> <pre><code>cd ~/WORKSPACE\nfor i in $(seq 1 5); do\n  REPO=$(jq -r '.repos[.current_repo_index]' .arch-explorer/manifest.json)\n  CTX_DIR=\"$PWD/$REPO/.context\" \\\n    claude --print \"Follow .arch-explorer/PROMPT.md\" --allowedTools '*'\n  echo \"--- Run $i complete (repo: $REPO) ---\"\ndone\n</code></pre> <p>Resume after interruption:</p> <p>Just run the wrapper again. The manifest tracks state; the agent picks up where it left off. <code>CTX_DIR</code> is recomputed from the manifest on each invocation, so the right sub-repo is always bound.</p>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#tips","level":2,"title":"Tips","text":"<ul> <li>Start small: list 1-2 repos in the manifest first. Add more   once you're confident in the output quality.</li> <li>GitNexus is optional: the enrichment phase is skipped   gracefully if GitNexus isn't connected. You still get structural   and principal analysis.</li> <li>Review between batches: check the run-log and generated   artifacts between batch runs. The agent is additive-only, but   early course correction saves wasted runs.</li> <li>Lens runs are the payoff: the first three phases build the   map; lens runs find the interesting things (security gaps,   performance cliffs, stability risks).</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/architecture-exploration/#history","level":2,"title":"History","text":"<ul> <li>2026-04-07: Original prompt created as <code>hack/agents/architecture-explorer.md</code>.</li> <li>2026-04-16: Moved to docs as a runbook for discoverability.</li> <li>2026-04-20: Added <code>CTX_DIR</code> verification at Step 3.2 and per-invocation   <code>CTX_DIR</code> binding in the wrapper, so the agent writes artifacts to the   sub-repo's <code>.context/</code> instead of the inherited workspace one.</li> </ul>","path":["Operations","Runbooks","Architecture Exploration"],"tags":[]},{"location":"operations/runbooks/backup-strategy/","level":1,"title":"Backup Strategy","text":"<p><code>ctx backup</code> was removed. File-level backup is not <code>ctx</code>'s responsibility; your OS or a dedicated backup tool handles it better and without locking you into a specific mount strategy.</p> <p>This runbook explains what to back up, how <code>ctx hub</code> reduces the surface, and what options exist for the rest.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-to-back-up","level":2,"title":"What To Back Up","text":"<p>Per project:</p> <ul> <li><code>.context/</code>: all context files, journal, state, scratchpad.</li> <li><code>.claude/</code>: Claude Code settings, hooks, skills specific to the   project. Skip this entry when it lives in git; the repo is the   backup.</li> </ul> <p>Per user:</p> <ul> <li><code>~/.ctx/</code>: global config, the encryption key (<code>~/.ctx/.ctx.key</code>),   hub data directory (if running a local hub).</li> </ul>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#how-hub-reduces-backup-needs","level":2,"title":"How Hub Reduces Backup Needs","text":"<p><code>ctx hub</code> replicates the knowledge surface across machines:</p> <ul> <li><code>DECISIONS.md</code></li> <li><code>LEARNINGS.md</code></li> <li><code>CONVENTIONS.md</code></li> <li><code>CONSTITUTION.md</code></li> <li><code>ARCHITECTURE.md</code></li> <li>Task items promoted to hub</li> </ul> <p>If you run <code>ctx hub</code> (as a server or by subscribing to someone else's), the data that matters most survives losing any single machine.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#what-hub-does-not-replicate","level":2,"title":"What Hub Does Not Replicate","text":"<p>Hub is not a file-level backup. The following still live only on the machine that produced them:</p> <ul> <li>Journal entries (<code>.context/journal/*.md</code>)</li> <li>Runtime state (<code>.context/state/*</code>)</li> <li>Session event log (<code>.context/events.jsonl</code>)</li> <li>Scratchpad (<code>.context/.pad</code>)</li> <li>Encrypted notify/webhook config (<code>.context/.notify.enc</code>)</li> <li>The encryption key itself (<code>~/.ctx/.ctx.key</code>)</li> </ul> <p>If you need those to survive a disk failure, use a file-level backup.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#example-strategies","level":2,"title":"Example Strategies","text":"","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#1-cron-rsync-to-nas-or-external-drive","level":3,"title":"1. cron + rsync to NAS or External Drive","text":"<pre><code># Daily at 03:00, mirror ~/WORKSPACE and ~/.ctx to NAS\n0 3 * * * rsync -a --delete \\\n    --exclude='node_modules' \\\n    --exclude='dist' \\\n    --exclude='.context/state' \\\n    ~/WORKSPACE/ /mnt/nas/backup/workspace/\n0 3 * * * rsync -a --delete ~/.ctx/ /mnt/nas/backup/ctx-global/\n</code></pre> <p>Adjust excludes for the trash you don't want to back up. The <code>.context/state/</code> dir is ephemeral per-session; skip it.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#2-cron-cp-to-a-cloud-synced-directory","level":3,"title":"2. cron + cp to a Cloud-Synced Directory","text":"<p>iCloud Drive, Dropbox, or any directory watched by a sync client:</p> <pre><code>0 3 * * * cp -a ~/WORKSPACE/some-project/.context \\\n    ~/CloudDrive/ctx-backups/some-project/$(date +\\%Y-\\%m-\\%d)\n</code></pre> <p>Daily snapshots, cloud provider handles the replication.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#3-time-machine-macos","level":3,"title":"3. Time Machine (macOS)","text":"<p>If you already run Time Machine, ensure <code>~/WORKSPACE</code> and <code>~/.ctx</code> are not in its exclusion list. Time Machine handles versioning; you get point-in-time recovery for free.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#4-borg-or-restic-for-versioned-backups","level":3,"title":"4. Borg or restic for Versioned Backups","text":"<p>For deduplicated, versioned, encrypted backups:</p> <pre><code># Borg init (once)\nborg init --encryption=repokey /mnt/nas/borg-ctx\n\n# Daily backup\nborg create /mnt/nas/borg-ctx::'ctx-{now}' \\\n    ~/WORKSPACE ~/.ctx \\\n    --exclude '*/node_modules' \\\n    --exclude '*/.context/state'\n</code></pre> <p>Use <code>restic</code> if you prefer S3-compatible targets.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#when-you-still-need-file-level-backup-even-with-hub","level":2,"title":"When You Still Need File-Level Backup Even With Hub","text":"<ul> <li>Journal: session histories are local-only until exported.</li> <li>Scratchpad: private notes, encrypted locally.</li> <li>Encryption key: losing <code>~/.ctx/.ctx.key</code> means losing access   to every encrypted file in every project.</li> <li>Non-hub projects: projects that never called <code>ctx hub   register</code> have zero cross-machine persistence.</li> </ul> <p>For these, pick one strategy above and forget about it.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/backup-strategy/#why-ctx-no-longer-ships-a-backup-command","level":2,"title":"Why <code>ctx</code> No Longer Ships a Backup Command","text":"<p>Backup is inherently environment-specific: SMB, NFS, S3, rsync, Time Machine, Borg, restic. Every user has a different story. The previous <code>ctx backup</code> picked SMB via GVFS, which was Linux-only and narrow. Chasing mount strategies would never generalize.</p> <p>Hub is the right answer for the data <code>ctx</code> owns (knowledge). For everything else, your OS or a dedicated backup tool is the right layer.</p>","path":["Backup Strategy"],"tags":[]},{"location":"operations/runbooks/breaking-migration/","level":1,"title":"Breaking Migration","text":"","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#breaking-migration-guide","level":1,"title":"Breaking Migration Guide","text":"<p>Template for upgrading across breaking CLI renames or behavior changes. Use this as a starting point when writing migration notes for a specific release, or hand it to your agent as context for generating release-specific guidance.</p> <p>When to use: When a release includes breaking changes (command renames, removed flags, changed defaults) that require user action.</p> <p>Companion: Upgrade guide covers the general upgrade flow. This runbook covers the breaking-change specifics.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-1-identify-what-changed","level":2,"title":"Step 1: Identify What Changed","text":"<p>Ask your agent to diff the CLI surface between the old and new version:</p> <pre><code>Compare the CLI command surface between the previous release tag\nand HEAD. For each change, categorize as: renamed, removed,\nnew, or changed-behavior. Include old and new command signatures.\n</code></pre> <p>Or use the <code>/_ctx-command-audit</code> skill after the rename.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-2-regenerate-infrastructure","level":2,"title":"Step 2: Regenerate Infrastructure","text":"<pre><code># Install the new binary\nmake build && sudo make install\n\n# Regenerate CLAUDE.md and permissions\nctx init --reset --merge\n</code></pre> <p><code>--merge</code> preserves your knowledge files (TASKS.md, DECISIONS.md, etc.) while regenerating infrastructure (permissions, CLAUDE.md managed sections).</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-3-update-the-plugin","level":2,"title":"Step 3: Update the Plugin","text":"<pre><code>/plugin -> select ctx -> Update now\n</code></pre> <p>Or, if using a local clone:</p> <pre><code>make plugin-reload\n# restart Claude Code\n</code></pre>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-4-update-personal-scripts","level":2,"title":"Step 4: Update Personal Scripts","text":"<p>Search your scripts and aliases for old command names:</p> <pre><code># Example: find references to old command names\ngrep -r \"ctx old-command\" ~/scripts/ ~/.zshrc ~/.bashrc\n</code></pre> <p>Replace with the new names per the changelog.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-5-update-hook-configs","level":2,"title":"Step 5: Update Hook Configs","text":"<p>If you have custom hooks in <code>.claude/settings.local.json</code> that reference <code>ctx</code> commands, update them:</p> <pre><code>jq '.hooks' .claude/settings.local.json | grep \"ctx \"\n</code></pre>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#step-6-verify","level":2,"title":"Step 6: Verify","text":"<p>Activate the project first, otherwise <code>ctx status</code> and <code>ctx drift</code> will fail with <code>Error: no context directory specified</code>:</p> <pre><code>eval \"$(ctx activate)\"\nctx status          # context files intact\nctx drift           # no broken references\nmake test           # if you're a contributor\n</code></pre> <p>See Activating a Context Directory.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/breaking-migration/#writing-release-specific-migration-notes","level":2,"title":"Writing Release-Specific Migration Notes","text":"<p>When preparing a release with breaking changes, create a section in the release notes using this template:</p> <pre><code>## Breaking Changes\n\n### `old-command` renamed to `new-command`\n\n**What changed**: `ctx old-command` is now `ctx new-command`.\nThe old name is removed (no deprecation alias).\n\n**Action required**:\n1. Run `ctx init --reset --merge` to update CLAUDE.md\n2. Update any scripts referencing `ctx old-command`\n3. Update hook configs if applicable\n\n**Why**: [brief rationale for the rename]\n</code></pre> <p>Repeat for each breaking change. Users should be able to follow the notes mechanically without needing to understand the codebase.</p>","path":["Operations","Runbooks","Breaking Migration"],"tags":[]},{"location":"operations/runbooks/codebase-audit/","level":1,"title":"Codebase Audit","text":"","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#codebase-audit","level":1,"title":"Codebase Audit","text":"<p>A structured audit of the codebase: dead code, magic strings, documentation drift, security surface, and roadmap opportunities.</p> <p>When to run: Before a release, after a long YOLO sprint, quarterly, or when planning the next phase of work.</p> <p>Time: ~15-30 minutes with a team of agents.</p>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#how-to-use-this-runbook","level":2,"title":"How to Use This Runbook","text":"<p>Start a Claude Code session with a clean git state (<code>git stash</code> or commit first). Paste or adapt the prompt below. The agent does the analysis; you triage the findings.</p>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#prompt","level":2,"title":"Prompt","text":"<pre><code>I want you to create an agent team to audit this codebase. Save each report as\na separate markdown file under `./ideas/` (or another directory if you prefer).\n\nUse read-only agents (subagent_type: Explore) for all analyses. No code changes.\n\nFor each report, use this structure:\n- Executive Summary (2-3 sentences + severity table)\n- Findings (grouped, with file:line references)\n- Ranked Recommendations (high/medium/low priority)\n- Methodology (what was examined, how)\n\nKeep reports actionable: every finding should suggest a concrete fix or next step.\n\n## Analyses to Run\n\n### 1. Extractable Patterns (session mining)\nSearch session JSONL files, journal entries, and task archives for repetitive\nmulti-step workflows. Count frequency of bash command sequences, slash command\nusage, and recurring user prompts. Identify patterns that could become skills\nor scripts. Cross-reference with existing skills to find coverage gaps.\nOutput: ranked list of automation opportunities with frequency data.\n\n### 2. Documentation Drift (godoc + inline)\nCompare every doc.go against its package's actual exports and behavior. Check\ninline godoc comments on exported functions against their implementations.\nScan for stale TODO/FIXME/HACK comments. Check package-level comments match\npackage names. Output: drift items ranked by severity with exact file:line refs.\n\n### 3. Maintainability\nLook for: functions >80 lines that have logical split points; switch blocks\nwith >5 cases that could be table-driven or extracted; inline comments that\nsay \"step 1\", \"step 2\" or similar (sign the block wants to be a function);\nfiles with >400 lines; packages with flat structure that could benefit from\nsub-packages; functions that seem misplaced in their file. Do NOT flag\nthings that are fine as-is just because they could theoretically be different.\nOutput: concrete refactoring suggestions, not style nitpicks.\n\n### 4. Security Review\nThis is a CLI app: focus on CLI-relevant attack surface, not web OWASP:\nfile path traversal (does user input flow into file paths unsanitized?),\ncommand injection (does user input flow into exec calls?), symlink following\n(does the tool follow symlinks when writing to .context/?), permission\nhandling (are file permissions set correctly?), sensitive data in outputs\n(do any commands leak secrets or session content?). Output: findings with\nseverity ratings and exploit scenarios.\n\n### 5. Blog Theme Discovery\nRead existing blog posts for style and narrative voice. Analyze git log,\nrecent session discussions, and DECISIONS.md for story arcs worth writing\nabout. Suggest 3-5 blog post themes with: title, angle, target audience,\nkey commits/sessions to reference, and a 2-sentence pitch. Prioritize\nthemes that build a coherent narrative across posts.\n\n### 6. Roadmap & Value Opportunities\nBased on current features, recent momentum, and gaps found in other analyses:\nwhat are the highest-value improvements? Consider: user-facing features,\ndeveloper experience, integration opportunities, and low-hanging fruit.\nOutput: prioritized list with effort/impact estimates (not time estimates).\n\n### 7. User-Facing Documentation\nEvaluate README, help text, and any user docs. Suggest improvements\nstructured as use-case pages: the problem, how ctx solves it, typical\nworkflow, gotchas. Identify gaps where a user would get stuck without\nreading source code. Output: list of documentation gaps and suggested\npage outlines.\n\n### 8. Agent Team Strategies\nBased on the codebase structure, suggest 2-3 agent team configurations for\nupcoming work sessions. For each: team composition (roles, agent types),\ntask distribution strategy, coordination approach, and which types of work\nit suits. Ground suggestions in actual project patterns, not generic advice.\n</code></pre>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#tips","level":2,"title":"Tips","text":"<ul> <li> <p>Clean state matters: the prompt says \"no code changes\" but accidents   happen. Start from a clean git state so you can <code>git checkout .</code> if needed.</p> </li> <li> <p>Adjust scope: drop analyses you don't need. Analyses 1-4 are the most   actionable. Analyses 5-8 are planning/creative and can be skipped if you   just want a technical audit.</p> </li> <li> <p>Reports feed TASKS.md: after the audit, read each report and create   tasks in the appropriate Phase section. The reports are input, not output.</p> </li> <li> <p>ideas/ is gitignored: reports saved there won't be committed. Move   specific findings to TASKS.md, DECISIONS.md, or LEARNINGS.md to persist them.</p> </li> </ul>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/codebase-audit/#history","level":2,"title":"History","text":"<ul> <li>2026-02-08: Original prompt created after a codebase audit sprint.</li> <li>2026-02-17: Improved with read-only agents, report structure template,   CLI-scoped security review, and maintainability thresholds.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Codebase Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/","level":1,"title":"Docs Semantic Audit","text":"","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#documentation-semantic-audit","level":1,"title":"Documentation Semantic Audit","text":"<p>Find structural problems that linters and link checkers cannot: weak pages that should be merged, heavy pages that should be split, missing cross-links, and narrative arcs that don't land.</p> <p>When to run: Before a release, after adding several new pages, when the site feels sprawling, or when you suspect narrative gaps.</p> <p>Time: ~20-40 minutes with an agent session.</p>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#why-this-is-a-runbook","level":2,"title":"Why This Is a Runbook","text":"<p>These judgments are inherently subjective and context-dependent. A page is \"weak\" relative to its neighbors; a narrative arc only matters if the docs intend to tell a story. Deterministic tools (broken-link checkers, word counters) can't do this. An LLM reading the full doc set can.</p>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#prompt","level":2,"title":"Prompt","text":"<p>Paste or adapt the following into a Claude Code session. The agent needs read access to <code>docs/</code> and the site nav structure.</p> <pre><code>Read every file under docs/ (including docs/blog/ and docs/recipes/).\nFor each file, note: title, word count, outbound links, inbound links\n(how many other pages link to it), and a one-line summary of its purpose.\n\nThen produce a report with these sections:\n\n## 1. Weak Dangling Pages\n\nPages that are thin, isolated, or redundant. Signs:\n- Under ~300 words with no unique content (just restates what another page says)\n- Zero or one inbound links (orphaned in the nav)\n- Content that would be stronger merged into an adjacent page\n- \"Try it in 5 minutes\" sections that assume installation already happened\n- Pages whose title doesn't work as a nav entry (too long, too vague)\n\nFor each: identify the page, explain why it's weak, and recommend\nmerge target or deletion.\n\n## 2. Overly Heavy Pages\n\nPages doing too much. Signs:\n- Over ~1500 words with multiple distinct topics\n- More than 4 H2 sections that could stand alone\n- Reader has to scroll past irrelevant content to find what they need\n- Mixed audience (beginner setup + advanced config on same page)\n\nFor each: identify the page, list the distinct topics, and suggest\nsplit points.\n\n## 3. Missing Cross-Links\n\nPlaces where a reader would naturally want to jump to related content\nbut no link exists. Look for:\n- Concepts mentioned but not linked (e.g., \"scratchpad\" without linking\n  to the scratchpad page)\n- Blog posts that describe features without linking to the reference docs\n- Recipes that reference workflows without linking to the relevant\n  getting-started section\n- Pages that end without a \"Next Up\" or \"See Also\" pointer\n\nFor each: source page, anchor text, suggested link target.\n\n## 4. Narrative Gaps\n\nThe docs should tell a coherent story: problem -> install -> first session\n-> daily workflow -> advanced patterns -> contributing. Look for:\n- Gaps in the progression (e.g., no bridge from \"first session\" to\n  \"daily habits\")\n- Blog posts that introduce concepts the reference docs don't cover\n- Recipes that assume knowledge no other page teaches\n- Features documented in CLI reference but missing from workflows/recipes\n\nFor each: describe the gap and suggest what page or section would fill it.\n\n## 5. Blog Cross-Linking Opportunities\n\nBlog posts are often written in isolation. Look for:\n- Posts that cover the same theme but don't reference each other\n- Posts that describe the evolution of a feature (natural \"part 1 / part 2\")\n- Posts that would benefit from a \"Related posts\" footer\n- Thematic clusters that could be linked from a recipe or reference page\n\nFor each: list the posts, the shared theme, and the suggested links.\n\n## Output Format\n\nFor every finding, include:\n- File path (docs/whatever.md)\n- Severity: high (actively confusing), medium (missed opportunity),\n  low (nice to have)\n- Concrete recommendation (merge into X, split at H2 Y, add link to Z)\n\nEnd with a prioritized action list: what to fix first.\n</code></pre>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#after-the-audit","level":2,"title":"After the Audit","text":"<ol> <li>Triage findings: not everything needs fixing. Focus on high severity.</li> <li>Merge weak pages first: fewer pages is almost always better.</li> <li>Add cross-links: cheapest improvement, highest reader impact.</li> <li>File split decisions in DECISIONS.md: page splits are architectural.</li> <li>Regenerate the site and spot-check nav after structural changes.</li> </ol>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/docs-semantic-audit/#history","level":2,"title":"History","text":"<ul> <li>2026-02-17: Created after merging <code>docs/re-explaining.md</code> into   <code>docs/about.md</code>, which surfaced the pattern of weak standalone   pages that dilute rather than add.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Docs Semantic Audit"],"tags":[]},{"location":"operations/runbooks/hub-deployment/","level":1,"title":"Hub Deployment","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#hub-deployment","level":1,"title":"Hub Deployment","text":"<p>Linear runbook for setting up a <code>ctx</code> Hub for yourself or a team. Consolidates pieces currently scattered across hub recipes and operations docs.</p> <p>When to use: First-time hub setup, or when onboarding a new team onto an existing hub.</p> <p>Prerequisites: <code>ctx</code> binary installed, network connectivity between hub and clients.</p> <p>Companion docs:</p> <ul> <li>Hub overview: what the hub   is and is not</li> <li>Hub operations: data directory, systemd,   backup, monitoring</li> <li>Hub failure modes: what can go wrong</li> </ul>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"Quick Start (foreground)Production (systemd) <pre><code>ctx hub start\n</code></pre> <p>See Hub Operations: Systemd Unit for the full unit file.</p> <pre><code>sudo systemctl enable --now ctx-hub\n</code></pre> <p>The hub creates <code>admin.token</code> on first start. Save this token; it is the only way to register clients.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-2-generate-the-admin-token","level":2,"title":"Step 2: Generate the Admin Token","text":"<p>On first start, the hub writes <code>admin.token</code> to the data directory (default <code>~/.ctx/hub-data/</code>):</p> <pre><code>cat ~/.ctx/hub-data/admin.token\n</code></pre> <p>This token has full admin privileges. Keep it secret.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-3-register-clients","level":2,"title":"Step 3: Register Clients","text":"<p>For each client (person or machine) that will connect:</p> <pre><code># On the hub machine\nctx hub register --name \"volkan-laptop\" --admin-token <admin-token>\n</code></pre> <p>This returns a client token. Distribute it securely to the client.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-4-connect-clients","level":2,"title":"Step 4: Connect Clients","text":"<p>On each client machine, register the project with the hub. The <code>ctx hub *</code> commands above run on the hub server itself and don't need a project. The <code>ctx connection *</code> commands below are different: they live inside a project (the encrypted hub config is stored at <code>.context/.connect.enc</code>), so you have to tell <code>ctx</code> which project first.</p> <pre><code># In the project directory on the client machine:\neval \"$(ctx activate)\"\nctx connection register <hub-address> --token <client-token>\n</code></pre> <p>Verify the connection:</p> <pre><code>ctx connection status\n</code></pre> <p>If the client doesn't have a project yet, run <code>ctx init</code> first, then <code>eval \"$(ctx activate)\"</code>. See Activating a Context Directory.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-5-verify-sync","level":2,"title":"Step 5: Verify Sync","text":"<p>Push a test entry from one client and verify it arrives. Make sure each client already ran <code>eval \"$(ctx activate)\"</code> from Step 4: otherwise <code>ctx add</code> and <code>ctx status</code> fail with <code>Error: no context directory specified</code>.</p> <pre><code># Client A (in its project directory, after activating):\nctx learning add \"Hub sync test\" --context \"Verifying hub setup\"\n\n# Client B (in its project directory, after activating):\nctx status   # should show the new learning\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-6-configure-backup","level":2,"title":"Step 6: Configure Backup","text":"<p>Set up regular backups of the hub data directory. See Hub Operations: Backup and Restore.</p> <p>Minimum:</p> <pre><code># Add to cron\n0 */6 * * * cp ~/.ctx/hub-data/entries.jsonl ~/backups/entries-$(date +\\%F).jsonl\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#step-7-configure-tls-when-available","level":2,"title":"Step 7: Configure TLS (When Available)","text":"<p>Coming Soon</p> <p>TLS support is planned (H-01/H-02). Until then, run the hub on a trusted network or behind a reverse proxy with TLS termination.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#team-onboarding-checklist","level":2,"title":"Team Onboarding Checklist","text":"<p>When adding a new team member to an existing hub:</p> <ul> <li> Generate a client token (<code>ctx hub register --name \"<name>\"</code>)</li> <li> Share the token and hub address securely</li> <li> Have them run <code>ctx connect <hub-address> --token <token></code></li> <li> Verify with <code>ctx connection status</code></li> <li> Point them to the Hub Getting Started recipe</li> </ul>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#connection-refused","level":3,"title":"\"Connection Refused\"","text":"<p>The hub isn't running or the port is wrong. Check:</p> <pre><code>ctx hub status          # on the hub machine\nss -tlnp | grep 9900   # default port\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#authentication-failed","level":3,"title":"\"Authentication Failed\"","text":"<p>The client token is wrong or was never registered. Re-register:</p> <pre><code>ctx hub register --name \"<name>\" --admin-token <admin-token>\n</code></pre>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/hub-deployment/#entries-not-syncing","level":3,"title":"Entries Not Syncing","text":"<p>Check that the client is listening:</p> <pre><code>ctx connection status\n</code></pre> <p>If connected but not syncing, check the hub logs for sequence mismatch errors. See Hub Failure Modes for details.</p>","path":["Operations","Runbooks","Hub Deployment"],"tags":[]},{"location":"operations/runbooks/new-contributor/","level":1,"title":"New Contributor","text":"","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#new-contributor-onboarding","level":1,"title":"New Contributor Onboarding","text":"<p>Step-by-step onboarding sequence for new contributors. Consolidates setup instructions currently scattered across the README, contributing guide, and setup docs.</p> <p>When to use: First-time contributor setup, or when verifying your development environment after a major upgrade.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-1-clone-the-repository","level":2,"title":"Step 1: Clone the Repository","text":"<pre><code>git clone https://github.com/ActiveMemory/ctx.git\ncd ctx\n</code></pre> <p>Or fork first on GitHub, then clone your fork.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-2-initialize-context","level":2,"title":"Step 2: Initialize Context","text":"<pre><code>ctx init\neval \"$(ctx activate)\"\n</code></pre> <p><code>ctx init</code> creates the <code>.context/</code> directory with knowledge files and the <code>.claude/</code> directory with agent configuration. <code>eval \"$(ctx activate)\"</code> tells <code>ctx</code> to use that directory for the rest of this runbook. If you skip the second line, the later steps fail with <code>Error: no context directory specified</code>.</p> <p>If <code>ctx</code> is not yet installed, proceed to Step 3 first, then come back.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-3-build-and-install","level":2,"title":"Step 3: Build and Install","text":"<pre><code>make build\nsudo make install\n</code></pre> <p>Verify:</p> <pre><code>ctx --version\n</code></pre>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-4-install-the-plugin-claude-code-users","level":2,"title":"Step 4: Install the Plugin (Claude Code Users)","text":"<p>If you use Claude Code, install the plugin from your local clone so skills and hooks reflect your working tree:</p> <ol> <li>Launch <code>claude</code></li> <li>Type <code>/plugin</code> and press Enter</li> <li>Select Marketplaces -> Add Marketplace</li> <li>Enter the absolute path to your clone (e.g., <code>~/WORKSPACE/ctx</code>)</li> <li>Back in <code>/plugin</code>, select Install and choose <code>ctx</code></li> </ol> <p>Verify:</p> <pre><code>claude /plugin list   # should show ctx\n</code></pre> <p>See Contributing: Install the Plugin for details on cache clearing.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-5-switch-to-dev-profile","level":2,"title":"Step 5: Switch to Dev Profile","text":"<pre><code>ctx config switch dev\n</code></pre> <p>This enables verbose logging and notify events (useful during development).</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-6-verify-hooks","level":2,"title":"Step 6: Verify Hooks","text":"<p>Start a Claude Code session and check that hooks fire:</p> <pre><code>claude\n</code></pre> <p>You should see <code>ctx</code> session hooks (ceremonies reminder, context loading) on session start. If not, check that the plugin is installed correctly (Step 4).</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-7-run-your-first-session","level":2,"title":"Step 7: Run Your First Session","text":"<p>In Claude Code:</p> <pre><code>/ctx-status\n</code></pre> <p>This should show context file health, active tasks, and recent decisions. If it works, your setup is complete.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-8-verify-context-persistence","level":2,"title":"Step 8: Verify Context Persistence","text":"<p>End the session and start a new one:</p> <pre><code>/ctx-remember\n</code></pre> <p>The agent should recall what happened in the previous session. This confirms that context persistence is working end-to-end.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#step-9-run-tests","level":2,"title":"Step 9: Run Tests","text":"<pre><code>make test     # unit tests\nmake audit    # full check: fmt + vet + lint + drift + docs + test\n</code></pre> <p>All tests should pass with a clean clone.</p>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#quick-reference","level":2,"title":"Quick Reference","text":"Task Command Build <code>make build</code> Install <code>sudo make install</code> Test <code>make test</code> Full audit <code>make audit</code> Rebuild docs site <code>make site</code> Serve docs locally <code>make site-serve</code> Clear plugin cache <code>make plugin-reload</code> Switch config profile <code>ctx config switch dev</code>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/new-contributor/#next-steps","level":2,"title":"Next Steps","text":"<ul> <li>Read the contributing guide   for project layout, code style, and PR process</li> <li>Check TASKS.md   for open work items</li> <li>Ask <code>/ctx-next</code> for suggested work</li> </ul>","path":["Operations","Runbooks","New Contributor"],"tags":[]},{"location":"operations/runbooks/plugin-release/","level":1,"title":"Plugin Release","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#plugin-release","level":1,"title":"Plugin Release","text":"<p>Plugin-specific release procedure. The general release checklist covers the full <code>ctx</code> release; this runbook covers the plugin-specific steps that are not part of that flow.</p> <p>When to use: When releasing plugin changes (new skills, hook updates, permission changes) independently of a <code>ctx</code> binary release, or as a sub-procedure within the full release.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#what-ships-in-the-plugin","level":2,"title":"What Ships in the Plugin","text":"<p>The plugin lives at <code>internal/assets/claude/</code> and includes:</p> Component Path What it does Skills <code>internal/assets/claude/skills/</code> User-facing <code>/ctx-*</code> slash commands Hooks <code>internal/assets/claude/hooks/</code> Pre/post tool-use hooks Plugin manifest <code>internal/assets/claude/.claude-plugin/plugin.json</code> Declares skills, hooks, version Marketplace <code>.claude-plugin/marketplace.json</code> Points Claude Code to the plugin","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-1-update-hooksjson-if-hooks-changed","level":2,"title":"Step 1: Update hooks.json (If Hooks Changed)","text":"<p>If you added, removed, or modified hooks:</p> <pre><code># Verify hook definitions match implementations\nmake audit\n</code></pre> <p>Check that <code>plugin.json</code> lists all hooks correctly. Missing hooks silently fail to fire.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-2-bump-version","level":2,"title":"Step 2: Bump Version","text":"<p>Update the version in three places:</p> <ul> <li><code>internal/assets/claude/.claude-plugin/plugin.json</code></li> <li><code>.claude-plugin/marketplace.json</code> (two fields)</li> <li><code>editors/vscode/package.json</code> + <code>package-lock.json</code>   (if VS Code extension is affected)</li> </ul> <p>The Release Script Does This</p> <p>If you're running <code>make release</code>, the script bumps these automatically from <code>VERSION</code>. Only bump manually if you're releasing the plugin independently.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-3-test-against-a-fresh-install","level":2,"title":"Step 3: Test Against a Fresh Install","text":"<pre><code># Clear cached plugin\nmake plugin-reload\n\n# Restart Claude Code, then:\nclaude /plugin list    # verify version\n</code></pre> <p>Test the critical paths:</p> <ul> <li> <code>/ctx-status</code> works</li> <li> Session hooks fire (ceremonies, context loading)</li> <li> At least one user-facing skill works end-to-end</li> <li> Pre-tool-use hooks block when they should</li> </ul>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-4-test-against-a-clean-project","level":2,"title":"Step 4: Test Against a Clean Project","text":"<p>Create a temporary project to verify the plugin works outside the <code>ctx</code> repo:</p> <pre><code>mkdir /tmp/test-ctx-plugin && cd /tmp/test-ctx-plugin\ngit init\nctx init\nclaude   # start a session, verify hooks fire\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-5-verify-skill-count","level":2,"title":"Step 5: Verify Skill Count","text":"<p>The plugin manifest declares all user-invocable skills. Verify the count matches:</p> <pre><code># Count skills in plugin.json\njq '.skills | length' internal/assets/claude/.claude-plugin/plugin.json\n\n# Count skill directories\nls -d internal/assets/claude/skills/ctx-*/ | wc -l\n</code></pre> <p>These numbers should match (some skills are not user-invocable and won't appear in both counts).</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#step-6-commit-and-tag","level":2,"title":"Step 6: Commit and Tag","text":"<p>If releasing independently of a binary release:</p> <pre><code>git add internal/assets/claude/ .claude-plugin/\ngit commit -m \"chore: release plugin v0.X.Y\"\ngit tag plugin-v0.X.Y\ngit push origin main --tags\n</code></pre> <p>If part of a full release, the release checklist handles this.</p>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#troubleshooting","level":2,"title":"Troubleshooting","text":"","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#skills-dont-appear-after-update","level":3,"title":"Skills Don't Appear After Update","text":"<p>Claude Code caches plugin files aggressively:</p> <pre><code>make plugin-reload    # clears cache\n# restart Claude Code\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#hooks-dont-fire","level":3,"title":"Hooks Don't Fire","text":"<p>Check that the hook is registered in <code>plugin.json</code> and that the command it calls exists:</p> <pre><code>jq '.hooks' internal/assets/claude/.claude-plugin/plugin.json\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/plugin-release/#version-mismatch","level":3,"title":"Version Mismatch","text":"<p>If <code>claude /plugin list</code> shows an old version after updating:</p> <pre><code>make plugin-reload\n# restart Claude Code\nclaude /plugin list   # should show new version\n</code></pre>","path":["Operations","Runbooks","Plugin Release"],"tags":[]},{"location":"operations/runbooks/release-checklist/","level":1,"title":"Release Checklist","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release-checklist","level":1,"title":"Release Checklist","text":"<p>The canonical pre-release sequence. This runbook ties together the audits, tests, and release steps that are otherwise scattered across docs and the operator's head.</p> <p>When to run: Before every release. No exceptions.</p> <p>Companion: The <code>/_ctx-release</code> skill automates the tag-and-push portion; this checklist covers everything before and after that automation.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#pre-release","level":2,"title":"Pre-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#1-run-the-codebase-audit","level":3,"title":"1. Run the Codebase Audit","text":"<p>Use the codebase audit runbook prompt with your agent. Focus on analyses 1-4 (extractable patterns, documentation drift, maintainability, security). Triage findings into TASKS.md; anything blocking ships before the release.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#2-run-the-docs-semantic-audit","level":3,"title":"2. Run the Docs Semantic Audit","text":"<p>Use the docs semantic audit runbook prompt. Fix high-severity findings (weak pages, broken narrative arcs). Medium-severity items can be deferred.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#3-sanitize-permissions","level":3,"title":"3. Sanitize Permissions","text":"<p>Follow the sanitize permissions runbook. Clean up <code>.claude/settings.local.json</code> before it gets committed as part of the release.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#4-run-the-full-test-suite","level":3,"title":"4. Run the Full Test Suite","text":"<pre><code>make audit    # fmt + vet + lint + drift + docs + test\nmake smoke    # integration smoke tests\n</code></pre> <p>All tests must pass. No exceptions.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#5-check-context-health","level":3,"title":"5. Check Context Health","text":"<p>Activate the project so the next commands know which <code>.context/</code> to read:</p> <pre><code>eval \"$(ctx activate)\"\nctx drift          # broken references, stale patterns\nctx status         # context file health\n/ctx-link-check    # dead links in docs\n</code></pre> <p>Fix anything flagged. If you see <code>Error: no context directory specified</code>, you skipped the <code>eval</code> line above. See Activating a Context Directory.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#6-review-tasksmd","level":3,"title":"6. Review TASKS.md","text":"<p>Scan for incomplete tasks tagged as release-blocking. Either finish them or explicitly defer with a reason in the task note.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#release","level":2,"title":"Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#7-bump-version","level":3,"title":"7. Bump Version","text":"<pre><code>echo \"0.X.0\" > VERSION\ngit add VERSION\ngit commit -m \"chore: bump version to 0.X.0\"\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#8-generate-release-notes","level":3,"title":"8. Generate Release Notes","text":"<p>In Claude Code:</p> <pre><code>/_ctx-release-notes\n</code></pre> <p>Review <code>dist/RELEASE_NOTES.md</code>. Ensure it captures all user-visible changes.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#9-cut-the-release","level":3,"title":"9. Cut the Release","text":"<pre><code>make release\n</code></pre> <p>Or in Claude Code: <code>/_ctx-release</code>. See Cutting a Release for the full step-by-step.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#post-release","level":2,"title":"Post-Release","text":"","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#10-verify-the-github-release","level":3,"title":"10. Verify the GitHub Release","text":"<ul> <li> GitHub Releases shows the new version</li> <li> All 6 binaries are attached</li> <li> SHA256 checksums are attached</li> <li> Release notes render correctly</li> </ul>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#11-update-the-plugin-marketplace","level":3,"title":"11. Update the Plugin Marketplace","text":"<p>If the plugin version changed, verify the marketplace entry:</p> <pre><code>claude /plugin list   # shows updated version\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#12-announce","level":3,"title":"12. Announce","text":"<p>Post in the project's communication channels. Reference the release notes.</p>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/release-checklist/#13-clean-up","level":3,"title":"13. Clean Up","text":"<pre><code>rm dist/RELEASE_NOTES.md   # consumed by the release script\ngit stash pop              # if you stashed earlier\n</code></pre>","path":["Operations","Runbooks","Release Checklist"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/","level":1,"title":"Sanitize Permissions","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#sanitize-permissions","level":1,"title":"Sanitize Permissions","text":"<p>Manual procedure for cleaning up <code>.claude/settings.local.json</code>. The agent may analyze and recommend, but you make every edit.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#why-manual-not-automated","level":2,"title":"Why Manual, Not Automated","text":"<p><code>settings.local.json</code> controls what the agent can do without asking. An agent that can edit its own permission file is a self-escalation vector, especially if the skill is auto-accepted. Keep this manual.</p> <p>When to run: After busy sessions where you clicked \"Allow\" many times, weekly hygiene (pair with <code>ctx drift</code>), or before committing <code>.claude/settings.local.json</code>.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-1-snapshot","level":2,"title":"Step 1: Snapshot","text":"<pre><code>cp .claude/settings.local.json /tmp/settings-backup-$(date +%Y%m%d).json\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-2-extract-the-allow-list","level":2,"title":"Step 2: Extract the Allow List","text":"<pre><code>jq '.permissions.allow[]' .claude/settings.local.json | sort\n</code></pre> <p>Eyeball it. You're looking for four categories:</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-3-identify-problems","level":2,"title":"Step 3: Identify Problems","text":"","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#a-garbage-nonsense","level":3,"title":"A. Garbage / Nonsense","text":"<p>Entries that are clearly broken or meaningless:</p> <pre><code>Bash(done)\nBash(__NEW_LINE_aa838494a90279c4__ echo \"\")\n</code></pre> <p>Action: Delete.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#b-one-off-commands-session-debris","level":3,"title":"B. One-Off Commands (Session Debris)","text":"<p>Entries with hardcoded paths, literal arguments, or exact commands that were accepted during a specific debugging session:</p> <pre><code>Bash(git -C /home/jose/WORKSPACE/ctx log --oneline --all -20)\nBash(/home/jose/WORKSPACE/ctx/ctx decision add \"Use PostgreSQL\" --context ...)\n</code></pre> <p>Signs of a one-off:</p> <ul> <li>Full absolute paths to specific files</li> <li>Literal string arguments (not wildcards)</li> <li>Very specific flag combinations</li> <li>Commands that look like they came from a single task</li> </ul> <p>Action: Delete unless you want to promote to a wildcard pattern.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#c-subsumed-entries-redundant","level":3,"title":"C. Subsumed Entries (Redundant)","text":"<p>A narrow entry that's already covered by a broader one:</p> <pre><code># Narrow (redundant):\nBash(ctx journal source)\nBash(git -C /home/jose/WORKSPACE/ctx log --oneline -5)\n\n# Broad (already covers the above):\nBash(ctx journal source:*)\nBash(git -C:*)\n</code></pre> <p>To find these, look for entries where removing the specific args would match an existing wildcard entry.</p> <p>Action: Delete the narrow entry.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#d-duplicate-intent-different-spelling","level":3,"title":"D. Duplicate Intent, Different Spelling","text":"<p>Same command with env vars in different order, or slight variations:</p> <pre><code>Bash(CGO_ENABLED=0 CTX_SKIP_PATH_CHECK=1 go test:*)\nBash(CTX_SKIP_PATH_CHECK=1 CGO_ENABLED=0 go test:*)\n</code></pre> <p>Action: Keep one, delete the other.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-4-check-for-security-concerns","level":2,"title":"Step 4: Check for Security Concerns","text":"<p>While you're in here, also flag:</p> Pattern Risk <code>Bash(git push:*)</code> Bypasses block-git-push.sh hook <code>Bash(rm -rf:*)</code> Recursive delete, no confirmation <code>Bash(sudo:*)</code> Privilege escalation <code>Bash(echo:*)</code>, <code>Bash(cat:*)</code> Can compose into writes to sensitive files <code>Bash(curl:*)</code>, <code>Bash(wget:*)</code> Arbitrary network access Any write to <code>.claude/</code> paths Agent self-modification <p>See the <code>/ctx-permission-sanitize</code> skill for the full threat matrix.</p>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-5-edit","level":2,"title":"Step 5: Edit","text":"<p>Edit <code>.claude/settings.local.json</code> directly in your editor. Remove flagged entries. Keep the JSON valid.</p> <pre><code># Validate JSON after editing\njq . .claude/settings.local.json > /dev/null && echo \"valid\" || echo \"BROKEN\"\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-6-verify","level":2,"title":"Step 6: Verify","text":"<pre><code># Compare before/after\ndiff /tmp/settings-backup-$(date +%Y%m%d).json .claude/settings.local.json\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#step-7-optionally-commit","level":2,"title":"Step 7: Optionally Commit","text":"<pre><code>git add .claude/settings.local.json\ngit commit -m \"chore: sanitize agent permissions\"\n</code></pre>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#asking-the-agent-for-help","level":2,"title":"Asking the Agent for Help","text":"<p>You can safely ask the agent to analyze the file:</p> <p>\"Look at my settings.local.json and tell me which permissions look like one-offs or are redundant.\"</p> <p>The agent can read and report. You do the edits.</p> <p>Do not add these to your allow list:</p> <ul> <li><code>Skill(ctx-permission-sanitize)</code></li> <li><code>Edit(.claude/settings.local.json)</code></li> <li>Any <code>Bash(...)</code> pattern that writes to <code>.claude/</code></li> </ul>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"operations/runbooks/sanitize-permissions/#history","level":2,"title":"History","text":"<ul> <li>2026-02-15: Created as manual-only procedure after deciding   against a self-modifying skill.</li> <li>2026-04-16: Moved from <code>hack/runbooks/</code> to <code>docs/operations/runbooks/</code>.</li> </ul>","path":["Operations","Runbooks","Sanitize Permissions"],"tags":[]},{"location":"recipes/","level":1,"title":"Recipes","text":"<p>Workflow recipes combining <code>ctx</code> commands and skills to solve specific problems.</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#getting-started","level":2,"title":"Getting Started","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#guide-your-agent","level":3,"title":"Guide Your Agent","text":"<p>How commands, skills, and conversational patterns work together. Train your agent to be proactive through ask, guide, reinforce.</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#setup-across-ai-tools","level":3,"title":"Setup across AI Tools","text":"<p>Initialize <code>ctx</code> and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf. Includes shell completion, watch mode for non-native tools, and verification.</p> <p>Uses: <code>ctx init</code>, <code>ctx setup</code>, <code>ctx agent</code>, <code>ctx completion</code>, <code>ctx watch</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#multilingual-session-parsing","level":3,"title":"Multilingual Session Parsing","text":"<p>Parse session journal entries written in other languages. Configure recognized session-header prefixes so the journal pipeline works for Turkish, Japanese, and any other locale.</p> <p>Uses: <code>ctx journal source</code>, <code>ctx journal import</code>, <code>session_prefixes</code> in <code>.ctxrc</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#keeping-context-in-a-separate-repo","level":3,"title":"Keeping Context in a Separate Repo","text":"<p>Store context files outside the project tree: in a private repo, shared directory, or anywhere else. Useful for open source projects with private context or multi-repo setups.</p> <p>Uses: <code>ctx init</code>, <code>CTX_DIR</code>, <code>.ctxrc</code>, <code>/ctx-status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#build-a-knowledge-base","level":3,"title":"Build a Knowledge Base","text":"<p>Stand up the editorial pipeline for knowledge-shaped work (research projects, vendor-spec analysis, post-incident reviews). Covers the pass-mode contract, source-coverage state-machine ledger, topic-adjacency pre-flight, cold-reader rubric, closeout/fold mechanism, and folder-shaped topic pages.</p> <p>Uses: <code>ctx init</code>, <code>ctx kb topic new</code>, <code>ctx kb note</code>, <code>ctx kb reindex</code>, <code>ctx handover write</code>, <code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, <code>/ctx-kb-site-review</code>, <code>/ctx-kb-ground</code>, <code>/ctx-kb-note</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#typical-kb-session","level":3,"title":"Typical KB Session","text":"<p>The everyday flow once the pipeline is set up: session start recall, ingest a transcript, ask grounded questions, park findings, wrap up via the mandatory handover.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, <code>/ctx-kb-note</code>, <code>/ctx-wrap-up</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#recover-an-aborted-kb-session","level":3,"title":"Recover an Aborted KB Session","text":"<p>What to do when the session ends after one or more editorial passes but before <code>/ctx-handover</code>. Closeouts survive the abort; the next session's <code>/ctx-remember</code> reads them as unfolded postdated artifacts; the next <code>/ctx-handover</code> folds them retroactively.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-handover</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#sessions","level":2,"title":"Sessions","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#the-complete-session","level":3,"title":"The Complete Session","text":"<p>Walk through a full <code>ctx</code> session from start to finish:</p> <ul> <li>Loading context,</li> <li>Picking what to work on,</li> <li>Committing with context,</li> <li>Capturing, reflecting, and saving a snapshot.</li> </ul> <p>Uses: <code>ctx status</code>, <code>ctx agent</code>, <code>/ctx-remember</code>, <code>/ctx-next</code>, <code>/ctx-commit</code>, <code>/ctx-reflect</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#session-ceremonies","level":3,"title":"Session Ceremonies","text":"<p>The two bookend rituals for every session: <code>/ctx-remember</code> at the start to load and confirm context, <code>/ctx-wrap-up</code> at the end to review the session and persist learnings, decisions, and tasks.</p> <p>Uses: <code>/ctx-remember</code>, <code>/ctx-wrap-up</code>, <code>/ctx-commit</code>, <code>ctx agent</code>, <code>ctx add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#browsing-and-enriching-past-sessions","level":3,"title":"Browsing and Enriching Past Sessions","text":"<p>Export your AI session history to a browsable journal site. Enrich entries with metadata and search across months of work.</p> <p>Uses: <code>ctx journal source/import</code>, <code>ctx journal site</code>, <code>ctx journal obsidian</code>, <code>ctx serve</code>, <code>/ctx-history</code>, <code>/ctx-journal-enrich</code>, <code>/ctx-journal-enrich-all</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#session-reminders","level":3,"title":"Session Reminders","text":"<p>Leave a message for your next session. Reminders surface automatically at session start and repeat until dismissed. Date-gate reminders to surface only after a specific date.</p> <p>Uses: <code>ctx remind</code>, <code>ctx remind list</code>, <code>ctx remind dismiss</code>, <code>ctx system check-reminders</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#reviewing-session-changes","level":3,"title":"Reviewing Session Changes","text":"<p>See what moved since your last session: context file edits, code commits, directories touched. Auto-detects session boundaries from state markers.</p> <p>Uses: <code>ctx change</code>, <code>ctx agent</code>, <code>ctx status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#pausing-context-hooks","level":3,"title":"Pausing Context Hooks","text":"<p>Silence all nudge hooks for a quick task that doesn't need ceremony overhead. Session-scoped: Other sessions are unaffected. Security hooks still fire.</p> <p>Uses: <code>ctx hook pause</code>, <code>ctx hook resume</code>, <code>/ctx-pause</code>, <code>/ctx-resume</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#knowledge-and-tasks","level":2,"title":"Knowledge and Tasks","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#persisting-decisions-learnings-and-conventions","level":3,"title":"Persisting Decisions, Learnings, and Conventions","text":"<p>Record architectural decisions with rationale, capture gotchas and lessons learned, and codify conventions so they survive across sessions and team members.</p> <p>Uses: <code>ctx decision add</code>, <code>ctx learning add</code>, <code>ctx convention add</code>, <code>ctx decision reindex</code>, <code>ctx learning reindex</code>, <code>/ctx-decision-add</code>, <code>/ctx-learning-add</code>, <code>/ctx-convention-add</code>, <code>/ctx-reflect</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#tracking-work-across-sessions","level":3,"title":"Tracking Work across Sessions","text":"<p>Add, prioritize, complete, snapshot, and archive tasks. Keep <code>TASKS.md</code> focused as your project evolves across dozens of sessions.</p> <p>Uses: <code>ctx task add</code>, <code>ctx task complete</code>, <code>ctx task archive</code>, <code>ctx task snapshot</code>, <code>/ctx-task-add</code>, <code>/ctx-archive</code>, <code>/ctx-next</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#using-the-scratchpad","level":3,"title":"Using the Scratchpad","text":"<p>Use the encrypted scratchpad for quick notes, working memory, and sensitive values during AI sessions. Natural language in, encrypted storage out.</p> <p>Uses: <code>ctx pad</code>, <code>/ctx-pad</code>, <code>ctx pad show</code>, <code>ctx pad edit</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#syncing-scratchpad-notes-across-machines","level":3,"title":"Syncing Scratchpad Notes across Machines","text":"<p>Distribute your scratchpad encryption key, push and pull encrypted notes via git, and resolve merge conflicts when two machines edit simultaneously.</p> <p>Uses: <code>ctx init</code>, <code>ctx pad</code>, <code>ctx pad resolve</code>, <code>scp</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#bridging-claude-code-auto-memory","level":3,"title":"Bridging Claude Code Auto Memory","text":"<p>Mirror Claude Code's auto memory (MEMORY.md) into <code>.context/</code> for version control, portability, and drift detection. Import entries into structured context files with heuristic classification.</p> <p>Uses: <code>ctx memory sync</code>, <code>ctx memory status</code>, <code>ctx memory diff</code>, <code>ctx memory import</code>, <code>ctx memory publish</code>, <code>ctx system check-memory-drift</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hooks-and-notifications","level":2,"title":"Hooks and Notifications","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-output-patterns","level":3,"title":"Hook Output Patterns","text":"<p>Choose the right output pattern for your Claude Code hooks: <code>VERBATIM</code> relay for user-facing reminders, hard gates for invariants, agent directives for nudges, and five more patterns across the spectrum.</p> <p>Uses: <code>ctx</code> plugin hooks, <code>settings.local.json</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#customizing-hook-messages","level":3,"title":"Customizing Hook Messages","text":"<p>Customize what hooks say without changing what they do. Override the QA gate for Python (<code>pytest</code> instead of <code>make lint</code>), silence noisy ceremony nudges, or tailor post-commit instructions for your stack.</p> <p>Uses: <code>ctx hook message list</code>, <code>ctx hook message show</code>, <code>ctx hook message edit</code>, <code>ctx hook message reset</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hook-sequence-diagrams","level":3,"title":"Hook Sequence Diagrams","text":"<p>Mermaid sequence diagrams for every system hook: entry conditions, state reads, output, throttling, and exit points. Includes throttling summary table and state file reference.</p> <p>Uses: All <code>ctx system</code> hooks</p>","path":["Recipes"],"tags":[]},{"location":"recipes/#auditing-system-hooks","level":3,"title":"Auditing System Hooks","text":"<p>The 12 system hooks that run invisibly during every session: what each one does, why it exists, and how to verify they're actually firing. Covers webhook-based audit trails, log inspection, and detecting silent hook failures.</p> <p>Uses: <code>ctx system</code>, <code>ctx hook notify</code>, <code>.context/logs/</code>, <code>.ctxrc</code> <code>notify.events</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#webhook-notifications","level":3,"title":"Webhook Notifications","text":"<p>Get push notifications when loops complete, hooks fire, or agents hit milestones. Webhook URL is encrypted: never stored in plaintext. Works with IFTTT, Slack, Discord, ntfy.sh, or any HTTP endpoint.</p> <p>Uses: <code>ctx hook notify setup</code>, <code>ctx hook notify test</code>, <code>ctx hook notify --event</code>, <code>.ctxrc</code> <code>notify.events</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#configuration-profiles","level":3,"title":"Configuration Profiles","text":"<p>Switch between dev and base runtime configurations without editing <code>.ctxrc</code> by hand. Verbose logging and webhooks for debugging, clean defaults for normal sessions.</p> <p>Uses: <code>ctx config switch</code>, <code>ctx config status</code>, <code>/ctx-config</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#maintenance","level":2,"title":"Maintenance","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#detecting-and-fixing-drift","level":3,"title":"Detecting and Fixing Drift","text":"<p>Keep context files accurate by detecting structural drift (stale paths, missing files, stale file ages) and task staleness.</p> <p>Uses: <code>ctx drift</code>, <code>ctx sync</code>, <code>ctx compact</code>, <code>ctx status</code>, <code>/ctx-drift</code>, <code>/ctx-status</code>, <code>/ctx-prompt-audit</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#state-directory-maintenance","level":3,"title":"State Directory Maintenance","text":"<p>Clean up session tombstones from <code>.context/state/</code>. Prune old per-session files, identify stale global markers, and keep the state directory lean.</p> <p>Uses: <code>ctx prune</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#troubleshooting","level":3,"title":"Troubleshooting","text":"<p>Diagnose hook failures, noisy nudges, stale context, and configuration issues. Start with <code>ctx doctor</code> for a structural health check, then use <code>/ctx-doctor</code> for agent-driven analysis of event patterns.</p> <p>Uses: <code>ctx doctor</code>, <code>ctx hook event</code>, <code>/ctx-doctor</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#claude-code-permission-hygiene","level":3,"title":"Claude Code Permission Hygiene","text":"<p>Keep <code>.claude/settings.local.json</code> clean: recommended safe defaults, what to never pre-approve, and a maintenance workflow for cleaning up session debris.</p> <p>Uses: <code>ctx init</code>, <code>/ctx-drift</code>, <code>/ctx-permission-sanitize</code>, <code>ctx permission snapshot</code>, <code>ctx permission restore</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#permission-snapshots","level":3,"title":"Permission Snapshots","text":"<p>Capture a known-good permission baseline as a golden image, then restore at session start to automatically drop session-accumulated permissions.</p> <p>Uses: <code>ctx permission snapshot</code>, <code>ctx permission restore</code>, <code>/ctx-permission-sanitize</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#turning-activity-into-content","level":3,"title":"Turning Activity into Content","text":"<p>Generate blog posts from project activity, write changelog posts from commit ranges, and publish a browsable journal site from your session history.</p> <p>The output is generic Markdown, but the skills are tuned for the <code>ctx</code>-style blog artifacts you see on this website.</p> <p>Uses: <code>ctx journal site</code>, <code>ctx journal obsidian</code>, <code>ctx serve</code>, <code>ctx journal import</code>, <code>/ctx-blog</code>, <code>/ctx-blog-changelog</code>, <code>/ctx-journal-enrich</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#importing-claude-code-plans","level":3,"title":"Importing Claude Code Plans","text":"<p>Import Claude Code plan files (<code>~/.claude/plans/*.md</code>) into <code>specs/</code> as permanent project specs. Filter by date, select interactively, and optionally create tasks referencing each imported spec.</p> <p>Uses: <code>/ctx-plan-import</code>, <code>/ctx-task-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#design-before-coding","level":3,"title":"Design Before Coding","text":"<p>Front-load design with a four-skill chain: brainstorm the approach, spec the design, task the work, implement step-by-step. Each step produces an artifact that feeds the next.</p> <p>Uses: <code>/ctx-brainstorm</code>, <code>/ctx-spec</code>, <code>/ctx-task-add</code>, <code>/ctx-implement</code>, <code>/ctx-decision-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#scrutinizing-a-plan","level":3,"title":"Scrutinizing a Plan","text":"<p>Once a plan exists, run an adversarial interview to surface what's weak, missing, or unexamined before you commit. Walks the plan depth-first: assumptions, failure modes, alternatives, sequencing, reversibility. The complement to brainstorm: brainstorm produces plans, this attacks them.</p> <p>Uses: <code>/ctx-plan</code>, <code>/ctx-spec</code>, <code>/ctx-decision-add</code>, <code>/ctx-learning-add</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#agents-and-automation","level":2,"title":"Agents and Automation","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#building-project-skills","level":3,"title":"Building Project Skills","text":"<p>Encode repeating workflows into reusable skills the agent loads automatically. Covers the full cycle: identify a pattern, create the skill, test with realistic prompts, and iterate until it triggers correctly.</p> <p>Uses: <code>/ctx-skill-create</code>, <code>ctx init</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#running-an-unattended-ai-agent","level":3,"title":"Running an Unattended AI Agent","text":"<p>Set up a loop where an AI agent works through tasks overnight without you at the keyboard, using <code>ctx</code> for persistent memory between iterations.</p> <p>This recipe shows how <code>ctx</code> supports long-running agent loops without losing context or intent.</p> <p>Uses: <code>ctx init</code>, <code>ctx loop</code>, <code>ctx watch</code>, <code>ctx load</code>, <code>/ctx-loop</code>, <code>/ctx-implement</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#when-to-use-a-team-of-agents","level":3,"title":"When to Use a Team of Agents","text":"<p>Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.</p> <p>This recipe covers the file overlap test, when teams make things worse, and what <code>ctx</code> provides at each level.</p> <p>Uses: <code>/ctx-worktree</code>, <code>/ctx-next</code>, <code>ctx status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#parallel-agent-development-with-git-worktrees","level":3,"title":"Parallel Agent Development with Git Worktrees","text":"<p>Split a large backlog across 3-4 agents using git worktrees, each on its own branch and working directory. Group tasks by file overlap, work in parallel, merge back.</p> <p>Uses: <code>/ctx-worktree</code>, <code>/ctx-next</code>, <code>git worktree</code>, <code>git merge</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#architecture-deep-dive","level":3,"title":"Architecture Deep Dive","text":"<p>Three-pass pipeline for understanding a codebase: map what exists, enrich with code intelligence, then hunt for where it will silently fail. Produces architecture docs, quantified dependency data, and ranked failure hypotheses.</p> <p>Uses: <code>/ctx-architecture</code>, <code>/ctx-architecture-enrich</code>, <code>/ctx-architecture-failure-analysis</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#writing-steering-files","level":3,"title":"Writing Steering Files","text":"<p>Tell your AI assistant how to behave with rule-based prompt injection that fires automatically when prompts match a description. Walks through scaffolding a steering file, previewing matches, and syncing to each AI tool's native format.</p> <p>Uses: <code>ctx steering add</code>, <code>ctx steering preview</code>, <code>ctx steering list</code>, <code>ctx steering sync</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#authoring-lifecycle-triggers","level":3,"title":"Authoring Lifecycle Triggers","text":"<p>Run executable shell scripts at session-start, pre-tool-use, file-save, and other lifecycle events. Script-based automation (complementary to steering's rule-based prompts), with a security-first workflow: scaffold disabled, test with mock input, enable only after review.</p> <p>Uses: <code>ctx trigger add</code>, <code>ctx trigger test</code>, <code>ctx trigger enable</code>, <code>ctx trigger disable</code>, <code>ctx trigger list</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#hub","level":2,"title":"Hub","text":"","path":["Recipes"],"tags":[]},{"location":"recipes/#hub-overview","level":3,"title":"Hub Overview","text":"<p>Mental model and three user stories for the <code>ctx</code> Hub. What flows, what doesn't, and when not to use it. Read this before any of the other Hub recipes.</p> <p>Uses: <code>ctx hub</code>, <code>ctx connection</code>, <code>ctx add --share</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-getting-started","level":3,"title":"<code>ctx</code> Hub: Getting Started","text":"<p>Stand up a single-node hub on localhost, register two projects, publish a decision from one, and watch it appear in the other. End-to-end in under five minutes.</p> <p>Uses: <code>ctx hub start</code>, <code>ctx connection register</code>, <code>ctx connection subscribe</code>, <code>ctx connection sync</code>, <code>ctx connection listen</code>, <code>ctx add --share</code>, <code>ctx agent --include-hub</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#personal-cross-project-brain","level":3,"title":"Personal Cross-Project Brain","text":"<p>Story 1 day-to-day workflow: one developer, many projects, one hub on localhost. Records a learning in project A, watches it show up automatically in project B. Walks through a realistic day of using the hub as passive infrastructure (no manual <code>sync</code>, no <code>git push</code>, no ceremony).</p> <p>Uses: <code>ctx add --share</code>, <code>ctx connection subscribe</code>, <code>ctx agent --include-hub</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#team-knowledge-bus","level":3,"title":"Team Knowledge Bus","text":"<p>Story 2 day-to-day workflow: a small trusted team sharing decisions, learnings, and conventions via a hub on an internal server. Covers the team publishing culture, what belongs on the hub vs. local, token management, and the social rules that make a shared knowledge stream stay signal-rich.</p> <p>Uses: <code>ctx add --share</code>, <code>ctx connection status</code>, <code>ctx connection subscribe</code>, <code>ctx hub status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-multi-machine","level":3,"title":"<code>ctx</code> Hub: Multi-Machine","text":"<p>Run the hub on a LAN host as a daemon and connect from project directories on other workstations. Firewall guidance, TLS via a reverse proxy, and safe daemon restart semantics.</p> <p>Uses: <code>ctx hub start --daemon</code>, <code>ctx hub stop</code>, <code>ctx connection register</code>, <code>ctx connection status</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/#ctx-hub-ha-cluster","level":3,"title":"<code>ctx</code> Hub: HA Cluster","text":"<p>Raft-based leader election across three or more nodes for redundancy. Covers bootstrap, runtime peer management, graceful stepdown, and the Raft-lite durability caveat.</p> <p>Uses: <code>ctx hub start --peers</code>, <code>ctx hub status</code>, <code>ctx hub peer add/remove</code>, <code>ctx hub stepdown</code></p>","path":["Recipes"],"tags":[]},{"location":"recipes/activating-context/","level":1,"title":"Activating a Context Directory","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#the-problem","level":2,"title":"The Problem","text":"<p>You ran a <code>ctx</code> command and got:</p> <pre><code>Error: no context directory specified for this project\n</code></pre> <p>This means <code>ctx</code> doesn't know which <code>.context/</code> directory to operate on. It will not guess, and it will not walk up from your current working directory looking for one; that behavior was removed deliberately, because silent inference was the source of several bugs (stray agent-created directories, cross-project bleed-through, webhook-route misrouting, sub-agent fragmentation). Every <code>ctx</code> command requires you to declare the target directory explicitly.</p> <p>This page shows you the three ways to do that and when to use each.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#tldr","level":2,"title":"TL;DR","text":"<p>If the project has already been initialized and you just need to bind it for your shell:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>That's 95% of the time. Add it to <code>.zshrc</code> / <code>.bashrc</code> per project with direnv, or run it once per terminal.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#when-you-see-the-error","level":2,"title":"When You See the Error","text":"<p>The exact error message depends on how many <code>.context/</code> directories are visible from the current directory:</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#zero-candidates","level":3,"title":"Zero Candidates","text":"<pre><code>Error: no context directory specified for this project\n</code></pre> <p>Either you haven't initialized this project yet (run <code>ctx init</code>) or you're in a directory that doesn't belong to a ctx-tracked project. If you know the project lives elsewhere, use one of the declaration methods below with its absolute path.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-candidate","level":3,"title":"One Candidate","text":"<pre><code>Error: no context directory specified; a likely candidate is at\n    /Users/you/repos/myproject/.context\n</code></pre> <p><code>ctx</code> found a single <code>.context/</code> on the way up from here but won't bind to it automatically. Run <code>eval \"$(ctx activate)\"</code> and <code>ctx</code> will emit the <code>export</code> for the candidate. Or set <code>CTX_DIR</code> by hand.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#multiple-candidates","level":3,"title":"Multiple Candidates","text":"<pre><code>Error: no context directory specified; multiple candidates visible:\n  /Users/you/repos/myproject/.context\n  /Users/you/repos/myproject/packages/web/.context\n</code></pre> <p>You're inside nested projects. Pick the one you mean:</p> <pre><code>ctx activate /Users/you/repos/myproject/.context\n# …copy and paste the `export` line it prints, or wrap in eval:\neval \"$(ctx activate /Users/you/repos/myproject/.context)\"\n</code></pre>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#three-ways-to-declare","level":2,"title":"Three Ways to Declare","text":"","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#1-ctx-activate-recommended-for-shells","level":3,"title":"1. <code>ctx activate</code> (Recommended for Shells)","text":"<p><code>ctx activate</code> emits a shell-native <code>export CTX_DIR=...</code> line to stdout. Wrap it in <code>eval</code> and the binding takes effect for the current shell:</p> <pre><code># Walk up from current dir and bind the single visible candidate:\neval \"$(ctx activate)\"\n\n# Bind a specific path explicitly:\neval \"$(ctx activate /abs/path/to/.context)\"\n\n# Clear the binding:\neval \"$(ctx deactivate)\"\n</code></pre> <p><code>ctx activate</code> validates paths strictly: the target must exist, be a directory, and contain at least one canonical context file (<code>CONSTITUTION.md</code> or <code>TASKS.md</code>). It refuses to emit for multiple upward candidates; pick one explicitly in that case.</p> <p>Under the hood, the emitted line is just:</p> <pre><code>export CTX_DIR='/abs/path/to/.context'\n</code></pre> <p>So you can copy it into your <code>.zshrc</code> / <code>.bashrc</code> if you want the binding permanent for a given shell setup. Better: use direnv with a per-project <code>.envrc</code>.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#2-ctx_dir-env-var","level":3,"title":"2. <code>CTX_DIR</code> Env Var","text":"<p>If you already know the path, export it directly:</p> <pre><code>export CTX_DIR=/abs/path/to/.context\nctx status\n</code></pre> <p><code>CTX_DIR</code> is the same variable <code>ctx activate</code> writes; <code>activate</code> is just a convenience that figures out the path for you.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#3-inline-one-shot","level":3,"title":"3. Inline One-Shot","text":"<p>For one-shot commands (CI jobs, scripts, debugging a specific project without changing your shell state), prefix the binding inline:</p> <pre><code>CTX_DIR=/abs/path/to/.context ctx status\n</code></pre> <p>This binds <code>CTX_DIR</code> for that invocation only.</p> <p><code>CTX_DIR</code> must be an absolute path with <code>.context</code> as its basename. Relative paths and other names are rejected on first use; the basename guard catches the common footgun (<code>export CTX_DIR=$(pwd)</code>) before stray writes can leak to the project root.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-ci-and-scripts","level":2,"title":"For CI and Scripts","text":"<p>Do not rely on shell activation in automated flows. Set <code>CTX_DIR</code> explicitly at the top of the script:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\nexport CTX_DIR=\"$GITHUB_WORKSPACE/.context\"\nctx status\nctx drift\n</code></pre>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#for-claude-code-users","level":2,"title":"For Claude Code Users","text":"<p>The <code>ctx</code> plugin's hooks are generated with <code>CTX_DIR=\"$CLAUDE_PROJECT_DIR/.context\"</code> prefixed to each command, so hook-driven <code>ctx</code> invocations resolve correctly without any per-session setup. You only need to activate manually when running <code>ctx</code> yourself in a terminal.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#one-project-one-context","level":2,"title":"One Project, One <code>.context/</code>","text":"<p>The context directory is not a free-floating bag of files. It is pinned to a project by contract: <code>filepath.Dir(ContextDir())</code> is the project root. That parent directory is what <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook scan for code, secret files, and <code>MEMORY.md</code> respectively.</p> <p>The practical consequences:</p> <ul> <li>Don't share one <code>.context/</code> across multiple projects. It holds   per-project journals, per-session state, and per-project secrets.   Pointing two codebases at the same directory corrupts all three.</li> <li>If you want to share knowledge (CONSTITUTION, CONVENTIONS,   ARCHITECTURE) across projects, use <code>ctx hub</code>. It cherry-picks   entries at the right granularity and keeps the per-project bits   where they belong.</li> <li>The <code>CTX_DIR</code> you activate is implicitly a project-root   declaration. Setting <code>CTX_DIR=/weird/place/.context</code> means   you're telling <code>ctx</code> the project root is <code>/weird/place/</code>. That's   your call to make; <code>ctx</code> does not police it.</li> </ul>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#recommended-layout","level":3,"title":"Recommended Layout","text":"<pre><code>~/WORKSPACE/my-to-do-list\n  ├── .git\n  ├── .context          ← owned by this project; do not share\n  ├── ideas\n  │   └── ...\n  ├── Makefile\n  ├── Makefile.ctx\n  └── specs\n      └── ...\n</code></pre> <p><code>.context/</code> sits at the project root, next to <code>.git</code>. <code>ctx activate</code> binds to it; every <code>ctx</code> subsystem reads the project from its parent.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/activating-context/#why-not-walk-up-automatically","level":2,"title":"Why Not Walk Up Automatically?","text":"<p>Nested projects, submodules, rogue agent-created <code>.context/</code> directories, and sub-agent sessions all produced silent misrouting under a walk-up model. For consistency, <code>ctx</code> does not guess,  and  requires the caller to declare. Every other decision flows from there.</p>","path":["Recipes","Getting Started","Activating a Context Directory"],"tags":[]},{"location":"recipes/architecture-deep-dive/","level":1,"title":"Architecture Deep Dive","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-problem","level":2,"title":"The Problem","text":"<p>Understanding a codebase at the surface level is easy. Understanding where it will break under real-world conditions takes three passes: mapping what exists, quantifying how it connects, and hunting for where it silently fails. Most teams stop at the first pass.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tldr","level":2,"title":"TL;DR","text":"<pre><code># Pass 1: Map the system\n/ctx-architecture\n\n# Pass 2: Enrich with code intelligence\n/ctx-architecture-enrich\n\n# Pass 3: Hunt for failure modes\n/ctx-architecture-failure-analysis\n</code></pre> <p>Each pass builds on the previous one. Run them in order. The output accumulates in <code>.context/</code>; each pass reads the prior artifacts and extends them.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-architecture</code> Skill Map modules, dependencies, data flow, patterns <code>/ctx-architecture-enrich</code> Skill Verify blast radius and flows with code intel <code>/ctx-architecture-failure-analysis</code> Skill Generate falsifiable incident hypotheses <code>ctx drift</code> CLI Detect stale paths and broken references <code>ctx status</code> CLI Quick structural overview","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-1-map-what-exists","level":3,"title":"Pass 1: Map What Exists","text":"<pre><code>/ctx-architecture\n</code></pre> <p>Produces:</p> <ul> <li>ARCHITECTURE.md: succinct project map (< 4000 tokens),   loaded at every session start</li> <li>DETAILED_DESIGN*.md: deep per-module reference with   exported API, data flow, danger zones, extension points</li> <li>CHEAT-SHEETS.md: lifecycle flow diagrams</li> <li>map-tracking.json: coverage state with confidence scores</li> </ul> <p>This pass forces deep code reading. No shortcuts, no code intelligence tools; the agent reads every module it analyzes. That forced reading is what makes the subsequent passes useful.</p> <p>When to run: First time on a codebase, or after significant structural changes (new packages, moved files, changed dependencies).</p> <p>Principal mode: Add <code>principal</code> to get strategic analysis (ARCHITECTURE-PRINCIPAL.md, DANGER-ZONES.md from P4):</p> <pre><code>/ctx-architecture principal\n</code></pre>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-2-enrich-with-code-intelligence","level":3,"title":"Pass 2: Enrich with Code Intelligence","text":"<pre><code>/ctx-architecture-enrich\n</code></pre> <p>Takes the Pass 1 artifacts as baseline and layers on verified, graph-backed data from GitNexus:</p> <ul> <li>Blast radius numbers for key functions</li> <li>Execution flow traces through hot paths</li> <li>Domain clustering validation</li> <li>Registration site discovery</li> </ul> <p>This pass does not replace reading; it quantifies what reading found. If Pass 1 says \"module X depends on module Y,\" Pass 2 says \"module X has 47 callers in module Y, and changing function Z would affect 12 downstream consumers.\"</p> <p>When to run: After Pass 1, when you need quantified confidence for refactoring decisions or risk assessment.</p> <p>Requires: GitNexus MCP server connected.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#pass-3-hunt-for-failure-modes","level":3,"title":"Pass 3: Hunt for Failure Modes","text":"<pre><code>/ctx-architecture-failure-analysis\n</code></pre> <p>The adversarial pass. Reads all prior artifacts, then systematically hunts for correctness bugs across 9 failure categories:</p> <ol> <li>Concurrency (races, deadlocks, goroutine leaks)</li> <li>Ordering assumptions (init, registration, shutdown)</li> <li>Cache staleness (TTL-less, read-your-writes, cross-process)</li> <li>Fan-out amplification (N+1, retry storms)</li> <li>Ownership and lifecycle (orphans, double-close)</li> <li>Error handling (silent swallowing, partial failure)</li> <li>Scaling cliffs (quadratic, unbounded, global locks)</li> <li>Idempotency failures (duplicate processing, retry mutations)</li> <li>State machine drift (illegal states, unvalidated transitions)</li> </ol> <p>Every finding must meet an evidence standard: code path, trigger, failure path, silence reason, and code evidence. A mandatory challenge phase attempts to disprove each finding before it is accepted. Findings carry a confidence level (High/Medium/Low) and explicit risk score.</p> <p>Produces DANGER-ZONES.md, a ranked inventory of findings split into Critical and Elevated tiers.</p> <p>When to run: Before releases, after major refactors, when investigating incident categories, or when onboarding.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#what-you-get","level":2,"title":"What You Get","text":"<p>After all three passes, <code>.context/</code> contains:</p> File From Purpose <code>ARCHITECTURE.md</code> Pass 1 System map (session-start context) <code>DETAILED_DESIGN*.md</code> Pass 1 Module-level deep reference <code>CHEAT-SHEETS.md</code> Pass 1 Lifecycle flow diagrams <code>map-tracking.json</code> Pass 1 Coverage and confidence data <code>CONVERGENCE-REPORT.md</code> Pass 1 What's covered, what's not <code>DANGER-ZONES.md</code> Pass 3 Ranked failure hypotheses <p>Pass 2 enriches Pass 1 artifacts in-place rather than creating new files.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#tips","level":2,"title":"Tips","text":"<ul> <li>Run Pass 1 with focus areas if the codebase is large.   The skill asks what to go deep on, so name the modules you're   about to change.</li> <li>You don't need all three passes every time. Pass 1 is   the foundation. Pass 2 and 3 are for when you need   quantified confidence or adversarial rigor.</li> <li>Re-run Pass 1 incrementally. It tracks coverage in   <code>map-tracking.json</code> and only re-analyzes stale modules.</li> <li>Pass 3 is most valuable before releases. The ranked   DANGER-ZONES.md is a pre-release checklist.</li> <li>The trilogy maps to a question progression: How does it   work? How well does it connect? Where will it break?</li> </ul>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/architecture-deep-dive/#see-also","level":2,"title":"See Also","text":"<p>See also: Detecting and Fixing Context Drift to keep architecture artifacts fresh between deep-dive sessions.</p> <p>See also: Detecting and Fixing Context Drift for structural checks that complement architecture analysis.</p>","path":["Architecture Deep Dive"],"tags":[]},{"location":"recipes/autonomous-loops/","level":1,"title":"Running an Unattended AI Agent","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-problem","level":2,"title":"The Problem","text":"<p>You have a project with a clear list of tasks, and you want an AI agent to work through them autonomously: overnight, unattended, without you sitting at the keyboard.</p> <p>Each iteration needs to remember what the previous one did, mark tasks as completed, and know when to stop.</p> <p>Without persistent memory, every iteration starts fresh and the loop collapses. With <code>ctx</code>, each iteration can pick up where the last one left off, but only if the agent persists its context as part of the work.</p> <p>Unattended operation works because the agent treats context persistence as a first-class deliverable, not an afterthought.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                                    # 1. init context\neval \"$(ctx activate)\"                      # 2. bind CTX_DIR for this shell\n# Edit TASKS.md with phased work items\nctx loop --tool claude --max-iterations 10  # 3. generate loop.sh\n./loop.sh 2>&1 | tee /tmp/loop.log &        # 4. run the loop\nctx watch --log /tmp/loop.log               # 5. process context updates\n# Next morning:\nctx status && ctx load                      # 6. review the results\n</code></pre> <p>Activate, or Set CTX_DIR Inline for Unattended Runs</p> <p><code>eval \"$(ctx activate)\"</code> is fine for an interactive terminal. For an overnight unattended loop, put the binding at the top of <code>loop.sh</code> instead (<code>export CTX_DIR=/abs/path/.context</code>) so the loop doesn't depend on a live shell. If you skip both, <code>ctx loop</code>, <code>ctx watch</code>, <code>ctx status</code>, and <code>ctx load</code> fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Read on for permissions, isolation, and completion signals.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> Command Initialize project context and prompt templates <code>ctx loop</code> Command Generate the loop shell script <code>ctx watch</code> Command Monitor AI output and persist context updates <code>ctx load</code> Command Display assembled context (for debugging) <code>/ctx-loop</code> Skill Generate loop script from inside Claude Code <code>/ctx-implement</code> Skill Execute a plan step-by-step with verification","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-1-initialize-for-unattended-operation","level":3,"title":"Step 1: Initialize for Unattended Operation","text":"<p>Start by creating a <code>.context/</code> directory configured so the agent can work without human input.</p> <pre><code>ctx init\n</code></pre> <p>This creates <code>.context/</code> with the template files (including a loop prompt at <code>.context/loop.md</code>), and seeds Claude Code permissions in <code>.claude/settings.local.json</code>. Install the <code>ctx</code> plugin for hooks and skills.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-2-populate-tasksmd-with-phased-work","level":3,"title":"Step 2: Populate <code>TASKS.md</code> with Phased Work","text":"<p>Open <code>.context/TASKS.md</code> and organize your work into phases. The agent works through these systematically, top to bottom, using priority tags to break ties.</p> <pre><code># Tasks\n\n## Phase 1: Foundation\n\n- [ ] Set up project structure and build system `#priority:high`\n- [ ] Configure testing framework `#priority:high`\n- [ ] Create CI pipeline `#priority:medium`\n\n## Phase 2: Core Features\n\n- [ ] Implement user registration `#priority:high`\n- [ ] Add email verification `#priority:high`\n- [ ] Create password reset flow `#priority:medium`\n\n## Phase 3: Hardening\n\n- [ ] Add rate limiting to API endpoints `#priority:medium`\n- [ ] Improve error messages `#priority:low`\n- [ ] Write integration tests `#priority:medium`\n</code></pre> <p>Phased organization matters because it gives the agent natural boundaries. Phase 1 tasks should be completable without Phase 2 code existing yet.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-3-configure-the-loop-prompt","level":3,"title":"Step 3: Configure the Loop Prompt","text":"<p>The loop prompt at <code>.context/loop.md</code> instructs the agent to operate autonomously:</p> <ol> <li>Read <code>.context/CONSTITUTION.md</code> first (hard rules, never violated)</li> <li>Load context from <code>.context/</code> files</li> <li>Pick one task per iteration</li> <li>Complete the task and update context files</li> <li>Commit changes (including <code>.context/</code>)</li> <li>Signal status with a completion signal</li> </ol> <p>You can customize <code>.context/loop.md</code> for your project. The critical parts are the one-task-per-iteration discipline, proactive context persistence, and completion signals at the end:</p> <pre><code>## Signal Status\n\nEnd your response with exactly ONE of:\n\n* `SYSTEM_CONVERGED`: All tasks in `TASKS.md` are complete (*this is the\n  signal the loop script detects by default*)\n* `SYSTEM_BLOCKED`: Cannot proceed, need human input (explain why)\n* (*no signal*): More work remains, continue to the next iteration\n\nNote: the loop script only checks for `SYSTEM_CONVERGED` by default.\n`SYSTEM_BLOCKED` is a convention for the human reviewing the log.\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-4-configure-permissions","level":3,"title":"Step 4: Configure Permissions","text":"<p>An unattended agent needs permission to use tools without prompting. By default, Claude Code asks for confirmation on file writes, bash commands, and other operations, which stops the loop and waits for a human who is not there.</p> <p>There are two approaches.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-a-explicit-allowlist-recommended","level":4,"title":"Option A: Explicit Allowlist (Recommended)","text":"<p>Grant only the permissions the agent needs. In <code>.claude/settings.local.json</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(make:*)\",\n      \"Bash(go:*)\",\n      \"Bash(git:*)\",\n      \"Bash(ctx:*)\",\n      \"Read\",\n      \"Write\",\n      \"Edit\"\n    ]\n  }\n}\n</code></pre> <p>Adjust the <code>Bash</code> patterns for your project's toolchain. The agent can run <code>make</code>, <code>go</code>, <code>git</code>, and <code>ctx</code> commands but cannot run arbitrary shell commands.</p> <p>This is recommended even in sandboxed environments because it limits blast radius.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#option-b-skip-all-permission-checks","level":4,"title":"Option B: Skip All Permission Checks","text":"<p>Claude Code supports a <code>--dangerously-skip-permissions</code> flag that disables all permission prompts:</p> <pre><code>claude --dangerously-skip-permissions -p \"$(cat .context/loop.md)\"\n</code></pre> <p>This Flag Means What It Says</p> <p>With <code>--dangerously-skip-permissions</code>, the agent can execute any shell command, write to any file, and make network requests without confirmation.</p> <p>Only use this on a sandboxed machine: ideally a virtual machine with no access to host credentials, no SSH keys, and no access to production systems.</p> <p>If you would not give an untrusted intern <code>sudo</code> on this machine, do not use this flag.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#enforce-isolation-at-the-os-level","level":4,"title":"Enforce Isolation at the OS Level","text":"<p>The only controls an agent cannot override are the ones enforced by the operating system, the container runtime, or the hypervisor.</p> <p>Do Not Skip This Section</p> <p>This is not optional hardening:</p> <p>An unattended agent with unrestricted OS access is an unattended shell with unrestricted OS access. </p> <p>The allowlist above is a strong first layer, but do not rely on a single runtime boundary.</p> <p>For unattended runs, enforce isolation at the infrastructure level:</p> Layer What to enforce User account Run the agent as a dedicated unprivileged user with no <code>sudo</code> access and no membership in privileged groups (<code>docker</code>, <code>wheel</code>, <code>adm</code>). Filesystem Restrict the project directory via POSIX permissions or ACLs. The agent should have no access to other users' files or system directories. Container Run inside a Docker/Podman sandbox. Mount only the project directory. Drop capabilities (<code>--cap-drop=ALL</code>). Disable network if not needed (<code>--network=none</code>). Never mount the Docker socket and do not run privileged containers. Prefer rootless containers. Virtual machine Prefer a dedicated VM with no shared folders, no host passthrough, and no keys to other machines. Network If the agent does not need the internet, disable outbound access entirely. If it does, restrict to specific domains via firewall rules. Resource limits Apply CPU, memory, and disk limits (cgroups/container limits). A runaway loop should not fill disk or consume all RAM. Self-modification Make instruction files read-only. <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, and <code>.context/CONSTITUTION.md</code> should not be writable by the agent user. If using project-local hooks, protect those too. <p>A minimal Docker setup for overnight runs:</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh 2>&1 | tee /tmp/loop.log\n</code></pre> <p>Defense in Depth</p> <p>Use multiple layers together: OS-level isolation (the boundary the agent cannot cross), a permission allowlist (what Claude Code will do within that boundary), and <code>CONSTITUTION.md</code> (a soft nudge for the common case).</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-5-generate-the-loop-script","level":3,"title":"Step 5: Generate the Loop Script","text":"<p>Use <code>ctx loop</code> to generate a <code>loop.sh</code> tailored to your AI tool:</p> <pre><code># Generate for Claude Code with a 10-iteration cap\nctx loop --tool claude --max-iterations 10\n\n# Generate for Aider\nctx loop --tool aider --max-iterations 10\n\n# Custom prompt file and output filename\nctx loop --tool claude --prompt my-prompt.md --output my-loop.sh\n</code></pre> <p>The generated script reads <code>.context/loop.md</code>, runs the tool, checks for completion signals, and loops until done or the cap is reached.</p> <p>You can also use the <code>/ctx-loop</code> skill from inside Claude Code.</p> <p>A Shell Loop Is the Best Practice</p> <p>The shell loop approach spawns a fresh AI process each iteration, so the only state that carries between iterations is what lives in <code>.context/</code> and git.</p> <p>Claude Code's built-in <code>/loop</code> runs iterations within the same session, which can allow context window state to leak between iterations. This can be convenient for short runs, but it is less reliable for unattended loops. </p> <p>See Shell Loop vs Built-in Loop for details.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-6-run-with-watch-mode","level":3,"title":"Step 6: Run with Watch Mode","text":"<p>Open two terminals. In the first, run the loop. In the second, run <code>ctx watch</code> to process context updates from the AI output.</p> <pre><code># Terminal 1: Run the loop\n./loop.sh 2>&1 | tee /tmp/loop.log\n\n# Terminal 2: Watch for context updates\nctx watch --log /tmp/loop.log\n</code></pre> <p>The watch command parses XML context-update commands from the AI output and applies them:</p> <pre><code><context-update type=\"complete\">user registration</context-update>\n<context-update type=\"learning\"\n  context=\"Setting up user registration\"\n  lesson=\"Email verification needs SMTP configured\"\n  application=\"Add SMTP setup to deployment checklist\"\n>SMTP Requirement</context-update>\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-7-completion-signals-end-the-loop","level":3,"title":"Step 7: Completion Signals End the Loop","text":"<p>The generated script checks for one completion signal per run. By default this is <code>SYSTEM_CONVERGED</code>. You can change it with the <code>--completion</code> flag:</p> <pre><code>ctx loop --tool claude --completion BOOTSTRAP_COMPLETE --max-iterations 5\n</code></pre> <p>The following signals are conventions used in <code>.context/loop.md</code>:</p> Signal Convention How the script handles it <code>SYSTEM_CONVERGED</code> All tasks in <code>TASKS.md</code> are done Detected by default (<code>--completion</code> default value) <code>SYSTEM_BLOCKED</code> Agent cannot proceed Only detected if you set <code>--completion</code> to this <code>BOOTSTRAP_COMPLETE</code> Initial scaffolding done Only detected if you set <code>--completion</code> to this <p>The script uses <code>grep -q</code> on the agent's output, so any string works as a signal. If you need to detect multiple signals in one run, edit the generated <code>loop.sh</code> to add additional <code>grep</code> checks.</p> <p>When you return in the morning, check the log and the context files:</p> <pre><code>tail -100 /tmp/loop.log\nctx status\nctx load\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#step-8-use-ctx-implement-for-plan-execution","level":3,"title":"Step 8: Use <code>/ctx-implement</code> for Plan Execution","text":"<p>Within each iteration, the agent can use <code>/ctx-implement</code> to execute multi-step plans with verification between steps. This is useful for complex tasks that touch multiple files.</p> <p>The skill breaks a plan into atomic, verifiable steps:</p> <pre><code>Step 1/6: Create user model .................. OK\nStep 2/6: Add database migration ............. OK\nStep 3/6: Implement registration handler ..... OK\nStep 4/6: Write unit tests ................... OK\nStep 5/6: Run test suite ..................... FAIL\n  -> Fixed: missing test dependency\n  -> Re-verify ............................... OK\nStep 6/6: Update TASKS.md .................... OK\n</code></pre> <p>Each step is verified (build, test, syntax check) before moving to the next.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A typical overnight run:</p> <pre><code>ctx init\n# Edit TASKS.md and .context/loop.md\n\nctx loop --tool claude --max-iterations 20\n\n./loop.sh 2>&1 | tee /tmp/loop.log &\nctx watch --log /tmp/loop.log\n\n# Next morning:\nctx status\nctx load\n</code></pre>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#why-autonomous-loops-work-proactive-context-persistence","level":2,"title":"Why Autonomous Loops Work: Proactive Context Persistence","text":"<p>The autonomous loop pattern works because the agent persists context as part of the job.</p> <p>Without proactive persistence, the loop degrades into disconnected iterations that repeat work, contradict decisions, and lose track of progress. The agent cannot rely on a human to prompt it. It must treat context updates as part of every task, not as a separate step.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-agent-playbook-self-check-drives-autonomy","level":3,"title":"The Agent Playbook Self-Check Drives Autonomy","text":"<p>The Agent Playbook defines a self-check prompt the agent applies continuously:</p> <pre><code>\"If this session ended right now, would the next session know what happened?\"\n</code></pre> <p>In interactive sessions, a human might remind the agent to save context. When the agent runs unattended, nobody is there to remind it.</p> <p>The self-check becomes the trigger: after meaningful work, the agent evaluates whether the context files reflect reality and updates them immediately if they do not.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#what-the-agent-does-proactively-between-iterations","level":3,"title":"What the Agent Does Proactively between Iterations","text":"<p>At milestones within an iteration, the agent persists without waiting for instructions:</p> Milestone What the agent does proactively Completes a task Marks it <code>[x]</code> in <code>TASKS.md</code> Discovers a gotcha Adds it to <code>LEARNINGS.md</code> Makes a design choice Records it in <code>DECISIONS.md</code> with rationale Identifies follow-up work Creates new tasks in <code>TASKS.md</code> with <code>#added</code> timestamp Hits an unexpected failure Documents the root cause before moving on","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#example-what-proactive-persistence-looks-like","level":3,"title":"Example: What Proactive Persistence Looks Like","text":"<p>Here is what the agent does during a single iteration beyond writing code:</p> <pre><code>Iteration 4:\n  1. Read TASKS.md -> pick \"Add email verification\" (#priority:high)\n  2. Add #started:2026-01-25-030012 to the task\n  3. Implement the feature (code, tests, docs if needed)\n  4. Tests pass -> mark task [x]\n  5. Add learning: \"SMTP config must be set before verification handler registers. Order matters in init().\"\n  6. Add decision: \"Use token-based verification links (not codes) because links work better in automated tests.\"\n  7. Create follow-up task: \"Add rate limiting to verification endpoint\" #added:...\n  8. Commit all changes including `.context/`\n  9. No signal emitted -> loop continues to iteration 5\n</code></pre> <p>Steps 2, 4, 5, 6, and 7 are proactive context persistence: </p> <p>The agent was not asked to do any of them.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#context-persistence-at-milestones","level":3,"title":"Context Persistence at Milestones","text":"<p>For long autonomous runs, the agent persists context at natural boundaries, often at phase transitions or after completing a cluster of related tasks. It updates <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>LEARNINGS.md</code> as it goes.</p> <p>If the loop crashes at 4 AM, the context files tell you exactly where to resume. You can also use <code>ctx journal source</code> to review the session transcripts.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#the-persistence-contract","level":3,"title":"The Persistence Contract","text":"<p>The autonomous loop has an implicit contract:</p> <ol> <li>Every iteration reads context: <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code></li> <li>Every iteration writes context: task updates, new learnings, decisions</li> <li>Every commit includes <code>.context/</code> so the next iteration sees changes</li> <li>Context stays current: if the loop stopped right now, nothing important is lost</li> </ol> <p>Break any part of this contract and the loop degrades.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#tips","level":2,"title":"Tips","text":"<p>Markdown Is Not Enforcement</p> <p>Your real guardrails are permissions and isolation, not Markdown. <code>CONSTITUTION.md</code> can nudge the agent, but it is probabilistic. </p> <p>The permission allowlist and OS isolation are deterministic:</p> <p>For unattended runs, trust the sandbox and the allowlist, not the prose.</p> <ul> <li>Start with a small iteration cap. Use <code>--max-iterations 5</code> on your first run.</li> <li>Keep tasks atomic. Each task should be completable in a single iteration.</li> <li>Check signal discipline. If the loop runs forever, the agent is not emitting   <code>SYSTEM_CONVERGED</code> or <code>SYSTEM_BLOCKED</code>. Make the signal requirement explicit   in <code>.context/loop.md</code>.</li> <li>Commit after context updates. Finish code, update <code>.context/</code>, commit including   <code>.context/</code>, then signal.</li> <li>Set up webhook notifications to get notified   when the loop completes, hits max iterations, or when hooks fire nudges.   The generated loop script includes <code>ctx hook notify</code> calls automatically.</li> </ul>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#next-up","level":2,"title":"Next Up","text":"<p>When to Use a Team of Agents →: Decision framework for choosing between a single agent, parallel worktrees, and a full agent team.</p>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/autonomous-loops/#see-also","level":2,"title":"See Also","text":"<ul> <li>Autonomous Loops: loop pattern, prompt templates, troubleshooting</li> <li>CLI Reference: <code>ctx</code> loop: flags and options</li> <li>CLI Reference: <code>ctx</code> watch: watch mode details</li> <li>CLI Reference: <code>ctx</code> init: init flags</li> <li>The Complete Session: interactive workflow</li> <li>Tracking Work Across Sessions: structuring TASKS.md</li> </ul>","path":["Recipes","Agents and Automation","Running an Unattended AI Agent"],"tags":[]},{"location":"recipes/build-a-knowledge-base/","level":1,"title":"Build a Knowledge Base","text":"","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#the-problem","level":2,"title":"The Problem","text":"<p>You are doing knowledge-shaped work (vendor-spec analysis, a research project, a post-incident review, domain modeling) and the standard five context files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>, <code>CONSTITUTION.md</code>) don't fit. Because those files are tuned for code-development context, not for evidence-tracked knowledge with confidence bands, contradictions, and external citations.</p> <p>You need a place where:</p> <ul> <li>Every claim is pinned to a source you can re-verify.</li> <li>Topics grow into folders as they earn their depth.</li> <li>Two passes against the same source don't silently disagree.</li> <li>The next session knows what's incomplete, not just what's done.</li> </ul> <p>That's what the editorial pipeline is for.</p> <p>Prefer Skills to Raw Commands</p> <p>The pipeline is driven by skills (<code>/ctx-kb-ingest</code>, <code>/ctx-kb-ask</code>, etc.). The CLI form (<code>ctx kb ingest</code>, etc.) exists for scripting and for non-Claude environments; the skill is the natural surface.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#tldr","level":2,"title":"TL;DR","text":"<pre><code>git init && ctx init                    # lays down the kb + ingest tree\nctx kb topic new \"Cursor Hooks\"         # scaffold a topic folder\n/ctx-kb-ingest ./docs/cursor-hooks.md \"cursor hooks\" # editorial pass\n/ctx-kb-ask \"does the kb say hooks fire async?\"      # grounded Q&A\n/ctx-wrap-up                            # ceremony; delegates to /ctx-handover\n                                        # for the per-session handover\n</code></pre>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> Command Scaffold <code>.context/kb/</code>, <code>.context/ingest/</code>, etc. <code>ctx kb topic new <name></code> Command Sole writer of topic-page scaffolds (folder shape) <code>ctx kb note \"<text>\"</code> Command Lightweight capture into <code>.context/ingest/findings.md</code> <code>ctx kb reindex</code> Command Refresh the <code>CTX:KB:TOPICS</code> managed block <code>ctx handover write</code> Command Per-session handover with closeout fold <code>/ctx-kb-ingest</code> Skill Mode-aware editorial pass (topic-page/triage/evidence) <code>/ctx-kb-ask</code> Skill Q&A grounded in the kb <code>/ctx-kb-site-review</code> Skill Mechanical structural audit <code>/ctx-kb-ground</code> Skill Read-only freshness audit over the kb's tracked sources <code>/ctx-kb-note</code> Skill Capture a finding for the next ingest pass <code>/ctx-wrap-up</code> Skill End-of-session ceremony; delegates to the handover step","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-0-initialize-and-declare-scope","level":2,"title":"Step 0: Initialize and Declare Scope","text":"<pre><code>git init && ctx init\n</code></pre> <p><code>ctx init</code> lays down the editorial scaffolding alongside the standard context files:</p> <pre><code>.context/\n├── kb/\n│   ├── index.md\n│   └── topics/.gitkeep\n├── ingest/\n│   ├── KB-RULES.md             # editorial constitution\n│   ├── 00-GROUND.md\n│   ├── 30-INGEST.md\n│   ├── 40-ASK.md\n│   ├── 50-SITE_REVIEW.md\n│   ├── OPERATOR.md\n│   ├── PROMPT.md               # hand-fallback router\n│   ├── closeouts/.gitkeep\n│   └── schemas/\n│       └── *.md                # 10 schema templates\n└── handovers/.gitkeep\n</code></pre> <p>Open <code>.context/kb/index.md</code> and replace the placeholder <code>## Scope</code> paragraph with a one-paragraph statement of what this kb covers and what it does not. <code>/ctx-kb-ingest</code> refuses to run against an undeclared kb; scope is the precondition.</p> <p>Git is required</p> <p><code>ctx init</code> now refuses to run without <code>.git/</code>. The editorial pipeline's provenance (closeout <code>sha</code>/<code>branch</code>, evidence-index in-repo SHA pins) depends on it. Run <code>git init</code> first if the project does not already have one.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-1-scaffold-a-topic","level":2,"title":"Step 1: Scaffold a Topic","text":"<p>Topic pages live in folders, not flat files:</p> <pre><code>ctx kb topic new \"Cursor Hooks\"\n</code></pre> <p>This creates <code>.context/kb/topics/cursor-hooks/index.md</code> from the embedded template. The slug is computed by lowercasing + kebab- casing; vendor-namespaced shapes like <code>cursor/hooks</code> are preserved so you can grow into nested topology (<code>topics/cursor/hooks/</code>, <code>topics/cursor/skills/</code>, <code>topics/cursor/rules/</code>) without breaking citations.</p> <p><code>ctx kb topic new</code> is the sole writer of topic-page scaffolds. Skills invoke this command rather than synthesize a scaffold by hand; the embedded template is the single source of truth.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-2-run-an-editorial-pass","level":2,"title":"Step 2: Run an Editorial Pass","text":"<pre><code>/ctx-kb-ingest ./inputs/2026-04-12-call.md \"cursor hooks\"\n</code></pre> <p>The skill begins with a pass-mode declaration:</p> <p>Pass-mode: <code>topic-page</code> Reason: the user supplied one primary source and the intended topic is clear. Definition of done: create or extend <code>kb/topics/cursor-hooks/index.md</code>,  cite EV rows, run <code>ctx kb site build</code>, record cold-reader orientation.</p> <p>Then it:</p> <ol> <li>Resolves sources (paths / URLs / MCP resources) and updates    the source-coverage ledger at    <code>.context/kb/source-coverage.md</code> (a state machine across all    sources the kb has touched).</li> <li>Scans for adjacent incomplete topics in the ledger and    surfaces them so the new page acknowledges sibling gaps.</li> <li>Synthesizes prose section by section into the topic page,    minting <code>EV-###</code> rows in <code>evidence-index.md</code> for every cited    claim.</li> <li>Sets the Confidence floor (the page never claims more    certainty than its weakest cited band).</li> <li>Writes a closeout under    <code>.context/ingest/closeouts/<TS>-ingest-closeout.md</code> with    frontmatter, the cold-reader orientation rubric, and a    ledger-state advance per source.</li> </ol> <p>Three pass modes:</p> <ul> <li><code>topic-page</code> (default): write or extend a topic page.</li> <li><code>triage</code>: admit / skip sources against scope; no <code>EV-###</code> minted.</li> <li><code>evidence-only</code>: mint <code>EV-###</code> rows tagged <code>evidence-only</code>;   do not touch a topic page (explicit-request-only escape hatch).</li> </ul> <p>Mid-pass mode-switching is forbidden: the skill commits to one mode and aborts cleanly if the work no longer fits.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-3-qa-grounded-in-the-kb","level":2,"title":"Step 3: Q&A Grounded in the KB","text":"<pre><code>/ctx-kb-ask \"does the kb say hooks fire async?\"\n</code></pre> <p><code>/ctx-kb-ask</code> reads the kb's prose, cites <code>EV-###</code> rows, and refuses to web-jump. If the kb cannot answer, it opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> and reports the gap, which a future <code>/ctx-kb-ingest</code> pass can close.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-4-audit-re-ground","level":2,"title":"Step 4: Audit + Re-Ground","text":"<pre><code>/ctx-kb-site-review        # mechanical structural audit\n/ctx-kb-ground             # refresh sources listed in grounding-sources.md\n</code></pre> <p><code>site-review</code> coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, and refuses to make judgment calls that require evidence (those go through ingest).</p> <p><code>ground</code> reads <code>.context/ingest/grounding-sources.md</code> — the kb's persistent watch list — and walks each declared source (URL, in-tree path, or MCP resource) to check whether it has drifted since the kb last cited it. The pass is read-only on the kb's prose and evidence: it annotates the source-coverage ledger's <code>Residue</code> / <code>Next action</code> cells and writes a ground closeout, but does NOT re-extract claims, mint <code>EV-###</code> rows, or touch topic pages. Drifted or new-to-kb sources are flagged for a follow-up <code>/ctx-kb-ingest</code>. Use ground for \"are the docs still current?\" hygiene; use <code>/ctx-kb-ingest</code> to actually absorb new material.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-5-browse-the-kb-locally","level":2,"title":"Step 5: Browse the KB Locally","text":"<p><code>.context/kb/</code> is a tree of Markdown files: topic pages live under <code>topics/<slug>/index.md</code> and cross-cutting artifacts (<code>glossary.md</code>, <code>evidence-index.md</code>, <code>outstanding-questions.md</code>, <code>domain-decisions.md</code>, <code>contradictions.md</code>, <code>timeline.md</code>, <code>source-map.md</code>, <code>source-coverage.md</code>, <code>relationship-map.md</code>) sit alongside them. Drop a minimal <code>zensical.toml</code> into <code>.context/kb/</code> and hand it to <code>ctx serve</code>:</p> <pre><code>ctx serve .context/kb/\n</code></pre> <p>The KB renders the same way the docs site you are reading right now does. Use the in-place evidence-index links to jump from a topic page to its <code>EV-###</code> rows and back. The site build is read-only: no skill or CLI writes through it.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#step-6-wrap-up-with-a-handover","level":2,"title":"Step 6: Wrap Up with a Handover","text":"<p>Run <code>/ctx-wrap-up</code> at session end; it owns the ceremony and delegates to the handover step (<code>/ctx-handover</code>) as its final action:</p> <pre><code>/ctx-wrap-up \"Cursor Hooks deep dive\"\n</code></pre> <p>The handover artifact lands at <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). It folds postdated closeouts into a <code>## Folded closeouts</code> section and archives the source closeout files under <code>.context/archive/closeouts/</code>. The next session's <code>/ctx-remember</code> reads the latest handover and folds any closeouts whose <code>generated-at</code> postdates it.</p> <p>The legitimate direct-invocation cases for <code>/ctx-handover</code> are <code>--no-fold</code> for a mid-session checkpoint, or recovery when a prior session ended before its wrap-up step. For the underlying CLI, see <code>ctx handover write</code>.</p>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#how-it-ladders-together","level":2,"title":"How It Ladders Together","text":"<pre><code>sources you supply\n       │\n       ▼\n/ctx-kb-ingest (mode-declared, source-coverage advanced)\n       │\n       ├──▶ topic-page  ──▶ .context/kb/topics/<slug>/index.md\n       ├──▶ evidence    ──▶ .context/kb/evidence-index.md (EV-###)\n       ├──▶ side rails  ──▶ glossary.md, contradictions.md,\n       │                    outstanding-questions.md, timeline.md,\n       │                    source-map.md, relationship-map.md\n       └──▶ closeout    ──▶ .context/ingest/closeouts/<TS>-...md\n                               │\n                               ▼\n                       (next session)\n                               │\n                               ▼\n                  /ctx-wrap-up → /ctx-handover folds\n                  → .context/handovers/<TS>-<slug>.md\n                  + archives source closeouts under\n                  .context/archive/closeouts/\n                               │\n                               ▼\n                  /ctx-remember reads handover + postdated\n                  unfolded closeouts as the recall surface\n</code></pre>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#bootstrap-vs-steady-state-ingest-first-ground-later","level":2,"title":"Bootstrap vs Steady State: Ingest First, Ground Later","text":"<p><code>/ctx-kb-ingest</code> and <code>/ctx-kb-ground</code> both read sources, which makes their relationship easy to misread. The distinction is authority, not input shape:</p> <ul> <li>Ingest writes. It mints <code>EV-###</code> rows, authors topic-page   prose, transitions source-coverage ledger states. The source   list is per-invocation (CLI args, inline gestures).</li> <li>Ground audits. It walks a persistent watch list in   <code>grounding-sources.md</code>, reports drift, annotates the ledger's   <code>Residue</code> and <code>Next action</code> cells, and never writes prose or   evidence. Drifted sources surface as flags pointing at   <code>/ctx-kb-ingest</code>.</li> </ul> <p>This drives the canonical flow:</p> <p>Bootstrap (pristine kb). Use <code>/ctx-kb-ingest <sources></code> to absorb the first wave of material. Ground has nothing to compare against in a pristine kb — <code>source-map.md</code> is empty, and <code>grounding-sources.md</code> would just prompt for entries.</p> <p>Curate the watch list. Once the kb has content, edit <code>grounding-sources.md</code> by hand to list the canonical sources the kb's claims depend on — the load-bearing citations worth checking for drift. Ground refuses to synthesise this list from <code>source-map.md</code> by design; the watch list is a deliberate human choice about what's worth tracking.</p> <p>Steady state. Ingest liberally as new material lands. Run <code>/ctx-kb-ground</code> periodically — before a release, after a vendor version bump, on whatever cadence fits — to detect drift on the tracked subset. Drift surfaces as flags in the ground closeout pointing at <code>/ctx-kb-ingest</code> for the actual write-side work.</p> <p>Rule of thumb:</p> Situation Skill \"I have new material I want absorbed.\" <code>/ctx-kb-ingest</code> \"Are the sources the kb depends on still current?\" <code>/ctx-kb-ground</code> \"Is the kb's structure clean (capitalisation, frontmatter)?\" <code>/ctx-kb-site-review</code>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#what-the-editorial-pipeline-is-not","level":2,"title":"What the Editorial Pipeline Is NOT","text":"<ul> <li>Not a substitute for <code>DECISIONS.md</code>. Project-level   architectural decisions stay in <code>.context/DECISIONS.md</code>. The   kb's <code>domain-decisions.md</code> is a kb-scoped artifact (different   schema, different write authority, different lifecycle).</li> <li>Not a substitute for <code>LEARNINGS.md</code>. Learnings have author   intent; kb claims have evidence backing. They're different   truth bases; do not cross-feed.</li> <li>Not for casual notes. Use <code>/ctx-kb-note</code> or <code>ctx kb note   \"<text>\"</code> to park a finding for the next ingest pass.</li> </ul>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/build-a-knowledge-base/#reference","level":2,"title":"Reference","text":"<ul> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code> (laid   down by <code>ctx init</code>)</li> <li>Skills reference:   <code>/ctx-kb-ingest</code>,   <code>/ctx-kb-ask</code>,   <code>/ctx-kb-site-review</code>,   <code>/ctx-kb-ground</code>,   <code>/ctx-kb-note</code>,   <code>/ctx-handover</code></li> <li>Related recipes:   Typical KB Session,   Recover an Aborted Session</li> </ul>","path":["Build a Knowledge Base"],"tags":[]},{"location":"recipes/building-skills/","level":1,"title":"Building Project Skills","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-problem","level":2,"title":"The Problem","text":"<p>You have workflows your agent needs to repeat across sessions: a deploy checklist, a review protocol, a release process. Each time, you re-explain the steps. The agent gets it mostly right but forgets edge cases you corrected last time.</p> <p>Skills solve this by encoding domain knowledge into a reusable document the agent loads automatically when triggered. A skill is not code - it is a structured prompt that captures what took you sessions to learn.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-skill-create\n</code></pre> <p>The skill-creator walks you through: identify a repeating workflow, draft a skill, test with realistic prompts, iterate until it triggers correctly and produces good output.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-skill-create</code> Skill Interactive skill creation and improvement workflow <code>ctx init</code> Command Deploys template skills to <code>.claude/skills/</code> on first setup","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-1-identify-a-repeating-pattern","level":3,"title":"Step 1: Identify a Repeating Pattern","text":"<p>Good skill candidates:</p> <ul> <li>Checklists you repeat: deploy steps, release prep, code review</li> <li>Decisions the agent gets wrong: if you keep correcting the same   behavior, encode the correction</li> <li>Multi-step workflows: anything with a sequence of commands and   conditional branches</li> <li>Domain knowledge: project-specific terminology, architecture   constraints, or conventions the agent cannot infer from code alone</li> </ul> <p>Not good candidates: one-off instructions, things the platform already handles (file editing, git operations), or tasks too narrow to reuse.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-2-create-the-skill","level":3,"title":"Step 2: Create the Skill","text":"<p>Invoke the skill-creator:</p> <pre><code>You: \"I want a skill for our deploy process\"\n\nAgent: [Asks about the workflow: what steps, what tools,\n        what edge cases, what the output should look like]\n</code></pre> <p>Or capture a workflow you just did:</p> <pre><code>You: \"Turn what we just did into a skill\"\n\nAgent: [Extracts the steps from conversation history,\n        confirms understanding, drafts the skill]\n</code></pre> <p>The skill-creator produces a <code>SKILL.md</code> file in <code>.claude/skills/your-skill/</code>.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-3-test-with-realistic-prompts","level":3,"title":"Step 3: Test with Realistic Prompts","text":"<p>The skill-creator proposes 2-3 test prompts - the kind of thing a real user would say. It runs each one and shows the result alongside a baseline (same prompt without the skill) so you can compare.</p> <pre><code>Agent: \"Here are test prompts I'd try:\n        1. 'Deploy to staging'\n        2. 'Ship the hotfix'\n        3. 'Run the release checklist'\n        Want to adjust these?\"\n</code></pre>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-4-iterate-on-the-description","level":3,"title":"Step 4: Iterate on the Description","text":"<p>The <code>description</code> field in frontmatter determines when a skill triggers. Claude tends to undertrigger - descriptions need to be specific and slightly \"pushy\":</p> <pre><code># Weak - too vague, will undertrigger\ndescription: \"Use for deployments\"\n\n# Strong - covers situations and synonyms\ndescription: >-\n  Use when deploying to staging or production, running the release\n  checklist, or when the user says 'ship it', 'deploy this', or\n  'push to prod'. Also use after merging to main when a deploy\n  is expected.\n</code></pre> <p>The skill-creator helps you tune this iteratively.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#step-5-deploy-as-template-optional","level":3,"title":"Step 5: Deploy as Template (Optional)","text":"<p>If the skill should be available to all projects (not just this one), place it in <code>internal/assets/claude/skills/</code> so <code>ctx init</code> deploys it to new projects automatically.</p> <p>Most project-specific skills stay in <code>.claude/skills/</code> and travel with the repo.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#skill-anatomy","level":2,"title":"Skill Anatomy","text":"<pre><code>my-skill/\n  SKILL.md         # Required: frontmatter + instructions (<500 lines)\n  scripts/         # Optional: deterministic code the skill can execute\n  references/      # Optional: detail loaded on demand (not always)\n  assets/          # Optional: output templates, not loaded into context\n</code></pre> <p>Key sections in <code>SKILL.md</code>:</p> Section Purpose Required? Frontmatter Name, description (trigger) Yes When to Use Positive triggers Yes When NOT to Use Prevents false activations Yes Process Steps and commands Yes Examples Good/bad output pairs Recommended Quality Checklist Verify before reporting completion For complex skills","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#tips","level":2,"title":"Tips","text":"<ul> <li>Description is everything. A great skill with a vague description   never fires. Spend time on trigger coverage - synonyms, concrete   situations, edge cases.</li> <li>Stay under 500 lines. If your skill is growing past this, move   detail into <code>references/</code> files and point to them from <code>SKILL.md</code>.</li> <li>Do not duplicate the platform. If the agent already knows how to   do something (edit files, run git commands), do not restate it. Tag   paragraphs as Expert/Activation/Redundant and delete Redundant ones.</li> <li>Explain why, not just what. \"Sort by date because users want   recent results first\" beats \"ALWAYS sort by date.\" The agent   generalizes from reasoning better than from rigid rules.</li> <li>Test negative triggers. Make sure the skill does not fire on   unrelated prompts. A skill that activates too broadly becomes noise.</li> </ul>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#next-up","level":2,"title":"Next Up","text":"<p>Parallel Agent Development with Git Worktrees ->: Split work across multiple agents using git worktrees.</p>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/building-skills/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: full listing of all bundled and   project-local skills</li> <li>Guide Your Agent: how commands, skills, and   conversational patterns work together</li> <li>Design Before Coding: the four-skill chain for   front-loading design work</li> </ul>","path":["Recipes","Agents and Automation","Building Project Skills"],"tags":[]},{"location":"recipes/claude-code-permissions/","level":1,"title":"Claude Code Permission Hygiene","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code's <code>.claude/settings.local.json</code> controls what the agent can do without asking. Over time, this file accumulates one-off permissions from individual sessions: Exact commands with hardcoded paths, duplicate entries, and stale skill references. </p> <p>A noisy \"allowlist\" makes it harder to spot dangerous permissions and  increases the surface area for unintended behavior.</p> <p>Since <code>settings.local.json</code> is <code>.gitignore</code>d, it drifts independently of your codebase. There is no PR review, no CI check: just whatever you clicked \"Allow\" on.</p> <p>This recipe shows what a well-maintained permission file looks like and how to keep it clean.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                            # seeds safe defaults\n/ctx-drift                          # detects missing/stale permissions\n/ctx-permission-sanitize               # audits for dangerous patterns\n</code></pre> <p>See Recommended Defaults for the full list.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx init</code> Populates default <code>ctx</code> permissions <code>/ctx-drift</code> Detects missing or stale permission entries <code>/ctx-permission-sanitize</code> Audits for dangerous patterns (security-focused)","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#recommended-defaults","level":2,"title":"Recommended Defaults","text":"<p>After running <code>ctx init</code>, your <code>settings.local.json</code> will have the <code>ctx</code> defaults pre-populated. Here is an opinionated safe starting point for a Go project using <code>ctx</code>:</p> <pre><code>{\n  \"permissions\": {\n    \"allow\": [\n      \"Bash(/tmp/ctx-*:*)\",\n      \"Bash(CGO_ENABLED=0 go build:*)\",\n      \"Bash(CGO_ENABLED=0 go test:*)\",\n      \"Bash(ctx:*)\",\n      \"Bash(git add:*)\",\n      \"Bash(git branch:*)\",\n      \"Bash(git check-ignore:*)\",\n      \"Bash(git checkout:*)\",\n      \"Bash(git commit:*)\",\n      \"Bash(git diff:*)\",\n      \"Bash(git log:*)\",\n      \"Bash(git remote:*)\",\n      \"Bash(git restore:*)\",\n      \"Bash(git show:*)\",\n      \"Bash(git stash:*)\",\n      \"Bash(git status:*)\",\n      \"Bash(git tag:*)\",\n      \"Bash(go build:*)\",\n      \"Bash(go fmt:*)\",\n      \"Bash(go test:*)\",\n      \"Bash(go vet:*)\",\n      \"Bash(golangci-lint run:*)\",\n      \"Bash(grep:*)\",\n      \"Bash(ls:*)\",\n      \"Bash(make:*)\",\n      \"Skill(ctx-convention-add)\",\n      \"Skill(ctx-decision-add)\",\n      \"Skill(ctx-learning-add)\",\n      \"Skill(ctx-task-add)\",\n      \"Skill(ctx-agent)\",\n      \"Skill(ctx-archive)\",\n      \"Skill(ctx-blog)\",\n      \"Skill(ctx-blog-changelog)\",\n      \"Skill(absorb)\",\n      \"Skill(ctx-commit)\",\n      \"Skill(ctx-drift)\",\n      \"Skill(ctx-implement)\",\n      \"Skill(ctx-journal-enrich)\",\n      \"Skill(ctx-journal-enrich-all)\",\n      \"Skill(ctx-loop)\",\n      \"Skill(ctx-next)\",\n      \"Skill(ctx-pad)\",\n      \"Skill(ctx-prompt-audit)\",\n      \"Skill(ctx-history)\",\n      \"Skill(ctx-reflect)\",\n      \"Skill(ctx-remember)\",\n      \"Skill(ctx-status)\",\n      \"Skill(ctx-worktree)\",\n      \"WebSearch\"\n    ],\n    \"deny\": [\n      \"Bash(sudo *)\",\n      \"Bash(git push *)\",\n      \"Bash(git push)\",\n      \"Bash(rm -rf /*)\",\n      \"Bash(rm -rf ~*)\",\n      \"Bash(curl *)\",\n      \"Bash(wget *)\",\n      \"Bash(chmod 777 *)\",\n      \"Read(**/.env)\",\n      \"Read(**/.env.*)\",\n      \"Read(**/*credentials*)\",\n      \"Read(**/*secret*)\",\n      \"Read(**/*.pem)\",\n      \"Read(**/*.key)\",\n      \"Edit(**/.env)\",\n      \"Edit(**/.env.*)\"\n    ]\n  }\n}\n</code></pre> <p>This Is a Starting Point, Not a Mandate</p> <p>Your project may need more or fewer entries. </p> <p>The goal is intentional permissions: Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#design-principles","level":3,"title":"Design Principles","text":"<p>Use wildcards for trusted binaries: If you trust the binary (your own project's CLI, <code>make</code>, <code>go</code>), a single wildcard like <code>Bash(ctx:*)</code> beats twenty subcommand entries. It reduces noise and means new subcommands work without re-prompting.</p> <p>Keep <code>git</code> commands granular: Unlike <code>ctx</code> or <code>make</code>, git has both safe commands (<code>git log</code>, <code>git status</code>) and destructive ones (<code>git reset --hard</code>, <code>git clean -f</code>). Listing safe commands individually prevents accidentally pre-approving dangerous ones.</p> <p>Pre-approve all <code>ctx-</code> skills: Skills shipped with <code>ctx</code> (<code>Skill(ctx-*)</code>) are safe to pre-approve. They are part of your project and you control their content. This prevents the agent from prompting on every skill invocation.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#default-deny-rules","level":3,"title":"Default Deny Rules","text":"<p><code>ctx init</code> automatically populates <code>permissions.deny</code> with rules that block dangerous operations. Deny rules are evaluated before allow rules: A denied pattern always prompts the user, even if it also matches an allow entry.</p> <p>The defaults block:</p> Pattern Why <code>Bash(sudo *)</code> Cannot enter password; will hang <code>Bash(git push *)</code> Must be explicit user action <code>Bash(rm -rf /*)</code> etc. Recursive delete of system/home directories <code>Bash(curl *)</code> / <code>wget</code> Arbitrary network requests <code>Bash(chmod 777 *)</code> World-writable permissions <code>Read/Edit(**/.env*)</code> Secrets and credentials <code>Read(**/*.pem, *.key)</code> Private keys <p>Read/Edit Deny Rules</p> <p><code>Read()</code> and <code>Edit()</code> deny rules have known upstream enforcement issues (<code>claude-code#6631,#24846</code>). </p> <p>They are included as defense-in-depth and intent documentation.</p> <p>Blocked by default deny rules: no action needed, <code>ctx init</code> handles these:</p> Pattern Risk <code>Bash(git push:*)</code> Must be explicit user action <code>Bash(sudo:*)</code> Privilege escalation <code>Bash(rm -rf:*)</code> Recursive delete with no confirmation <code>Bash(curl:*)</code> / <code>Bash(wget:*)</code> Arbitrary network requests <p>Requires manual discipline: Never add these to <code>allow</code>:</p> Pattern Risk <code>Bash(git reset:*)</code> Can discard uncommitted work <code>Bash(git clean:*)</code> Deletes untracked files <code>Skill(ctx-permission-sanitize)</code> Edits this file: self-modification vector <code>Skill(release)</code> Runs the release pipeline: high impact","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#hooks-regex-safety-net","level":2,"title":"Hooks: Regex Safety Net","text":"<p>Deny rules handle prefix-based blocking natively. Hooks complement them by catching patterns that require regex matching: Things deny rules can't express.</p> <p>The <code>ctx</code> plugin ships these blocking hooks:</p> Hook What it blocks <code>ctx system block-non-path-ctx</code> Running <code>ctx</code> from wrong path <p>Project-local hooks (not part of the plugin) catch regex edge cases:</p> Hook What it blocks <code>block-dangerous-commands.sh</code> Mid-command <code>sudo</code>/<code>git push</code> (after <code>&&</code>), copies to bin dirs, absolute-path <code>ctx</code> <p>Pre-Approved + Hook-Blocked = Silent Block</p> <p>If you pre-approve a command that a hook blocks, the user never sees the confirmation dialog. The agent gets a block response and must handle it, which is confusing.</p> <p>It's better not to pre-approve commands that hooks are designed to intercept.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#the-maintenance-workflow","level":2,"title":"The Maintenance Workflow","text":"","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#after-busy-sessions","level":3,"title":"After Busy Sessions","text":"<p>Permissions accumulate fastest during debugging and exploration sessions. After a session where you clicked \"Allow\" many times:</p> <ol> <li>Open <code>.claude/settings.local.json</code> in your editor;</li> <li>Look for entries at the bottom of the allowlist (new entries append there);</li> <li>Delete anything that looks session-specific:<ul> <li>Exact commands with hardcoded paths,</li> <li>Commands with literal string arguments,</li> <li>Entries that duplicate an existing wildcard.</li> </ul> </li> </ol> <p>See the Sanitize Permissions runbook for a step-by-step procedure.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#periodically","level":3,"title":"Periodically","text":"<p>Run <code>/ctx-drift</code> to catch permission drift:</p> <ul> <li>Missing <code>Bash(ctx:*)</code> wildcard;</li> <li>Missing <code>Skill(ctx-*)</code> entries for installed skills;</li> <li>Stale <code>Skill(ctx-*)</code> entries for removed skills;</li> <li>Granular <code>Bash(ctx <subcommand>:*)</code> entries that should be consolidated.</li> </ul> <p>Run <code>/ctx-permission-sanitize</code> to catch security issues:</p> <ul> <li>Hook bypass patterns</li> <li>Destructive commands</li> <li>Overly broad permissions</li> <li>Injection vectors</li> </ul>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#when-adding-new-skills","level":3,"title":"When Adding New Skills","text":"<p>If you create a custom <code>ctx-*</code> skill, add its <code>Skill()</code> entry to the allowlist manually. </p> <p><code>ctx init</code> only populates the default permissions: It won't pick up custom skills.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#golden-image-snapshots","level":3,"title":"Golden Image Snapshots","text":"<p>If manual cleanup is too tedious, use a golden image to automate it: </p> <p>Snapshot a curated permission set, then restore at session start to automatically  drop session-accumulated permissions. See the Permission Snapshots recipe for the full workflow.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#adapting-for-other-languages","level":2,"title":"Adapting for Other Languages","text":"<p>The recommended defaults above are Go-specific. For other stacks, swap the build/test tooling:</p> <p>Node.js / TypeScript:</p> <pre><code>\"Bash(npm run:*)\",\n\"Bash(npm test:*)\",\n\"Bash(npx:*)\",\n\"Bash(node:*)\"\n</code></pre> <p>Python:</p> <pre><code>\"Bash(pytest:*)\",\n\"Bash(python:*)\",\n\"Bash(pip show:*)\",\n\"Bash(ruff:*)\"\n</code></pre> <p>Rust:</p> <pre><code>\"Bash(cargo build:*)\",\n\"Bash(cargo test:*)\",\n\"Bash(cargo clippy:*)\",\n\"Bash(cargo fmt:*)\"\n</code></pre> <p>The <code>ctx</code>, <code>git</code>, and skill entries remain the same across all stacks.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#next-up","level":2,"title":"Next Up","text":"<p>Permission Snapshots →: Save and restore permission baselines for reproducible setups.</p>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/claude-code-permissions/#see-also","level":2,"title":"See Also","text":"<ul> <li>Setting Up <code>ctx</code> Across AI Tools: full setup recipe   including <code>settings.local.json</code> creation</li> <li>Context Health: keeping <code>.context/</code> files accurate</li> <li>Sanitize Permissions runbook:   manual cleanup procedure</li> </ul>","path":["Recipes","Maintenance","Claude Code Permission Hygiene"],"tags":[]},{"location":"recipes/configuration-profiles/","level":1,"title":"Configuration Profiles","text":"","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#configuration-profiles","level":1,"title":"Configuration Profiles","text":"<p>Switch between dev and base runtime configurations without editing <code>.ctxrc</code> by hand. Useful when you want verbose logging and webhook notifications during development, then clean defaults for normal sessions.</p> <p>Uses: <code>ctx config switch</code>, <code>ctx config status</code>, <code>/ctx-config</code></p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#how-it-works","level":2,"title":"How It Works","text":"<p>The <code>ctx</code> repo ships two source profiles committed to git:</p> File Profile Description <code>.ctxrc.base</code> base All defaults, notifications off <code>.ctxrc.dev</code> dev Verbose logging, webhook notifications on <p>The working copy (<code>.ctxrc</code>) is gitignored. Switching profiles copies the source file over <code>.ctxrc</code>, so your runtime configuration is always a clean snapshot of one of the two sources.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#switching-profiles","level":2,"title":"Switching Profiles","text":"<pre><code># Switch to dev (verbose logging, notifications)\nctx config switch dev\n\n# Switch to base (defaults)\nctx config switch base\n\n# Toggle to the opposite profile\nctx config switch\n\n# \"prod\" is an alias for \"base\"\nctx config switch prod\n</code></pre> <p>The detection heuristic checks for an uncommented <code>notify:</code> line in <code>.ctxrc</code>: present means dev, absent means base.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#checking-the-active-profile","level":2,"title":"Checking the Active Profile","text":"<pre><code>ctx config status\n</code></pre> <p>Output examples:</p> <pre><code>active: dev (verbose logging enabled)\nactive: base (defaults)\nactive: none (.ctxrc does not exist)\n</code></pre>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#typical-workflow","level":2,"title":"Typical Workflow","text":"<ol> <li>Start of a debugging session: switch to dev for verbose    logging and webhook notifications so you can trace hook    activity and get push alerts.</li> </ol> <pre><code>ctx config switch dev\n</code></pre> <ol> <li> <p>Work through the issue: hooks log verbosely, webhooks fire    on key events (commits, ceremony nudges, drift warnings).</p> </li> <li> <p>Done debugging: switch back to base to silence the noise.</p> </li> </ol> <pre><code>ctx config switch base\n</code></pre>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#customizing-profiles","level":2,"title":"Customizing Profiles","text":"<p>Edit the source files directly:</p> <ul> <li><code>.ctxrc.dev</code>: add any <code>.ctxrc</code> keys you want active during   development (e.g., <code>log_level: debug</code>, <code>notify.events</code>,   <code>notify.webhook_url</code>).</li> <li><code>.ctxrc.base</code>: keep this minimal. It represents your   \"production\" defaults.</li> </ul> <p>After editing a source file, re-run <code>ctx config switch <profile></code> to apply the changes to the working copy.</p> <p>Commit Your Profiles</p> <p>Both <code>.ctxrc.base</code> and <code>.ctxrc.dev</code> should be committed to git so team members share the same profile definitions. The working copy <code>.ctxrc</code> stays gitignored.</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/configuration-profiles/#using-the-skill","level":2,"title":"Using the Skill","text":"<p>In a Claude Code session, say any of:</p> <ul> <li>\"switch to dev mode\"</li> <li>\"switch to base\"</li> <li>\"what profile am I on?\"</li> <li>\"toggle verbose logging\"</li> </ul> <p>The <code>/ctx-config</code> skill handles the rest.</p> <p>See also: <code>ctx config</code> reference, Configuration</p>","path":["Recipes","Maintenance","Configuration Profiles"],"tags":[]},{"location":"recipes/context-health/","level":1,"title":"Detecting and Fixing Drift","text":"","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> files drift: you rename a package, delete a module, or finish a sprint, and suddenly <code>ARCHITECTURE.md</code> references paths that no longer exist, <code>TASKS.md</code> is 80 percent completed checkboxes, and <code>CONVENTIONS.md</code> describes patterns you stopped using two months ago.</p> <p>Stale context is worse than no context: </p> <p>An AI tool that trusts outdated references will hallucinate confidently.</p> <p>This recipe shows how to detect drift, fix it, and keep your <code>.context/</code> directory lean and accurate.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx drift                      # detect problems\nctx drift --fix                # auto-fix the easy ones\nctx sync --dry-run && ctx sync # reconcile after refactors\nctx compact --archive          # archive old completed tasks\nctx fmt                        # normalize line widths\nctx status                     # verify\n</code></pre> <p>Or just ask your agent: \"Is our context clean?\"</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, every command above fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx drift</code> Command Detect stale paths, missing files, violations <code>ctx drift --fix</code> Command Auto-fix simple issues <code>ctx sync</code> Command Reconcile context with codebase structure <code>ctx compact</code> Command Archive completed tasks, clean up empty sections <code>ctx fmt</code> Command Normalize context files to 80-char line width <code>ctx status</code> Command Quick health overview <code>/ctx-drift</code> Skill Structural plus semantic drift detection <code>/ctx-architecture</code> Skill Refresh <code>ARCHITECTURE.md</code> from actual codebase <code>/ctx-status</code> Skill In-session context summary <code>/ctx-prompt-audit</code> Skill Audit prompt quality and token efficiency","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#the-workflow","level":2,"title":"The Workflow","text":"<p>The best way to maintain context health is conversational: Ask your agent, guide it, and let it detect problems, explain them, and fix them with your approval. CLI commands exist for CI pipelines, scripting, and fine-grained control. </p> <p>For day-to-day maintenance, talk to your agent.</p> <p>Your Questions Reinforce the Pattern</p> <p>Asking \"is our context clean?\" does two things:</p> <ul> <li>It triggers a drift check right now</li> <li>It reinforces the habit</li> </ul> <p>This is reinforcement, not enforcement.</p> <p>Do not wait for the agent to be proactive on its own: </p> <p>Guide your agent, especially in early sessions.</p> <p>Over time, you will ask less and the agent will start offering more.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-1-ask-your-agent","level":3,"title":"Step 1: Ask Your Agent","text":"<p>The simplest way to check context health:</p> <pre><code>Is our context clean?\nAnything stale?\nHow healthy are our context files?\n</code></pre> <p>Or invoke the skill directly:</p> <pre><code>/ctx-drift\n</code></pre> <p>The agent performs two layers of analysis:</p> <p>Layer 1, structural checks (via <code>ctx drift</code>): Dead paths, missing files, completed task counts, constitution violations. Fast and programmatic.</p> <p>Layer 2, semantic analysis (agent-driven): Does <code>CONVENTIONS.md</code> describe patterns the code no longer follows? Does <code>DECISIONS.md</code> contain entries whose rationale no longer applies? Are there learnings about bugs that are now fixed? This is where the agent adds value the CLI cannot: It reads both context files and source code and compares them.</p> <p>The agent reports both layers together, explains each finding in plain language, and offers to fix what it can.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-2-maintenance-at-session-start","level":3,"title":"Step 2: Maintenance at Session Start","text":"<p>You do not need to ask explicitly. </p> <p>Using Claude Code</p> <p><code>ctx</code> ships with Claude Code hooks that remind the agent  at the right time to take initiative. </p> <p>Checking context health at the session start, offering to persist learnings  before you quit, and flagging drift when it matters. The agent stays  proactive without you having to prompt it:</p> <pre><code>Agent: Good morning. I've loaded the context files. A few things\n       before we start:\n\n       - ARCHITECTURE.md references `pkg/auth/` which is now empty\n       - DECISIONS.md hasn't been updated in 40 days\n       - There are 18 completed tasks ready for archival\n\n       Want me to run a quick maintenance pass, or should we jump\n       straight into today's work?\n</code></pre> <p>☝️️ this is what persistent, initiative-driven sessions feel like when context is  treated as a system instead of a prompt.</p> <p>If the agent does not offer this on its own, a gentle nudge is enough:</p> <pre><code>Anything stale before we start?\nHow's the context looking?\n</code></pre> <p>This turns maintenance from a scheduled chore into a conversation that happens when it matters.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-3-real-time-detection-during-work","level":3,"title":"Step 3: Real-Time Detection during Work","text":"<p>Agents can notice drift while working: When a mismatch is directly in the path of their current task. If an agent reads <code>ARCHITECTURE.md</code> to find where to add a handler and <code>internal/handlers/</code> doesn't exist, it will notice because the stale reference blocks its work:</p> <pre><code>Agent: ARCHITECTURE.md references `internal/handlers/` but that directory\n       doesn't exist. I'll look at the actual source tree to find where\n       handlers live now.\n</code></pre> <p>This happens reliably when the drift intersects the task. What is less reliable is the agent generalizing from one mismatch to \"there might be more stale references; let me run drift detection\" That leap requires the agent to know <code>/ctx-drift</code> exists and to decide the current task should pause for maintenance.</p> <p>If you want that behavior, reinforce it:</p> <pre><code>Good catch. Yes, run /ctx-drift and clean up any other stale references.\n</code></pre> <p>Over time, agents that have seen this pattern will start offering proactively. But do not expect it from a cold start.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#step-4-archival-and-cleanup","level":3,"title":"Step 4: Archival and Cleanup","text":"<p><code>ctx drift</code> detects when <code>TASKS.md</code> has more than 10 completed items and flags it as a staleness warning. Running <code>ctx drift --fix</code> archives completed tasks automatically. </p> <p>You can also run <code>/ctx-archive</code> to compact on demand.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#knowledge-health-flow","level":3,"title":"Knowledge Health Flow","text":"<p>Over time, LEARNINGS.md and DECISIONS.md accumulate entries that overlap or partially repeat each other. The <code>check-persistence</code> hook detects when entry counts exceed a configurable threshold and surfaces a nudge:</p> <p>\"LEARNINGS.md has 25+ entries. Consider running /ctx-consolidate to merge overlapping items.\"</p> <p>The consolidation workflow:</p> <ol> <li>Review: <code>/ctx-consolidate</code> groups entries by keyword similarity    and presents candidate merges for your approval.</li> <li>Merge: Approved groups are combined into single entries that    preserve the key information from each original.</li> <li>Archive: Originals move to <code>.context/archive/</code>, not deleted --    the full history is preserved in git and the archive directory.</li> <li>Verify: Run <code>ctx drift</code> after consolidation to confirm no    cross-references were broken by the merge.</li> </ol> <p>This replaces ad-hoc cleanup with a repeatable, nudge-driven cycle: detect accumulation, review candidates, merge with approval, archive originals.</p> <p>See also: Knowledge Capture for the recording workflow that feeds into this maintenance cycle.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-doctor-the-superset-check","level":2,"title":"<code>ctx doctor</code>: The Superset Check","text":"<p><code>ctx doctor</code> combines drift detection with hook auditing, configuration checks, event logging status, and token size reporting in a single command. If you want one command that covers structural health, hooks, and state:</p> <pre><code>ctx doctor          # everything in one pass\nctx doctor --json   # machine-readable for scripting\n</code></pre> <p>Use <code>/ctx-doctor</code> Too</p> <p>For agent-driven diagnosis that adds semantic analysis on top of the structural checks, use <code>/ctx-doctor</code>. </p> <p>See the Troubleshooting recipe for the full workflow.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#cli-reference","level":2,"title":"CLI Reference","text":"<p>The conversational approach above uses CLI commands under the hood. When you need direct control, use the commands directly.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift","level":3,"title":"<code>ctx drift</code>","text":"<p>Scan context files for structural problems:</p> <pre><code>ctx drift\n</code></pre> <p>Sample output:</p> <pre><code>Drift Report\n============\n\nWarnings (3):\n  ARCHITECTURE.md:14  path \"internal/api/router.go\" does not exist\n  ARCHITECTURE.md:28  path \"pkg/auth/\" directory is empty\n  CONVENTIONS.md:9    path \"internal/handlers/\" not found\n\nViolations (1):\n  TASKS.md            31 completed tasks (recommend archival)\n\nStaleness:\n  DECISIONS.md        last modified 45 days ago\n  LEARNINGS.md        last modified 32 days ago\n\nExit code: 1 (warnings found)\n</code></pre> Level Meaning Action Warning Stale path references, missing files Fix or remove Violation Constitution rule heuristic failures, heavy clutter Fix soon Staleness Files not updated recently Review content <p>Exit codes: <code>0</code> equals clean, <code>1</code> equals warnings, <code>3</code> equals violations.</p> <p>For CI integration:</p> <pre><code>ctx drift --json | jq '.warnings | length'\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-drift-fix","level":3,"title":"<code>ctx drift --fix</code>","text":"<p>Auto-fix mechanical issues:</p> <pre><code>ctx drift --fix\n</code></pre> <p>This handles removing dead path references, updating unambiguous renames,  clearing empty sections. Issues requiring judgment are flagged but left for you.</p> <p>Run <code>ctx drift</code> again afterward to confirm what remains.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-sync","level":3,"title":"<code>ctx sync</code>","text":"<p>After a refactor, reconcile context with the actual codebase structure:</p> <pre><code>ctx sync --dry-run   # preview first\nctx sync             # apply\n</code></pre> <p><code>ctx sync</code> scans for structural changes, compares with <code>ARCHITECTURE.md</code>,  checks for new dependencies worth documenting, and identifies context referring  to code that no longer exists.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-compact","level":3,"title":"<code>ctx compact</code>","text":"<p>Consolidate completed tasks and clean up empty sections:</p> <pre><code>ctx compact            # move completed tasks to Completed section,\n                       # remove empty sections\nctx compact --archive  # also archive old tasks to .context/archive/\n</code></pre> <ul> <li>Tasks: moves completed items (with all subtasks done) into the Completed   section of <code>TASKS.md</code></li> <li>All files: removes empty sections left behind</li> <li>With <code>--archive</code>: writes tasks older than 7 days to   <code>.context/archive/tasks-YYYY-MM-DD.md</code></li> </ul> <p>Without <code>--archive</code>, nothing is deleted: Tasks are reorganized in place.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-fmt","level":3,"title":"<code>ctx fmt</code>","text":"<p>Normalize context file line widths:</p> <pre><code>ctx fmt              # wrap long lines to 80 chars\nctx fmt --check      # CI: exit 1 if files need formatting\n</code></pre> <p>Long task descriptions, decision rationale, and learning entries accumulate as single-line entries. <code>ctx fmt</code> wraps them at word boundaries with 2-space continuation indent for list items. Headings, tables, and comments are preserved.</p> <p>Idempotent: safe to run repeatedly.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-status","level":3,"title":"<code>ctx status</code>","text":"<p>Quick health overview:</p> <pre><code>ctx status --verbose\n</code></pre> <p>Shows file counts, token estimates, modification times, and drift warnings in a single glance.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#ctx-prompt-audit","level":3,"title":"<code>/ctx-prompt-audit</code>","text":"<p>Checks whether your context files are readable, compact, and token-efficient for the model.</p> <pre><code>/ctx-prompt-audit\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Conversational approach (recommended):</p> <pre><code>Is our context clean?  -> agent runs structural plus semantic checks\nFix what you can       -> agent auto-fixes and proposes edits\nArchive the done tasks -> agent runs ctx compact --archive\nHow's token usage?     -> agent checks ctx status\n</code></pre> <p>CLI approach (for CI, scripts, or direct control):</p> <pre><code>ctx drift                      # 1. Detect problems\nctx drift --fix                # 2. Auto-fix the easy ones\nctx sync --dry-run && ctx sync # 3. Reconcile after refactors\nctx compact --archive          # 4. Archive old completed tasks\nctx fmt                        # 5. Normalize line widths\nctx status                     # 6. Verify\n</code></pre>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#tips","level":2,"title":"Tips","text":"<p>Agents cross-reference context files with source code during normal work. When drift intersects their current task, they will notice: a renamed package, a deleted directory, a path that doesn't resolve. But they rarely generalize from one mismatch to a full audit on their own. Reinforce the pattern: when an agent mentions a stale reference, ask it to run <code>/ctx-drift</code>. Over time, it starts offering.</p> <p>When an agent says \"this reference looks stale,\" it is usually right.</p> <p>Semantic drift is more damaging than structural drift:  <code>ctx drift</code> catches dead paths. But <code>CONVENTIONS.md</code> describing a pattern your  code stopped following three weeks ago is worse. When you ask  \"is our context clean?\", the agent can do both checks.</p> <p>Use <code>ctx status</code> as a quick check: It shows file counts, token estimates, and drift warnings in a single glance. Good for a fast \"is everything ok?\" before diving into work.</p> <p>Drift detection in CI: add <code>ctx drift --json</code> to your CI pipeline and fail on exit code 3 (violations). This catches constitution-level problems before they reach upstream.</p> <p>Do not over-compact: Completed tasks have historical value. The <code>--archive</code> flag preserves them in <code>.context/archive/</code> so you can search past work without cluttering active context.</p> <p>Sync is cautious by default: Use <code>--dry-run</code> after large refactors, then apply.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#next-up","level":2,"title":"Next Up","text":"<p>Claude Code Permission Hygiene →: Recommended permission defaults and maintenance workflow for Claude Code.</p>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/context-health/#see-also","level":2,"title":"See Also","text":"<ul> <li>Troubleshooting: full diagnostic workflow using   <code>ctx doctor</code>, event logs, and <code>/ctx-doctor</code></li> <li>Tracking Work Across Sessions: task lifecycle and archival</li> <li>Persisting Decisions, Learnings, and Conventions:   keeping knowledge files current</li> <li>The Complete Session: where maintenance fits in the daily workflow</li> <li>CLI Reference: full flag documentation for all commands</li> <li>Context Files: structure and purpose of each <code>.context/</code> file</li> </ul>","path":["Recipes","Maintenance","Detecting and Fixing Drift"],"tags":[]},{"location":"recipes/customizing-hook-messages/","level":1,"title":"Customizing Hook Messages","text":"","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> hooks speak <code>ctx</code>'s language, not your project's. The QA gate says \"lint the ENTIRE project\" and \"make build,\" but your Python project uses <code>pytest</code> and <code>ruff</code>. The post-commit nudge suggests running lints, but your project uses <code>npm test</code>. You could remove the hook entirely, but then you lose the logic (counting, state tracking, adaptive frequency) just to change the words.</p> <p>How do you customize what hooks say without removing what they do?</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx hook message list                     # see all hooks and their messages\nctx hook message show qa-reminder gate    # view the current template\nctx hook message edit qa-reminder gate    # copy default to .context/ for editing\nctx hook message reset qa-reminder gate   # revert to embedded default\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root: hook message overrides live in your <code>.context/</code> directory, so <code>ctx</code> needs to know which one. If you skip the <code>eval</code>, <code>ctx hook message ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose <code>ctx hook message list</code> CLI command Show all hook messages with category and override status <code>ctx hook message show</code> CLI command Print the effective message template <code>ctx hook message edit</code> CLI command Copy embedded default to <code>.context/</code> for editing <code>ctx hook message reset</code> CLI command Delete user override, revert to default","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#how-it-works","level":2,"title":"How It Works","text":"<p>Hook messages use a 3-tier fallback:</p> <ol> <li>User override: <code>.context/hooks/messages/{hook}/{variant}.txt</code></li> <li>Embedded default: compiled into the <code>ctx</code> binary</li> <li>Hardcoded fallback: belt-and-suspenders safety net</li> </ol> <p>The hook logic (when to fire, counting, state tracking, cooldowns) is unchanged. Only the content (what text gets emitted) comes from the template. You customize what the hook says without touching how it decides to speak.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#finding-the-original-templates","level":3,"title":"Finding the Original Templates","text":"<p>The default templates live in the <code>ctx</code> source tree at:</p> <pre><code>internal/assets/hooks/messages/{hook}/{variant}.txt\n</code></pre> <p>You can also browse them on GitHub: <code>internal/assets/hooks/messages/</code></p> <p>Or use <code>ctx hook message show</code> to print any template without digging through source code:</p> <pre><code>ctx hook message show qa-reminder gate        # QA gate instructions\nctx hook message show check-persistence nudge  # persistence nudge\nctx hook message show post-commit nudge        # post-commit reminder\n</code></pre> <p>The <code>show</code> output includes the template source and available variables -- everything you need to write a replacement.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables","level":3,"title":"Template Variables","text":"<p>Some messages use Go <code>text/template</code> variables for dynamic content:</p> <pre><code>No context files updated in {{.PromptsSinceNudge}}+ prompts.\nHave you discovered learnings, made decisions,\nestablished conventions, or completed tasks\nworth persisting?\n</code></pre> <p>The <code>show</code> and <code>edit</code> commands list available variables for each message. When writing a replacement, keep the same <code>{{.VariableName}}</code> placeholders to preserve dynamic content. Variables that you omit render as <code><no value></code>: no error, but the output may look odd.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#intentional-silence","level":3,"title":"Intentional Silence","text":"<p>An empty template file (0 bytes or whitespace-only) means \"don't emit a message\". The hook still runs its logic but produces no output. This lets you silence specific messages without removing the hook from <code>hooks.json</code>.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-python-project-qa-gate","level":2,"title":"Example: Python Project QA Gate","text":"<p>The default QA gate says \"lint the ENTIRE project\" and references <code>make lint</code>. For a Python project, you want <code>pytest</code> and <code>ruff</code>:</p> <pre><code># See the current default\nctx hook message show qa-reminder gate\n\n# Copy it to .context/ for editing\nctx hook message edit qa-reminder gate\n\n# Edit the override\n</code></pre> <p>Replace the content in <code>.context/hooks/messages/qa-reminder/gate.txt</code>:</p> <pre><code>HARD GATE! DO NOT COMMIT without completing ALL of these steps first:\n(1) Run the full test suite: pytest -x\n(2) Run the linter: ruff check .\n(3) Verify a clean working tree\nRun tests and linter BEFORE every git commit, no exceptions.\n</code></pre> <p>The hook still fires on every <code>Edit</code> call. The logic is identical. Only the instructions changed.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-silencing-ceremony-nudges","level":2,"title":"Example: Silencing Ceremony Nudges","text":"<p>The ceremony check nudges you to use <code>/ctx-remember</code> and <code>/ctx-wrap-up</code>. If your team has a different workflow and finds these noisy:</p> <pre><code>ctx hook message edit check-ceremonies both\nctx hook message edit check-ceremonies remember\nctx hook message edit check-ceremonies wrapup\n</code></pre> <p>Then empty each file:</p> <pre><code>echo -n \"\" > .context/hooks/messages/check-ceremonies/both.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/remember.txt\necho -n \"\" > .context/hooks/messages/check-ceremonies/wrapup.txt\n</code></pre> <p>The hooks still track ceremony usage internally, but they no longer emit any visible output.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#example-javascript-project-post-commit","level":2,"title":"Example: JavaScript Project Post-Commit","text":"<p>The default post-commit nudge mentions generic \"lints and tests.\" For a JavaScript project:</p> <pre><code>ctx hook message edit post-commit nudge\n</code></pre> <p>Replace with:</p> <pre><code>Commit succeeded. 1. Offer context capture to the user: Decision (design\nchoice?), Learning (gotcha?), or Neither. 2. Ask the user: \"Want me to\nrun npm test and eslint before you push?\" Do NOT push. The user pushes\nmanually.\n</code></pre>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#the-two-categories","level":2,"title":"The Two Categories","text":"<p>Not all messages are equal. The <code>list</code> command shows each message's category:</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#customizable-17-messages","level":3,"title":"Customizable (17 Messages)","text":"<p>Messages that are opinions: project-specific wording that benefits from customization. These are the primary targets for override.</p> Hook Variant Description check-freshness stale Technology constant freshness warning check-ceremonies both Both ceremonies missing check-ceremonies remember Start-of-session ceremony check-ceremonies wrapup End-of-session ceremony check-context-size checkpoint Context capacity warning check-context-size oversize Injection oversize nudge check-context-size window Context window usage warning (>80%) check-journal both Unimported sessions + unenriched entries check-journal unenriched Unenriched journal entries check-journal unimported Unimported sessions check-knowledge warning Knowledge file growth check-map-staleness stale Architecture map staleness check-persistence nudge Context persistence nudge post-commit nudge Post-commit context capture qa-reminder gate Pre-commit QA gate","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#ctx-specific-10-messages","level":3,"title":"ctx-Specific (10 Messages)","text":"<p>Messages specific to <code>ctx</code>'s own development workflow. You can customize them, but <code>edit</code> will warn you first.</p> Hook Variant Description block-dangerous-commands cp-to-bin Block copy to bin dirs block-dangerous-commands install-to-local-bin Block copy to ~/.local/bin block-dangerous-commands mid-git-push Block git push block-dangerous-commands mid-sudo Block sudo block-non-path-ctx absolute-path Block absolute path invocation block-non-path-ctx dot-slash Block ./ctx invocation block-non-path-ctx go-run Block go run invocation check-reminders reminders Pending reminders relay check-resources alert Resource pressure alert check-version key-rotation Key rotation nudge check-version mismatch Version mismatch","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#template-variables-reference","level":2,"title":"Template Variables Reference","text":"Hook Variant Variables check-freshness stale <code>{{.StaleFiles}}</code> check-context-size checkpoint (none) check-context-size oversize <code>{{.TokenCount}}</code> check-context-size window <code>{{.TokenCount}}</code>, <code>{{.Percentage}}</code> check-ceremonies both, remember, wrapup (none) check-journal both <code>{{.UnimportedCount}}</code>, <code>{{.UnenrichedCount}}</code> check-journal unenriched <code>{{.UnenrichedCount}}</code> check-journal unimported <code>{{.UnimportedCount}}</code> check-knowledge warning <code>{{.FileWarnings}}</code> check-map-staleness stale <code>{{.LastRefreshDate}}</code>, <code>{{.ModuleCount}}</code> check-persistence nudge <code>{{.PromptsSinceNudge}}</code> check-reminders reminders <code>{{.ReminderList}}</code> check-resources alert <code>{{.AlertMessages}}</code> check-version key-rotation <code>{{.KeyAgeDays}}</code> check-version mismatch <code>{{.BinaryVersion}}</code>, <code>{{.PluginVersion}}</code> post-commit nudge (none) qa-reminder gate (none) block-dangerous-commands all variants (none) block-non-path-ctx all variants (none) <p>Templates that reference undefined variables render <code><no value></code>:  no error, graceful degradation.</p>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#tips","level":2,"title":"Tips","text":"<ul> <li>Override files are version-controlled: they live in <code>.context/</code>   alongside your other context files. Team members get the same   customized messages.</li> <li>Start with <code>show</code>: always check the current default before editing.   The embedded template is the baseline your override replaces.</li> <li>Use <code>reset</code> to undo: if a customization causes confusion, reset   reverts to the embedded default instantly.</li> <li>Empty file = silence: you don't need to delete the hook. An empty   override file silences the message while preserving the hook's logic.</li> <li>JSON output for scripting: <code>ctx hook message list --json</code> returns   structured data for automation.</li> </ul>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/customizing-hook-messages/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hook Output Patterns: understanding   VERBATIM relays, agent directives, and hard gates</li> <li>Auditing System Hooks: verifying hooks   are running and auditing their output</li> <li>Configuration: project-level settings   via <code>.ctxrc</code></li> </ul>","path":["Recipes","Hooks and Notifications","Customizing Hook Messages"],"tags":[]},{"location":"recipes/design-before-coding/","level":1,"title":"Design Before Coding","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-problem","level":2,"title":"The Problem","text":"<p>You start coding a feature. Halfway through, you realize the approach doesn't handle a key edge case. You refactor. Then you discover the CLI interface doesn't fit the existing patterns. More refactoring.</p> <p>The design work happened during implementation, mixed in with debugging and trial-and-error. The result works, but the spec was never written down, the trade-offs were never recorded, and the next session has no idea why things are shaped this way.</p> <p>How do you front-load design so the implementation is straightforward?</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-brainstorm          # explore the design space\n/ctx-spec                # write the spec document\n/ctx-task-add            # break it into tasks\n/ctx-implement           # execute step-by-step\n</code></pre> <p>Four skills, used in sequence. Each produces an artifact that feeds the next.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-brainstorm</code> Skill Structured design dialogue: explore approaches <code>/ctx-spec</code> Skill Scaffold and fill out a spec from the template <code>/ctx-task-add</code> Skill Add implementation tasks to TASKS.md <code>/ctx-implement</code> Skill Execute a plan step-by-step with verification <code>/ctx-decision-add</code> Skill Record design choices made during brainstorming","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-1-brainstorm-the-design","level":3,"title":"Step 1: Brainstorm the Design","text":"<p>Start with <code>/ctx-brainstorm</code> when you have a rough idea but haven't committed to an approach. The skill follows a gated process:</p> <ol> <li>Understand context: what exists today, what's the gap</li> <li>Clarify the idea: one question at a time, not a wall of questions</li> <li>Surface non-functional requirements: performance, security, compatibility</li> <li>Lock understanding: confirm before exploring solutions</li> <li>Explore 2-3 approaches: with trade-offs for each</li> <li>Stress-test: poke holes in the chosen approach</li> <li>Present the design: detailed enough to implement from</li> </ol> <pre><code>You: \"I want to add RSS feed generation to the blog.\"\n\nAgent: \"Before we design, let me understand: is this for the journal\n  site, the docs site, or both?\"\n\nYou: \"Journal site only.\"\n\nAgent: [asks about update frequency, content scope, existing infra...]\n       [presents 3 approaches with trade-offs]\n\nYou: \"Option 2 - generate at build time.\"\n\nAgent: \"Let me stress-test that. What happens when...\"\n       [probes edge cases]\n       \"Design looks solid. Want me to record the approach as a\n       decision and write a spec?\"\n</code></pre> <p>The brainstorm produces a validated design in conversation. It doesn't produce a file - that's what the spec is for.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-2-write-the-spec","level":3,"title":"Step 2: Write the Spec","text":"<p>Once the design is clear, use <code>/ctx-spec</code> to produce the written artifact. The skill reads the project's spec template (<code>specs/tpl/spec-template.md</code>) and walks through each section:</p> <pre><code>You: /ctx-spec (rss feed generation)\n\nAgent: \"Creating specs/rss-feed.md from template.\"\n\n       \"Problem: What user-visible problem does this solve? Why now?\"\n\nYou: \"Users can't subscribe to blog updates. They have to check\n      the site manually.\"\n\nAgent: [writes the Problem section, moves to Approach]\n\n       \"Approach: High-level, how does this work?\"\n\nYou: \"Generate feed.xml during ctx journal site. Atom format.\"\n\nAgent: [writes Approach, moves to Happy Path, then Edge Cases...]\n</code></pre> <p>The skill spends extra time on Edge Cases and Error Handling: These are where specs earn their value. The skill pushes for at least three edge cases and won't accept \"none\" without a challenge.</p> <p>Sections that don't apply can be skipped. The result is a complete spec at <code>specs/{feature-name}.md</code>.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-3-break-into-tasks","level":3,"title":"Step 3: Break into Tasks","text":"<p>After the spec is written, the skill offers to create tasks:</p> <pre><code>Agent: \"Spec written to specs/rss-feed.md. Want me to break this\n  into tasks in TASKS.md?\"\n\nYou: \"Yes.\"\n\nAgent: [creates a Phase section referencing the spec, adds tasks]\n       \"Added Phase 0.8: RSS/Atom Feed Generation with 4 tasks:\n         - [ ] Implement feed.xml generation in ctx journal site\n         - [ ] Add --feed flag and .ctxrc feed config\n         - [ ] Write tests for feed generation\n         - [ ] Add recipe and update docs\"\n</code></pre> <p>Each task is independently completable and references the spec via <code>Spec: specs/rss-feed.md</code> in the Phase header.</p> <p>You can also invoke <code>/ctx-task-add</code> directly at any point to add individual tasks.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#step-4-implement-step-by-step","level":3,"title":"Step 4: Implement Step-by-Step","text":"<p>Pick up tasks with <code>/ctx-implement</code>. The skill loads the spec, breaks work into atomic steps, and checkpoints after every 3-5 steps with a build and test verification:</p> <pre><code>You: /ctx-implement (specs/rss-feed.md)\n\nAgent: \"Loaded spec. Breaking into steps:\n         1. Add FeedEntry type to journal package\n         2. Implement feed.xml template\n         3. Wire into site generation pipeline\n         [builds and tests after step 3]\n         4. Add --feed flag to ctx journal site\n         5. Add .ctxrc feed configuration\n         [builds and tests after step 5]\n         ...\"\n</code></pre> <p>If a build or test fails, the agent stops, diagnoses, and fixes before continuing.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#when-to-skip-steps","level":2,"title":"When to Skip Steps","text":"<p>Not every feature needs all four steps. Use your judgment:</p> Situation Start at Vague idea, multiple valid approaches Step 1: Brainstorm Clear approach, need to document it Step 2: Spec Spec already exists, need to plan work Step 3: Tasks Tasks exist, ready to code Step 4: Implement <p>A brainstorm without a spec is fine for small decisions. A spec without a brainstorm is fine when the design is obvious. The full chain is for features complex enough to warrant front-loaded design.</p>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need skill names. Natural language works:</p> You say What happens \"Let's think through this feature\" <code>/ctx-brainstorm</code> \"Spec this out\" <code>/ctx-spec</code> \"Write a design doc for...\" <code>/ctx-spec</code> \"Break this into tasks\" <code>/ctx-task-add</code> \"Implement the spec\" <code>/ctx-implement</code> \"Let's design before we build\" Starts at brainstorm","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#tips","level":2,"title":"Tips","text":"<ul> <li>Brainstorm first when uncertain. If you can articulate the approach in   two sentences, skip to spec. If you can't, brainstorm.</li> <li>Specs prevent scope creep. The Non-Goals section is as important as the   approach. Writing down what you won't do keeps implementation focused.</li> <li>Edge cases are the point. A spec that only describes the happy path   isn't a spec - it's a wish. The <code>/ctx-spec</code> skill pushes for at least 3   edge cases because that's where designs break.</li> <li>Record decisions during brainstorming. When you choose between   approaches, the agent offers to persist the trade-off via   <code>/ctx-decision-add</code>. Accept - future sessions need to know why, not   just what.</li> <li>Specs are living documents. Update them when implementation reveals   new constraints. A spec that diverges from reality is worse than no spec.</li> <li>The spec template is customizable. Edit <code>specs/tpl/spec-template.md</code>   to match your project's needs. The <code>/ctx-spec</code> skill reads whatever   template it finds there.</li> </ul>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/design-before-coding/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: /ctx-brainstorm:   structured design dialogue</li> <li>Skills Reference: /ctx-spec:   spec scaffolding from template</li> <li>Skills Reference: /ctx-implement:   step-by-step execution with verification</li> <li>Tracking Work Across Sessions: task lifecycle   and archival</li> <li>Importing Claude Code Plans: turning ephemeral plans   into permanent specs</li> <li>Persisting Decisions, Learnings, and Conventions:   capturing design trade-offs</li> </ul>","path":["Recipes","Knowledge and Tasks","Design Before Coding"],"tags":[]},{"location":"recipes/external-context/","level":1,"title":"Keeping Context in a Separate Repo","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> files contain project-specific decisions, learnings, conventions, and tasks. By default, they live in <code>.context/</code> inside the project tree, and that works well when the context can be public.</p> <p>But sometimes you need the context outside the project:</p> <ul> <li>Open-source projects with private context: Your architectural notes,   internal task lists, and scratchpad entries shouldn't ship with the public   repo.</li> <li>Compliance or IP concerns: Context files reference sensitive design   rationale that belongs in a separate access-controlled repository.</li> <li>Personal preference: You want to keep notes separate from code.</li> </ul> <p><code>ctx</code> supports this by letting you point <code>CTX_DIR</code> anywhere. This recipe shows how to set that up and how to tell your AI assistant where to find the context.</p> <p>One <code>.context/</code> per project</p> <p>The parent of the context directory is the project root by contract. <code>ctx sync</code>, <code>ctx drift</code>, and the memory-drift hook all read the codebase at <code>filepath.Dir(ContextDir())</code>. Pointing two projects at the same directory corrupts their journals, state, and secrets. To share knowledge (CONSTITUTION / CONVENTIONS / ARCHITECTURE) across projects, use <code>ctx hub</code>, not a shared <code>.context/</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tldr","level":2,"title":"TL;DR","text":"<p>Create the external context directory, initialize it, and bind it:</p> <pre><code>mkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\ncd ~/repos/myproject\n\n# Bind CTX_DIR to the external location, then init creates files there.\nexport CTX_DIR=~/repos/myproject-context/.context\nctx init\n</code></pre> <p>All <code>ctx</code> commands now use the external directory. If you share the setup across shells, add the <code>export CTX_DIR=...</code> line to your shell rc, or source a per-project <code>.envrc</code> with direnv.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#what-works-what-quietly-degrades","level":2,"title":"What Works, What Quietly Degrades","text":"<p>The single-source-anchor contract states that <code>filepath.Dir(CTX_DIR)</code> is the project root. When the context lives outside the project tree, <code>ctx</code> still resolves correctly for every operation that reads or writes inside <code>.context/</code>. But any operation that scans the codebase scans the wrong tree, and does so silently:</p> Operation Behavior with external <code>.context/</code> <code>ctx status</code>, <code>agent</code>, <code>add</code> ✅ Works. Operates on files inside <code>CTX_DIR</code>. Journal, scratchpad, hub ✅ Works. Same reason. <code>ctx sync</code> ⚠️ Scans the context repo, not the code repo. <code>ctx drift</code> ⚠️ Same. Reports nothing useful. Memory-drift hook (<code>MEMORY.md</code>) ⚠️ Looks for <code>MEMORY.md</code> next to the external <code>.context/</code>, not the code. <p>Nothing errors. The code-aware operations just find an empty or unrelated tree where the project root should be.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#workaround-symlink-the-context-into-the-code-tree","level":3,"title":"Workaround: symlink the <code>.context/</code> into the code tree","text":"<p>If you want both the privacy of an external git repo and working <code>ctx sync</code> / <code>drift</code> / memory-drift, symlink the external <code>.context/</code> into the code repo and point <code>CTX_DIR</code> at the symlink:</p> <pre><code># External repo holds the real files\nmkdir -p ~/repos/myproject-context && cd ~/repos/myproject-context && git init\n\n# Symlink it into the code repo\nln -s ~/repos/myproject-context/.context ~/repos/myproject/.context\n\n# Bind CTX_DIR to the symlink path; ctx init will follow it\nexport CTX_DIR=~/repos/myproject/.context\nctx init\n</code></pre> <p>Now <code>filepath.Dir(CTX_DIR)</code> is the code repo, so code-aware operations scan the right tree. The actual files still live in the external repo and commit there. Add <code>.context</code> to the code repo's <code>.gitignore</code> (or <code>.git/info/exclude</code>) so the symlink itself isn't tracked by the code repo.</p> <p>The basename guard is permissive about symlinks: it checks the declared name, not the resolved target, so a <code>.context</code> symlink pointing anywhere is accepted as long as the declared basename is <code>.context</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> CLI command Initialize context directory <code>ctx activate</code> CLI command Emit <code>export CTX_DIR=...</code> for the shell <code>CTX_DIR</code> Env variable Declare context directory per-session <code>.ctxrc</code> Config file Per-project configuration <code>/ctx-status</code> Skill Verify context is loading correctly","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-1-create-the-private-context-repo","level":3,"title":"Step 1: Create the Private Context Repo","text":"<p>Create a separate repository for your context files. This can live anywhere: a private GitHub repo, a shared drive, a sibling directory:</p> <pre><code># Create the context repo\nmkdir -p ~/repos/myproject-context\ncd ~/repos/myproject-context\ngit init\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-2-initialize-ctx-pointing-at-it","level":3,"title":"Step 2: Initialize <code>ctx</code> Pointing at It","text":"<p>From your project root, declare <code>CTX_DIR</code> pointing to the external location, then initialize:</p> <pre><code>cd ~/repos/myproject\nCTX_DIR=~/repos/myproject-context/.context ctx init\n</code></pre> <p>This creates the canonical <code>.context/</code> file set inside <code>~/repos/myproject-context/</code> instead of <code>~/repos/myproject/.context/</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-3-make-it-stick","level":3,"title":"Step 3: Make It Stick","text":"<p>Declaring <code>CTX_DIR</code> on every command is tedious. Pick one of these methods to make the configuration permanent. The context directory itself must be declared via <code>CTX_DIR</code>; <code>.ctxrc</code> does not carry the path.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-a-ctx_dir-environment-variable-recommended","level":4,"title":"Option A: <code>CTX_DIR</code> Environment Variable (Recommended)","text":"<pre><code># Direct path. Works for ctx status / agent / add but degrades\n# code-aware operations. See \"What Works, What Quietly Degrades\".\nexport CTX_DIR=~/repos/myproject-context/.context\n\n# Or, with the symlink approach above, point at the symlink path\n# inside the code repo so code-aware operations stay healthy.\nexport CTX_DIR=~/repos/myproject/.context\n</code></pre> <p>Put either form in your shell profile (<code>~/.bashrc</code>, <code>~/.zshrc</code>) or a direnv <code>.envrc</code>.</p> <p>For a single session, run <code>eval \"$(ctx activate)\"</code> from any directory inside the project where exactly one <code>.context/</code> candidate is visible (the symlink counts). <code>activate</code> does not accept a path argument; bind a specific path by exporting <code>CTX_DIR</code> directly instead.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#option-b-ctxrc-for-other-settings","level":4,"title":"Option B: <code>.ctxrc</code> for Other Settings","text":"<p>Put any settings (token budget, priority order, freshness files) in a <code>.ctxrc</code> at the project root (<code>dirname(CTX_DIR)</code>), which here is the parent of the external <code>.context/</code>:</p> <pre><code># ~/repos/myproject-context/.ctxrc\ntoken_budget: 16000\n</code></pre> <p><code>.ctxrc</code> is always read from the parent of <code>CTX_DIR</code>, so this file is picked up whenever <code>CTX_DIR</code> points at <code>~/repos/myproject-context/.context</code>.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#resolution","level":4,"title":"Resolution","text":"<p><code>ctx</code> reads the context directory from a single channel: the <code>CTX_DIR</code> environment variable. When <code>CTX_DIR</code> is unset, <code>ctx</code> errors with a \"no context directory specified\" hint pointing at <code>ctx activate</code> and this recipe. When set, the value must be an absolute path with <code>.context</code> as its basename; relative paths and other names are rejected on first use.</p> <p>See Activating a Context Directory for the full recipe.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-4-agent-auto-discovery-via-bootstrap","level":3,"title":"Step 4: Agent Auto-Discovery via Bootstrap","text":"<p>When context lives outside the project tree, your AI assistant needs to know where to find it. The <code>ctx system bootstrap</code> command resolves the configured context directory and communicates it to the agent automatically:</p> <pre><code>$ ctx system bootstrap\nctx system bootstrap\n====================\n\ncontext_dir: /home/user/repos/myproject-context/.context\n\nFiles:\n  CONSTITUTION.md, TASKS.md, DECISIONS.md, ...\n</code></pre> <p>The <code>CLAUDE.md</code> template generated by <code>ctx init</code> already instructs the agent to run <code>ctx system bootstrap</code> at session start. Because <code>CTX_DIR</code> is inherited by child processes, your agent picks up the external path automatically.</p> <p>Here is the relevant section from <code>CLAUDE.md</code> for reference:</p> <pre><code><!-- CLAUDE.md -->\n1. **Run `ctx system bootstrap`**: CRITICAL, not optional.\n   This tells you where the context directory is. If it returns any\n   error, relay the error output to the user verbatim, point them at\n   https://ctx.ist/recipes/activating-context/ for setup, and STOP.\n   Do not try to recover; the user decides.\n</code></pre> <p>Moreover, every nudge (context checkpoint, persistence reminder, etc.) also includes a <code>Context: /home/user/repos/myproject-context/.context</code> footer, so the agent remains anchored to the correct directory even in long sessions.</p> <p>Export <code>CTX_DIR</code> in your shell profile so every hook process inherits it:</p> <pre><code>export CTX_DIR=~/repos/myproject-context/.context\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-5-share-with-teammates","level":3,"title":"Step 5: Share with Teammates","text":"<p>Teammates clone both repos and export <code>CTX_DIR</code>:</p> <pre><code># Clone the project\ngit clone git@github.com:org/myproject.git\ncd myproject\n\n# Clone the private context repo\ngit clone git@github.com:org/myproject-context.git ~/repos/myproject-context\nexport CTX_DIR=~/repos/myproject-context/.context\n</code></pre> <p>If teammates use different paths, each developer sets their own <code>CTX_DIR</code>.</p> <p>For encryption key distribution across the team, see the Syncing Scratchpad Notes recipe.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#step-6-day-to-day-sync","level":3,"title":"Step 6: Day-to-Day Sync","text":"<p>The external context repo has its own git history. Treat it like any other repo: commit and push after sessions:</p> <pre><code>cd ~/repos/myproject-context\n\n# After a session\ngit add -A\ngit commit -m \"Session: refactored auth module, added rate-limit learning\"\ngit push\n</code></pre> <p>Your AI assistant can do this too. When ending a session:</p> <pre><code>You: \"Save what we learned and push the context repo.\"\n\nAgent: [runs ctx learning add, then commits and pushes the context repo]\n</code></pre> <p>You can also set up a post-session habit: project code gets committed to the project repo, context gets committed to the context repo.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember the flags; simply ask your assistant:</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#set-up-your-system-using-natural-language","level":3,"title":"Set Up Your System Using Natural Language","text":"<pre><code>You: \"Set up ctx to use ~/repos/myproject-context as the context directory.\"\n\nAgent: \"I'll set CTX_DIR to that path, run ctx init to materialize\n       it, and show you the export line to add to your shell\n       profile. Want me to seed the core context files too?\"\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#configure-separate-repo-for-context-folder-using-natural-language","level":3,"title":"Configure Separate Repo for <code>.context</code> Folder Using Natural Language","text":"<pre><code>You: \"My context is in a separate repo. Can you load it?\"\n\nAgent: [reads CTX_DIR, loads context from the external dir]\n       \"Loaded. You have 3 pending tasks, last session was about the auth\n       refactor.\"\n</code></pre>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#tips","level":2,"title":"Tips","text":"<ul> <li>Start simple. If you don't need external context yet, don't set it up.   The default <code>.context/</code> in-tree is the easiest path. Move to an external   repo when you have a concrete reason.</li> <li>One context repo per project. Sharing a single context directory across   multiple projects corrupts journals, state, and secrets. Use <code>ctx hub</code> for   cross-project knowledge sharing.</li> <li>Export <code>CTX_DIR</code> in your shell profile so hooks and tools inherit the   path without per-command flags.</li> <li>Commit both repos at session boundaries. Context without code history   (or code without context history) loses half the value.</li> </ul>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#next-up","level":2,"title":"Next Up","text":"<p>The Complete Session →: Walk through a full <code>ctx</code> session from start to finish.</p>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/external-context/#see-also","level":2,"title":"See Also","text":"<ul> <li>Setting Up <code>ctx</code> Across AI Tools: initial setup recipe</li> <li>Syncing Scratchpad Notes Across Machines: distribute   encryption keys when context is shared</li> <li>CLI Reference: full command list and global options</li> </ul>","path":["Recipes","Getting Started","Keeping Context in a Separate Repo"],"tags":[]},{"location":"recipes/guide-your-agent/","level":1,"title":"Guide Your Agent","text":"<p>Commands vs. Skills</p> <p>Commands (<code>ctx status</code>, <code>ctx task add</code>) run in your terminal.</p> <p>Skills (<code>/ctx-reflect</code>, <code>/ctx-next</code>) run inside your AI coding assistant.</p> <p>Recipes combine both.</p> <p>Think of commands as structure and skills as behavior.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#proactive-behavior","level":2,"title":"Proactive Behavior","text":"<p>These recipes show explicit commands and skills, but agents trained on the <code>ctx</code> playbook are proactive: They offer to save learnings after debugging, record decisions after trade-offs, create follow-up tasks after completing work, and suggest what to work on next.</p> <p>Your questions train the agent. Asking \"what have we learned?\" or \"is our context clean?\" does two things:</p> <ul> <li>It triggers the workflow right now,</li> <li>and it reinforces the pattern.</li> </ul> <p>The more you guide, the more the agent habituates the behavior and begins offering on its own.</p> <p>Each recipe includes a Conversational Approach section showing these natural-language patterns.</p> <p>Tip</p> <p>Don't wait passively for proactive behavior: especially in early sessions.</p> <p>Ask, guide, reinforce. Over time, you ask less and the agent offers more.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#next-up","level":2,"title":"Next Up","text":"<p>Setup Across AI Tools →: Initialize <code>ctx</code> and configure hooks for Claude Code, OpenCode, Cursor, Aider, Copilot, or Windsurf.</p>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/guide-your-agent/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session   lifecycle from start to finish</li> <li>Prompting Guide: general tips for   working effectively with AI coding assistants</li> </ul>","path":["Recipes","Getting Started","Guide Your Agent"],"tags":[]},{"location":"recipes/hook-output-patterns/","level":1,"title":"Hook Output Patterns","text":"","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code hooks can output text, JSON, or nothing at all. But the format of that output determines who sees it and who acts on it. </p> <p>Choose the wrong pattern, and your carefully crafted warning gets silently  absorbed by the agent, or your agent-directed nudge gets dumped on the user  as noise.</p> <p>This recipe catalogs the known hook output patterns and explains when to use each one.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#tldr","level":2,"title":"TL;DR","text":"<p>Eight patterns from full control to full invisibility: </p> <ul> <li>hard gate (<code>exit 2</code>), </li> <li>VERBATIM relay (agent MUST show), </li> <li>agent directive (context injection), </li> <li>and silent side-effect (background work).</li> </ul> <p>Most hooks belong in the middle.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#the-spectrum","level":2,"title":"The Spectrum","text":"<p>These patterns form a spectrum based on who decides what the user sees:</p> Pattern Who decides? Hard gate Hook decides (agent can't proceed) VERBATIM relay Hook decides (agent must show) Escalating severity Hook suggests, agent judges urgency Conditional relay Hook sets criteria, agent evaluates Suggested action Hook proposes, agent + user decide Agent directive Agent decides entirely Silent injection Nobody: invisible background context Silent side-effect Nobody: invisible background work <p>The spectrum runs from full hook control (hard gate) to full invisibility (silent side effect). </p> <p>Most hooks belong somewhere in the middle.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-1-hard-gate","level":2,"title":"Pattern 1: Hard Gate","text":"<p>Block the tool call entirely. The agent cannot proceed: it must find another approach or tell the user.</p> <pre><code>echo '{\"decision\": \"block\", \"reason\": \"Use ctx from PATH, not ./ctx\"}'\n</code></pre> <p>When to use: Enforcing invariants that must never be violated: Constitution rules, security boundaries, destructive command prevention.</p> <p>Hook type: <code>PreToolUse</code> only (Claude Code first-class mechanism).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system block-non-path-ctx</code>: Enforces the PATH invocation rule</li> <li><code>block-git-push.sh</code>: Requires explicit user approval for pushes (project-local)</li> <li><code>block-dangerous-commands.sh</code>: Prevents <code>sudo</code>, copies to <code>~/.local/bin</code> (project-local)</li> </ul> <p>Trade-off: The agent gets a block response with a reason. Good reasons help the agent recover (\"use X instead\"); bad reasons leave it stuck.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-2-verbatim-relay","level":2,"title":"Pattern 2: VERBATIM Relay","text":"<p>Force the agent to show this to the user as-is. The explicit instruction overcomes the agent's tendency to silently absorb context.</p> <pre><code>echo \"IMPORTANT: Relay this warning to the user VERBATIM before answering their question.\"\necho \"\"\necho \"┌─ Journal Reminder ─────────────────────────────\"\necho \"│ You have 12 sessions not yet exported.\"\necho \"└────────────────────────────────────────────────\"\n</code></pre> <p>When to use: Actionable reminders the user needs to see regardless of what they asked: Stale backups, unimported sessions, resource warnings.</p> <p>Hook type: <code>UserPromptSubmit</code> (runs before the agent sees the prompt).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-journal</code>: Unexported sessions and unenriched entries</li> <li><code>ctx system check-context-size</code>: Context capacity warning</li> <li><code>ctx system check-resources</code>: Resource pressure (memory, swap, disk, load): <code>DANGER</code> only</li> <li><code>ctx system check-freshness</code>: Technology constant staleness warning</li> </ul> <p>Trade-off: Noisy if overused. Every VERBATIM relay adds a preamble before the agent's actual answer. Throttle with once-per-day markers or adaptive frequency.</p> <p>Key detail: The phrase <code>IMPORTANT: Relay this ... VERBATIM</code> is what makes this work. Without it, agents tend to process the information internally and never surface it. The explicit instruction is the pattern: the box-drawing is just fancy formatting.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-3-agent-directive","level":2,"title":"Pattern 3: Agent Directive","text":"<p>Tell the agent to do something, not the user. The agent decides whether and how to involve the user.</p> <pre><code>echo \"┌─ Persistence Checkpoint (prompt #25) ───────────\"\necho \"│ No context files updated in 15+ prompts.\"\necho \"│ Have you discovered learnings, decisions,\"\necho \"│ or completed tasks worth persisting?\"\necho \"└──────────────────────────────────────────────────\"\n</code></pre> <p>When to use: Behavioral nudges. The hook detects a condition and asks the agent to consider an action. The user may never need to know.</p> <p>Hook type: <code>UserPromptSubmit</code>.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-persistence</code>: Nudges the agent to persist context</li> </ul> <p>Trade-off: No guarantee the agent acts. The nudge is one signal among many in the context window. Strong phrasing helps (\"Have you...?\" is better than \"Consider...\"), but ultimately the agent decides.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-4-silent-context-injection","level":2,"title":"Pattern 4: Silent Context Injection","text":"<p>Load context with no visible output. The agent gets enriched without either party noticing.</p> <pre><code>ctx agent --budget 4000 >/dev/null || true\n</code></pre> <p>When to use: Background context loading that should be invisible. The agent benefits from the information, but neither it, nor the user needs to know it happened.</p> <p>Hook type: <code>PreToolUse</code> with <code>.*</code> matcher (runs on every tool call).</p> <p>Examples in <code>ctx</code>:</p> <ul> <li>The <code>ctx agent</code> <code>PreToolUse</code> hook: injects project context silently</li> </ul> <p>Trade-off: Adds latency to every tool call. Keep the injected content small and fast to generate.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-5-silent-side-effect","level":2,"title":"Pattern 5: Silent Side-Effect","text":"<p>Do work, produce no output: Housekeeping that needs no acknowledgment.</p> <pre><code>find \"$CTX_TMPDIR\" -type f -mtime +15 -delete\n</code></pre> <p>When to use: Cleanup, log rotation, temp file management. Anything where the action is the point and nobody needs to know it happened.</p> <p>Hook type: Any hook where output is irrelevant.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li>Log rotation, marker file cleanup, state directory maintenance</li> </ul> <p>Trade-off: None, if the action is truly invisible. If it can fail in a way that matters, consider logging.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-6-conditional-relay","level":3,"title":"Pattern 6: Conditional Relay","text":"<p>Tell the agent to relay only if a condition holds in context.</p> <pre><code>echo \"If the user's question involves modifying .context/ files,\"\necho \"relay this warning VERBATIM:\"\necho \"\"\necho \"┌─ Context Integrity ─────────────────────────────\"\necho \"│ CONSTITUTION.md has not been verified in 7 days.\"\necho \"└────────────────────────────────────────────────\"\necho \"\"\necho \"Otherwise, proceed normally.\"\n</code></pre> <p>When to use: Warnings that only matter in certain contexts. Avoids noise when the user is doing unrelated work.</p> <p>Trade-off: Depends on the agent's judgment about when the condition holds. More fragile than VERBATIM relay, but less noisy.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-7-suggested-action","level":3,"title":"Pattern 7: Suggested Action","text":"<p>Give the agent a specific command to propose to the user.</p> <pre><code>echo \"┌─ Stale Dependencies ──────────────────────────\"\necho \"│ go.sum is 30+ days newer than go.mod.\"\necho \"│ Suggested: run \\`go mod tidy\\`\"\necho \"│ Ask the user before proceeding.\"\necho \"└───────────────────────────────────────────────\"\n</code></pre> <p>When to use: The hook detects a fixable condition and knows the fix. Goes beyond a nudge: Gives the agent a concrete next step. The agent still asks for permission but knows exactly what to propose.</p> <p>Trade-off: The suggestion might be wrong or outdated. The \"ask the user before proceeding\" part is critical.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#pattern-8-escalating-severity","level":3,"title":"Pattern 8: Escalating Severity","text":"<p>Different urgency tiers with different relay expectations.</p> <pre><code># INFO: agent processes silently, mentions if relevant\necho \"INFO: Last test run was 3 days ago.\"\n\n# WARN: agent should mention to user at next natural pause\necho \"WARN: 12 uncommitted changes across 3 branches.\"\n\n# CRITICAL: agent must relay immediately, before any other work\necho \"CRITICAL: Relay VERBATIM before answering. Disk usage at 95%.\"\n</code></pre> <p>When to use: When you have multiple hooks producing output and need to avoid overwhelming the user. <code>INFO</code> gets absorbed, <code>WARN</code> gets mentioned, <code>CRITICAL</code> interrupts.</p> <p>Examples in <code>ctx</code>:</p> <ul> <li><code>ctx system check-resources</code>: Uses two tiers (<code>WARNING</code>/<code>DANGER</code>) internally   but only fires the VERBATIM relay at <code>DANGER</code> level: <code>WARNING</code> is silent.   See <code>ctx system</code> for the user-facing command that shows both tiers.</li> </ul> <p>Trade-off: Requires agent training or convention to recognize the tiers. Without a shared protocol, the prefixes are just text.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#choosing-a-pattern","level":2,"title":"Choosing a Pattern","text":"<pre><code>Is the agent about to do something forbidden?\n  └─ Yes → Hard gate\n\nDoes the user need to see this regardless of what they asked?\n  └─ Yes → VERBATIM relay\n  └─ Sometimes → Conditional relay\n\nShould the agent consider an action?\n  └─ Yes, with a specific fix → Suggested action\n  └─ Yes, open-ended → Agent directive\n\nIs this background context the agent should have?\n  └─ Yes → Silent injection\n\nIs this housekeeping?\n  └─ Yes → Silent side-effect\n</code></pre>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#design-tips","level":2,"title":"Design Tips","text":"<p>Throttle aggressively: VERBATIM relays that fire every prompt will be ignored or resented. Use once-per-day markers (<code>touch $REMINDED</code>), adaptive frequency (every Nth prompt), or staleness checks (only fire if condition persists).</p> <p>Include actionable commands: \"You have 12 unimported sessions\" is less useful than \"You have 12 unimported sessions. Run: <code>ctx journal import --all</code>.\" Give the user (or agent) the exact next step.</p> <p>Use box-drawing for visual structure: The <code>┌─ ─┐ │ └─ ─┘</code> pattern makes hook output visually distinct from agent prose. It also signals \"this is machine-generated, not agent opinion.\"</p> <p>Test the silence path: Most hook runs should produce no output (the condition isn't met). Make sure the common case is fast and silent.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#common-pitfalls","level":2,"title":"Common Pitfalls","text":"<p>Lessons from 19 days of hook debugging in <code>ctx</code>. Every one of these was encountered, debugged, and fixed in production.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#silent-misfire-wrong-key-name","level":3,"title":"Silent Misfire: Wrong Key Name","text":"<pre><code>{ \"PreToolUseHooks\": [ ... ] }\n</code></pre> <p>The key is <code>PreToolUse</code>, not <code>PreToolUseHooks</code>. Claude Code validates silently: A misspelled key means the hook is ignored with no error. Always test with a debug <code>echo</code> first to confirm the hook fires before adding real logic.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#json-escaping-breaks-shell-commands","level":3,"title":"JSON Escaping Breaks Shell Commands","text":"<p>Go's <code>json.Marshal</code> escapes <code>></code>, <code><</code>, and <code>&</code> as Unicode sequences (<code>\\u003e</code>) by default. This breaks shell commands in generated config:</p> <pre><code>\"command\": \"ctx agent 2\\u003e/dev/null\"\n</code></pre> <p>Fix: use <code>json.Encoder</code> with <code>SetEscapeHTML(false)</code> when generating hook configuration.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#stdin-not-environment-variables","level":3,"title":"<code>stdin</code>, Not Environment Variables","text":"<p>Hook input arrives as JSON via <code>stdin</code>, not environment variables:</p> <pre><code># Wrong:\nCOMMAND=\"$CLAUDE_TOOL_INPUT\"\n\n# Right:\nHOOK_INPUT=$(cat)\nCOMMAND=$(echo \"$HOOK_INPUT\" | jq -r '.tool_input.command // empty')\n</code></pre>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#regex-overfitting","level":3,"title":"Regex Overfitting","text":"<p>A regex meant to catch <code>ctx</code> as a binary will also match <code>ctx</code> as a directory component:</p> <pre><code># Too broad: blocks: git -C /home/jose/WORKSPACE/ctx status\n(/home/|/tmp/|/var/)[^ ]*ctx[^ ]*\n\n# Narrow to binary only:\n(/home/|/tmp/|/var/)[^ ]*/ctx( |$)\n</code></pre> <p>Test hook regexes against paths that contain the target string as a substring, not just as the final component.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#repetition-fatigue","level":3,"title":"Repetition Fatigue","text":"<p>Injecting context on every tool call sounds safe. In practice, after seeing the same context injection fifteen times, the agent treats it as background noise: Conventions stated in the injected context get violated because salience has been destroyed by repetition.</p> <p>Fix: cooldowns. <code>ctx agent --session $PPID --cooldown 10m</code> injects at most once per ten minutes per session using a tombstone file in <code>/tmp/</code>. This is not an optimization; it is a correction for a design flaw. Every injection consumes attention budget: 50 tool calls at 4,000 tokens each means 200,000 tokens of repeated context, most of it wasted.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#hardcoded-paths","level":3,"title":"Hardcoded Paths","text":"<p>A username rename (<code>parallels</code> to <code>jose</code>) broke every hook at once. Use <code>$CLAUDE_PROJECT_DIR</code> instead of absolute paths:</p> <pre><code>\"command\": \"\\\"$CLAUDE_PROJECT_DIR\\\"/.claude/hooks/block-git-push.sh\"\n</code></pre> <p>If the platform provides a runtime variable for paths, always use it.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#next-up","level":2,"title":"Next Up","text":"<p>Webhook Notifications →: Get push notifications when loops complete, hooks fire, or agents hit milestones.</p>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-output-patterns/#see-also","level":2,"title":"See Also","text":"<ul> <li>Customizing Hook Messages: override   what hooks say without changing what they do</li> <li>Claude Code Permission Hygiene: how   permissions and hooks work together</li> <li>Defense in Depth:   why hooks matter for agent security</li> </ul>","path":["Recipes","Hooks and Notifications","Hook Output Patterns"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/","level":1,"title":"Hook Sequence Diagrams","text":"","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#hook-lifecycle","level":2,"title":"Hook Lifecycle","text":"<p>This page documents the <code>ctx</code> system hooks: the built-in <code>ctx system *</code> subcommands that Claude Code invokes via <code>.claude/hooks.json</code> at lifecycle events. These are owned by <code>ctx</code> itself, not authored by users.</p> <p>Not to Be Confused with <code>ctx trigger</code></p> <p><code>ctx</code> has three distinct hook-like layers:</p> <ul> <li><code>ctx system</code> hooks (this page): built-in, owned   by <code>ctx</code>, wired into Claude Code via   <code>internal/assets/claude/hooks/hooks.json</code>.</li> <li><code>ctx trigger</code>: user-authored shell scripts in   <code>.context/hooks/<type>/*.sh</code>. See   <code>ctx trigger</code> reference and the   trigger authoring recipe.</li> <li>Claude Code hooks configured directly in   <code>.claude/settings.local.json</code>, tool-specific, not   portable across AI tools.</li> </ul> <p>This page is only about the first category.</p> <p>Every <code>ctx system</code> hook is a Go binary invoked by Claude Code at one of three lifecycle events: <code>PreToolUse</code> (before a tool runs, can block), <code>PostToolUse</code> (after a tool completes), or <code>UserPromptSubmit</code> (on every user prompt, before any tools run). Hooks receive JSON on stdin and emit JSON or plain text on stdout.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#pretooluse-hooks","level":2,"title":"PreToolUse Hooks","text":"<p>These fire before a tool executes. They can block, gate, or inject context.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#context-load-gate","level":3,"title":"Context-Load-Gate","text":"<p>Matcher: <code>.*</code> (all tools)</p> <p>Injects the full context packet on first tool use of a session. One-shot per session.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as context-load-gate\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Git as git log\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized\n    alt not initialized\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check ctx-loaded-{session} marker\n    alt marker exists\n        Hook-->>CC: (silent exit, already fired)\n    end\n    Hook->>State: Create marker (one-shot guard)\n    Hook->>State: Prune stale session files\n    loop Each file in ReadOrder\n        alt GLOSSARY or TASK\n            Note over Hook: Skip (Task mentioned in footer only)\n        else DECISION or LEARNING\n            Hook->>Ctx: Extract index table only\n        else other files\n            Hook->>Ctx: Read full content\n        end\n        Hook->>Hook: Estimate tokens per file\n    end\n    Hook->>Git: Detect changes since last session\n    Hook->>Hook: Build injection (files + changes + token counts)\n    Hook-->>CC: JSON {additionalContext: injection}\n    Hook->>Hook: Send webhook (metadata only)\n    Hook->>State: Write oversize flag if tokens > threshold</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-non-path-ctx","level":3,"title":"Block-Non-Path-ctx","text":"<p>Matcher: <code>Bash</code></p> <p>Blocks <code>./ctx</code>, <code>go run ./cmd/ctx</code>, or absolute-path <code>ctx</code> invocations. Constitutionally enforced.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-non-path-ctx\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Test regex: relative-path, go-run, absolute-path\n    alt no match\n        Hook-->>CC: (silent exit)\n    end\n    alt absolute-path + test exception\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason + constitution suffix}\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#qa-reminder","level":3,"title":"Qa-Reminder","text":"<p>Matcher: <code>Bash</code></p> <p>Gate nudge before any git command. Reminds agent to lint/test.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as qa-reminder\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Check command contains \"git\"\n    alt no git command\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, gate, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: QA gate}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#specs-nudge","level":3,"title":"Specs-Nudge","text":"<p>Matcher: <code>EnterPlanMode</code></p> <p>Nudges agent to save plans/specs when new implementation detected.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as specs-nudge\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: specs nudge}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#posttooluse-hooks","level":2,"title":"PostToolUse Hooks","text":"<p>These fire after a tool completes. They observe, nudge, and track state.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#post-commit","level":3,"title":"Post-Commit","text":"<p>Matcher: <code>Bash</code></p> <p>Fires after <code>git commit</code> (not amend). Nudges for context capture and checks version drift.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as post-commit\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"git commit\"?\n    alt not a git commit\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Regex: command contains \"--amend\"?\n    alt is amend\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook->>Hook: AppendDir(message)\n    Hook-->>CC: JSON {additionalContext: post-commit nudge}\n    Hook->>Hook: Relay(message)\n    Hook->>Hook: CheckVersionDrift()</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-task-completion","level":3,"title":"Check-Task-Completion","text":"<p>Matcher: <code>Edit</code>, <code>Write</code></p> <p>Configurable-interval nudge after edits. Per-session counter resets after firing.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-task-completion\n    participant State as .context/state/\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read task nudge interval\n    alt interval <= 0 (disabled)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read per-session counter\n    Hook->>Hook: Increment counter\n    alt counter < interval\n        Hook->>State: Write counter\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Reset counter to 0\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: JSON {additionalContext: task nudge}\n    Hook->>Hook: Relay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#userpromptsubmit-hooks","level":2,"title":"UserPromptSubmit Hooks","text":"<p>These fire on every user prompt, before any tools run. They perform health checks, track state, and nudge for housekeeping.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-context-size","level":3,"title":"Check-Context-Size","text":"<p>Adaptive context window monitoring. Fires checkpoints, window warnings, and billing alerts based on prompt count and token usage.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-context-size\n    participant State as .context/state/\n    participant Session as Session JSONL\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized\n    Hook->>Hook: Read input, resolve session ID\n    Hook->>Hook: Check paused\n    alt paused\n        Hook-->>CC: Pause acknowledgment message\n    end\n    Hook->>State: Increment session prompt counter\n    Hook->>Session: Read token info (tokens, model, window)\n\n    rect rgb(255, 240, 240)\n        Note over Hook: Billing check (independent, never suppressed)\n        alt tokens >= billing threshold (one-shot)\n            Hook->>Tpl: LoadMessage(hook, billing, vars)\n            Hook-->>CC: Billing warning nudge box\n            Hook->>Hook: NudgeAndRelay(billing message)\n        end\n    end\n\n    Hook->>State: Check wrap-up marker\n    alt wrapped up recently (< 2h)\n        Hook->>State: Write stats (event: suppressed)\n        Hook-->>CC: (silent exit)\n    end\n\n    rect rgb(240, 248, 255)\n        Note over Hook: Adaptive frequency check\n        alt count > 30 and count % 3 == 0\n            Note over Hook: High frequency trigger\n        else count > 15 and count % 5 == 0\n            Note over Hook: Medium frequency trigger\n        else\n            Hook->>State: Write stats (event: silent)\n            Hook-->>CC: (silent exit)\n        end\n    end\n\n    alt context window >= 80%\n        Hook->>Tpl: LoadMessage(hook, window, vars)\n        Hook-->>CC: Window warning nudge box\n        Hook->>Hook: NudgeAndRelay(window message)\n    else checkpoint trigger\n        Hook->>Tpl: LoadMessage(hook, checkpoint)\n        Hook-->>CC: Checkpoint nudge box\n        Hook->>Hook: NudgeAndRelay(checkpoint message)\n    end\n    Hook->>State: Write session stats</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-ceremonies","level":3,"title":"Check-Ceremonies","text":"<p>Daily check for <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> usage in recent journal entries.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-ceremonies\n    participant State as .context/state/\n    participant Journal as Journal files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Read recent files (lookback window)\n    alt no journal files\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Scan for /ctx-remember and /ctx-wrap-up\n    alt both ceremonies present\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Note over Hook: variant: both | remember | wrapup\n    Hook-->>CC: Nudge box (missing ceremonies)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-freshness","level":3,"title":"Check-Freshness","text":"<p>Daily check for technology-dependent constants that may need review.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-freshness\n    participant State as .context/state/\n    participant FS as Filesystem\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>FS: Stat tracked files (5 source files)\n    alt all files modified within 6 months\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {StaleFiles})\n    Hook-->>CC: Nudge box (stale file list + review URL)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-journal","level":3,"title":"Check-Journal","text":"<p>Daily check for unimported sessions and unenriched journal entries.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-journal\n    participant State as .context/state/\n    participant Journal as Journal dir\n    participant Claude as Claude projects dir\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Check dir exists\n    Hook->>Claude: Check dir exists\n    alt either dir missing\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Journal: Get newest entry mtime\n    Hook->>Claude: Count .jsonl files newer than journal\n    Hook->>Journal: Count unenriched entries\n    alt unimported == 0 and unenriched == 0\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, {counts})\n    Note over Hook: variant: both | unimported | unenriched\n    Hook-->>CC: Nudge box (counts)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-knowledge","level":3,"title":"Check-Knowledge","text":"<p>Daily check for knowledge file entry/line counts exceeding configured thresholds.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-knowledge\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant RC as .ctxrc\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>RC: Read thresholds (decisions, learnings, conventions)\n    alt all thresholds disabled (0)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Ctx: Parse DECISIONS.md entry count\n    Hook->>Ctx: Parse LEARNINGS.md entry count\n    Hook->>Ctx: Count CONVENTIONS.md lines\n    Hook->>Hook: Compare against thresholds\n    alt all within limits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, warning, {FileWarnings})\n    Hook-->>CC: Nudge box (file warnings)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-map-staleness","level":3,"title":"Check-Map-Staleness","text":"<p>Daily check for architecture map age and relevant code changes.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-map-staleness\n    participant State as .context/state/\n    participant Tracking as map-tracking.json\n    participant Git as git log\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tracking: Read map-tracking.json\n    alt missing, invalid, or opted out\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Parse LastRun date\n    alt map not stale (< N days)\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Git: Count commits touching internal/ since LastRun\n    alt no relevant commits\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, stale, {date, count})\n    Hook-->>CC: Nudge box (last refresh + commit count)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle marker</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-memory-drift","level":3,"title":"Check-Memory-Drift","text":"<p>Per-session check for MEMORY.md changes since last sync.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-memory-drift\n    participant State as .context/state/\n    participant Mem as memory.Discover\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check session tombstone\n    alt already nudged this session\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: DiscoverMemoryPath(projectRoot)\n    alt auto memory not active\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Mem: HasDrift(contextDir, sourcePath)\n    alt no drift\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, fallback)\n    Hook-->>CC: Nudge box (drift reminder)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch session tombstone</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-persistence","level":3,"title":"Check-Persistence","text":"<p>Tracks context file modification and nudges when edits happen without persisting context. Adaptive threshold based on prompt count.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-persistence\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Read persistence state {Count, LastNudge, LastMtime}\n    alt first prompt (no state)\n        Hook->>State: Initialize state {Count:1, LastNudge:0, LastMtime:now}\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Increment Count\n    Hook->>Ctx: Get current context mtime\n    alt context modified since LastMtime\n        Hook->>State: Reset LastNudge = Count, update LastMtime\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: sinceNudge = Count - LastNudge\n    Hook->>Hook: PersistenceNudgeNeeded(Count, sinceNudge)?\n    alt threshold not reached\n        Hook->>State: Write state\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, nudge, vars)\n    Hook-->>CC: Nudge box (prompt count, time since last persist)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Update LastNudge = Count, write state</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-reminders","level":3,"title":"Check-Reminders","text":"<p>Per-prompt check for due reminders. No throttle.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-reminders\n    participant Store as Reminders store\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Store: ReadReminders()\n    alt load error\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter by due date (After <= today)\n    alt no due reminders\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, reminders, {list})\n    Hook-->>CC: Nudge box (reminder list + dismiss hints)\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-resources","level":3,"title":"Check-Resources","text":"<p>Checks system resources (memory, swap, disk, load). Fires on every prompt. No initialization required.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-resources\n    participant Sys as sysinfo\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: HookPreamble (parse input, check pause)\n    alt paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Sys: Collect snapshot (memory, swap, disk, load)\n    Hook->>Sys: Evaluate thresholds per metric\n    alt max severity < Danger\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Filter alerts to Danger level only\n    Hook->>Hook: Build alertMessages from danger alerts\n    Hook->>Tpl: LoadMessage(hook, alert, {alertMessages}, fallback)\n    Hook-->>CC: Nudge box (danger alerts)\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#check-version","level":3,"title":"Check-Version","text":"<p>Daily binary-vs-plugin version comparison with piggybacked key rotation check.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as check-version\n    participant State as .context/state/\n    participant Config as Binary + Plugin version\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Check daily throttle marker\n    alt throttled\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read binary version\n    alt dev build\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Config: Read plugin version\n    alt plugin version not found or parse error\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Hook: Compare major.minor\n    alt versions match\n        Hook->>State: Touch throttle\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, mismatch, {versions})\n    Hook-->>CC: Nudge box (version mismatch)\n    Hook->>Hook: NudgeAndRelay(message)\n    Hook->>State: Touch throttle\n    Hook->>Hook: CheckKeyAge() (piggybacked)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#heartbeat","level":3,"title":"Heartbeat","text":"<p>Silent per-prompt pulse. Tracks prompt count, context modification, and token usage. The agent never sees this hook's output.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as heartbeat\n    participant State as .context/state/\n    participant Ctx as .context/ files\n    participant Notify as Webhook + EventLog\n\n    CC->>Hook: stdin {session_id}\n    Hook->>Hook: Check initialized + HookPreamble\n    alt not initialized or paused\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>State: Increment heartbeat counter\n    Hook->>Ctx: Get latest context file mtime\n    Hook->>State: Compare with last recorded mtime\n    Hook->>State: Update mtime record\n    Hook->>State: Read session token info\n    Hook->>Notify: Send heartbeat notification\n    Hook->>Notify: Append to event log\n    Hook->>State: Write heartbeat log entry\n    Note over Hook: No stdout - agent never sees this</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#project-local-hooks","level":2,"title":"Project-Local Hooks","text":"<p>These hooks are configured in <code>settings.local.json</code> and are not shipped with ctx. They are specific to individual developer setups.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#block-dangerous-commands","level":3,"title":"Block-Dangerous-Commands","text":"<p>Lifecycle: PreToolUse. Matcher: <code>Bash</code></p> <p>Blocks dangerous shell patterns (sudo, git push, cp to bin). No initialization or pause checks: always active.</p> <pre><code>sequenceDiagram\n    participant CC as Claude Code\n    participant Hook as block-dangerous-commands\n    participant Tpl as Message Template\n\n    CC->>Hook: stdin {command, session_id}\n    Hook->>Hook: Extract command\n    alt command empty\n        Hook-->>CC: (silent exit)\n    end\n    Note over Hook: Cascade: first matching regex wins\n    Hook->>Hook: Test MidSudo regex\n    alt match\n        Hook->>Hook: variant = sudo\n    end\n    Hook->>Hook: Test MidGitPush regex (if no variant)\n    alt match\n        Hook->>Hook: variant = git-push\n    end\n    Hook->>Hook: Test CpMvToBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = cp-to-bin\n    end\n    Hook->>Hook: Test InstallToLocalBin regex (if no variant)\n    alt match\n        Hook->>Hook: variant = install-to-bin\n    end\n    alt no variant matched\n        Hook-->>CC: (silent exit)\n    end\n    Hook->>Tpl: LoadMessage(hook, variant, fallback)\n    Hook-->>CC: JSON {decision: BLOCK, reason}\n    Hook->>Hook: NudgeAndRelay(message)</code></pre>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#throttling-summary","level":2,"title":"Throttling Summary","text":"Hook Lifecycle Throttle Type Scope context-load-gate PreToolUse One-shot marker Per session block-non-path-ctx PreToolUse None Every match qa-reminder PreToolUse None Every git command specs-nudge PreToolUse None Every prompt post-commit PostToolUse None Every git commit check-task-completion PostToolUse Configurable interval Per session check-context-size UserPromptSubmit Adaptive counter Per session check-ceremonies UserPromptSubmit Daily marker Once per day check-freshness UserPromptSubmit Daily marker Once per day check-journal UserPromptSubmit Daily marker Once per day check-knowledge UserPromptSubmit Daily marker Once per day check-map-staleness UserPromptSubmit Daily marker Once per day check-memory-drift UserPromptSubmit Session tombstone Once per session check-persistence UserPromptSubmit Adaptive counter Per session check-reminders UserPromptSubmit None Every prompt check-resources UserPromptSubmit None Every prompt check-version UserPromptSubmit Daily marker Once per day heartbeat UserPromptSubmit None Every prompt block-dangerous-commands PreToolUse * None Every match <p>* Project-local hook (settings.local.json), not shipped with ctx.</p>","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hook-sequence-diagrams/#state-file-reference","level":2,"title":"State File Reference","text":"<p>All state files live in <code>.context/state/</code>.</p> File Pattern Hook Purpose <code>ctx-loaded-{session}</code> context-load-gate One-shot injection marker <code>ctx-paused-{session}</code> (all) Session pause marker <code>ctx-wrapped-up</code> check-context-size Suppress nudges after wrap-up (2h expiry) <code>freshness-checked</code> check-freshness Daily throttle <code>ceremony-reminded</code> check-ceremonies Daily throttle <code>journal-reminded</code> check-journal Daily throttle <code>knowledge-reminded</code> check-knowledge Daily throttle <code>map-staleness-reminded</code> check-map-staleness Daily throttle <code>version-checked</code> check-version Daily throttle <code>memory-drift-nudged-{session}</code> check-memory-drift Per-session tombstone <code>ctx-context-count-{session}</code> check-context-size Prompt counter <code>stats-{session}.jsonl</code> check-context-size Session stats log <code>persist-{session}</code> check-persistence Counter + mtime state <code>ctx-task-count-{session}</code> check-task-completion Prompt counter <code>heartbeat-count-{session}</code> heartbeat Prompt counter <code>heartbeat-mtime-{session}</code> heartbeat Last context mtime","path":["Hook Sequence Diagrams"],"tags":[]},{"location":"recipes/hub-cluster/","level":1,"title":"HA Cluster","text":"","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#ctx-hub-high-availability-cluster","level":1,"title":"<code>ctx</code> Hub: High-Availability Cluster","text":"<p>Run multiple hub nodes with Raft-based leader election for redundancy. Any follower can take over if the leader dies.</p> <p>This recipe assumes you've read the <code>ctx</code> Hub overview and the Multi-machine setup. HA only makes sense in the \"small trusted team\" story; a personal cross-project brain on one workstation does not need three Raft peers.</p> <p>Raft-Lite</p> <p><code>ctx</code> uses Raft only for leader election, not for data consensus. Entry replication happens via sequence-based gRPC sync on the append-only JSONL store. This is simpler than full Raft log replication and is possible because the store is append-only and clients are idempotent. The implication: a write accepted by the leader is durable on the leader immediately; followers catch up asynchronously. If the leader crashes between accepting a write and replicating it, that write can be lost. Do not use the hub as a bank ledger.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#topology","level":2,"title":"Topology","text":"<p>A minimum HA cluster is three nodes. Two is worse than one: it doubles failure probability without providing quorum.</p> <pre><code>         +-------------+\n         |  client(s)  |\n         +------+------+\n                |\n    +-----------+-----------+\n    |           |           |\n+---v---+   +---v---+   +---v---+\n| hub A |   | hub B |   | hub C |\n| :9900 |   | :9900 |   | :9900 |\n+-------+   +-------+   +-------+\n    ^           ^           ^\n    +-----------+-----------+\n        Raft (leader election)\n        gRPC (data sync)\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-1-bootstrap-the-first-node","level":2,"title":"Step 1: Bootstrap the First Node","text":"<pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n</code></pre> <p>The node starts a Raft election as soon as it sees its peers.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-2-start-the-other-nodes","level":2,"title":"Step 2: Start the Other Nodes","text":"<p>On <code>hub-b.lan</code>:</p> <pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-c.lan:9900\n</code></pre> <p>On <code>hub-c.lan</code>:</p> <pre><code>ctx hub start --daemon \\\n  --port 9900 \\\n  --peers hub-a.lan:9900,hub-b.lan:9900\n</code></pre> <p>After a few seconds, one node wins the election and becomes the leader. The other two are followers.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-3-verify-cluster-state","level":2,"title":"Step 3: Verify Cluster State","text":"<p>From any node:</p> <pre><code>ctx hub status\n</code></pre> <p>Expected output:</p> <pre><code>role:       leader\npeers:      hub-a.lan:9900 (leader)\n            hub-b.lan:9900 (follower, in-sync)\n            hub-c.lan:9900 (follower, in-sync)\nentries:    1248\nuptime:     3h42m\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#step-4-register-clients-with-failover-peers","level":2,"title":"Step 4: Register Clients with Failover Peers","text":"<p>The <code>ctx hub *</code> commands above run on the hub nodes themselves and don't need a project. The <code>ctx connection *</code> commands below are different: they live inside a project (the encrypted hub config is stored at <code>.context/.connect.enc</code>), so you have to tell <code>ctx</code> which project first.</p> <p>When registering a client, give it the full peer list:</p> <pre><code># In the project directory on the client:\neval \"$(ctx activate)\"\nctx connection register hub-a.lan:9900 \\\n  --token ctx_adm_... \\\n  --peers hub-b.lan:9900,hub-c.lan:9900\n</code></pre> <p>If the leader becomes unreachable, the client reconnects to the next peer. Followers redirect to the current leader, so writes always land on the right node.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#runtime-membership-changes","level":2,"title":"Runtime Membership Changes","text":"<p>Add a new peer without downtime:</p> <pre><code>ctx hub peer add hub-d.lan:9900\n</code></pre> <p>Remove a decommissioned peer:</p> <pre><code>ctx hub peer remove hub-c.lan:9900\n</code></pre>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#planned-maintenance","level":2,"title":"Planned Maintenance","text":"<p>Before taking a leader offline, hand off leadership:</p> <pre><code>ssh hub-a.lan 'ctx hub stepdown'\n</code></pre> <p><code>stepdown</code> triggers a new election among the remaining followers before the leader goes offline. In-flight clients briefly pause, then reconnect to the new leader.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#failure-modes-at-a-glance","level":2,"title":"Failure Modes at a Glance","text":"Event What happens Leader crashes New election; clients reconnect to new leader Follower crashes No write impact; catches up on restart Network partition (majority) Majority side keeps serving; minority read-only Network partition (split) No quorum; all nodes read-only Disk full on leader Writes rejected; read traffic continues <p>For the full list, see Hub failure modes.</p>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-cluster/#see-also","level":2,"title":"See Also","text":"<ul> <li>Multi-machine recipe: single-node   deployment</li> <li>Hub operations: backup and   maintenance</li> <li>Hub security model: TLS, tokens</li> </ul>","path":["Recipes","Hub","HA Cluster"],"tags":[]},{"location":"recipes/hub-getting-started/","level":1,"title":"Getting Started","text":"","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#ctx-hub-getting-started","level":1,"title":"<code>ctx</code> Hub: Getting Started","text":"<p>Stand up a single-node <code>ctx</code> Hub on localhost, register two projects, publish a decision from one, and see it appear in the other, all in under five minutes.</p> <p>Read This First</p> <p>If you haven't already, skim the <code>ctx</code> Hub overview. It explains the mental model, names the two user stories (personal vs small team), and (importantly) lists what the hub does not do. This recipe assumes you already know you want the feature.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-youll-get-out-of-this-recipe","level":2,"title":"What You'll Get out of This Recipe","text":"<p>By the end, you will have:</p> <ol> <li>A local hub process running on port <code>9900</code>.</li> <li>Two project directories both registered with the <code>ctx</code> Hub.</li> <li>A decision published from project <code>alpha</code> that appears    automatically in project <code>beta</code>'s <code>.context/hub/</code> and in    <code>ctx agent --include-hub</code> output.</li> </ol> <p>Concretely, the payoff this unlocks: a lesson you record in one project becomes visible to your agent the next time you open another project, without touching local files in the second project or opening another editor window.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#what-this-recipe-does-not-cover","level":2,"title":"What This Recipe Does Not Cover","text":"<ul> <li>Sharing <code>.context/journal/</code>, <code>.context/pad</code>, or any other   local state. The hub only fans out <code>decision</code>, <code>learning</code>,   <code>convention</code>, and <code>task</code> entries. Everything else stays local.</li> <li>Multi-user attribution. The hub identifies projects, not   people.</li> <li>Running over a LAN; see   Multi-machine setup.</li> <li>Redundancy; see HA cluster.</li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li><code>ctx</code> installed and on <code>PATH</code></li> <li>Two project directories, each already initialized with   <code>ctx init</code></li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-1-start-the-hub","level":2,"title":"Step 1: Start the Hub","text":"<p>In a dedicated terminal:</p> <pre><code>ctx hub start\n</code></pre> <p>On first run, the hub generates an admin token and prints it to stdout. Copy it; you'll need it for each project registration:</p> <pre><code>ctx hub listening on :9900\nadmin token: ctx_adm_7f3a1c2d...\ndata dir: ~/.ctx/hub-data/\n</code></pre> <p>The admin token is written to <code>~/.ctx/hub-data/admin.token</code> so you can recover it later. Treat it like a password.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-2-register-the-first-project","level":2,"title":"Step 2: Register the First Project","text":"<p><code>ctx hub start</code> above runs on the hub server and doesn't need a project. Step 2 is different: the encrypted hub config is stored inside a project at <code>.context/.connect.enc</code>, so you have to tell <code>ctx</code> which project first.</p> <pre><code>cd ~/projects/alpha\neval \"$(ctx activate)\"\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\n</code></pre> <p>This stores an encrypted connection config in <code>.context/.connect.enc</code>. The admin token is exchanged for a per-project client token; the admin token itself is never persisted in the project.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-3-choose-what-to-receive","level":2,"title":"Step 3: Choose What to Receive","text":"<pre><code>ctx connection subscribe decision learning convention\n</code></pre> <p>Only the entry types you subscribe to will be delivered by <code>sync</code> and <code>listen</code>.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-4-publish-a-decision","level":2,"title":"Step 4: Publish a Decision","text":"<p>Either use <code>ctx add --share</code> to write locally and push to the <code>ctx</code> Hub:</p> <pre><code>ctx decision add \"Use UTC timestamps everywhere\" --share \\\n  --context \"We had timezone drift between the API and journal\" \\\n  --rationale \"Single source of truth avoids conversion bugs\" \\\n  --consequence \"The UI does conversion at render time\"\n</code></pre> <p>Or publish an existing entry directly:</p> <pre><code>ctx connection publish decision \"Use UTC timestamps everywhere\"\n</code></pre>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-5-register-a-second-project-and-sync","level":2,"title":"Step 5: Register a Second Project and Sync","text":"<pre><code>cd ~/projects/beta\neval \"$(ctx activate)\"   # bind CTX_DIR for this project\nctx connection register localhost:9900 --token ctx_adm_7f3a1c2d...\nctx connection subscribe decision learning convention\nctx connection sync\n</code></pre> <p>The decision from <code>alpha</code> now appears in <code>~/projects/beta/.context/hub/decisions.md</code> with an origin tag and timestamp.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-6-watch-entries-arrive-live","level":2,"title":"Step 6: Watch Entries Arrive Live","text":"<p>Instead of re-running <code>sync</code>, stream new entries as they land:</p> <pre><code>ctx connection listen\n</code></pre> <p>Leave this running in a terminal; every <code>--share</code> publish from any registered project will appear in <code>.context/hub/</code> immediately.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#step-7-feed-shared-knowledge-into-the-agent","level":2,"title":"Step 7: Feed Shared Knowledge into the Agent","text":"<p>Once entries exist in <code>.context/hub/</code>, include them in the agent context packet:</p> <pre><code>ctx agent --include-hub\n</code></pre> <p>Shared entries are added as a dedicated tier in the budget-aware assembly, scored by recency and type relevance.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#auto-sync-on-session-start","level":2,"title":"Auto-Sync on Session Start","text":"<p>After <code>register</code>, the <code>check-hub-sync</code> hook pulls new entries at the start of each session (daily throttled). Most users never need to call <code>ctx connection sync</code> manually.</p>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-getting-started/#where-to-go-next","level":2,"title":"Where to Go Next","text":"<ul> <li>Multi-machine hub: run the hub   on a LAN host and connect from other workstations.</li> <li>HA cluster: Raft-based leader   election for high availability.</li> <li>Hub operations: daemon mode,   backup, log rotation, JSONL store layout.</li> <li>Hub security model: token   lifecycle, encryption at rest, threat model.</li> <li><code>ctx connect</code> reference and   <code>ctx hub start</code> reference.</li> </ul>","path":["Recipes","Hub","Getting Started"],"tags":[]},{"location":"recipes/hub-multi-machine/","level":1,"title":"Multi-Machine","text":"","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#ctx-hub-multi-machine","level":1,"title":"<code>ctx</code> Hub: Multi-Machine","text":"<p>Run the hub on a LAN host and connect from project directories on other workstations. This recipe is the Story 2 (\"small trusted team\") shape described in the <code>ctx</code> Hub overview; read that first if you haven't, especially the trust-model warnings.</p> <p>This recipe assumes you've already walked through Getting Started and understand what flows through the hub (decisions, learnings, conventions, tasks, not journals, scratchpad, or raw context files).</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#topology","level":2,"title":"Topology","text":"<pre><code>+------------------+        +------------------+\n| workstation A    |        | workstation B    |\n|  ~/projects/x    |        |  ~/projects/y    |\n|  ctx connection  |        |  ctx connection  |\n+---------+--------+        +---------+--------+\n          |                           |\n          +-----------+   +-----------+\n                      v   v\n              +-------------------+\n              | LAN host \"nexus\"  |\n              | ctx hub start     |\n              | --daemon          |\n              | :9900             |\n              +-------------------+\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-1-start-the-daemon-on-the-lan-host","level":2,"title":"Step 1: Start the Daemon on the LAN Host","text":"<p>On the machine that will hold the hub (call it <code>nexus</code>):</p> <pre><code>ctx hub start --daemon --port 9900\n</code></pre> <p>The daemon writes a PID file to <code>~/.ctx/hub-data/hub.pid</code>. Stop it later with:</p> <pre><code>ctx hub stop\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-2-firewall-and-port","level":2,"title":"Step 2: Firewall and Port","text":"<p>Open port <code>9900/tcp</code> on <code>nexus</code> to the LAN only. Never expose the hub to the public internet without a reverse proxy and TLS in front of it (see Hub security model).</p> <p>Typical LAN allowlist rules:</p> firewalldufwnftables <pre><code>sudo firewall-cmd --zone=internal \\\n  --add-port=9900/tcp --permanent\nsudo firewall-cmd --reload\n</code></pre> <pre><code>sudo ufw allow from 192.168.1.0/24 to any port 9900 proto tcp\n</code></pre> <pre><code>sudo nft add rule inet filter input ip saddr 192.168.1.0/24 \\\n  tcp dport 9900 accept\n</code></pre>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-3-retrieve-the-admin-token","level":2,"title":"Step 3: Retrieve the Admin Token","text":"<p>The daemon prints the admin token to stdout on first run. Running as a daemon, that output goes to the log instead:</p> <pre><code>cat ~/.ctx/hub-data/admin.token\n</code></pre> <p>Copy the token over a trusted channel (SSH, password manager, or an encrypted note). Do not email it or put it in chat.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-4-register-projects-from-each-workstation","level":2,"title":"Step 4: Register Projects from Each Workstation","text":"<p>The <code>ctx hub *</code> commands above run on the LAN host (<code>nexus</code>) and don't need a project. Step 4 is different: each workstation registers from inside a project (the encrypted hub config and the fan-out inbox both live under <code>.context/</code>), so you have to tell <code>ctx</code> which project first.</p> <p>On workstation <code>A</code>:</p> <pre><code>cd ~/projects/x\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n</code></pre> <p>On workstation <code>B</code>:</p> <pre><code>cd ~/projects/y\neval \"$(ctx activate)\"\nctx connection register nexus.local:9900 --token ctx_adm_...\nctx connection subscribe decision learning convention\n</code></pre> <p>Each registration exchanges the admin token for a per-project client token. Only the client token is persisted in <code>.context/.connect.enc</code>, encrypted with the same AES-256-GCM scheme <code>ctx</code> uses for notification credentials.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#step-5-verify","level":2,"title":"Step 5: Verify","text":"<p>From either workstation:</p> <pre><code>ctx connection status\n</code></pre> <p>You should see the <code>ctx</code> Hub address, role (<code>leader</code> for single-node), subscription filters, and the sequence number you're synced to.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#tls-recommended","level":2,"title":"TLS (Recommended)","text":"<p>For anything beyond a trusted home LAN, terminate TLS in front of the hub. The hub speaks gRPC, so the reverse proxy must speak HTTP/2:</p> <pre><code>server {\n    listen 443 ssl http2;\n    server_name nexus.example.com;\n\n    ssl_certificate     /etc/letsencrypt/live/nexus.example.com/fullchain.pem;\n    ssl_certificate_key /etc/letsencrypt/live/nexus.example.com/privkey.pem;\n\n    location / {\n        grpc_pass grpc://127.0.0.1:9900;\n    }\n}\n</code></pre> <p>Point <code>ctx connection register</code> at the public hostname and port 443.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#handling-daemon-restarts","level":2,"title":"Handling Daemon Restarts","text":"<p>The hub is append-only JSONL, so restarts are safe. Clients keep their last-seen sequence in <code>.context/hub/.sync-state.json</code> and pick up exactly where they left off on the next <code>sync</code> or <code>listen</code> reconnect.</p>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-multi-machine/#see-also","level":2,"title":"See Also","text":"<ul> <li>HA cluster recipe: for redundancy</li> <li>Hub operations: backup, rotation</li> <li>Hub failure modes</li> <li>Hub security model</li> </ul>","path":["Recipes","Hub","Multi-Machine"],"tags":[]},{"location":"recipes/hub-overview/","level":1,"title":"Overview","text":"","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#ctx-hub-overview","level":1,"title":"<code>ctx</code> Hub: Overview","text":"<p>Start here before the other hub recipes. This page answers what the hub is, who it's for, why you'd run one, and, equally important, what it is not.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#mental-model-in-one-paragraph","level":2,"title":"Mental Model in One Paragraph","text":"<p>The hub is a fan-out channel for structured knowledge entries across projects. When you publish a decision, learning, convention, or task with <code>--share</code>, the hub stores it in an append-only log and delivers it to every other project subscribed to that type. The next time your agent loads context in any of those projects, shared entries can be included in the context packet alongside local ones.</p> <p>That's the whole feature. It is a project-to-project knowledge bus for a small, curated set of entry types. It is not a shared memory, a shared journal, or a multi-user database.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-flows-through-the-hub","level":2,"title":"What Flows through the Hub","text":"<p>Only four entry types:</p> Type What it is <code>decision</code> Architectural decisions with rationale <code>learning</code> Gotchas, lessons, surprising behaviors <code>convention</code> Coding patterns and standards <code>task</code> Work items worth sharing across projects <p>Each entry is an immutable record with a content blob, the publishing project's name as <code>Origin</code>, a timestamp, and a hub-assigned sequence number. Once published, entries are never rewritten.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#what-does-not-flow-through-the-hub","level":2,"title":"What Does Not Flow through the Hub","text":"<p>This is the part new users get wrong most often:</p> <ul> <li>Session journals (<code>~/.claude/</code> logs, <code>.context/journal/</code>)   stay local. The hub does not sync your AI session history.</li> <li>Scratchpad (<code>.context/pad</code>) stays local. Encrypted notes   never leave the machine they were written on.</li> <li>Local context files as a whole (<code>TASKS.md</code>,   <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>) are not   mirrored wholesale. Only entries you explicitly <code>--share</code>, or   publish later with <code>ctx connection publish</code>, cross the boundary.</li> <li>Anything under <code>.context/</code> that isn't one of the four entry   types above. Configuration, state, logs, memory, journal   metadata: all local.</li> </ul> <p>If you were expecting \"now my agent in project B can see everything my agent did in project A,\" that's not this feature. Local session density still lives on the local machine.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#two-user-stories","level":2,"title":"Two User Stories","text":"<p>The hub makes sense in two different shapes. Pick the one that matches your situation; the mechanics are identical but the trust model and threat surface are very different.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-1-personal-cross-project-brain","level":3,"title":"Story 1: Personal Cross-Project Brain","text":"<p>One developer, many projects, one hub, usually on localhost.</p> <p>You're working across several projects on the same machine (or a handful of machines you own). You want a lesson learned debugging project A to show up when you open project B a week later, without re-discovering it. You want a convention you codified in one project to be visible as-you-type in another.</p> <p>Concrete payoff:</p> <ul> <li><code>ctx learning add --share \"...\"</code> in project A →   <code>ctx agent --include-hub</code> in project B shows that learning   in the next context packet.</li> <li>A decision recorded in your personal \"dotfiles\" project is   instantly visible to every other project on your workstation.</li> <li>Cross-project conventions (e.g., \"use UTC timestamps   everywhere\") live in one place and propagate.</li> </ul> <p>Trust model: high, because you trust every participant since every participant is you. Run the hub on localhost or on your own LAN, use the default single-node setup, don't worry about TLS.</p> <p>Start here: Getting Started for the one-time setup, then Personal cross-project brain for the day-to-day workflow.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#story-2-small-trusted-team","level":3,"title":"Story 2: Small Trusted Team","text":"<p>A few teammates, projects they each own, one hub on a LAN host they all trust.</p> <p>Your team has a handful of services and you want a shared \"things we've learned the hard way\" stream. Someone on the platform team records a convention about timestamp handling; everyone else's agents see it the next session. An on-call engineer records a learning from a 3 AM incident; the rest of the team inherits the lesson without needing to read the postmortem.</p> <p>Concrete payoff:</p> <ul> <li>Team conventions propagate without needing a wiki or chat.</li> <li>Lessons from one team member become available to everyone   else's agent context packets automatically.</li> <li>Cross-project decisions (shared libraries, deployment   patterns, naming rules) live in a single log the whole team   reads.</li> </ul> <p>Trust model: the hub assumes everyone holding a client token is friendly. There is no per-user attribution you can rely on, <code>Origin</code> is self-asserted by the publishing client, and there is no read ACL beyond the subscription filter. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.</p> <p>Operational shape: run the hub on a LAN host (or a three-node HA cluster for redundancy), put TLS in front of it for anything beyond a home LAN, distribute client tokens over a trusted channel.</p> <p>Start here: Multi-machine setup for the deployment, Team knowledge bus for the day-to-day team workflow, then HA cluster if you need redundancy.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#identity-projects-not-users","level":2,"title":"Identity: Projects, Not Users","text":"<p>The hub has no concept of users. Its unit of identity is the project. <code>ctx connection register</code> binds a hub token to a project directory, not to a person. Two developers working on the same project share either:</p> <ul> <li>The same <code>.connect.enc</code>, copied between machines over a   trusted channel, or</li> <li>Different project names (<code>alpha@laptop-a</code>,   <code>alpha@laptop-b</code>), because the hub rejects duplicate   registrations of the same project name.</li> </ul> <p>Either works; neither gives you per-human attribution. If you need \"who wrote this,\" the hub is the wrong tool.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#when-not-to-use-it","level":2,"title":"When Not to Use It","text":"<ul> <li>Solo, single-project work. Local <code>.context/</code> files are   enough. The hub adds operational surface for no payoff.</li> <li>Untrusted participants. The hub assumes everyone with a   client token is friendly. It is not hardened against hostile   insiders or compromised tokens.</li> <li>Compliance-sensitive environments. There is no audit   trail that can prove who published what, only which   project published what, and <code>Origin</code> is self-asserted.</li> <li>Secrets or PII. Entry content is stored plaintext on the   hub and fanned out to every subscribed client. Don't publish   anything you wouldn't paste in a team chat.</li> <li>Wholesale journal sharing. See \"what does not flow\"   above. If that's what you want, this feature won't provide   it. Talk to us in the issue tracker about what would.</li> </ul>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#how-entries-reach-your-agent","level":2,"title":"How Entries Reach Your Agent","text":"<p>Once a project is registered and subscribed, entries arrive by three mechanisms:</p> <ol> <li><code>ctx connection sync</code>: an on-demand pull, replays    everything new since the last sequence you saw.</li> <li><code>ctx connection listen</code>: a long-lived gRPC stream that    writes new entries to <code>.context/hub/</code> as they arrive.</li> <li><code>check-hub-sync</code> hook: runs at session start, daily    throttled, so most users never call <code>sync</code> manually.</li> </ol> <p>Once entries exist in <code>.context/hub/</code>, <code>ctx agent --include-hub</code> adds a dedicated tier to the budget-aware context packet, scored by recency and type relevance. That's the end of the pipeline.</p>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-overview/#where-to-go-next","level":2,"title":"Where to Go Next","text":"If you're… Read Trying it for yourself on one machine Getting Started A solo developer using the hub day-to-day Personal cross-project brain Setting up for a small team on a LAN Multi-machine setup A small team using the hub day-to-day Team knowledge bus Running redundant nodes HA cluster Operating a hub in production Operations Assessing the security posture Security model Debugging a hub in trouble Failure modes Just reading the commands <code>ctx connect</code>, <code>ctx serve</code>, <code>ctx hub</code>","path":["Recipes","Hub","Overview"],"tags":[]},{"location":"recipes/hub-personal/","level":1,"title":"Personal Cross-Project Brain","text":"","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#personal-cross-project-brain","level":1,"title":"Personal Cross-Project Brain","text":"<p>This recipe shows how one developer uses a <code>ctx</code> Hub across their own projects day-to-day, the \"Story 1\" shape from the Hub overview. You're not setting up infrastructure for a team; you're making a lesson you learned last Tuesday in project A automatically surface when you open project B next Thursday.</p> <p>Prerequisites: a working <code>ctx</code> Hub on localhost (see Getting Started for the roughly five-minute setup). This recipe assumes the hub is already running and you've registered at least two projects.</p> <p>Activate Each Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> after each <code>cd <project></code> (or wire it into direnv). The hub server (<code>ctx hub start</code>, etc.) runs on the server and doesn't need this; the commands in this recipe (<code>ctx add --share</code>, <code>ctx agent --include-hub</code>, <code>ctx connection ...</code>) live inside a project and do. If you skip the <code>eval</code>, they'll fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#the-core-loop","level":2,"title":"The Core Loop","text":"<p>Every day, the same three verbs matter:</p> <ol> <li>Record: notice a decision, learning, or    convention and capture it with <code>ctx add --share</code>.</li> <li>Subscribe: every project you care about is    subscribed to the types you want delivered (set once    with <code>ctx connection subscribe</code>).</li> <li>Load: your agent picks up shared entries on next    session start via the auto-sync hook, or explicitly    via <code>ctx agent --include-hub</code>.</li> </ol> <p>That's the whole workflow. The rest of this recipe fills in the concrete moments where each verb matters.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#a-realistic-day","level":2,"title":"A Realistic Day","text":"<p>You have three projects on your workstation:</p> <ul> <li><code>~/projects/api</code>, a Go service you're actively   developing</li> <li><code>~/projects/cli</code>, a companion CLI that consumes the   API</li> <li><code>~/projects/dotfiles</code>, your personal conventions and   cross-project learnings</li> </ul> <p>All three are registered with a single hub running on <code>localhost:9900</code> (started once at boot, or via a systemd user unit; see Hub operations). All three subscribe to <code>decision</code>, <code>learning</code>, and <code>convention</code>.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#0900-start-work-on-api","level":3,"title":"09:00 - Start Work on <code>api</code>","text":"<p>You <code>cd ~/projects/api</code> and start a Claude Code session. Behind the scenes, the plugin's <code>PreToolUse</code> hook calls <code>ctx agent --budget 8000 --include-hub</code> before the first tool call. Agent loads:</p> <ul> <li>Local <code>.context/</code> (TASKS, DECISIONS, LEARNINGS, etc.)</li> <li>Foundation steering files (always-inclusion)</li> <li>Everything you've shared from the other two projects</li> </ul> <p>So the \"use UTC timestamps everywhere\" decision you recorded in <code>dotfiles</code> last week is already in Claude's context for this session, without any manual <code>sync</code>.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1030-you-discover-a-gotcha","level":3,"title":"10:30 - You Discover a Gotcha","text":"<p>While debugging, you find that the API's retry loop silently drops the last error when the transport times out. This is the kind of thing you'd normally add to <code>LEARNINGS.md</code> in <code>api/</code>. But it's useful across every Go service you'll ever write, not just this one. So:</p> <pre><code>ctx learning add --share \\\n  --context \"Go http.Client retries mask the final error\" \\\n  --lesson  \"Transport timeouts don't surface as errors when the retry loop re-assigns err without wrapping. Check for context.DeadlineExceeded on the request context instead.\" \\\n  --application \"Any retry loop over http.Client.Do that uses a per-attempt timeout\"\n</code></pre> <p>The <code>--share</code> flag does two things:</p> <ol> <li>Writes the learning to <code>api/.context/LEARNINGS.md</code>    locally (as a normal <code>ctx learning add</code> would).</li> <li>Publishes the same entry to the <code>ctx</code> Hub, which stores it    in the append-only JSONL and fans it out to every    subscribed client.</li> </ol> <p>Within seconds, <code>cli/.context/hub/learnings.md</code> and <code>dotfiles/.context/hub/learnings.md</code> both contain a copy of this learning (the <code>ctx connection listen</code> daemon picks it up from the <code>ctx</code> Hub's Listen stream).</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1200-you-switch-to-cli","level":3,"title":"12:00 - You Switch to <code>cli</code>","text":"<p><code>cd ~/projects/cli</code>, open a new session. The agent packet for <code>cli</code> now includes the learning you just recorded in <code>api</code>, because <code>cli</code> is subscribed to <code>learning</code> and the entry has already been synced into <code>cli/.context/hub/learnings.md</code>.</p> <p>You don't have to re-explain the retry-loop gotcha. Claude already sees it.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1400-you-codify-a-convention","level":3,"title":"14:00 - You Codify a Convention","text":"<p>You've been writing error messages in <code>api</code> and decided you want a consistent pattern: lowercase start, no trailing period, single-sentence. This is a convention, not a decision; it applies to every Go project you touch. Record it in <code>dotfiles</code> (since that's your \"personal standards\" project), and share it:</p> <pre><code>cd ~/projects/dotfiles\nctx convention add --share \\\n  \"Error messages: lowercase start, no trailing period, single sentence (follows Go's stdlib style)\"\n</code></pre> <p>The convention lands in <code>dotfiles/CONVENTIONS.md</code> locally and fans out to <code>api</code> and <code>cli</code> via the hub. The next Claude Code session in either project gets the convention injected into the steering-adjacent slot of the agent packet.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#1630-end-of-day","level":3,"title":"16:30 - End of Day","text":"<p>You didn't run <code>ctx connection sync</code> once. You didn't <code>git push</code> anything between projects. You didn't remember to tell your agent about the retry-loop gotcha in the new project. The hub did all of it for you.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-the-workflow-actually-looks-like","level":2,"title":"What the Workflow Actually Looks Like","text":"<p>Stripped of prose, the day's commands were:</p> <pre><code># Morning: nothing. Agent loads --include-hub automatically.\n\n# Mid-morning: record a learning that should cross projects\nctx learning add --share \\\n  --context \"...\" --lesson \"...\" --application \"...\"\n\n# Afternoon: codify a convention in the \"standards\" project\nctx convention add --share \"...\"\n\n# Evening: nothing. Everything's already propagated.\n</code></pre> <p>The hub is passive infrastructure. You never talk to it directly; you talk through it by using <code>--share</code> on commands you were already running.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#tips-for-solo-use","level":2,"title":"Tips for Solo Use","text":"<p>Pick a \"standards\" project. One of your projects should play the role of \"canonical source for rules you want everywhere.\" Your dotfiles, a personal scratch repo, or a dedicated <code>ctx-standards</code> project all work. Record cross-cutting conventions there and let the hub propagate them to everything else.</p> <p>Subscribe to <code>task</code> only if you want cross-project todos. The four subscribable types are <code>decision</code>, <code>learning</code>, <code>convention</code>, <code>task</code>. Tasks are usually project-local; subscribing makes every hub-shared task from every project show up in every other project's agent packet. That's probably not what you want. Skip <code>task</code> in <code>ctx connection subscribe</code> unless you have a specific reason.</p> <p>Run the hub as a user-level daemon so you don't have to remember to start it. On Linux with systemd:</p> <pre><code># ~/.config/systemd/user/ctx-hub.service\n[Unit]\nDescription=ctx Hub (personal)\n\n[Service]\nType=simple\nExecStart=/usr/local/bin/ctx hub start\nRestart=on-failure\n\n[Install]\nWantedBy=default.target\n</code></pre> <pre><code>systemctl --user enable --now ctx-hub.service\n</code></pre> <p>Don't overthink subscription filters. For personal use, subscribe every project to all four types at first (or three, if you skip <code>task</code>). Tune later if the context packets get noisy.</p> <p>Local storage is fine; no TLS needed. The hub runs on localhost. No one else is on the network. Skip the TLS setup from the Multi-machine recipe; it's relevant when the hub is on a LAN host serving multiple workstations, not when it's a personal daemon.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"<p>Not a setup guide. For the one-time hub install and project registration, use Getting Started.</p> <p>Not a team guide. If you're sharing across humans, not just across your own projects, read Team knowledge bus instead; the trust model and operational concerns are different.</p> <p>Not production operations. For backup, log rotation, failure recovery, and HA, see Hub operations and Hub failure modes.</p>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-personal/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hub overview: when to use the Hub   and when not to.</li> <li>Team knowledge bus: the multi-human   companion recipe.</li> <li><code>ctx connect</code>: the client-side   commands used above (<code>subscribe</code>, <code>publish</code>, <code>sync</code>,   <code>listen</code>, <code>status</code>).</li> <li><code>ctx add</code>: the <code>--share</code> flag   reference.</li> <li><code>ctx hub</code>: operator commands for   starting, stopping, and inspecting the hub.</li> </ul>","path":["Recipes","Hub","Personal Cross-Project Brain"],"tags":[]},{"location":"recipes/hub-team/","level":1,"title":"Team Knowledge Bus","text":"","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#team-knowledge-bus","level":1,"title":"Team Knowledge Bus","text":"<p>This recipe shows how a small trusted team uses a <code>ctx</code> Hub as a shared knowledge bus, the \"Story 2\" shape from the Hub overview. You're not building a wiki, you're not replacing your issue tracker, and you're not running a multi-tenant service. You're connecting 3-10 developers who trust each other so that lessons, decisions, and conventions flow between them without ceremony.</p> <p>Prerequisites:</p> <ul> <li>A running <code>ctx</code> Hub on a LAN host or internal server   everyone on the team can reach. See   Multi-machine setup for the   deployment guide.</li> <li>Each team member has <code>ctx</code> installed and has   <code>ctx connection register</code>-ed their working projects with   the hub.</li> <li>Each project on each workstation has been activated for   the shell with <code>eval \"$(ctx activate)\"</code>. The hub server   (<code>ctx hub start</code>, etc.) doesn't need this, but the   client side (<code>ctx connection ...</code>, <code>ctx add --share</code>)   lives in a project and does. If you skip activation,   those client commands fail with <code>Error: no context   directory specified</code>. See   Activating a Context Directory.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#trust-model-read-this-first","level":2,"title":"Trust Model: Read This First","text":"<p>The hub assumes everyone holding a client token is friendly. There's no per-user attribution you can rely on, no read ACL beyond subscription filters, and <code>Origin</code> is self-asserted by the publishing client. Treat the hub like a team wiki: useful because everyone can write to it, not because it can prove who wrote what.</p> <p>If your team is:</p> <ul> <li>✅ 3-10 engineers, all known to each other, all   trusted with production access</li> <li>✅ On a single internal network or behind a VPN</li> <li>✅ Comfortable with \"the hub assumes friendly   participants\"</li> </ul> <p>…this recipe fits. If your team is:</p> <ul> <li>❌ Larger than ~15, with turnover</li> <li>❌ Includes contractors, untrusted agents, or   compromised-workstation concerns</li> <li>❌ Needs audit trails that prove who published what</li> <li>❌ Requires per-team-member isolation</li> </ul> <p>…you're in \"Story 3\" territory, which the hub does not support today. Use a wiki or a dedicated knowledge platform instead.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#the-teams-three-verbs","level":2,"title":"The Team's Three Verbs","text":"<p>Everyone on the team does three things, same as in the personal recipe, but with different social expectations:</p> <ol> <li>Record: when you learn something that would save    a teammate time, capture it with <code>ctx add --share</code>.</li> <li>Subscribe: every engineer's project directories    subscribe to the types the team cares about.</li> <li>Load: agents pick up shared entries automatically    via the auto-sync hook and the <code>--include-hub</code> flag    in the PreToolUse hook pipeline.</li> </ol> <p>The operational shape is identical to solo use. What's different is the culture around publishing: when do you <code>--share</code>, and what belongs on the hub vs. in your local <code>.context/</code>.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-goes-on-the-hub-team-rules-of-thumb","level":2,"title":"What Goes on the Hub (Team Rules of Thumb)","text":"<p>Share it if it's true for more than one person. The central question: \"would the next teammate who hits this problem save time if they already knew this?\" If yes, <code>--share</code>. If no, record it locally and move on.</p> <p>Decisions:</p> <ul> <li>✅ Cross-service decisions (database choice, auth   model, deployment pattern, monitoring stack).</li> <li>✅ Policy decisions that apply to all services   (naming, API versioning, error-message format).</li> <li>❌ Internal implementation decisions inside a single   service (\"chose a map over a slice here because lookups   dominate\").</li> <li>❌ One-off tactical calls for a specific PR.</li> </ul> <p>Learnings:</p> <ul> <li>✅ Gotchas, surprising behavior, flaky infrastructure   quirks, anything you'd tell a teammate over coffee   with \"watch out for X\".</li> <li>✅ Lessons from incidents, right after the postmortem   is the highest-value time to share.</li> <li>❌ Internal debugging notes that only make sense with   context from your current branch.</li> </ul> <p>Conventions:</p> <ul> <li>✅ Repo layout, commit message format, pre-commit   hooks, review expectations.</li> <li>✅ Language-level style decisions that apply across   services.</li> <li>❌ Per-service idioms (\"in <code>billing/</code> we prefer…\").</li> </ul> <p>Tasks: almost always project-local. Don't subscribe to <code>task</code> unless the team has a specific reason (e.g., a cross-cutting migration you want visible everywhere).</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#a-realistic-week","level":2,"title":"A Realistic Week","text":"<p>Monday, 3 AM incident, shared learning</p> <p>On-call engineer Alice gets paged: the payment service starts returning 500s after a dependency update. After an hour she finds the culprit: a breaking change in a transitive gRPC dep that only manifests under high concurrency. Postmortem on Tuesday, but right now she records the learning:</p> <pre><code>ctx learning add --share \\\n  --context \"Payment service 3 AM incident, 2026-04-03\" \\\n  --lesson  \"grpc-go v1.62+ changes DialContext behavior under high \\\n  concurrency: connections from a single channel can deadlock if the \\\n  server emits GOAWAY mid-stream. Symptom: 500 errors cluster in \\\n  30s bursts, no error in grpc client logs.\" \\\n  --application \"Any service on grpc-go. Pin to v1.61 or patch with \\\n  keepalive: https://github.com/grpc/grpc-go/issues/...\" \n</code></pre> <p>By Tuesday morning, every other engineer's agent context packet contains this learning. When Bob starts work on the <code>ledger</code> service (which also uses grpc-go), his Claude Code session already knows about the gotcha without Bob having to read the incident channel.</p> <p>Wednesday, cross-service decision</p> <p>The team agrees on a new pattern for API versioning: header-based instead of URL-based. Platform lead Carol records the decision:</p> <pre><code>ctx decision add --share \\\n  --context \"Need consistent API versioning across all 6 services. \\\n  Current URL-based /v1/ isn't working for gradual rollouts.\" \\\n  --rationale \"Header-based versioning lets us route by header at the \\\n  edge, which makes canary rollouts trivial. URL-based versioning \\\n  forces clients to update their paths.\" \\\n  --consequence \"All new endpoints use X-API-Version header. \\\n  Existing /v1/ endpoints stay. Deprecation schedule in q3.\" \\\n  \"Use header-based API versioning for new endpoints\"\n</code></pre> <p>Every engineer's next session knows about this decision automatically. When Dave starts adding endpoints to the <code>inventory</code> service on Thursday, Claude already prompts him for the header pattern instead of defaulting to <code>/v1/</code>.</p> <p>Friday, convention drift caught at review</p> <p>Dave notices that his PR auto-formatted some error messages to end with periods. He recalls the team convention is \"no trailing period\" but can't remember where it was documented. He runs <code>ctx connection status</code>, sees the hub is healthy, greps his local <code>.context/hub/conventions.md</code>, and finds:</p> <pre><code>## [2026-03-12] Error message format\nLowercase start, no trailing period, single sentence.\n</code></pre> <p>He fixes the PR. No lookup on the wiki, no question in chat, no context-switch penalty.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#workflow-tips-for-teams","level":2,"title":"Workflow Tips for Teams","text":"<p>Designate a \"champion\" for decisions. The team lead or platform engineer should be the person who explicitly <code>--share</code>s cross-cutting decisions. Other team members share learnings freely but should ask \"should this be a decision?\" in review before <code>--share</code>ing a decision. This keeps the decision stream signal-rich.</p> <p>Publish postmortem learnings immediately, not after the meeting. The postmortem itself is a document; the actionable rules that come out of it belong on the hub, and they should land within an hour of the incident. \"Share fast, edit later\" is the rule.</p> <p>Delete noisy entries, don't tolerate them. The hub is append-only, but the <code>.context/hub/</code> mirror on each client is just Markdown. If a shared learning turns out to be wrong or obsolete, remove it from local mirrors and stop the hub daemon to truncate <code>entries.jsonl</code> (see Hub operations). Noisy shared feeds lose trust fast.</p> <p>Don't subscribe every project to every type. For backend engineers, subscribing to <code>decision + learning + convention</code> is usually right. For platform or DevOps projects, adding <code>task</code> makes sense. For a prototype or experiment project, subscribing only to <code>convention</code> might be enough.</p> <p>Run a single hub, not one per team. If two teams need to share knowledge, they should share a hub. Splitting hubs by team creates silos, which is often exactly the thing you were trying to solve.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#operational-concerns","level":2,"title":"Operational Concerns","text":"<p>The team recipe assumes someone owns the hub host. That person (or a small group) is responsible for:</p> <ul> <li>Uptime: the hub is infrastructure; treat it like   any other internal service you run. See   Hub operations.</li> <li>Backups: <code>entries.jsonl</code> is the source of truth.   Snapshot it to the same backup tier as your other   internal data.</li> <li>Upgrades: cadence the team agrees on. Major   upgrades may require everyone to re-register, so do   them at natural breaks.</li> <li>Failures: see   Hub failure modes   for the standard oncall playbook.</li> </ul> <p>Optional but recommended: run a 3-node Raft cluster so the hub survives individual node failures. See HA cluster. For teams under 10 people, a single-node hub with daily backups is usually fine.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#token-management","level":2,"title":"Token Management","text":"<p>Every team member has a client token stored in their <code>.context/.connect.enc</code>. Rules of thumb:</p> <ul> <li>One token per engineer per project. Not one token   per team; not one shared token. Each engineer   registers each of their working projects separately.</li> <li>Token compromise = revoke immediately. When an   engineer leaves, their tokens should be removed from   <code>clients.json</code> on the hub. This is a manual operation   today; see Hub security for the   revocation steps.</li> <li>No checked-in tokens. <code>.context/.connect.enc</code> is   encrypted with the local machine key, but don't push   it to shared repos; it's per-workstation.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#what-this-recipe-is-not","level":2,"title":"What This Recipe Is Not","text":"<p>Not a wiki replacement. The hub is for structured entries, not prose. Put your architecture overviews, onboarding docs, and design discussions in a real wiki.</p> <p>Not an audit log. <code>Origin</code> on the hub is self-asserted. If compliance requires provenance, the hub is the wrong tool.</p> <p>Not a ticket system. Task sharing works, but mature teams already have Jira/Linear/Github Issues. Don't try to replace those with hub tasks; use the hub for lightweight cross-project todos that your existing tracker doesn't capture well.</p> <p>Not a production service for end users. This is internal team infrastructure. Do not expose the hub to customers, partners, or the open internet.</p>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/hub-team/#see-also","level":2,"title":"See Also","text":"<ul> <li>Hub overview: when to use the   hub and when not to.</li> <li>Personal cross-project brain:   the single-developer companion recipe.</li> <li>Multi-machine setup:   standing up the hub on a LAN host.</li> <li>HA cluster: optional redundancy   for larger teams.</li> <li>Hub operations: backup,   rotation, monitoring.</li> <li>Hub security: threat model   and hardening checklist.</li> </ul>","path":["Recipes","Hub","Team Knowledge Bus"],"tags":[]},{"location":"recipes/import-plans/","level":1,"title":"Importing Claude Code Plans","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code plan files (<code>~/.claude/plans/*.md</code>) are ephemeral: They have structured context, approach, and file lists, but they're orphaned after the session ends. The filenames are UUIDs, so you can't tell what's in them without opening each one.</p> <p>How do you turn a useful plan into a permanent project spec?</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tldr","level":2,"title":"TL;DR","text":"<pre><code>You: /ctx-plan-import\nAgent: [lists plans with dates and titles]\n       1. 2026-02-28  Add authentication middleware\n       2. 2026-02-27  Refactor database connection pool\nYou: \"import 1\"\nAgent: [copies to specs/add-authentication-middleware.md]\n</code></pre> <p>Plans are copied (not moved) to <code>specs/</code>, slugified by their H1 heading.</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-plan-import</code> Skill List, filter, and import plan files to specs <code>/ctx-task-add</code> Skill Optionally add a task referencing the spec","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-1-list-available-plans","level":3,"title":"Step 1: List Available Plans","text":"<p>Invoke the skill and it lists plans with modification dates and titles:</p> <pre><code>You: /ctx-plan-import\n\nAgent: Found 3 plan files:\n         1. 2026-02-28  Add authentication middleware\n         2. 2026-02-27  Refactor database connection pool\n         3. 2026-02-25  Import plans skill\n       Which plans would you like to import?\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-2-filter-optional","level":3,"title":"Step 2: Filter (Optional)","text":"<p>You can narrow the list with arguments:</p> Argument Effect <code>--today</code> Only plans modified today <code>--since YYYY-MM-DD</code> Only plans modified on or after the date <code>--all</code> Import everything without prompting (none) Interactive selection <pre><code>You: /ctx-plan-import --today\nYou: /ctx-plan-import --since 2026-02-27\nYou: /ctx-plan-import --all\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-3-select-and-import","level":3,"title":"Step 3: Select and Import","text":"<p>Pick one or more plans by number:</p> <pre><code>You: \"import 1 and 3\"\n\nAgent: Imported 2 plan(s):\n         ~/.claude/plans/abc123.md -> specs/add-authentication-middleware.md\n         ~/.claude/plans/ghi789.md -> specs/import-plans-skill.md\n       Want me to add tasks referencing these specs?\n</code></pre> <p>The agent reads the H1 heading from each plan and slugifies it for the filename. If a plan has no H1 heading, the original filename (minus extension) is used as the slug.</p>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#step-4-add-follow-up-tasks-optional","level":3,"title":"Step 4: Add Follow-Up Tasks (Optional)","text":"<p>If you say yes, the agent creates tasks in <code>TASKS.md</code> that reference the imported specs:</p> <pre><code>You: \"yes, add tasks\"\n\nAgent: [runs /ctx-task-add for each spec]\n       Added:\n         - [ ] Implement authentication middleware (spec: specs/add-authentication-middleware.md)\n         - [ ] Import plans skill (spec: specs/import-plans-skill.md)\n</code></pre>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember the exact skill name:</p> You say What happens \"import my plans\" <code>/ctx-plan-import</code> (interactive) \"save today's plans as specs\" <code>/ctx-plan-import --today</code> \"import all plans from this week\" <code>/ctx-plan-import --since ...</code> \"turn that plan into a spec\" <code>/ctx-plan-import</code> (filtered)","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#tips","level":2,"title":"Tips","text":"<ul> <li>Plans are copied, not moved: The originals stay in <code>~/.claude/plans/</code>.   Claude Code manages that directory; <code>ctx</code> doesn't delete from it.</li> <li>Conflict handling: If <code>specs/{slug}.md</code> already exists, the agent   asks whether to overwrite or pick a different name.</li> <li>Specs are project memory: Once imported, specs are tracked in git   and available to future sessions. Reference them from <code>TASKS.md</code> phase   headers with <code>Spec: specs/slug.md</code>.</li> <li>Pair with <code>/ctx-implement</code>: After importing a plan as a spec, use   <code>/ctx-implement</code> to execute it step-by-step with verification.</li> </ul>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/import-plans/#see-also","level":2,"title":"See Also","text":"<ul> <li>Skills Reference: /ctx-plan-import:   full skill description</li> <li>The Complete Session: where plan import fits   in the session flow</li> <li>Tracking Work Across Sessions: managing tasks   that reference imported specs</li> </ul>","path":["Recipes","Knowledge and Tasks","Importing Claude Code Plans"],"tags":[]},{"location":"recipes/knowledge-capture/","level":1,"title":"Persisting Decisions, Learnings, and Conventions","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-problem","level":2,"title":"The Problem","text":"<p>You debug a subtle issue, discover the root cause, and move on.</p> <p>Three weeks later, a different session hits the same issue. The knowledge existed briefly in one session's memory but was never written down.</p> <p>Architectural decisions suffer the same fate: you weigh trade-offs, pick an approach, and six sessions later the AI suggests the alternative you already rejected.</p> <p>How do you make sure important context survives across sessions?</p> <p>Prefer Skills to Raw Commands</p> <p>Use <code>/ctx-decision-add</code> and <code>/ctx-learning-add</code> instead of raw <code>ctx add</code> commands. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx add ...</code> / <code>ctx reindex</code> / <code>ctx decision ...</code> / <code>ctx learning ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-reflect               # surface items worth persisting\n/ctx-decision-add \"Title\"  # record with context/rationale/consequence\n/ctx-learning-add \"Title\"  # record with context/lesson/application\n</code></pre> <p>Or just tell your agent: \"What have we learned this session?\"</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx decision add</code> Command Record an architectural decision <code>ctx learning add</code> Command Record a gotcha, tip, or lesson <code>ctx convention add</code> Command Record a coding pattern or standard <code>ctx reindex</code> Command Rebuild both quick-reference indices <code>ctx decision reindex</code> Command Rebuild the DECISIONS.md index <code>ctx learning reindex</code> Command Rebuild the LEARNINGS.md index <code>/ctx-decision-add</code> Skill AI-guided decision capture with validation <code>/ctx-learning-add</code> Skill AI-guided learning capture with validation <code>/ctx-convention-add</code> Skill AI-guided convention recording with placement <code>/ctx-reflect</code> Skill Surface items worth persisting at breakpoints","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#the-workflow","level":2,"title":"The Workflow","text":"<p>Decision, Learning, or Convention?</p> <ul> <li>If you chose between alternatives, it is a decision.</li> <li>If you discovered something surprising, it is a learning.</li> <li>If you are codifying a repeated pattern, it is a convention.</li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-1-understand-what-to-persist","level":3,"title":"Step 1: Understand What to Persist","text":"<p>Three context files serve different purposes:</p> <p>Decisions (<code>DECISIONS.md</code>) answer \"why is it this way?\" They record trade-offs between alternatives with structured fields: context, rationale, and consequences. Decisions prevent re-debating settled questions.</p> <p>Learnings (<code>LEARNINGS.md</code>) answer \"what did we discover the hard way?\" They record gotchas and debugging insights specific to this project with structured fields: context, lesson, and application. </p> <p>Learnings prevent repeating past mistakes.</p> <p>Conventions (<code>CONVENTIONS.md</code>) answer \"how do we do things here?\" They record patterns and standards. No structured fields required: just a name, a rule, and an example. Conventions keep code consistent across sessions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-2-record-decisions","level":3,"title":"Step 2: Record Decisions","text":"<p>Decisions have three structured fields: </p> <ul> <li>context, </li> <li>rationale, </li> <li>and consequences.</li> </ul> <p>The easiest way to record one is to tell your agent:</p> <pre><code>You: \"Save that as a decision: we picked file-based cooldown\n      tokens over env vars.\"\n\nAgent: \"Recorded in DECISIONS.md:\n  - Decision: Use file-based cooldown tokens instead of env vars\n  - Context: Hook subprocesses cannot persist env vars to parent shell\n  - Rationale: File tokens survive across processes. Simpler than\n    IPC. Cleanup is automatic via TTL.\n  - Consequence: Tombstone files accumulate in /tmp. Cannot share\n    state across machines.\"\n</code></pre> <p>The agent was present for the trade-off discussion. It fills in the structured fields from what it observed, which is hard to reconstruct later as a terminal one-liner.</p> <p>When multiple options were considered, the agent includes rejected alternatives in the rationale automatically.</p> <p>The <code>/ctx-decision-add</code> skill also guides you through the fields interactively. For quick decisions, it supports a Y-statement:</p> <p>\"In the context of [situation], facing [constraint], we decided for [choice] and against [alternatives], to achieve [benefit], accepting that [trade-off].\"</p> CLI Command for Scripting and Automation <p>When no agent is in the loop (CI pipelines, shell scripts, hooks), use the CLI directly with structured flags:</p> <pre><code>ctx decision add \"Use file-based cooldown tokens instead of env vars\" \\\n  --context \"Hook subprocesses cannot persist env vars to parent shell\" \\\n  --rationale \"File tokens survive across processes. Simpler than IPC. Cleanup is automatic via TTL.\" \\\n  --consequence \"Tombstone files accumulate in /tmp. Cannot share state across machines.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-3-record-learnings","level":3,"title":"Step 3: Record Learnings","text":"<p>Learnings have three structured fields: context, lesson, and application.</p> <p>Again, your agent is the best tool for this. It was there when you discovered the gotcha and can express it more completely than a backslash-escaped terminal command:</p> <pre><code>You: \"That subprocess thing was painful. Save it as a learning.\"\n\nAgent: \"Added to LEARNINGS.md:\n  - Learning: Claude Code hooks run in a subprocess\n  - Context: Set env var in PreToolUse hook, but it was not visible\n    in the main session\n  - Lesson: Hook scripts execute in a child process. Env changes do\n    not propagate to parent.\n  - Application: Use tombstone files for hook-to-session\n    communication. Never rely on hook env vars.\"\n</code></pre> <p>You said one sentence. The agent produced a structured entry with all three fields filled from the session context.</p> <p>That is the point: the agent has the full conversation and can be far more expressive than what you would type at a command prompt.</p> <p>The <code>/ctx-learning-add</code> skill applies three quality filters:</p> <ol> <li>Could someone Google this in 5 minutes?</li> <li>Is it specific to this codebase?</li> <li>Did it take real effort to discover?</li> </ol> <p>All three must pass.</p> <p>Learnings capture principles and heuristics, not code snippets.</p> CLI Command for Scripting and Automation <p>When no agent is in the loop:</p> <pre><code>ctx learning add \"Claude Code hooks run in a subprocess\" \\\n  --context \"Set env var in PreToolUse hook, but it was not visible in the main session\" \\\n  --lesson \"Hook scripts execute in a child process. Env changes do not propagate to parent.\" \\\n  --application \"Use tombstone files for hook-to-session communication. Never rely on hook env vars.\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-4-record-conventions","level":3,"title":"Step 4: Record Conventions","text":"<p>Conventions are simpler: just a name, a rule, and optionally a section.</p> <p>These are short enough that either approach works:</p> <pre><code>You: \"We've been using kebab-case for every CLI flag. Codify that.\"\n\nAgent: \"Added to CONVENTIONS.md under Naming:\n  'Use kebab-case for all CLI flag names.'\"\n</code></pre> <p>Or from the terminal:</p> <pre><code>ctx convention add \"Use kebab-case for all CLI flag names\" --section \"Naming\"\n</code></pre> <p>Conventions work best for rules that come up repeatedly. Codify a pattern the third time you see it, not the first.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-5-reindex-after-manual-edits","level":3,"title":"Step 5: Reindex After Manual Edits","text":"<p><code>DECISIONS.md</code> and <code>LEARNINGS.md</code> maintain a quick-reference index at the top: a compact table of date and title for each entry. The index updates automatically via <code>ctx add</code>, but falls out of sync after hand edits.</p> <pre><code>ctx reindex\n</code></pre> <p>This single command regenerates both indices. You can also reindex individually with <code>ctx decision reindex</code> or <code>ctx learning reindex</code>.</p> <p>Run reindex after any manual edit. The index lets AI tools scan all entries without reading the full file, which matters when token budgets are tight.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-6-use-ctx-reflect-to-surface-what-to-capture","level":3,"title":"Step 6: Use <code>/ctx-reflect</code> to Surface What to Capture","text":"<p>Keep It Conversational</p> <p><code>/ctx-reflect</code> is not the only way to trigger reflection.</p> <p>Agents trained on the <code>ctx</code> playbook naturally surface persist-worthy items at breakpoints, even without invoking the skill explicitly.</p> <p>A conversational prompt like \"anything worth saving?\" or \"let's wrap up\" can trigger the same review.</p> <p>The skill provides a structured checklist, but the behavior is available through natural conversation.</p> <p>At natural breakpoints (after completing a feature, fixing a bug, or before ending a session) use <code>/ctx-reflect</code> to identify items worth persisting.</p> <pre><code>/ctx-reflect\n</code></pre> <p>The skill walks through learnings, decisions, tasks, and session notes, skipping categories with nothing to report. The output includes specific commands for each suggested persist:</p> <pre><code>This session implemented file-based cooldown for `ctx agent` and\ndiscovered that hook subprocesses cannot set env vars in the parent.\n\nI'd suggest persisting:\n- **Learning**: Hook subprocesses cannot propagate env vars\n  `ctx learning add \"...\" --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Decision**: File-based cooldown tokens over env vars\n  `ctx decision add \"...\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n</code></pre> <p>The skill always asks before persisting.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#step-7-the-conversational-approach","level":3,"title":"Step 7: The Conversational Approach","text":"<p>The commands shown in Steps 2 to 4 are precise, but you rarely need to type them yourself. When working with an agent that has loaded the <code>ctx</code> playbook, natural language triggers the same operations.</p> <p>You ask, the agent acts:</p> You say What the agent does \"What have we learned this session?\" Reviews recent work, identifies learnings, offers to persist \"Save that as a decision\" Captures the trade-off you just discussed with structured fields \"Any gotchas worth remembering?\" Scans recent work for non-obvious lessons \"Record that convention\" Adds the pattern you just agreed on to <code>CONVENTIONS.md</code> \"Let's wrap up\" Runs through the reflect checklist conversationally <p>Example exchange:</p> <pre><code>You: \"What did we learn from that debugging session?\"\n\nAgent: \"We discovered three things worth persisting:\n  1. Hook subprocesses can't propagate env vars to the parent. We\n     spent 20 minutes on this before switching to tombstone files.\n  2. Tombstone files need a TTL or they accumulate in /tmp.\n  3. The cooldown logic must check file mtime, not just existence.\n  Want me to save these as learnings?\"\n\nYou: \"Yes, all of them.\"\n\nAgent: \"Done. Added all three to LEARNINGS.md with structured fields.\"\n</code></pre> <p>The agent can also be proactive without being asked. The playbook encourages agents to persist context at natural milestones.</p> <p>You may see:</p> <ul> <li>After fixing a tricky bug: \"I noticed this was a non-obvious issue.   Want me to save it as a learning?\"</li> <li>After choosing between approaches: \"We just made a trade-off between   file-based tokens and IPC. Should I record this as a decision?\"</li> <li>At session milestones: the agent checks what is worth persisting and offers a   brief summary.</li> </ul> <p>When agents run unattended (no human in the loop), they may persist directly:</p> <ul> <li>\"I've added the subprocess env var gotcha to <code>LEARNINGS.md</code> so we don't hit it again.\"</li> <li>\"Recorded the decision to use file-based tokens over env vars in <code>DECISIONS.md</code>.\"</li> <li>\"Marked the cooldown task done and added a follow-up for TTL cleanup.\"</li> </ul> <p>This is behavior by design.</p> <p>The playbook's self-check prompt, \"If this session ended right now, would the next session know what happened?\" drives agents to persist early and often rather than waiting for explicit instructions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#command-line-approach-scripting-and-automation","level":3,"title":"Command-Line Approach (Scripting and Automation)","text":"<pre><code># Decision: record the trade-off\nctx decision add \"Use PostgreSQL over SQLite\" \\\n  --context \"Need concurrent multi-user access\" \\\n  --rationale \"SQLite locks on writes; Postgres handles concurrency\" \\\n  --consequence \"Requires a database server; team needs Postgres training\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Learning: record the gotcha\nctx learning add \"SQL migrations must be idempotent\" \\\n  --context \"Deploy failed when migration ran twice after rollback\" \\\n  --lesson \"CREATE TABLE without IF NOT EXISTS fails on retry\" \\\n  --application \"Always use IF NOT EXISTS guards in migrations\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Convention: record the pattern\nctx convention add \"API handlers return structured errors\" --section \"API\"\n\n# Reindex after manual edits\nctx reindex\n\n# Reflect at breakpoints (from AI assistant)\n# /ctx-reflect\n</code></pre>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#conversational-approach-interactive-sessions","level":3,"title":"Conversational Approach (Interactive Sessions)","text":"<pre><code>You: \"We just picked Postgres over SQLite for the concurrency\n      reasons. Save that?\"\n\nAgent: \"Recorded. Added to DECISIONS.md:\n  - Decision: Use PostgreSQL over SQLite\n  - Context: Need concurrent multi-user access\n  - Rationale: SQLite locks on writes; Postgres handles concurrency\n  - Consequence: Requires a database server; team needs Postgres\n    training\"\n\nYou: \"Also that migration thing. The deploy blew up when the\n      migration ran twice.\"\n\nAgent: \"Got it. Added to LEARNINGS.md:\n  - Learning: SQL migrations must be idempotent\n  - Lesson: CREATE TABLE without IF NOT EXISTS fails on retry\n  - Application: Always use IF NOT EXISTS guards in migrations\"\n\nYou: \"Good. Anything else worth saving before we wrap up?\"\n\nAgent: \"We've been using structured error returns in every API\n  handler this session. Want me to codify that as a convention?\"\n\nYou: \"Do it.\"\n\nAgent: \"Done. Added 'API handlers return structured errors' to\n  CONVENTIONS.md under the API section.\"\n</code></pre> <p>Both approaches produce the same structured entries in the same context files.</p> <ul> <li>The conversational approach is the natural fit for interactive sessions; </li> <li>the CLI commands are better suited for scripts, hooks, and automation pipelines.</li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#tips","level":2,"title":"Tips","text":"<ul> <li>Record decisions at the moment of choice. The alternatives you considered and   the reasons you rejected them fade quickly. Capture trade-offs while they are   fresh.</li> <li>Learnings should fail the Gemini test. If someone could find it in a 5-minute   Gemini search, it does not belong in <code>LEARNINGS.md</code>.</li> <li>Conventions earn their place through repetition. Add a convention the third   time you see a pattern, not the first.</li> <li>Use <code>/ctx-reflect</code> at natural breakpoints. The checklist catches items you   might otherwise lose.</li> <li>Keep the entries self-contained. Each entry should make sense on its own. A   future session may load only one due to token budget constraints.</li> <li>Reindex after every hand edit. It takes less than a second. A stale index   causes AI tools to miss entries.</li> <li>Prefer the structured fields. The verbosity forces clarity. A decision without   a rationale is just a fact. A learning without an application is just a story.</li> <li>Talk to your agent, do not type commands. In interactive sessions, the   conversational approach is the recommended way to capture knowledge. Say   \"save that as a learning\" or \"any decisions worth recording?\" and let the   agent handle the structured fields. Reserve the CLI commands for scripting,   automation, and CI/CD pipelines where there is no agent in the loop.</li> <li>Trust the agent's proactive instincts. Agents trained on the <code>ctx</code> playbook will   offer to persist context at milestones. A brief \"want me to save this?\" is   cheaper than re-discovering the same lesson three sessions later.</li> <li> <p>Relax provenance per-project if <code>--session-id</code>, <code>--branch</code>, or <code>--commit</code>   are impractical (e.g., manual notes outside an AI session). Add to <code>.ctxrc</code>:</p> <pre><code>provenance_required:\n  session_id: false   # allow entries without --session-id\n  branch: true        # still require --branch\n  commit: true        # still require --commit\n</code></pre> <p>Default is all three required. Only human config relaxes: Agents cannot bypass, and that's by design.</p> </li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#next-up","level":2,"title":"Next Up","text":"<p>Tracking Work Across Sessions →: Add, prioritize, complete, and archive tasks across sessions.</p>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/knowledge-capture/#see-also","level":2,"title":"See Also","text":"<ul> <li>Tracking Work Across Sessions: managing the tasks that   decisions and learnings support</li> <li>The Complete Session: full session lifecycle including   reflection and context persistence</li> <li>Detecting and Fixing Drift: keeping knowledge files   accurate as the codebase evolves</li> <li>CLI Reference: full documentation for <code>ctx add</code>,   <code>ctx decision</code>, <code>ctx learning</code></li> <li>Context Files: format and conventions for <code>DECISIONS.md</code>,   <code>LEARNINGS.md</code>, and <code>CONVENTIONS.md</code></li> </ul>","path":["Recipes","Knowledge and Tasks","Persisting Decisions, Learnings, and Conventions"],"tags":[]},{"location":"recipes/memory-bridge/","level":1,"title":"Bridging Claude Code Auto Memory","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code maintains per-project auto memory at <code>~/.claude/projects/<slug>/memory/MEMORY.md</code>. This file is:</p> <ul> <li>Outside the repo - not version-controlled, not portable</li> <li>Machine-specific - tied to one <code>~/.claude/</code> directory</li> <li>Invisible to <code>ctx</code> - context loading and hooks don't read it</li> </ul> <p>Meanwhile, <code>ctx</code> maintains structured context files (DECISIONS.md, LEARNINGS.md, CONVENTIONS.md) that are git-tracked, portable, and token-budgeted - but Claude Code doesn't automatically write to them.</p> <p>The two systems hold complementary knowledge with no bridge between them.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx memory sync          # Mirror MEMORY.md into .context/memory/mirror.md\nctx memory status        # Check for drift\nctx memory diff          # See what changed since last sync\n</code></pre> <p>The <code>check-memory-drift</code> hook nudges automatically when MEMORY.md changes - you don't need to remember to sync manually.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx memory ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx memory sync</code> CLI command Copy MEMORY.md to mirror, archive previous <code>ctx memory status</code> CLI command Show drift, timestamps, line counts <code>ctx memory diff</code> CLI command Show changes since last sync <code>ctx memory import</code> CLI command Classify and promote entries to .context/ files <code>ctx memory publish</code> CLI command Push curated .context/ content to MEMORY.md <code>ctx memory unpublish</code> CLI command Remove published block from MEMORY.md <code>ctx system check-memory-drift</code> Hook Nudge when MEMORY.md has changed (once/session)","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#how-it-works","level":2,"title":"How It Works","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#discovery","level":3,"title":"Discovery","text":"<p>Claude Code encodes project paths as directory names under <code>~/.claude/projects/</code>. The encoding replaces <code>/</code> with <code>-</code> and prefixes with <code>-</code>:</p> <pre><code>/home/jose/WORKSPACE/ctx  →  ~/.claude/projects/-home-jose-WORKSPACE-ctx/\n</code></pre> <p><code>ctx memory</code> uses this encoding to locate MEMORY.md automatically from your project root - no configuration needed.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#mirroring","level":3,"title":"Mirroring","text":"<p>When you run <code>ctx memory sync</code>:</p> <ol> <li>The previous mirror is archived to <code>.context/memory/archive/mirror-<timestamp>.md</code></li> <li>MEMORY.md is copied to <code>.context/memory/mirror.md</code></li> <li>Sync state is updated in <code>.context/state/memory-import.json</code></li> </ol> <p>The mirror is git-tracked, so it travels with the project. Archives provide a fallback for projects that don't use git.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#drift-detection","level":3,"title":"Drift Detection","text":"<p>The <code>check-memory-drift</code> hook compares MEMORY.md's modification time against the mirror. When drift is detected, the agent sees:</p> <pre><code>┌─ Memory Drift ────────────────────────────────────────────────\n│ MEMORY.md has changed since last sync.\n│ Run: ctx memory sync\n│ Context: .context\n└────────────────────────────────────────────────────────────────\n</code></pre> <p>The nudge fires once per session to avoid noise.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#typical-workflow","level":2,"title":"Typical Workflow","text":"","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#at-session-start","level":3,"title":"At Session Start","text":"<p>If the hook fires a drift nudge, sync before diving into work:</p> <pre><code>ctx memory diff     # Review what changed\nctx memory sync     # Mirror the changes\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#periodic-check","level":3,"title":"Periodic Check","text":"<pre><code>ctx memory status\n# Memory Bridge Status\n#   Source:      ~/.claude/projects/.../memory/MEMORY.md\n#   Mirror:      .context/memory/mirror.md\n#   Last sync:   2026-03-05 14:30 (2 hours ago)\n#\n#   MEMORY.md:  47 lines\n#   Mirror:     32 lines\n#   Drift:      detected (source is newer)\n#   Archives:   3 snapshots in .context/memory/archive/\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#dry-run","level":3,"title":"Dry Run","text":"<p>Preview what sync would do without writing:</p> <pre><code>ctx memory sync --dry-run\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#storage-layout","level":2,"title":"Storage Layout","text":"<pre><code>.context/\n├── memory/\n│   ├── mirror.md                          # Raw copy of MEMORY.md (often git-tracked)\n│   └── archive/\n│       ├── mirror-2026-03-05-143022.md    # Timestamped pre-sync snapshots\n│       └── mirror-2026-03-04-220015.md\n├── state/\n│   └── memory-import.json                 # Sync tracking state\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#edge-cases","level":2,"title":"Edge Cases","text":"Scenario Behavior Auto memory not active <code>sync</code> exits 1 with message. <code>status</code> reports \"not active\". Hook skips silently. First sync (no mirror) Creates mirror without archiving. MEMORY.md is empty Syncs to empty mirror (valid). Not initialized Init guard rejects (same as all <code>ctx</code> commands).","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#importing-entries","level":2,"title":"Importing Entries","text":"<p>Once you've synced, you can classify and promote entries into structured <code>.context/</code> files:</p> <pre><code>ctx memory import --dry-run    # Preview classification\nctx memory import              # Actually promote entries\n</code></pre> <p>Each entry is classified by keyword heuristics:</p> Keywords Target <code>always use</code>, <code>prefer</code>, <code>never use</code>, <code>standard</code> CONVENTIONS.md <code>decided</code>, <code>chose</code>, <code>trade-off</code>, <code>approach</code> DECISIONS.md <code>gotcha</code>, <code>learned</code>, <code>watch out</code>, <code>bug</code>, <code>caveat</code> LEARNINGS.md <code>todo</code>, <code>need to</code>, <code>follow up</code> TASKS.md Everything else Skipped <p>Entries that don't match any pattern are skipped - they stay in the mirror for manual review. Deduplication (hash-based) prevents re-importing the same entry on subsequent runs.</p> <p>Review Before Importing</p> <p>Use <code>--dry-run</code> first. The heuristic classifier is deliberately simple - it may misclassify ambiguous entries. Review the plan, then import.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-workflow","level":3,"title":"Full Workflow","text":"<pre><code>ctx memory sync                # 1. Mirror MEMORY.md\nctx memory import --dry-run    # 2. Preview what would be imported\nctx memory import              # 3. Promote entries to .context/ files\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#publishing-context-to-memorymd","level":2,"title":"Publishing Context to <code>MEMORY.md</code>","text":"<p>Push curated <code>.context/</code> content back into MEMORY.md so Claude Code sees structured project context on session start - without needing hooks.</p> <pre><code>ctx memory publish --dry-run    # Preview what would be published\nctx memory publish              # Write to MEMORY.md\nctx memory publish --budget 40  # Tighter line budget\n</code></pre> <p>Published content is wrapped in markers:</p> <pre><code><!-- ctx:published -->\n# Project Context (managed by ctx)\n\n## Pending Tasks\n- [ ] Implement feature X\n...\n<!-- ctx:end -->\n</code></pre> <p>Rules:</p> <ul> <li><code>ctx</code> owns everything between the markers</li> <li>Claude owns everything outside the markers</li> <li><code>ctx memory import</code> reads only outside the markers</li> <li><code>ctx memory publish</code> replaces only inside the markers</li> </ul> <p>To remove the published block entirely:</p> <pre><code>ctx memory unpublish\n</code></pre> <p>Publish at Wrap-Up, Not on Commit</p> <p>The best time to publish is during session wrap-up, after persisting decisions and learnings. Never auto-publish - give yourself a chance to review what's going into MEMORY.md.</p>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/memory-bridge/#full-bidirectional-workflow","level":3,"title":"Full Bidirectional Workflow","text":"<pre><code>ctx memory sync                 # 1. Mirror MEMORY.md\nctx memory import --dry-run     # 2. Check what Claude wrote\nctx memory import               # 3. Promote entries to .context/\nctx memory publish --dry-run    # 4. Check what would be published\nctx memory publish              # 5. Push context to MEMORY.md\n</code></pre>","path":["Recipes","Knowledge and Tasks","Bridging Claude Code Auto Memory"],"tags":[]},{"location":"recipes/multi-tool-setup/","level":1,"title":"Setup Across AI Tools","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-problem","level":2,"title":"The Problem","text":"<p>You have installed <code>ctx</code> and want to set it up with your AI coding assistant so that context persists across sessions. Different tools have different integration depths. For example: </p> <ul> <li>Claude Code supports native hooks that load and save context automatically.</li> <li>Cursor injects context via its system prompt.</li> <li>Aider reads context files through its <code>--read</code> flag.</li> </ul> <p>This recipe walks through the complete setup for each tool, from initialization through verification, so you end up with a working memory layer regardless of which AI tool you use.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tldr","level":2,"title":"TL;DR","text":"<pre><code>cd your-project\nctx init                      # creates .context/\neval \"$(ctx activate)\"        # bind CTX_DIR for this shell\nsource <(ctx completion zsh)  # shell completion (or bash/fish)\n\n# ## Claude Code (automatic after plugin install) ##\nclaude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n\n# ## OpenCode ##\nctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n\n# ## Cursor / Aider / Copilot / Windsurf ##\nctx setup cursor # or: aider, copilot, windsurf\n\n# ## Companion tools (highly recommended) ##\nnpx gitnexus analyze          # code knowledge graph\n# Add Gemini Search MCP server for grounded web search\n</code></pre> <p>Activate the Project Once Per Shell</p> <p>Run <code>eval \"$(ctx activate)\"</code> after <code>ctx init</code>. The <code>ctx setup</code>, <code>ctx init</code>, and <code>ctx completion</code> commands work without it, but if you skip the <code>eval</code>, most others (<code>ctx agent</code>, <code>ctx load</code>, <code>ctx watch</code>, <code>ctx journal ...</code>) fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Create a <code>.ctxrc</code> in your project root to configure token budgets, context directory, drift thresholds, and more.</p> <p>Then start your AI tool and ask: \"Do you remember?\"</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx init</code> Create <code>.context/</code> directory, templates, and permissions <code>ctx setup</code> Generate integration configuration for a specific AI tool <code>ctx agent</code> Print a token-budgeted context packet for AI consumption <code>ctx load</code> Output assembled context in read order (for manual pasting) <code>ctx watch</code> Auto-apply context updates from AI output (non-native tools) <code>ctx completion</code> Generate shell autocompletion for bash, zsh, or fish <code>ctx journal import</code> Import sessions to editable journal Markdown","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-1-initialize-ctx","level":3,"title":"Step 1: Initialize <code>ctx</code>","text":"<p>Run <code>ctx init</code> in your project root. This creates the <code>.context/</code> directory with all template files and seeds <code>ctx</code> permissions in <code>settings.local.json</code>.</p> <pre><code>cd your-project\nctx init\n</code></pre> <p>This produces the following structure:</p> <pre><code>.context/\n  CONSTITUTION.md     # Hard rules the AI must never violate\n  TASKS.md            # Current and planned work\n  CONVENTIONS.md      # Code patterns and standards\n  ARCHITECTURE.md     # System overview\n  DECISIONS.md        # Architectural decisions with rationale\n  LEARNINGS.md        # Lessons learned, gotchas, tips\n  GLOSSARY.md         # Domain terms and abbreviations\n  AGENT_PLAYBOOK.md   # How AI tools should use this system\n</code></pre> <p>Using a Different <code>.context</code> Directory</p> <p>The <code>.context/</code> directory doesn't have to live inside your project. Point <code>ctx</code> to an external folder by exporting <code>CTX_DIR</code> (the only declaration channel).</p> <p>Useful when context must stay private while the code is public, or when you want to commit notes to a separate repo.</p> <p>Caveats (the recipe covers both with workarounds):</p> <ul> <li>Code-aware operations degrade silently. <code>ctx sync</code>, <code>ctx drift</code>,   and the memory-drift hook read the codebase from   <code>dirname(CTX_DIR)</code>. With an external <code>.context/</code>, that's the   context repo, not your code repo. They scan the wrong tree without   erroring. The recipe shows a symlink workaround that keeps both   healthy.</li> <li>One <code>.context/</code> per project, always. Sharing one directory   across multiple projects corrupts journals, state, and secrets.   For cross-project knowledge sharing (CONSTITUTION, CONVENTIONS,   ARCHITECTURE, etc.) use <code>ctx hub</code>, not a   shared <code>.context/</code>.</li> </ul> <p>See External Context for the full recipe and Configuration for the resolver details.</p> <p>For Claude Code, install the <code>ctx</code> plugin to get hooks and skills:</p> <pre><code>claude /plugin marketplace add ActiveMemory/ctx\nclaude /plugin install ctx@activememory-ctx\n</code></pre> <p>If you only need the core files (useful for lightweight setups), use the <code>--minimal</code> flag:</p> <pre><code>ctx init --minimal\n</code></pre> <p>This creates only <code>TASKS.md</code>, <code>DECISIONS.md</code>, and <code>CONSTITUTION.md</code>.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-2-generate-tool-specific-hooks","level":3,"title":"Step 2: Generate Tool-Specific Hooks","text":"<p>If you are using a tool other than Claude Code (which is configured automatically by <code>ctx init</code>), generate its integration configuration:</p> <pre><code># For Cursor\nctx setup cursor\n\n# For Aider\nctx setup aider\n\n# For GitHub Copilot\nctx setup copilot\n\n# For Windsurf\nctx setup windsurf\n</code></pre> <p>Each command prints the configuration you need. How you apply it depends on the tool.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#claude-code","level":4,"title":"Claude Code","text":"<p>No action needed. Just install <code>ctx</code> from the Marketplace as <code>ActiveMemory/ctx</code>.</p> <p>Claude Code Is a First-Class Citizen</p> <p>With the <code>ctx</code> plugin installed, Claude Code gets hooks and skills automatically. The <code>PreToolUse</code> hook runs <code>ctx agent --budget 4000</code> on every tool call (with a 10-minute cooldown so it only fires once per window).</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#opencode","level":4,"title":"OpenCode","text":"<p>Run the one-liner from the project root:</p> <pre><code>ctx setup opencode --write && ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>This deploys a lifecycle plugin, slash command skills, <code>AGENTS.md</code>, and registers the <code>ctx</code> MCP server globally. See <code>ctx</code> for OpenCode for full details.</p> <p>OpenCode Is a First-Class Citizen</p> <p>With the plugin installed, OpenCode gets lifecycle hooks and skills automatically. Context loads at session start, survives compaction, and persists at session end, with no manual steps needed.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#vs-code","level":4,"title":"VS Code","text":"<p>Install the <code>ctx</code> extension from the VS Code Marketplace (publisher: <code>activememory</code>). Then, from your project root:</p> <pre><code>ctx init && eval \"$(ctx activate)\"\n</code></pre> <p>Open Copilot Chat and type <code>@ctx /init</code> to verify. The extension auto-downloads the <code>ctx</code> CLI if it isn't on PATH. See <code>ctx</code> for VS Code for full details.</p> <p>VS Code Is a First-Class Citizen</p> <p>The extension carries its own runtime. No <code>ctx setup</code> step is needed. It registers a <code>@ctx</code> chat participant with 45 slash commands, automatic hooks (file save, git commit, <code>.context/</code> change, dependency-file edit), and a reminder status-bar indicator. Unlike embedded harnesses, the extension ships through its own pipeline to the VS Code Marketplace.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#cursor","level":4,"title":"Cursor","text":"<p>Add the system prompt snippet to <code>.cursor/settings.json</code>:</p> <pre><code>{\n  \"ai.systemPrompt\": \"Read .context/TASKS.md and .context/CONVENTIONS.md before responding. Follow rules in .context/CONSTITUTION.md.\"\n}\n</code></pre> <p>Context files appear in Cursor's file tree. You can also paste a context packet directly into chat:</p> <pre><code>ctx agent --budget 4000 | xclip    # Linux\nctx agent --budget 4000 | pbcopy   # macOS\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#aider","level":4,"title":"Aider","text":"<p>Create <code>.aider.conf.yml</code> so context files are loaded on every session:</p> <pre><code>read:\n  - .context/CONSTITUTION.md\n  - .context/TASKS.md\n  - .context/CONVENTIONS.md\n  - .context/DECISIONS.md\n</code></pre> <p>Then start Aider normally:</p> <pre><code>aider\n</code></pre> <p>Or specify files on the command line:</p> <pre><code>aider --read .context/TASKS.md --read .context/CONVENTIONS.md\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-3-set-up-shell-completion","level":3,"title":"Step 3: Set Up Shell Completion","text":"<p>Shell completion lets you tab-complete <code>ctx</code> subcommands and flags, which is especially useful while learning the CLI.</p> <pre><code># Bash (add to ~/.bashrc)\nsource <(ctx completion bash)\n\n# Zsh (add to ~/.zshrc)\nsource <(ctx completion zsh)\n\n# Fish\nctx completion fish > ~/.config/fish/completions/ctx.fish\n</code></pre> <p>After sourcing, typing <code>ctx a<TAB></code> completes to <code>ctx agent</code>, and <code>ctx journal <TAB></code> shows <code>list</code>, <code>show</code>, and <code>export</code>.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-4-verify-the-setup-works","level":3,"title":"Step 4: Verify the Setup Works","text":"<p>Start a fresh session in your AI tool and ask:</p> <p>\"Do you remember?\"</p> <p>A correctly configured tool responds with specific context: current tasks from <code>TASKS.md</code>, recent decisions, and previous session topics. It should not say \"I don't have memory\" or \"Let me search for files.\"</p> <p>This question checks the passive side of memory. A properly set-up agent is also proactive: it treats context maintenance as part of its job:</p> <ul> <li>After a debugging session, it offers to save a learning.</li> <li>After a trade-off discussion, it asks whether to record the decision.</li> <li>After completing a task, it suggests follow-up items.</li> </ul> <p>The \"do you remember?\" check verifies both halves: recall and responsibility.</p> <p>For example, after resolving a tricky bug, a proactive agent might say:</p> <pre><code>That Redis timeout issue was subtle. Want me to save this as a *learning*\nso we don't hit it again?\n</code></pre> <p>If you see behavior like this, the setup is working end to end.</p> <p>In Claude Code, you can also invoke the <code>/ctx-status</code> skill:</p> <pre><code>/ctx-status\n</code></pre> <p>This prints a summary of all context files, token counts, and recent activity, confirming that hooks are loading context.</p> <p>If context is not loading, check the basics:</p> Symptom Fix <code>ctx: command not found</code> Ensure <code>ctx</code> is in your PATH: <code>which ctx</code> Hook errors Verify plugin is installed: <code>claude /plugin list</code> Context not refreshing Cooldown may be active; wait 10 minutes or set <code>--cooldown 0</code>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-5-enable-watch-mode-for-non-native-tools","level":3,"title":"Step 5: Enable Watch Mode for Non-Native Tools","text":"<p>Tools like Aider, Copilot, and Windsurf do not support native hooks for saving context automatically. For these, run <code>ctx watch</code> alongside your AI tool.</p> <p>Pipe the AI tool's output through <code>ctx watch</code>:</p> <pre><code># Terminal 1: Run Aider with output logged\naider 2>&1 | tee /tmp/aider.log\n\n# Terminal 2: Watch the log for context updates\nctx watch --log /tmp/aider.log\n</code></pre> <p>Or for any generic tool:</p> <pre><code>your-ai-tool 2>&1 | tee /tmp/ai.log &\nctx watch --log /tmp/ai.log\n</code></pre> <p>When the AI emits structured update commands, <code>ctx watch</code> parses and applies them automatically:</p> <pre><code><context-update type=\"learning\"\n  context=\"Debugging rate limiter\"\n  lesson=\"Redis MULTI/EXEC does not roll back on error\"\n  application=\"Wrap rate-limit checks in Lua scripts instead\"\n>Redis Transaction Behavior</context-update>\n</code></pre> <p>To preview changes without modifying files:</p> <pre><code>ctx watch --dry-run --log /tmp/ai.log\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#step-6-import-session-transcripts-optional","level":3,"title":"Step 6: Import Session Transcripts (Optional)","text":"<p>If you want to browse past session transcripts, import them to the journal:</p> <pre><code>ctx journal import --all\n</code></pre> <p>This converts raw session data into editable Markdown files in <code>.context/journal/</code>. You can then enrich them with metadata using <code>/ctx-journal-enrich-all</code> inside your AI assistant.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Here is the condensed setup for all three tools:</p> <pre><code># ## Common (run once per project) ##\ncd your-project\nctx init\nsource <(ctx completion zsh)       # or bash/fish\n\n# ## Claude Code (automatic, just verify) ##\n# Start Claude Code, then ask: \"Do you remember?\"\n\n# ## OpenCode ##\nctx setup opencode --write\n# Start OpenCode, then ask: \"Do you remember?\"\n\n# ## Cursor ##\nctx setup cursor\n# Add the system prompt to .cursor/settings.json\n# Paste context: ctx agent --budget 4000 | pbcopy\n\n# ## Aider ##\nctx setup aider\n# Create .aider.conf.yml with read: paths\n# Run watch mode alongside: ctx watch --log /tmp/aider.log\n\n# ## Verify any Tool ##\n# Ask your AI: \"Do you remember?\"\n# Expect: specific tasks, decisions, recent context\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>ctx init</code> (not <code>--minimal</code>) for your first project. The full   template set gives the agent more to work with, and you can always delete   files later.</li> <li>For Claude Code, the token budget is configured in the plugin's <code>hooks.json</code>.   To customize, adjust the <code>--budget</code> flag in the <code>ctx agent</code> hook command.</li> <li>The <code>--session $PPID</code> flag isolates cooldowns per Claude Code process, so   parallel sessions do not suppress each other.</li> <li>Commit your <code>.context/</code> directory to version control. Several <code>ctx</code> features   (journals, changelogs, blog generation) rely on git history.</li> <li>For Cursor and Copilot, keep <code>CONVENTIONS.md</code> visible. These tools treat   open files as higher-priority context.</li> <li>Run <code>ctx drift</code> periodically to catch stale references before they confuse   the agent.</li> <li>The agent playbook instructs the agent to persist context at natural   milestones (completed tasks, decisions, gotchas). In practice, this   works best when you reinforce the habit: a quick \"anything worth saving?\"   after a debugging session goes a long way.</li> </ul>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#companion-tools-highly-recommended","level":2,"title":"Companion Tools (Highly Recommended)","text":"<p><code>ctx</code> skills can leverage external MCP servers for web search and code intelligence. <code>ctx</code> works without them, but they significantly improve agent behavior across sessions. The investment is small and the benefits compound. Skills like <code>/ctx-code-review</code>, <code>/ctx-explain</code>, and <code>/ctx-refactor</code> all become noticeably better with these tools connected.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gemini-search","level":3,"title":"Gemini Search","text":"<p>Provides grounded web search with citations. Used by skills and the agent playbook as the preferred search backend (faster and more accurate than built-in web search).</p> <p>Setup: Add the Gemini Search MCP server to your Claude Code settings. See the Gemini Search MCP documentation for installation.</p> <p>Verification: <pre><code># The agent checks this automatically during /ctx-remember\n# Manual test: ask the agent to search for something\n</code></pre></p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#gitnexus","level":3,"title":"GitNexus","text":"<p>Provides a code knowledge graph with symbol resolution, blast radius analysis, and domain clustering. Used by skills like <code>/ctx-refactor</code> (impact analysis) and <code>/ctx-code-review</code> (dependency awareness).</p> <p>Setup: Add the GitNexus MCP server to your Claude Code settings, then index your project:</p> <pre><code>npx gitnexus analyze\n</code></pre> <p>Verification: <pre><code># The agent checks this automatically during /ctx-remember\n# If the index is stale, it will suggest rehydrating\n</code></pre></p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#suppressing-the-check","level":3,"title":"Suppressing the Check","text":"<p>If you don't use companion tools and want to skip the availability check at session start, add to <code>.ctxrc</code>:</p> <pre><code>companion_check: false\n</code></pre>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#future-direction","level":3,"title":"Future Direction","text":"<p>The companion tool integration is evolving toward a pluggable model: bring your own search engine, bring your own code intelligence. The current integration is MCP-based and limited to Gemini Search and GitNexus. If you use a different search or code intelligence tool, skills will degrade gracefully to built-in capabilities.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#next-up","level":2,"title":"Next Up","text":"<p>Keeping Context in a Separate Repo →: Store context files outside the project tree for multi-repo or open source setups.</p>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multi-tool-setup/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session lifecycle recipe</li> <li>Multilingual Session Parsing: configure session header prefixes for other languages</li> <li>CLI Reference: all commands and flags</li> <li>Integrations: detailed per-tool integration docs</li> </ul>","path":["Recipes","Getting Started","Setup Across AI Tools"],"tags":[]},{"location":"recipes/multilingual-sessions/","level":1,"title":"Multilingual Session Parsing","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#the-problem","level":2,"title":"The Problem","text":"<p>Your team works across languages. Session files written by AI tools might use headers like <code># Oturum: 2026-01-15 - API Düzeltme</code> (Turkish) or <code># セッション: 2026-01-15 - テスト</code> (Japanese) instead of <code># Session: 2026-01-15 - Fix API</code>.</p> <p>By default, <code>ctx</code> only recognizes <code>Session:</code> as a session header prefix. Files with other prefixes are silently skipped during journal import and journal generation: They look like regular Markdown, not sessions.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#tldr","level":2,"title":"TL;DR","text":"<p>Add recognized prefixes to <code>.ctxrc</code>:</p> <pre><code>session_prefixes:\n  - \"Session:\"      # English (include to keep default)\n  - \"Oturum:\"       # Turkish\n  - \"セッション:\"     # Japanese\n</code></pre> <p>Restart your session. All configured prefixes are now recognized.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#how-it-works","level":2,"title":"How It Works","text":"<p>The Markdown session parser detects session files by looking for an H1 header that starts with a known prefix followed by a date:</p> <pre><code># Session: 2026-01-15 - Fix API Rate Limiting\n# Oturum: 2026-01-15 - API Düzeltme\n# セッション: 2026-01-15 - テスト\n</code></pre> <p>The list of recognized prefixes comes from <code>session_prefixes</code> in <code>.ctxrc</code>. When the key is absent or empty, <code>ctx</code> falls back to the built-in default: <code>[\"Session:\"]</code>.</p> <p>Date-only headers (<code># 2026-01-15 - Morning Work</code>) are always recognized regardless of prefix configuration.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#configuration","level":2,"title":"Configuration","text":"","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#adding-a-language","level":3,"title":"Adding a Language","text":"<p>Add the prefix with a trailing colon to your <code>.ctxrc</code>:</p> <pre><code>session_prefixes:\n  - \"Session:\"\n  - \"Sesión:\"       # Spanish\n</code></pre> <p>Include Session: Explicitly</p> <p>When you override <code>session_prefixes</code>, the default is replaced, not extended. If you still want English headers recognized, include <code>\"Session:\"</code> in your list.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#team-setup","level":3,"title":"Team Setup","text":"<p>Commit <code>.ctxrc</code> to the repo so all team members share the same prefix list. This ensures <code>ctx journal import</code> and journal generation pick up sessions from all team members regardless of language.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#common-prefixes","level":3,"title":"Common Prefixes","text":"Language Prefix English <code>Session:</code> Turkish <code>Oturum:</code> Spanish <code>Sesión:</code> French <code>Session:</code> German <code>Sitzung:</code> Japanese <code>セッション:</code> Korean <code>세션:</code> Portuguese <code>Sessão:</code> Chinese <code>会话:</code>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#verifying","level":3,"title":"Verifying","text":"<p>After configuring, test with <code>ctx journal source</code>. Sessions with the new prefixes should appear in the output.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> from the project root. If you skip it, <code>ctx journal ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#what-this-does-not-do","level":2,"title":"What This Does NOT Do","text":"<ul> <li>Change the interface language: <code>ctx</code> output is always English.   This setting only controls which session files <code>ctx</code> can parse.</li> <li>Generate headers: <code>ctx</code> never writes session headers. The prefix   list is recognition-only (input, not output).</li> <li>Affect JSONL sessions: Claude Code JSONL transcripts don't use   header prefixes. This only applies to Markdown session files in   <code>.context/sessions/</code>.</li> </ul>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/multilingual-sessions/#see-also","level":2,"title":"See Also","text":"<p>See also: Setup Across AI Tools - complete multi-tool setup including Markdown session configuration.</p> <p>See also: CLI Reference - full <code>.ctxrc</code> field reference including <code>session_prefixes</code>.</p>","path":["Recipes","Getting Started","Multilingual Session Parsing"],"tags":[]},{"location":"recipes/parallel-worktrees/","level":1,"title":"Parallel Agent Development with Git Worktrees","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-problem","level":2,"title":"The Problem","text":"<p>You have a large backlog (10, 20, 30 open tasks) and many of them are independent: docs work that doesn't touch Go code, a new package that doesn't overlap with existing ones, test coverage for a stable module.</p> <p>Running one agent at a time means serial execution. You want 3-4 agents working in parallel, each on its own track, without stepping on each other's files.</p> <p>Git worktrees solve this. </p> <p>Each worktree is a separate working directory with its own branch, but they  share the same <code>.git</code> object database. Combined with <code>ctx</code>'s persistent context,  each agent session picks up the full project state and works independently.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-worktree                                   # 1. group tasks by file overlap\ngit worktree add ../myproject-docs -b work/docs # 2. create worktrees\ncd ../myproject-docs && claude                  # 3. launch agents (one per track)\n/ctx-worktree teardown docs                     # 4. merge back and clean up\n</code></pre> <p><code>TASKS.md</code> will conflict on merge: Accept all <code>[x]</code> completions from both sides.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-worktree</code> Skill Create, list, and tear down worktrees <code>/ctx-next</code> Skill Pick tasks from the backlog for each track <code>git worktree</code> Command Underlying git worktree management <code>git merge</code> Command Merge completed tracks back to main","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-1-assess-the-backlog","level":3,"title":"Step 1: Assess the Backlog","text":"<p>Start in your main checkout. Ask the agent to analyze your tasks and group them by blast radius: which files and directories each task touches.</p> <pre><code>/ctx-worktree\nLook at TASKS.md and group the pending tasks into 2-3 independent\ntracks based on which files they'd touch. Show me the grouping\nbefore creating anything.\n</code></pre> <p>The agent reads <code>TASKS.md</code>, estimates file overlap, and proposes groups:</p> <pre><code>Proposed worktree groups:\n\n  work/docs   # recipe updates, blog post (touches: docs/)\n  work/crypto # scratchpad encryption infra (touches: internal/crypto/)\n  work/tests  # journal test coverage (touches: internal/cli/journal/)\n</code></pre>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-2-create-the-worktrees","level":3,"title":"Step 2: Create the Worktrees","text":"<p>Once you approve the grouping, the agent creates worktrees as sibling directories:</p> <pre><code>Create the worktrees for those three groups.\n</code></pre> <p>Behind the scenes:</p> <pre><code>git worktree add ../myproject-docs -b work/docs\ngit worktree add ../myproject-crypto -b work/crypto\ngit worktree add ../myproject-tests -b work/tests\n</code></pre> <p>Each worktree is a full working copy on its own branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-3-launch-agents","level":3,"title":"Step 3: Launch Agents","text":"<p>Open a separate terminal (or editor window) for each worktree and start a Claude Code session:</p> <pre><code># Terminal 1\ncd ../myproject-docs\nclaude\n\n# Terminal 2\ncd ../myproject-crypto\nclaude\n\n# Terminal 3\ncd ../myproject-tests\nclaude\n</code></pre> <p>Each agent sees the full project, including <code>.context/</code>, and can work independently. </p> <p>Do Not Initialize Context in Worktrees</p> <p>Do not run <code>ctx init</code> in worktrees: The <code>.context</code> directory is already tracked in <code>git</code>.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-4-work","level":3,"title":"Step 4: Work","text":"<p>Each agent works through its assigned tasks. They can read <code>TASKS.md</code> to know what's assigned to their track, use <code>/ctx-next</code> to pick the next item, and commit normally on their <code>work/*</code> branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-5-merge-back","level":3,"title":"Step 5: Merge Back","text":"<p>As each track finishes, return to the main checkout and merge:</p> <pre><code>/ctx-worktree teardown docs\n</code></pre> <p>The agent checks for uncommitted changes, merges <code>work/docs</code> into your current branch, removes the worktree, and deletes the branch.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-6-handle-tasksmd-conflicts","level":3,"title":"Step 6: Handle <code>TASKS.md</code> Conflicts","text":"<p><code>TASKS.md</code> will almost always conflict when merging: Multiple agents will mark different tasks as <code>[x]</code>. This is expected and easy to resolve:</p> <p>Accept all completions from both sides. No task should go from <code>[x]</code> back to <code>[ ]</code>. The merge resolution is always additive.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#step-7-cleanup","level":3,"title":"Step 7: Cleanup","text":"<p>After all tracks are merged, verify everything is clean:</p> <pre><code>/ctx-worktree list\n</code></pre> <p>Should show only the main working tree. All <code>work/*</code> branches should be gone.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't have to use the skill directly for every step. These natural prompts work:</p> <ul> <li>\"I have a big backlog. Can we split it across worktrees?\"</li> <li>\"Which of these tasks can run in parallel without conflicts?\"</li> <li>\"Merge the docs track back in.\"</li> <li>\"Clean up all the worktrees, we're done.\"</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#what-works-differently-in-worktrees","level":2,"title":"What Works Differently in Worktrees","text":"<p>The encryption key lives at <code>~/.ctx/.ctx.key</code> (user-level, outside the project). Because all worktrees on the same machine share this path, <code>ctx pad</code> and <code>ctx hook notify</code> work in worktrees automatically - no special setup needed.</p> <p>One thing to watch:</p> <ul> <li>Journal enrichment: <code>ctx journal import</code> and <code>ctx journal enrich</code>   write files relative to the current working directory. Enrichments   created in a worktree stay there and are discarded on teardown.   Enrich journals on the main branch after merging: the JSONL session   logs are always intact, and you don't lose any data.</li> </ul> <p>Context Files Will Merge Just Fine</p> <p>Tracked context files (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>) work normally; <code>git</code> handles them.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#tips","level":2,"title":"Tips","text":"<ul> <li>3-4 worktrees max. Beyond that, merge complexity outweighs the   parallelism benefit. The skill enforces this limit.</li> <li>Group by package or directory, not by priority. Two high-priority   tasks that touch the same files must be in the same track.</li> <li><code>TASKS.md</code> will conflict on merge. This is normal. Accept all <code>[x]</code>   completions: The resolution is always additive.</li> <li>Don't run <code>ctx init</code> in worktrees. The <code>.context/</code> directory is   tracked in git. Running init overwrites shared context files.</li> <li>Name worktrees by concern, not by number. <code>work/docs</code> and   <code>work/crypto</code> are more useful than <code>work/track-1</code> and <code>work/track-2</code>.</li> <li>Commit frequently in each worktree. Smaller commits make merge   conflicts easier to resolve.</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#next-up","level":2,"title":"Next Up","text":"<p>Back to the beginning: Guide Your Agent →</p> <p>Or explore the full recipe list.</p>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/parallel-worktrees/#see-also","level":2,"title":"See Also","text":"<ul> <li>Running an Unattended AI Agent: for serial   autonomous loops instead of parallel tracks</li> <li>Tracking Work Across Sessions: managing the   task backlog that feeds into parallelization</li> <li>The Complete Session: the complete session workflow   end-to-end, with examples</li> </ul>","path":["Recipes","Agents and Automation","Parallel Agent Development with Git Worktrees"],"tags":[]},{"location":"recipes/permission-snapshots/","level":1,"title":"Permission Snapshots","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-problem","level":2,"title":"The Problem","text":"<p>Claude Code's <code>.claude/settings.local.json</code> accumulates one-off permissions every time you click \"Allow\". After busy sessions the file is full of session-specific entries that expand the agent's surface area beyond intent.</p> <p>Since <code>settings.local.json</code> is <code>.gitignore</code>d, there is no PR review or CI check. The file drifts independently on every machine, and there is no built-in way to reset to a known-good state.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-permission-sanitize               # audit for dangerous patterns\nctx permission snapshot            # save golden image\n# ... sessions accumulate cruft ...\nctx permission restore             # reset to golden state\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx permission ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#the-solution","level":2,"title":"The Solution","text":"<p>Save a curated <code>settings.local.json</code> as a golden image, then restore from it to drop session-accumulated permissions. The golden file (<code>.claude/settings.golden.json</code>) is committed to version control and shared with the team.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Command/Skill Role in this workflow <code>ctx permission snapshot</code> Save settings.local.json as golden image <code>ctx permission restore</code> Reset settings.local.json from golden image <code>/ctx-permission-sanitize</code> Audit for dangerous patterns before snapshotting","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#step-by-step","level":2,"title":"Step by Step","text":"","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#1-curate-your-permissions","level":3,"title":"1. Curate Your Permissions","text":"<p>Start with a clean <code>settings.local.json</code>. Optionally run <code>/ctx-permission-sanitize</code> to remove dangerous patterns first.</p> <p>Review the file manually. Every entry should be there because you decided it belongs, not because you clicked \"Allow\" once during debugging.</p> <p>See the Permission Hygiene recipe for recommended defaults.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#2-take-a-snapshot","level":3,"title":"2. Take a Snapshot","text":"<pre><code>ctx permission snapshot\n# Saved golden image: .claude/settings.golden.json\n</code></pre> <p>This creates a byte-for-byte copy. No re-encoding, no indent changes.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#3-commit-the-golden-file","level":3,"title":"3. Commit the Golden File","text":"<pre><code>git add .claude/settings.golden.json\ngit commit -m \"Add permission golden image\"\n</code></pre> <p>The golden file is not gitignored (unlike <code>settings.local.json</code>). This is intentional: it becomes a team-shared baseline.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#4-auto-restore-at-the-session-start","level":3,"title":"4. Auto-Restore at the Session Start","text":"<p>Add this instruction to your <code>CLAUDE.md</code>:</p> <pre><code>## On Session Start\n\nRun `ctx permission restore` to reset permissions to the golden image.\n</code></pre> <p>The agent will restore the golden image at the start of every session, automatically dropping any permissions accumulated during previous sessions.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#5-update-when-intentional-changes-are-made","level":3,"title":"5. Update When Intentional Changes Are Made","text":"<p>When you add a new permanent permission (not a one-off debugging entry):</p> <pre><code># Edit settings.local.json with the new permission\n# Then update the golden image:\nctx permission snapshot\ngit add .claude/settings.golden.json\ngit commit -m \"Update permission golden image: add cargo test\"\n</code></pre>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#conversational-approach","level":2,"title":"Conversational Approach","text":"<p>You don't need to remember exact commands. These natural-language prompts work with agents trained on the <code>ctx</code> playbook:</p> What you say What happens \"Save my current permissions as baseline\" Agent runs <code>ctx permission snapshot</code> \"Reset permissions to the golden image\" Agent runs <code>ctx permission restore</code> \"Clean up my permissions\" Agent runs <code>/ctx-permission-sanitize</code> then snapshot \"What permissions did I accumulate?\" Agent diffs local vs golden","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#next-up","level":2,"title":"Next Up","text":"<p>Turning Activity into Content →: Generate blog posts, changelogs, and journal sites from your project activity.</p>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/permission-snapshots/#see-also","level":2,"title":"See Also","text":"<ul> <li>Permission Hygiene: recommended defaults and   maintenance workflow</li> <li>CLI Reference: <code>ctx</code> permission:   full command documentation</li> </ul>","path":["Recipes","Maintenance","Permission Snapshots"],"tags":[]},{"location":"recipes/publishing/","level":1,"title":"Turning Activity into Content","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-problem","level":2,"title":"The Problem","text":"<p>Your <code>.context/</code> directory is full of decisions, learnings, and session history.</p> <p>Your <code>git log</code> tells the story of a project evolving.</p> <p>But none of this is visible to anyone outside your terminal.</p> <p>You want to turn this raw activity into:</p> <ul> <li>a browsable journal site,</li> <li>blog posts,</li> <li>changelog posts.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx journal import --all             # 1. import sessions to markdown\n\n/ctx-journal-enrich-all             # 2. add metadata and tags\n\nctx journal site --serve            # 3. build and serve the journal\n\n/ctx-blog about the caching layer   # 4. draft a blog post\n/ctx-blog-changelog v0.1.0 \"v0.2\"   # 5. write a changelog post\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx journal ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Read on for details on each stage.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx journal import</code> Command Import session JSONL to editable Markdown <code>ctx journal site</code> Command Generate a static site from journal entries <code>ctx journal obsidian</code> Command Generate an Obsidian vault from journal entries <code>ctx serve</code> Command Serve any zensical directory (default: journal) <code>ctx site feed</code> Command Generate Atom feed from finalized blog posts <code>make journal</code> Makefile Shortcut for import + site rebuild <code>/ctx-journal-enrich-all</code> Skill Full pipeline: import if needed, then batch-enrich (recommended) <code>/ctx-journal-enrich</code> Skill Add metadata, summaries, and tags to one entry <code>/ctx-blog</code> Skill Draft a blog post from recent project activity <code>/ctx-blog-changelog</code> Skill Write a themed post from a commit range","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-1-import-sessions-to-markdown","level":3,"title":"Step 1: Import Sessions to Markdown","text":"<p>Raw session data lives as JSONL files in Claude Code's internal storage. The first step is converting these into readable, editable Markdown.</p> <pre><code># Import all sessions from the current project\nctx journal import --all\n\n# Import from all projects (if you work across multiple repos)\nctx journal import --all --all-projects\n\n# Import a single session by ID or slug\nctx journal import abc123\nctx journal import gleaming-wobbling-sutherland\n</code></pre> <p>Imported files land in <code>.context/journal/</code> as individual Markdown files with session metadata and the full conversation transcript.</p> <p><code>--all</code> is safe by default: Only new sessions are imported. Existing files are skipped. Use <code>--regenerate</code> to re-import existing files (YAML frontmatter is preserved). Use <code>--regenerate --keep-frontmatter=false -y</code> to regenerate everything including frontmatter.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-2-enrich-entries-with-metadata","level":3,"title":"Step 2: Enrich Entries with Metadata","text":"<p>Raw entries have timestamps and conversations but lack the structured metadata that makes a journal searchable. Use <code>/ctx-journal-enrich-all</code> to process your entire backlog at once:</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and summaries from the conversation.</p> <p>For large backlogs (20+ entries), it can spawn subagents to process entries in parallel.</p> <p>To enrich a single entry instead:</p> <pre><code>/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich 2026-01-24\n</code></pre> <p>After enrichment, an entry gains YAML frontmatter:</p> <pre><code>---\ntitle: \"Implement Redis caching for API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n</code></pre> <p>This metadata powers better navigation in the journal site: </p> <ul> <li>titles replace slugs, </li> <li>summaries appear in the index, </li> <li>and search covers topics and technologies.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-3-generate-the-journal-site","level":3,"title":"Step 3: Generate the Journal Site","text":"<p>With entries exported and enriched, generate the static site:</p> <pre><code># Generate site files\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally (opens at http://localhost:8000)\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n</code></pre> <p>The site is generated in <code>.context/journal-site/</code> by default. It uses zensical for static site generation (<code>pipx install zensical</code>).</p> <p>Or use the Makefile shortcut that combines export and rebuild:</p> <pre><code>make journal\n</code></pre> <p>This runs <code>ctx journal import --all</code> followed by <code>ctx journal site --build</code>, then reminds you to enrich before rebuilding. To serve the built site, use <code>make journal-serve</code> or <code>ctx serve</code> (serve-only, no regeneration).</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#alternative-export-to-obsidian-vault","level":3,"title":"Alternative: Export to Obsidian Vault","text":"<p>If you use Obsidian for knowledge management, generate a vault instead of (or alongside) the static site:</p> <pre><code>ctx journal obsidian\nctx journal obsidian --output ~/vaults/ctx-journal\n</code></pre> <p>This produces an Obsidian-ready directory with wikilinks, MOC (Map of Content) pages for topics/files/types, and a \"Related Sessions\" footer on each entry for graph connectivity. Open the output directory in Obsidian as a vault.</p> <p>The vault uses the same enriched source entries as the static site. Both outputs can coexist: The static site goes to <code>.context/journal-site/</code>, the vault to <code>.context/journal-obsidian/</code>.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-4-draft-blog-posts-from-activity","level":3,"title":"Step 4: Draft Blog Posts from Activity","text":"<p>When your project reaches a milestone worth sharing, use <code>/ctx-blog</code> to draft a post from recent activity. The skill gathers context from multiple sources: <code>git log</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, completed tasks, and journal entries.</p> <pre><code>/ctx-blog about the caching layer we just built\n/ctx-blog last week's refactoring work\n/ctx-blog lessons learned from the migration\n</code></pre> <p>The skill gathers recent commits, decisions, and learnings; identifies a narrative arc; drafts an outline for approval; writes the full post; and saves it to <code>docs/blog/YYYY-MM-DD-slug.md</code>.</p> <p>Posts are written in first person with code snippets, commit references, and an honest discussion of what went wrong.</p> <p>The Output Is <code>zensical</code>-Flavored Markdown</p> <p>The blog skills produce Markdown tuned for a zensical site: <code>topics:</code> frontmatter (zensical's tag field), a <code>docs/blog/</code> output path, and a banner image reference. </p> <p>The content is still standard Markdown and can be adapted to other  static site generators, but the defaults assume a <code>zensical</code>  project structure.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-5-write-changelog-posts-from-commit-ranges","level":3,"title":"Step 5: Write Changelog Posts from Commit Ranges","text":"<p>For release notes or \"what changed\" posts, <code>/ctx-blog-changelog</code> takes a starting commit and a theme, then analyzes everything that changed:</p> <pre><code>/ctx-blog-changelog 040ce99 \"building the journal system\"\n/ctx-blog-changelog HEAD~30 \"what's new in v0.2.0\"\n/ctx-blog-changelog v0.1.0 \"the road to v0.2.0\"\n</code></pre> <p>The skill diffs the commit range, identifies the most-changed files, and constructs a narrative organized by theme rather than chronology, including a key commits table and before/after comparisons.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#step-6-generate-the-blog-feed","level":3,"title":"Step 6: Generate the Blog Feed","text":"<p>After publishing blog posts, generate the Atom feed so readers and automation can discover new content:</p> <pre><code>ctx site feed\n</code></pre> <p>This scans <code>docs/blog/</code> for finalized posts (<code>reviewed_and_finalized: true</code>), extracts title, date, author, topics, and summary, and writes a valid Atom 1.0 feed to <code>site/feed.xml</code>. The feed is also generated automatically as part of <code>make site</code>.</p> <p>The feed is available at ctx.ist/feed.xml.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#the-conversational-approach","level":2,"title":"The Conversational Approach","text":"<p>You can also drive your publishing anytime with natural language:</p> <pre><code>\"write about what we did this week\"\n\"turn today's session into a blog post\"\n\"make a changelog post covering everything since the last release\"\n\"enrich the last few journal entries\"\n</code></pre> <p>The agent has full visibility into your <code>.context/</code> state (tasks completed, decisions recorded, learnings captured), so its suggestions are grounded in what actually happened.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>The full pipeline from raw transcripts to published content:</p> <pre><code># 1. Import all sessions\nctx journal import --all\n\n# 2. In Claude Code: enrich all entries with metadata\n/ctx-journal-enrich-all\n\n# 3. Build and serve the journal site\nmake journal\nmake journal-serve\n\n# 3b. Or generate an Obsidian vault\nctx journal obsidian\n\n# 4. In Claude Code: draft a blog post\n/ctx-blog about the features we shipped this week\n\n# 5. In Claude Code: write a changelog post\n/ctx-blog-changelog v0.1.0 \"what's new in v0.2.0\"\n</code></pre> <p>The journal pipeline is idempotent at every stage. You can rerun <code>ctx journal import --all</code> without losing enrichment. You can rebuild the site as many times as you want.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#tips","level":2,"title":"Tips","text":"<ul> <li>Import regularly. Run <code>ctx journal import --all</code> after each session to keep   your journal current. Only new sessions are imported: Existing files are   skipped by default.</li> <li>Use batch enrichment. <code>/ctx-journal-enrich-all</code> filters noise (suggestion   sessions, trivial sessions, multipart continuations) so you do not have to   decide what is worth enriching.</li> <li>Keep journal files in <code>.gitignore</code>. Session journals can contain sensitive   data: file contents, commands, internal discussions, and error messages with   stack traces. Add <code>.context/journal/</code> and <code>.context/journal-site/</code> to   <code>.gitignore</code>.</li> <li>Use <code>/ctx-blog</code> for narrative posts and <code>/ctx-blog-changelog</code> for release   posts. One finds a story in recent activity, the other explains a commit   range by theme.</li> <li>Edit the drafts. These skills produce drafts, not final posts. Review the   narrative, add your perspective, and remove anything that does not serve the   reader.</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#next-up","level":2,"title":"Next Up","text":"<p>Running an Unattended AI Agent →: Set up an AI agent that works through tasks overnight without you at the keyboard.</p>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/publishing/#see-also","level":2,"title":"See Also","text":"<ul> <li>Session Journal: journal system, enrichment schema</li> <li>CLI Reference: <code>ctx</code> journal: import, list, show session history</li> <li>CLI Reference: <code>ctx</code> journal site: static site generation</li> <li>CLI Reference: <code>ctx</code> journal obsidian: Obsidian vault export</li> <li>CLI Reference: <code>ctx</code> serve: serve-only (no regeneration)</li> <li>Browsing and Enriching Past Sessions: journal browsing workflow</li> <li>The Complete Session: capturing context during a session</li> </ul>","path":["Recipes","Maintenance","Turning Activity into Content"],"tags":[]},{"location":"recipes/recover-aborted-session/","level":1,"title":"Recover an Aborted KB Session","text":"","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#the-problem","level":2,"title":"The Problem","text":"<p>You ran one or more <code>/ctx-kb-ingest</code> passes, then the session ended before <code>/ctx-wrap-up</code>. Maybe you closed the laptop, the connection dropped, or you just forgot the wrap-up step.</p> <p>You come back the next day and ask \"do you remember?\" and the agent picks up the previous handover, but the editorial work since the last handover seems to be missing from the readback.</p> <p>It isn't missing. It's unfolded. Here's how the pipeline handles it and how to close the loop manually.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-remember                                       # picks up the unfolded \n                                                    # closeouts automatically\n/ctx-handover \"recovery: fold the orphan closeouts\" # direct invocation is \n                                                    # appropriate for recovery\n</code></pre> <p>The recovery path is the one legitimate place to invoke <code>/ctx-handover</code> directly. Normally <code>/ctx-wrap-up</code> owns session-end and delegates to the handover step; the abort broke that path, so a hand-rolled handover invocation is how you close the loop without re-running the full wrap-up ceremony.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#how-the-fold-mechanism-survives-an-abort","level":2,"title":"How the Fold Mechanism Survives an Abort","text":"<p>Two artifacts make abort-recovery work without any cleanup:</p> <ol> <li> <p>Closeouts are immutable once written. Every editorial    pass writes a closeout under    <code>.context/ingest/closeouts/<TS>-<mode>-closeout.md</code> before    the pass reports <code>done</code>. If the session dies, the closeout    is already on disk.</p> </li> <li> <p><code>/ctx-remember</code> folds unfolded closeouts into the    readback. The skill always reads the latest handover.    When <code>.context/kb/</code> exists, it additionally reads any    closeouts whose <code>generated-at</code> postdates the handover.    The <code>## What changed</code> and <code>## Source-coverage updates</code>    sections from each unfolded closeout are surfaced in    recall.</p> </li> </ol> <p>So an aborted session never loses editorial work; it just delays the handover fold by one session.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-1-confirm-the-orphan-closeouts","level":2,"title":"Step 1: Confirm the Orphan Closeouts","text":"<pre><code>ls -la .context/ingest/closeouts/\n</code></pre> <p>Files there with <code>generated-at</code> postdating your latest handover are the unfolded ones. You can read any closeout directly to see what it claims about its pass:</p> <pre><code>cat .context/ingest/closeouts/<TS>-ingest-closeout.md\n</code></pre> <p>Look at:</p> <ul> <li>The Pass-mode body block (<code>Declared / Reason /   Definition of done / Result</code>): what the pass committed to   and whether it claimed success or <code>deferred</code>.</li> <li>The Source-coverage updates section: what state   transitions hit the ledger.</li> <li>The Next pass hint: the exact resumption invocation   the closeout recommends, if the pass deferred.</li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-2-run-ctx-remember","level":2,"title":"Step 2: Run <code>/ctx-remember</code>","text":"<pre><code>/ctx-remember\n</code></pre> <p>The readback will include the editorial-state summary as part of the standard readback shape. If everything looks consistent, proceed to Step 3.</p> <p>If the readback surfaces something surprising (a closeout claiming <code>topic-page: produced</code> for a slug whose file is missing, a <code>comprehensive</code> ledger advance against a source whose page is <code>speculative</code>, etc.), fix the underlying inconsistency before folding. (Doctor advisories for these shapes are on the Phase-7 backlog.)</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#step-3-write-the-recovery-handover","level":2,"title":"Step 3: Write the Recovery Handover","text":"<p>This step is the one legitimate direct invocation of <code>/ctx-handover</code>. In normal session-end the call goes through <code>/ctx-wrap-up</code>; here the prior session aborted, so you reach for the handover step directly to retire the orphan closeouts:</p> <pre><code>/ctx-handover \"recovery: fold orphan closeouts from yesterday\"\n</code></pre> <p>Or via the CLI:</p> <pre><code>ctx handover write \"recovery: fold orphan closeouts from yesterday\" \\\n  --summary \"Folded N orphan closeouts from the aborted session.\" \\\n  --next \"Resume <topic> per the closeout's Next pass hint.\"\n</code></pre> <p>The handover:</p> <ul> <li>Reads the latest handover cursor.</li> <li>Finds all closeouts whose <code>generated-at</code> is after the cursor.</li> <li>Folds their summaries into a <code>## Folded closeouts</code> section.</li> <li>Archives the source closeout files under   <code>.context/archive/closeouts/</code> (closeouts are   append-never-rewrite; archival moves bytes but does not   modify them).</li> </ul> <p>After the handover lands, the orphan closeouts are now durably tied to a session boundary; the next <code>/ctx-remember</code> reads just the new handover (and any closeouts postdating it), without re-folding the recovered ones.</p>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#edge-cases","level":2,"title":"Edge Cases","text":"Case Behavior Closeout has malformed frontmatter Handover fold skips it with a warning to stderr. Hand-edit the malformed file (typically a missing <code>generated-at</code>) and re-run <code>ctx handover write</code> to fold it next time. Closeout's <code>generated-at</code> is before the last handover but was never folded Treated as already-folded (silently skipped; the cursor is the source of truth). If you genuinely want to re-fold it, hand-edit the closeout's <code>generated-at</code> forward. You aborted during an ingest pass, before its closeout was written No closeout exists; the pass left no recall residue. Treat the source(s) as un-ingested and re-run <code>/ctx-kb-ingest</code>. The source-coverage ledger row may show stale residue from a prior pass; the next ingest will advance it correctly. Multiple sessions piled up unfolded closeouts One handover run folds them all in a single shot. The fold is cursor-driven, not session-driven. You want recall without consuming closeouts <code>ctx handover write ... --no-fold</code> writes a handover with frontmatter but leaves the closeouts in place. The next handover (without <code>--no-fold</code>) folds everything postdating the latest handover cursor.","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#when-this-matters","level":2,"title":"When This Matters","text":"<ul> <li>After a network drop / laptop close mid-session.</li> <li>When you ran <code>/ctx-kb-ingest</code> from a sub-agent that   finished without calling <code>/ctx-handover</code>.</li> <li>After porting work from another environment (e.g. you   rsynced <code>.context/ingest/closeouts/</code> from a different   machine) and want to integrate the work into the destination   project's recall thread.</li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/recover-aborted-session/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Typical KB Session</li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["Recover an Aborted KB Session"],"tags":[]},{"location":"recipes/scratchpad-sync/","level":1,"title":"Syncing Scratchpad Notes Across Machines","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-problem","level":2,"title":"The Problem","text":"<p>You work from multiple machines: a desktop and a laptop, or a local machine and a remote dev server.</p> <p>The scratchpad entries are encrypted. The ciphertext (<code>.context/scratchpad.enc</code>) travels with git, but the encryption key lives outside the project at <code>~/.ctx/.ctx.key</code> and is never committed. Without the key on each machine, you cannot read or write entries.</p> <p>How do you distribute the key and keep the scratchpad in sync?</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx init                                                  # 1. generates key\neval \"$(ctx activate)\"                                    # 2. bind CTX_DIR\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key        # 3. copy key\nchmod 600 ~/.ctx/.ctx.key                                 # 4. secure it\n# Normal git push/pull syncs the encrypted scratchpad.enc\n# On conflict: ctx pad resolve → rebuild → git add + commit\n</code></pre> <p>Activate Each Machine</p> <p>Run <code>eval \"$(ctx activate)\"</code> from the project root on every machine that reads or writes the scratchpad: after each <code>ctx init</code>, or after each clone on machine B. If you skip it, <code>ctx pad ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Finding Your Key File</p> <p>The key is always at <code>~/.ctx/.ctx.key</code> - one key, one machine.</p> <p>Treat the Key like a Password</p> <p>The scratchpad key is the only thing protecting your encrypted entries.</p> <p>Store a backup in a secure enclave such as a password manager, and treat it with the same care you would give passwords, certificates, or API tokens.</p> <p>Anyone with the key can decrypt every scratchpad entry.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx init</code> CLI command Initialize context (generates the key automatically) <code>ctx pad add</code> CLI command Add a scratchpad entry <code>ctx pad rm</code> CLI command Remove entries by stable ID (supports ranges) <code>ctx pad edit</code> CLI command Edit a scratchpad entry <code>ctx pad resolve</code> CLI command Show both sides of a merge conflict <code>ctx pad merge</code> CLI command Merge entries from other scratchpad files <code>ctx pad import</code> CLI command Bulk-import lines from a file <code>ctx pad export</code> CLI command Export blob entries to a directory <code>scp</code> Shell Copy the key file between machines <code>git push</code> / <code>git pull</code> Shell Sync the encrypted file via <code>git</code> <code>/ctx-pad</code> Skill Natural language interface to pad commands","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-1-initialize-on-machine-a","level":3,"title":"Step 1: Initialize on Machine A","text":"<p>Run <code>ctx init</code> on your first machine. The key is created automatically at <code>~/.ctx/.ctx.key</code>:</p> <pre><code>ctx init\n# ...\n# Created ~/.ctx/.ctx.key (0600)\n# Created .context/scratchpad.enc\n</code></pre> <p>The key lives outside the project directory and is never committed. The <code>.enc</code> file is tracked in git.</p> <p>Key Folder Change (v0.7.0+)</p> <p>If you built <code>ctx</code> from source or upgraded past v0.6.0, the key location changed to <code>~/.ctx/.ctx.key</code>. Check these legacy folders and copy your key manually:</p> <pre><code># Old locations (pick whichever exists)\nls ~/.local/ctx/keys/        # pre-v0.7.0 user-level\nls .context/.ctx.key         # pre-v0.6.0 project-local\n\n# Copy to the new location\nmkdir -p ~/.ctx && chmod 700 ~/.ctx\ncp <old-key-path> ~/.ctx/.ctx.key\nchmod 600 ~/.ctx/.ctx.key\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-2-copy-the-key-to-machine-b","level":3,"title":"Step 2: Copy the Key to Machine B","text":"<p>Use any secure transfer method. The key is always at <code>~/.ctx/.ctx.key</code>:</p> <pre><code># scp - create the target directory first\nssh user@machine-b \"mkdir -p ~/.ctx && chmod 700 ~/.ctx\"\nscp ~/.ctx/.ctx.key user@machine-b:~/.ctx/.ctx.key\n\n# Or use a password manager, USB drive, etc.\n</code></pre> <p>Set permissions on Machine B:</p> <pre><code>chmod 600 ~/.ctx/.ctx.key\n</code></pre> <p>Secure the Transfer</p> <p>The key is a raw 256-bit AES key. Anyone with the key can decrypt the scratchpad. Use an encrypted channel (SSH, password manager, vault). </p> <p>Never paste it in plaintext over email or chat.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-3-normal-pushpull-workflow","level":3,"title":"Step 3: Normal Push/Pull Workflow","text":"<p>The encrypted file is committed, so standard git sync works:</p> <pre><code># Machine A: add entries and push\nctx pad add \"staging API key: sk-test-abc123\"\ngit add .context/scratchpad.enc\ngit commit -m \"Update scratchpad\"\ngit push\n\n# Machine B: pull and read\ngit pull\nctx pad\n#   1. staging API key: sk-test-abc123\n</code></pre> <p>Both machines have the same key, so both can decrypt the same <code>.enc</code> file.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-4-read-and-write-from-either-machine","level":3,"title":"Step 4: Read and Write from Either Machine","text":"<p>Once the key is distributed, all <code>ctx pad</code> commands work identically on both machines. Entries added on Machine A are visible on Machine B after a <code>git pull</code>, and vice versa.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#step-5-handle-merge-conflicts","level":3,"title":"Step 5: Handle Merge Conflicts","text":"<p>If both machines add entries between syncs, pulling will create a merge conflict on <code>.context/scratchpad.enc</code>. Git cannot merge binary (encrypted) content automatically.</p> <p>The fastest approach is <code>ctx pad merge</code>: It reads both conflict sides, deduplicates, and writes the union:</p> <pre><code># Extract theirs to a temp file, then merge it in\ngit show :3:.context/scratchpad.enc > /tmp/theirs.enc\ngit checkout --ours .context/scratchpad.enc\nctx pad merge /tmp/theirs.enc\n\n# Done: Commit the resolved scratchpad:\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n</code></pre> <p>Alternatively, use <code>ctx pad resolve</code> to inspect both sides manually:</p> <pre><code>ctx pad resolve\n# === Ours (this machine) ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n#\n# === Theirs (incoming) ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n</code></pre> <p>Then reconstruct the merged scratchpad:</p> <pre><code># Start fresh with all entries from both sides\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\n# Mark the conflict resolved\ngit add .context/scratchpad.enc\ngit commit -m \"Resolve scratchpad merge conflict\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#merge-conflict-walkthrough","level":2,"title":"Merge Conflict Walkthrough","text":"<p>Here's a full scenario showing how conflicts arise and how to resolve them:</p> <p>1. Both machines start in sync (1 entry):</p> <pre><code>Machine A: 1. staging API key: sk-test-abc123\nMachine B: 1. staging API key: sk-test-abc123\n</code></pre> <p>2. Both add entries independently:</p> <pre><code>Machine A adds: \"check DNS after deploy\"\nMachine B adds: \"new endpoint: api.example.com/v2\"\n</code></pre> <p>3. Machine A pushes first. Machine B pulls and gets a conflict:</p> <pre><code>git pull\n# CONFLICT (content): Merge conflict in .context/scratchpad.enc\n</code></pre> <p>4. Machine B runs <code>ctx pad resolve</code>:</p> <pre><code>ctx pad resolve\n# === Ours ===\n#   1. staging API key: sk-test-abc123\n#   2. new endpoint: api.example.com/v2\n#\n# === Theirs ===\n#   1. staging API key: sk-test-abc123\n#   2. check DNS after deploy\n</code></pre> <p>5. Rebuild with entries from both sides and commit:</p> <pre><code># Clear and rebuild (or use the skill to guide you)\nctx pad add \"staging API key: sk-test-abc123\"\nctx pad add \"check DNS after deploy\"\nctx pad add \"new endpoint: api.example.com/v2\"\n\ngit add .context/scratchpad.enc\ngit commit -m \"Merge scratchpad: keep entries from both machines\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#conversational-approach","level":3,"title":"Conversational Approach","text":"<p>When working with an AI assistant, you can resolve conflicts naturally:</p> <pre><code>You: \"I have a scratchpad merge conflict. Can you resolve it?\"\n\nAgent: \"Let me extract theirs and merge it in.\"\n       [runs git show :3:.context/scratchpad.enc > /tmp/theirs.enc]\n       [runs git checkout --ours .context/scratchpad.enc]\n       [runs ctx pad merge /tmp/theirs.enc]\n       \"Merged 2 new entries (1 duplicate skipped). Want me to\n       commit the resolution?\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#tips","level":2,"title":"Tips","text":"<ul> <li>Back up the key: If you lose it, you lose access to all encrypted   entries. Store a copy in your password manager.</li> <li>One key per project: Each <code>ctx init</code> generates a unique key.   Don't reuse keys across projects.</li> <li>Keys work in worktrees: Because the key lives at <code>~/.ctx/.ctx.key</code>   (outside the project), git worktrees on the same machine share the key   automatically. No special setup needed.</li> <li>Plaintext fallback for non-sensitive projects: If encryption adds   friction and you have nothing sensitive, set <code>scratchpad_encrypt: false</code>   in <code>.ctxrc</code>. Merge conflicts become trivial text merges.</li> <li>Never commit the key: The key is stored outside the project at   <code>~/.ctx/.ctx.key</code> and should never be copied into the repository.</li> </ul>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#next-up","level":2,"title":"Next Up","text":"<p>Hook Output Patterns →: Choose the right output pattern for your Claude Code hooks.</p>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-sync/#see-also","level":2,"title":"See Also","text":"<ul> <li>Scratchpad: feature overview, all commands, when   to use scratchpad vs context files</li> <li>Persisting Decisions, Learnings, and Conventions:   for structured knowledge that outlives the scratchpad</li> </ul>","path":["Recipes","Knowledge and Tasks","Syncing Scratchpad Notes Across Machines"],"tags":[]},{"location":"recipes/scratchpad-with-claude/","level":1,"title":"Using the Scratchpad","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-problem","level":2,"title":"The Problem","text":"<p>During a session you accumulate quick notes, reminders, intermediate values, and sometimes sensitive tokens. They don't fit <code>TASKS.md</code> (not work items) or <code>DECISIONS.md</code> (not decisions). They don't have the structured fields that <code>LEARNINGS.md</code> requires.</p> <p>Without somewhere to put them, they get lost between sessions.</p> <p>How do you capture working memory that persists across sessions without polluting your structured context files?</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx pad add \"check DNS propagation after deploy\"\nctx pad         # list entries\nctx pad show 1  # print entry (pipe-friendly)\n</code></pre> <p>Entries are encrypted at rest and travel with <code>git</code>. </p> <p>Use the <code>/ctx-pad</code> skill to manage entries from inside your AI session.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx pad ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx pad</code> CLI command List all scratchpad entries <code>ctx pad show N</code> CLI command Output raw text of entry N (pipe-friendly) <code>ctx pad add</code> CLI command Add a new entry <code>ctx pad edit</code> CLI command Replace, append to, or prepend to an entry <code>ctx pad add --file</code> CLI command Ingest a file as a blob entry <code>ctx pad show N --out</code> CLI command Extract a blob entry to a file <code>ctx pad rm</code> CLI command Remove entries by stable ID (supports ranges) <code>ctx pad normalize</code> CLI command Reassign entry IDs as 1..N <code>ctx pad mv</code> CLI command Reorder entries <code>ctx pad --tag</code> CLI command Filter entries by tag <code>ctx pad tags</code> CLI command List all tags with counts <code>ctx pad import</code> CLI command Bulk-import lines from a file (or stdin) <code>ctx pad export</code> CLI command Export all blob entries to a directory <code>/ctx-pad</code> Skill Natural language interface to all pad commands","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-1-add-a-note-naturally","level":3,"title":"Step 1: Add a Note Naturally","text":"<p>You don't need to remember any syntax. Just tell your assistant what to jot down:</p> <pre><code>You: \"jot down: check DNS propagation after deploy\"\n\nAgent: \"Added to scratchpad:\n  1. check DNS propagation after deploy\"\n</code></pre> <p>Behind the scenes, the agent runs <code>ctx pad add \"check DNS propagation after deploy\"</code>.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-2-view-your-scratchpad","level":3,"title":"Step 2: View Your Scratchpad","text":"<pre><code>You: \"show my scratchpad\"\n\nAgent: \"Your scratchpad has 3 entries:\n  1. check DNS propagation after deploy\n  2. staging API endpoint: api.staging.example.com/v2\n  3. retry limit should be 5, not 3\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-3-edit-an-entry","level":3,"title":"Step 3: Edit an Entry","text":"<pre><code>You: \"update entry 2 to include the new endpoint\"\n\nAgent: \"Updated entry 2:\n  was: staging API endpoint: api.staging.example.com/v2\n  now: staging API endpoint: api.prod.example.com/v2\"\n</code></pre> <p>For partial edits, the agent can use <code>--append</code> or <code>--prepend</code>:</p> <pre><code>You: \"append the port number to entry 2\"\n\nAgent: \"Updated entry 2:\n  staging API endpoint: api.prod.example.com/v2 :8443\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-4-compose-entries","level":3,"title":"Step 4: Compose Entries","text":"<p>Use <code>ctx pad show N</code> to pipe entry content into other operations:</p> <pre><code>You: \"append the content of entry 3 to entry 1\"\n\nAgent: [runs ctx pad edit 1 --append \"$(ctx pad show 3)\"]\n       \"Done. Entry 1 is now:\n        check DNS propagation after deploy retry limit should be 5, not 3\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-5-reorder-by-priority","level":3,"title":"Step 5: Reorder by Priority","text":"<pre><code>You: \"move entry 4 to the top\"\n\nAgent: [runs ctx pad mv 4 1]\n       \"Moved entry 4 to position 1. Scratchpad reordered.\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-6-clean-up","level":3,"title":"Step 6: Clean Up","text":"<pre><code>You: \"remove entry 2, it's done\"\n\nAgent: [runs ctx pad rm 2]\n       \"Removed entry 2. 3 entries remaining.\n        (IDs are stable; remaining entries keep their IDs.)\"\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-7-store-a-file-as-a-blob","level":3,"title":"Step 7: Store a File as a Blob","text":"<p>The scratchpad can hold small files (up to 64 KB) as encrypted blob entries. The file is base64-encoded and stored alongside a label you provide:</p> <pre><code># Ingest a file: the first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# List shows the label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-8-extract-a-blob","level":3,"title":"Step 8: Extract a Blob","text":"<p>Use <code>show --out</code> to write the decoded file back to disk:</p> <pre><code># Write blob entry to a file\nctx pad show 2 --out ./recovered-deploy.yaml\n\n# Or print to stdout (for piping)\nctx pad show 2 | head -5\n</code></pre> <p>Blob entries are encrypted identically to text entries: They're just base64-encoded before encryption. The <code>--out</code> flag decodes and writes the raw bytes.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-9-bulk-import-notes","level":3,"title":"Step 9: Bulk Import Notes","text":"<p>When you have a file with many notes (one per line), import them in bulk instead of adding one at a time:</p> <pre><code># Import from a file: Each non-empty line becomes an entry\nctx pad import notes.txt\n\n# Or pipe from stdin\ngrep TODO *.go | ctx pad import -\n</code></pre> <p>All entries are written in a single encrypt/write cycle, regardless of how many lines the file contains.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-10-export-blobs-to-disk","level":3,"title":"Step 10: Export Blobs to Disk","text":"<p>Export all blob entries to a directory as individual files. Each blob's label becomes the filename:</p> <pre><code># Export to a directory (created if needed)\nctx pad export ./ideas\n\n# Preview what would be exported\nctx pad export --dry-run ./ideas\n\n# Force overwrite existing files\nctx pad export --force ./backup\n</code></pre> <p>When a file already exists, a unix timestamp is prepended to the filename to avoid collisions. Use <code>--force</code> to overwrite instead.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#step-11-tag-entries-for-organization","level":3,"title":"Step 11: Tag Entries for Organization","text":"<p>Tags let you categorize entries without any structure beyond a <code>#word</code> token in the text. Add them when creating or editing entries:</p> <pre><code>You: \"jot down: check DNS propagation #later\"\nYou: \"tag entry 2 as urgent\"\n\nAgent: [runs ctx pad edit 2 --tag urgent]\n       \"Updated entry 2.\"\n</code></pre> <p>Filter your scratchpad by tag:</p> <pre><code>You: \"show me everything tagged later\"\n\nAgent: [runs ctx pad --tag later]\n       \"  1. check DNS propagation #later\n        3. review PR feedback #later #ci\"\n</code></pre> <p>Entry IDs are stable; they don't shift when other entries are deleted, so <code>ctx pad rm 3</code> always targets the same entry regardless of deletions or active filters. Use <code>ctx pad normalize</code> to reassign IDs as 1..N.</p> <p>Exclude a tag with <code>~</code>:</p> <pre><code>ctx pad --tag ~later         # everything NOT tagged #later\nctx pad --tag later --tag ci # entries with BOTH tags (AND logic)\n</code></pre> <p>See what tags you're using:</p> <pre><code>You: \"what tags do I have?\"\n\nAgent: [runs ctx pad tags]\n       \"ci       1\n        later    2\n        urgent   1\"\n</code></pre> <p>Tags work on blob entries too; they're extracted from the label:</p> <pre><code>ctx pad add \"deploy config #prod\" --file ./deploy.yaml\nctx pad --tag prod\n#   1. deploy config #prod [BLOB]\n</code></pre>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#using-ctx-pad-in-a-session","level":2,"title":"Using <code>/ctx-pad</code> in a Session","text":"<p>Invoke the <code>/ctx-pad</code> skill first, then describe what you want in natural language. Without the skill prefix, the agent may route your request to <code>TASKS.md</code> or another context file instead of the scratchpad.</p> <pre><code>You: /ctx-pad jot down: check DNS after deploy\nYou: /ctx-pad show my scratchpad\nYou: /ctx-pad delete entry 3\n</code></pre> <p>Once the skill is active, it translates intent into commands:</p> You say (after <code>/ctx-pad</code>) What the agent does \"jot down: check DNS after deploy\" <code>ctx pad add \"check DNS after deploy\"</code> \"remember this: retry limit is 5\" <code>ctx pad add \"retry limit is 5\"</code> \"show my scratchpad\" / \"what's on my pad\" <code>ctx pad</code> \"show me entry 3\" <code>ctx pad show 3</code> \"delete the third one\" / \"remove entry 3\" <code>ctx pad rm 3</code> \"remove entries 3 through 5\" <code>ctx pad rm 3-5</code> \"renumber my scratchpad\" <code>ctx pad normalize</code> \"change entry 2 to ...\" <code>ctx pad edit 2 \"new text\"</code> \"append ' +important' to entry 3\" <code>ctx pad edit 3 --append \" +important\"</code> \"prepend 'URGENT:' to entry 1\" <code>ctx pad edit 1 --prepend \"URGENT: \"</code> \"prioritize entry 4\" / \"move to the top\" <code>ctx pad mv 4 1</code> \"import my notes from notes.txt\" <code>ctx pad import notes.txt</code> \"export all blobs to ./ideas\" <code>ctx pad export ./ideas</code> \"show entries tagged later\" <code>ctx pad --tag later</code> \"show everything except later\" <code>ctx pad --tag ~later</code> \"what tags do I have\" <code>ctx pad tags</code> \"tag entry 5 as urgent\" <code>ctx pad edit 5 --tag urgent</code> <p>When in Doubt, Use the CLI Directly</p> <p>The <code>ctx pad</code> commands work the same whether you run them yourself or let the skill invoke them. </p> <p>If the agent misroutes a request, fall back to <code>ctx pad add \"...\"</code> in your terminal.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#when-to-use-scratchpad-vs-context-files","level":2,"title":"When to Use Scratchpad vs Context Files","text":"Situation Use Temporary reminders (\"check X after deploy\") Scratchpad Session-start reminders (\"remind me next session\") <code>ctx remind</code> Working values during debugging (ports, endpoints, counts) Scratchpad Sensitive tokens or API keys (short-term storage) Scratchpad Quick notes that don't fit anywhere else Scratchpad Work items with completion tracking <code>TASKS.md</code> Trade-offs between alternatives with rationale <code>DECISIONS.md</code> Reusable lessons with context/lesson/application <code>LEARNINGS.md</code> Codified patterns and standards <code>CONVENTIONS.md</code> <p>Decision Guide</p> <ul> <li>If it has structured fields (context, rationale, lesson, application),   it belongs in a context file like <code>DECISIONS.md</code> or <code>LEARNINGS.md</code>.</li> <li>If it's a work item you'll mark done, it belongs in <code>TASKS.md</code>.</li> <li>If you want a message relayed VERBATIM at the next session start,   it belongs in <code>ctx remind</code>.</li> <li>If it's a quick note, reminder, or working value (especially if it's   sensitive or ephemeral) it belongs on the scratchpad.</li> </ul> <p>Scratchpad Is Not a Junk Drawer</p> <p>The scratchpad is for working memory, not long-term storage.</p> <p>If a note is still relevant after several sessions, promote it:</p> <p>A persistent reminder becomes a task, a recurring value becomes a convention, a hard-won insight becomes a learning.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#tips","level":2,"title":"Tips","text":"<ul> <li>Entries persist across sessions: The scratchpad is committed   (encrypted) to git, so entries survive session boundaries. Pick up   where you left off.</li> <li>Entries are numbered and reorderable: Use <code>ctx pad mv</code> to put   high-priority items at the top.</li> <li><code>ctx pad show N</code> enables unix piping: Output raw entry text   with no numbering prefix. Compose with <code>--append</code>, <code>--prepend</code>, or   other shell tools.</li> <li>Never mention the key file contents to the AI: The agent knows   how to use <code>ctx pad</code> commands but should never read or print   the encryption key (<code>~/.ctx/.ctx.key</code>) directly.</li> <li>Encryption is transparent: You interact with plaintext; the   encryption/decryption happens automatically on every read/write.</li> </ul>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#next-up","level":2,"title":"Next Up","text":"<p>Syncing Scratchpad Notes Across Machines →: Distribute encryption keys and scratchpad data across environments.</p>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scratchpad-with-claude/#see-also","level":2,"title":"See Also","text":"<ul> <li>Scratchpad: feature overview, all commands,   encryption details, plaintext override</li> <li>Persisting Decisions, Learnings, and Conventions:   for structured knowledge that outlives the scratchpad</li> <li>The Complete Session: full session lifecycle   showing how the scratchpad fits into the broader workflow</li> </ul>","path":["Recipes","Knowledge and Tasks","Using the Scratchpad"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/","level":1,"title":"Scrutinizing a Plan","text":"<p>When you have a plan and want it attacked, not validated, the <code>/ctx-plan</code> skill runs an adversarial interview. It surfaces what's weak, missing, or unexamined before you commit.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#when-to-use-it","level":2,"title":"When to Use It","text":"<ul> <li>Before starting a multi-day implementation.</li> <li>After writing a spec but before opening the first PR.</li> <li>When the team aligned suspiciously fast on a complex change.</li> <li>When you've drafted something and the obvious next step is to   hand it to a senior reviewer.</li> </ul> <p><code>/ctx-plan</code> is not a brainstorming tool. Use <code>/ctx-brainstorm</code> for that. <code>/ctx-plan</code> assumes the plan exists and asks: does it survive contact with reality?</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#stop-conditions","level":2,"title":"Stop Conditions","text":"<p>The skill runs depth-first through assumptions, tradeoffs, and failure modes. Stop when one of these is true:</p> <ul> <li>Plan converges. The bet is clear, the sequencing is justified,   and you can articulate what would falsify it.</li> <li>Plan dissolves. The interview surfaces a load-bearing   assumption you can't defend; the plan needs rework, not more   scrutiny.</li> <li>You hit information you don't have. Pause, gather it,   resume.</li> </ul> <p>If the agent loops on a single angle (asking variations of the same question), say so. The skill is designed to walk depth-first, not exhaust one branch.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#worked-example","level":2,"title":"Worked Example","text":"<pre><code>You: /ctx-plan\n\nI want to migrate our queue from RabbitMQ to NATS. I've drafted a\ntwo-week plan: spike NATS in staging, port one consumer, port the\nrest, decommission RabbitMQ.\n\nAgent: One question at a time. What breaks if your message ordering\n       guarantees aren't equivalent across the two systems?\n\nYou: NATS gives us per-subject ordering by default; we don't actually\n     need cross-subject ordering, so I think we're fine.\n\nAgent: How would you notice if you were wrong? What's the loudest\n       symptom of a stray out-of-order message in production today?\n\nYou: ...probably a duplicate notification. Which we already see\n     occasionally. So we wouldn't notice immediately.\n\nAgent: Then your migration's correctness check needs to be stronger\n       than \"no incidents.\" Before you port consumer two, what\n       evidence would convince you that consumer one's ordering\n       behavior is correct under load?\n\nYou: Right. Add a dedicated assertion test in staging that replays\n     a known-out-of-order sequence and verifies the consumer\n     dedupes correctly. Otherwise we're just hoping.\n\nAgent: Good. Next: what would change your mind about NATS itself?\n       What rejected alternative is closest to coming back?\n</code></pre> <p>The interview keeps going until the bet is articulated, the falsifiable conditions are written down, and the rejected alternatives have a recall trigger.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#output","level":2,"title":"Output","text":"<p><code>/ctx-plan</code> produces a clearer plan, not a document. Persist the deltas via:</p> <ul> <li><code>/ctx-spec</code> if the conclusions belong in a feature spec.</li> <li><code>/ctx-decision-add</code> if a tradeoff resolved into an   architectural decision.</li> <li><code>/ctx-learning-add</code> if you discovered a project-specific   gotcha during the interview.</li> </ul> <p>The skill itself is in <code>internal/assets/claude/skills/ctx-plan/SKILL.md</code>; the working contract lives there, the recipe is the on-ramp.</p>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/scrutinizing-a-plan/#see-also","level":2,"title":"See Also","text":"<ul> <li>Design Before Coding: the   brainstorming counterpart, used before a plan exists.</li> <li><code>ctx-spec</code>: scaffolds a feature spec from   the project template.</li> </ul>","path":["Scrutinizing a Plan"],"tags":[]},{"location":"recipes/session-archaeology/","level":1,"title":"Browsing and Enriching Past Sessions","text":"","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-problem","level":2,"title":"The Problem","text":"<p>After weeks of AI-assisted development you have dozens of sessions scattered across JSONL files in <code>~/.claude/projects/</code>. Finding the session where you debugged the Redis connection pool, or remembering what you decided about the caching strategy three Tuesdays ago, often means grepping raw JSON.</p> <p>There is no table of contents, no search, and no summaries.</p> <p>This recipe shows how to turn that raw session history into a browsable, searchable, and enriched journal site you can navigate in your browser.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tldr","level":2,"title":"TL;DR","text":"<p>Export and Generate</p> <pre><code>ctx journal import --all\nctx journal site --serve\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx journal ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p> <p>Enrich</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>Rebuild</p> <pre><code>ctx journal site --serve\n</code></pre> <p>Read on for what each stage does and why.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx journal source</code> Command List parsed sessions with metadata <code>ctx journal source --show</code> Command Inspect a specific session in detail <code>ctx journal import</code> Command Import sessions to editable journal Markdown <code>ctx journal site</code> Command Generate a static site from journal entries <code>ctx journal obsidian</code> Command Generate an Obsidian vault from journal entries <code>ctx journal schema check</code> Command Validate JSONL files and report schema drift <code>ctx journal schema dump</code> Command Print the embedded JSONL schema definition <code>ctx serve</code> Command Serve any zensical directory (default: journal) <code>/ctx-history</code> Skill Browse sessions inside your AI assistant <code>/ctx-journal-enrich</code> Skill Add frontmatter metadata to a single entry <code>/ctx-journal-enrich-all</code> Skill Full pipeline: import if needed, then batch-enrich","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-workflow","level":2,"title":"The Workflow","text":"<p>The session journal follows a four-stage pipeline.</p> <p>Each stage is idempotent and safe to re-run:</p> <p>By default, each stage skips entries that have already been processed.</p> <pre><code>import -> enrich -> rebuild\n</code></pre> Stage Tool What it does Skips if Where Import <code>ctx journal import --all</code> Converts session JSONL to Markdown File already exists (safe default) CLI or agent Enrich <code>/ctx-journal-enrich-all</code> Adds frontmatter, summaries, topic tags Frontmatter already present Agent only Rebuild <code>ctx journal site --build</code> Generates browsable static HTML N/A CLI only Obsidian <code>ctx journal obsidian</code> Generates Obsidian vault with wikilinks N/A CLI only <p>Where Do You Run Each Stage?</p> <p>Import (Steps 1 to 3) works equally well from the terminal or inside your AI assistant via <code>/ctx-history</code>. The CLI is fine here: the agent adds no special intelligence, it just runs the same command.</p> <p>Enrich (Step 4) requires the agent: it reads conversation content and produces structured metadata.</p> <p>Rebuild and serve (Step 5) is a terminal operation that starts a long-running server.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-1-list-your-sessions","level":3,"title":"Step 1: List Your Sessions","text":"<p>Start by seeing what sessions exist for the current project:</p> <pre><code>ctx journal source\n</code></pre> <p>Sample output:</p> <pre><code>Sessions (newest first)\n=======================\n\n  Slug                           Project   Date         Duration  Turns  Tokens\n  gleaming-wobbling-sutherland   ctx       2026-02-07   1h 23m    47     82,341\n  twinkly-stirring-kettle        ctx       2026-02-06   0h 45m    22     38,102\n  bright-dancing-hopper          ctx       2026-02-05   2h 10m    63     124,500\n  quiet-flowing-dijkstra         ctx       2026-02-04   0h 18m    11     15,230\n  ...\n</code></pre> <p>Slugs Look Cryptic?</p> <p>These auto-generated slugs (<code>gleaming-wobbling-sutherland</code>) are hard to recognize later.</p> <p>Use <code>/ctx-journal-enrich</code> to add human-readable titles, topic tags, and summaries to exported journal entries, making them easier to find.</p> <p>Filter by project or tool if you work across multiple codebases:</p> <pre><code>ctx journal source --project ctx --limit 10\nctx journal source --tool claude-code\nctx journal source --all-projects\n</code></pre>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-2-inspect-a-specific-session","level":3,"title":"Step 2: Inspect a Specific Session","text":"<p>Before exporting everything, inspect a single session to see its metadata and conversation summary:</p> <pre><code>ctx journal source --show --latest\n</code></pre> <p>Or look up a specific session by its slug, partial ID, or UUID:</p> <pre><code>ctx journal source --show gleaming-wobbling-sutherland\nctx journal source --show twinkly\nctx journal source --show abc123\n</code></pre> <p>Add <code>--full</code> to see the complete message content instead of the summary view:</p> <pre><code>ctx journal source --show --latest --full\n</code></pre> <p>This is useful for checking what happened before deciding whether to export and enrich it.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-3-import-sessions-to-the-journal","level":3,"title":"Step 3: Import Sessions to the Journal","text":"<p>Import converts raw session data into editable Markdown files in <code>.context/journal/</code>:</p> <pre><code># Import all sessions from the current project\nctx journal import --all\n\n# Import a single session\nctx journal import gleaming-wobbling-sutherland\n\n# Include sessions from all projects\nctx journal import --all --all-projects\n</code></pre> <p><code>--keep-frontmatter=false</code> Discards Enrichments</p> <p><code>--keep-frontmatter=false</code> discards enriched YAML frontmatter during regeneration.</p> <p>Back up your journal before using this flag.</p> <p>Each imported file contains session metadata (date, time, duration, model, project, git branch), a tool usage summary, and the full conversation transcript.</p> <p>Re-importing is safe. Running <code>ctx journal import --all</code> only imports new sessions: Existing files are never touched. Use <code>--dry-run</code> to preview what would be imported without writing anything.</p> <p>To re-import existing files (e.g., after a format improvement), use <code>--regenerate</code>: Conversation content is regenerated while preserving any YAML frontmatter you or the enrichment skill has added. You'll be prompted before any files are overwritten.</p> <p><code>--regenerate</code> Replaces the Markdown Body</p> <p><code>--regenerate</code> preserves YAML frontmatter but replaces the entire Markdown body with freshly generated content from the source JSONL.</p> <p>If you manually edited the conversation transcript (added notes, redacted sensitive content, restructured sections), those edits will be lost.</p> <p>BACK UP YOUR JOURNAL FIRST.</p> <p>To protect entries you've hand-edited, you can explicitly lock them:</p> <pre><code>ctx journal lock <pattern>\n</code></pre> <p>Locked entries are always skipped, regardless of flags.</p> <p>If you prefer to add <code>locked: true</code> directly in frontmatter during enrichment, run <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>:</p> <pre><code>ctx journal sync\n</code></pre> <p>See <code>ctx journal lock --help</code> and <code>ctx journal sync --help</code> for details.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-4-enrich-with-metadata","level":3,"title":"Step 4: Enrich with Metadata","text":"<p>Raw imports have timestamps and transcripts but lack the semantic metadata that makes sessions searchable: topics, technology tags, outcome status, and summaries. The <code>/ctx-journal-enrich*</code> skills add this structured frontmatter.</p> <p>Locked entries are skipped by enrichment skills, just as they are by import. Lock entries you want to protect before running batch enrichment.</p> <p>Batch enrichment (recommended):</p> <pre><code>/ctx-journal-enrich-all\n</code></pre> <p>The skill finds all unenriched entries, filters out noise (suggestion sessions, very short sessions, multipart continuations), and processes each one by extracting titles, topics, technologies, and  summaries from the conversation.</p> <p>It shows you a grouped summary before applying changes so you can scan quickly rather than reviewing one by one.</p> <p>For large backlogs (20+ entries), the skill can spawn subagents to process entries in parallel.</p> <p>Single-entry enrichment:</p> <pre><code>/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-02-06\n</code></pre> <p>Each enriched entry gets YAML frontmatter like this:</p> <pre><code>---\ntitle: \"Implement Redis caching middleware\"\ndate: 2026-02-06\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/api/middleware/cache.go\n---\n</code></pre> <p>The skill also generates a summary and can extract decisions,  learnings, and tasks mentioned during the session.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#step-5-generate-and-serve-the-site","level":3,"title":"Step 5: Generate and Serve the Site","text":"<p>With imported and enriched journal files, generate the static site:</p> <pre><code># Generate site structure only\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate, build, and serve locally\nctx journal site --serve\n</code></pre> <p>Then open <code>http://localhost:8000</code> to browse.</p> <p>The site includes a date-sorted index, individual session pages with full conversations, search (press <code>/</code>), dark mode, and enriched titles in the navigation when frontmatter exists.</p> <p>You can also serve an already-generated site without regenerating using <code>ctx serve</code> (serve-only, no regeneration).</p> <p>The site generator requires <code>zensical</code> (<code>pipx install zensical</code>).</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#where-does-agent-add-value","level":2,"title":"Where Does Agent Add Value?","text":"<p>Export, list, and show are mechanical:  The agent runs the same CLI commands you would, so you can stay  in your terminal for those.</p> <p>The agent earns its keep in enrich. It reads conversation content, understands what happened, and produces structured metadata. That is agent work, not CLI work.</p> <p>You can also ask your agent to browse sessions conversationally instead of remembering flags:</p> <pre><code>What did we work on last week?\nShow me the session about Redis.\nImport everything to the journal.\n</code></pre> <p>This is convenient but not required: <code>ctx journal source</code> gives you the same inventory.</p> <p>Where the agent genuinely helps is chaining the pipeline:</p> <pre><code>You:   What happened last Tuesday?\nAgent: Last Tuesday you worked on two sessions:\n       - bright-dancing-hopper (2h 10m): refactored the middleware\n         pipeline and added Redis caching\n       - quiet-flowing-dijkstra (18m): quick fix for a nil pointer\n         in the config loader\n       Want me to export and enrich them?\nYou:   Yes, do it.\nAgent: Exports both, enriches, then proposes frontmatter.\n</code></pre> <p>The value is staying in one context while the agent runs import -> enrich without you manually switching tools.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A typical pipeline from raw sessions to a browsable site:</p> <pre><code># Terminal: import and generate\nctx journal import --all\nctx journal site --serve\n</code></pre> <pre><code># AI assistant: enrich\n/ctx-journal-enrich-all\n</code></pre> <pre><code># Terminal: rebuild with enrichments\nctx journal site --serve\n</code></pre> <p>If your project includes <code>Makefile.ctx</code> (deployed by <code>ctx init</code>), use <code>make journal</code> to combine import and rebuild stages. Then enrich inside Claude Code, then <code>make journal</code> again to pick up enrichments.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#session-retention-and-cleanup","level":2,"title":"Session Retention and Cleanup","text":"<p>Claude Code does not keep JSONL transcripts forever. Understanding its cleanup behavior helps you avoid losing session history.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#default-behavior","level":3,"title":"Default Behavior","text":"<p>Claude Code retains session transcripts for approximately 30 days. After that, JSONL files are automatically deleted during cleanup. Once deleted, <code>ctx journal</code> can no longer see those sessions - the data is gone.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#the-cleanupperioddays-setting","level":3,"title":"The <code>cleanupPeriodDays</code> Setting","text":"<p>Claude Code exposes a <code>cleanupPeriodDays</code> setting in its configuration (<code>~/.claude/settings.json</code>) that controls retention:</p> Value Behavior <code>30</code> (default) Transcripts older than 30 days are deleted <code>60</code>, <code>90</code>, etc. Extends the retention window <code>0</code> Disables writing new transcripts entirely - not \"keep forever\" <p>Setting <code>cleanupPeriodDays</code> To 0</p> <p>Setting this to <code>0</code> does not mean \"never delete.\" It disables transcript creation altogether. No new JSONL files are written, which means <code>ctx journal</code> sees nothing new. This is rarely what you want.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#why-journal-import-matters","level":3,"title":"Why Journal Import Matters","text":"<p>The journal import pipeline (Steps 1-4 above) is your archival mechanism. Imported Markdown files in <code>.context/journal/</code> persist independently of Claude Code's cleanup cycle. Even after the source JSONL files are deleted, your journal entries remain.</p> <p>Recommendation: import regularly - weekly, or after any session worth revisiting. A quick <code>ctx journal import --all</code> takes seconds and ensures nothing falls through the 30-day window.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#quick-archival-checklist","level":3,"title":"Quick Archival Checklist","text":"<ol> <li>Run <code>ctx journal import --all</code> at least weekly</li> <li>Enrich high-value sessions with <code>/ctx-journal-enrich</code> before the    details fade from your own memory</li> <li>Lock enriched entries (<code>ctx journal lock <pattern></code>) to protect them    from accidental regeneration</li> <li>Rebuild the journal site periodically to keep it current</li> </ol>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>/ctx-history</code> inside your AI assistant. If you want to quickly check what happened in a recent session without leaving your editor, <code>/ctx-history</code> lets you browse interactively without importing.</li> <li>Large sessions may be split automatically. Sessions with 200+ messages can be split into multiple parts (<code>session-abc123.md</code>, <code>session-abc123-p2.md</code>, <code>session-abc123-p3.md</code>) with navigation links between them. The site generator can handle this.</li> <li>Suggestion sessions can be separated. Claude Code can generate short suggestion sessions for autocomplete. These may appear under a separate section in the site index, so they do not clutter your main session list.</li> <li>Your agent is a good session browser. You do not need to remember slugs, dates, or flags. Ask \"what did we do yesterday?\" or \"find the session about Redis\"  and it can map the question to recall commands.</li> </ul> <p>Journal Files Are Sensitive</p> <p>Journal files MUST be <code>.gitignore</code>d.</p> <p>Session transcripts can contain sensitive data such as file contents, commands, error messages with stack traces, and potentially API keys.</p> <p>Add <code>.context/journal/</code>, <code>.context/journal-site/</code>, and  <code>.context/journal-obsidian/</code> to your <code>.gitignore</code>.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#next-up","level":2,"title":"Next Up","text":"<p>Persisting Decisions, Learnings, and Conventions →: Record decisions, learnings, and conventions so they survive across sessions.</p>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-archaeology/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: where session saving fits in the daily workflow</li> <li>Turning Activity into Content: generating blog posts from session history</li> <li>Session Journal: full documentation of the journal system</li> <li>CLI Reference: <code>ctx</code> journal: all journal subcommands and flags</li> <li>CLI Reference: <code>ctx</code> serve: serve-only (no regeneration)</li> <li>Context Files: the <code>.context/</code> directory structure</li> </ul>","path":["Recipes","Sessions","Browsing and Enriching Past Sessions"],"tags":[]},{"location":"recipes/session-ceremonies/","level":1,"title":"Session Ceremonies","text":"","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#the-problem","level":2,"title":"The Problem","text":"<p>Sessions have two critical moments: the start and the end.</p> <ul> <li>At the start, you need the agent to load context and confirm it knows what is going on. </li> <li>At the end, you need to capture whatever the session produced before the conversation disappears.</li> </ul> <p>Most <code>ctx</code> skills work conversationally: \"jot down: check DNS after deploy\" is as good as <code>/ctx-pad add \"check DNS after deploy\"</code>. But session boundaries are different. They are well-defined moments with specific requirements, and partial execution is costly.</p> <p>If the agent only half-loads context at the start, it works from stale assumptions. If it only half-persists at the end, learnings and decisions are lost.</p> <p>This Is One of the Few Times Being Explicit Matters</p> <p>Session ceremonies are the two bookend skills that mark these boundaries. </p> <p>They are the exception to the conversational rule:</p> <p>Invoke <code>/ctx-remember</code> and <code>/ctx-wrap-up</code>  explicitly as slash commands.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tldr","level":2,"title":"TL;DR","text":"<p>Start: <code>/ctx-remember</code>: load context, get a structured readback.</p> <p>End: <code>/ctx-wrap-up</code>: review session, propose candidates, persist approved items.</p> <p>Use the slash commands, not conversational triggers, for completeness.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#explicit-invocation-matters","level":2,"title":"Explicit Invocation Matters","text":"<p>Most <code>ctx</code> skills encourage natural language. These two are different:</p> <p>Well-defined moments: Sessions have clear boundaries. A slash command marks the boundary unambiguously.</p> <p>Ambiguity risk: \"Do you remember?\" could mean many things. <code>/ctx-remember</code> means exactly one thing: load context and present a structured readback.</p> <p>Completeness: Conversational triggers risk partial execution. The agent might load some files but skip the session history, or persist one learning but forget to check for uncommitted changes. The slash command runs the full ceremony.</p> <p>Muscle memory: Typing <code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end becomes a habit, like opening and closing braces.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-remember</code> Skill Load context and present structured readback <code>/ctx-wrap-up</code> Skill Gather session signal, propose and persist context <code>/ctx-commit</code> Skill Commit with context capture (offered by wrap-up) <code>ctx agent</code> CLI Load token-budgeted context packet <code>ctx journal source</code> CLI List recent sessions <code>ctx add</code> CLI Persist learnings, decisions, conventions, tasks","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-start-ctx-remember","level":2,"title":"Session Start: <code>/ctx-remember</code>","text":"<p>Invoke at the beginning of every session:</p> <pre><code>/ctx-remember\n</code></pre> <p>The skill silently:</p> <ol> <li>Loads the context packet via <code>ctx agent --budget 4000</code></li> <li>Reads <code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code></li> <li>Checks recent sessions via <code>ctx journal source --limit 3</code></li> </ol> <p>Then presents a structured readback with four sections:</p> <ul> <li>Last session: topic, date, what was accomplished</li> <li>Active work: pending and in-progress tasks</li> <li>Recent context: 1-2 relevant decisions or learnings</li> <li>Next step: suggestion or question about what to focus on</li> </ul> <p>The readback should feel like recall, not a file system tour. If the agent says \"Let me check if there are files...\" instead of a confident summary, the skill is not working correctly.</p> <p>What about 'do you remember?'</p> <p>The conversational trigger still works. But <code>/ctx-remember</code> guarantees the full ceremony runs: </p> <ul> <li>context packet, </li> <li>file reads, </li> <li>session history,</li> <li>and all four readback sections. </li> </ul> <p>The conversational version may cut corners.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#session-end-ctx-wrap-up","level":2,"title":"Session End: <code>/ctx-wrap-up</code>","text":"<p>Invoke before ending a session where meaningful work happened:</p> <pre><code>/ctx-wrap-up\n</code></pre> <p>The skill runs four phases:</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-1-gather-signal","level":3,"title":"Phase 1: Gather Signal","text":"<p>Silently checks <code>git diff --stat</code>, recent commits, and scans the conversation for themes: architectural choices, gotchas, patterns established, follow-up work identified.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-2-propose-candidates","level":3,"title":"Phase 2: Propose Candidates","text":"<p>Presents a structured list grouped by type:</p> <pre><code>## Session Wrap-Up\n\n### Learnings (2 candidates)\n1. **PyMdownx details extension breaks pre/code rendering**\n   - Context: Journal site showed broken code blocks inside details tags\n   - Lesson: details extension wraps content in <details> HTML, which\n     interferes with <pre><code> rendering\n   - Application: Use fenced code blocks instead of indented code inside\n     admonitions when details extension is active\n\n2. **Hook subprocesses cannot propagate env vars**\n   - Context: Set env var in PreToolUse hook, invisible in main session\n   - Lesson: Hooks execute in child processes; env changes don't propagate\n   - Application: Use tombstone files for hook-to-session communication\n\n### Decisions (1 candidate)\n1. **File-based cooldown tokens over env vars**\n   - Context: Need session-scoped cooldown for ctx agent auto-loading\n   - Rationale: File tokens survive across processes, simpler than IPC\n   - Consequence: Tombstone files accumulate in /tmp; need TTL cleanup\n\nPersist all? Or select which to keep?\n</code></pre> <p>Each candidate has complete structured fields, not just a title. Empty categories are omitted.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-3-persist","level":3,"title":"Phase 3: Persist","text":"<p>After you approve (all, some, or modified), the skill runs the appropriate <code>ctx add</code> commands and reports results.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#nudge-suppression","level":3,"title":"Nudge Suppression","text":"<p>After persisting, the skill marks the session as wrapped up via <code>ctx system mark-wrapped-up</code>. This suppresses context checkpoint nudges for 2 hours so the wrap-up ceremony itself does not trigger noisy reminders.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#phase-4-commit-offer","level":3,"title":"Phase 4: Commit Offer","text":"<p>If there are uncommitted changes, offers to run <code>/ctx-commit</code>. Does not auto-commit.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#when-to-skip","level":2,"title":"When to Skip","text":"<p>Not every session needs ceremonies.</p> <p>Skip <code>/ctx-remember</code> when:</p> <ul> <li>You are doing a quick one-off lookup (reading a file, checking a value)</li> <li>Context was already loaded this session via <code>/ctx-agent</code></li> <li>You are continuing immediately after a previous session and context is   still fresh</li> </ul> <p>Skip <code>/ctx-wrap-up</code> when:</p> <ul> <li>Nothing meaningful happened (only read files, answered a question)</li> <li>You already persisted everything manually during the session</li> <li>The session was trivial (typo fix, quick config change)</li> </ul> <p>A good heuristic: if the session produced something a future session should know about, run <code>/ctx-wrap-up</code>. If not, just close.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#quick-reference","level":2,"title":"Quick Reference","text":"<pre><code># Session start\n/ctx-remember\n\n# ... do work ...\n\n# Session end\n/ctx-wrap-up\n</code></pre> <p>That is the complete ceremony. Two commands, bookending your session.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#relationship-to-other-skills","level":2,"title":"Relationship to Other Skills","text":"Skill When Purpose <code>/ctx-remember</code> Session start Load and confirm context <code>/ctx-reflect</code> Mid-session breakpoints Checkpoint at milestones <code>/ctx-wrap-up</code> Session end Full session review and persist <code>/ctx-commit</code> After completing work Commit with context capture <p><code>/ctx-reflect</code> is for mid-session checkpoints. <code>/ctx-wrap-up</code> is for end-of-session: it is more thorough, covers the full session arc, and includes the commit offer. If you already ran <code>/ctx-reflect</code> recently, <code>/ctx-wrap-up</code> avoids proposing the same candidates again.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#tips","level":2,"title":"Tips","text":"<ul> <li>Make it a habit: The value of ceremonies compounds over sessions. Each <code>/ctx-wrap-up</code> makes the next <code>/ctx-remember</code> richer.</li> <li>Trust the candidates: The agent scans the full conversation. It often catches learnings you forgot about.</li> <li>Edit before approving: If a proposed candidate is close but not quite right, tell the agent what to change. Do not settle for a vague learning when a precise one is possible.</li> <li>Do not force empty ceremonies: If <code>/ctx-wrap-up</code> finds nothing worth persisting, that is fine. A session that only read files and answered questions does not need artificial learnings.</li> </ul>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#next-up","level":2,"title":"Next Up","text":"<p>Browsing and Enriching Past Sessions →: Export session history to a browsable journal and enrich entries with metadata.</p>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-ceremonies/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: the full session workflow   that ceremonies bookend</li> <li>Persisting Decisions, Learnings, and Conventions:   deep dive on what gets persisted during wrap-up</li> <li>Detecting and Fixing Drift: keeping context files   accurate between ceremonies</li> <li>Pausing Context Hooks: skip ceremonies entirely   for quick tasks that don't need them</li> </ul>","path":["Recipes","Sessions","Session Ceremonies"],"tags":[]},{"location":"recipes/session-changes/","level":1,"title":"Reviewing Session Changes","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-changed-while-you-were-away","level":2,"title":"What Changed While You Were Away?","text":"<p>Between sessions, teammates commit code, context files get updated, and decisions pile up. <code>ctx change</code> gives you a single-command summary of everything that moved since your last session.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#quick-start","level":2,"title":"Quick Start","text":"<pre><code># Auto-detects your last session and shows what changed\nctx change\n\n# Check what changed in the last 48 hours\nctx change --since 48h\n\n# Check since a specific date\nctx change --since 2026-03-10\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx change</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#how-reference-time-works","level":2,"title":"How Reference Time Works","text":"<p><code>ctx change</code> needs a reference point to compare against. It tries these sources in order:</p> <ol> <li><code>--since</code> flag: explicit duration (<code>24h</code>, <code>72h</code>) or date    (<code>2026-03-10</code>, RFC3339 timestamp)</li> <li>Session markers: <code>ctx-loaded-*</code> files in <code>.context/state/</code>;    picks the second-most-recent (your previous session start)</li> <li>Event log: last <code>context-load-gate</code> event from    <code>.context/state/events.jsonl</code></li> <li>Fallback: 24 hours ago</li> </ol> <p>The marker-based detection means <code>ctx change</code> usually just works without any flags: it knows when you last loaded context and shows everything after that.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#what-it-reports","level":2,"title":"What It Reports","text":"","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#context-file-changes","level":3,"title":"Context File Changes","text":"<p>Any <code>.md</code> file in <code>.context/</code> modified after the reference time:</p> <pre><code>### Context File Changes\n- `TASKS.md` - modified 2026-03-11 14:30\n- `DECISIONS.md` - modified 2026-03-11 09:15\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#code-changes","level":3,"title":"Code Changes","text":"<p>Git activity since the reference time:</p> <pre><code>### Code Changes\n- **12 commits** since reference point\n- **Latest**: Fix journal enrichment ordering\n- **Directories touched**: internal, docs, specs\n- **Authors**: jose, claude\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#integrating-into-session-start","level":2,"title":"Integrating into Session Start","text":"<p>Pair <code>ctx change</code> with the <code>/ctx-remember</code> ceremony for a complete session-start picture:</p> <pre><code># 1. Load context (this also creates the session marker)\nctx agent --budget 4000\n\n# 2. See what changed since your last session\nctx change\n</code></pre> <p>Or script it:</p> <pre><code># .context/hooks/session-start.sh\nctx agent --budget 4000\necho \"---\"\nctx change\n</code></pre>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#team-workflows","level":2,"title":"Team Workflows","text":"<p>When multiple people share a <code>.context/</code> directory, <code>ctx change</code> shows who changed what:</p> <pre><code># After pulling from remote\ngit pull\nctx change --since 72h\n</code></pre> <p>This surfaces context file changes from teammates that you might otherwise miss in the commit log.</p>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-changes/#tips","level":2,"title":"Tips","text":"<ul> <li>No changes? If nothing shows up, the reference time might be   wrong. Use <code>--since 48h</code> to widen the window.</li> <li>Works without git. Context file changes are detected by   filesystem mtime, not git. Code changes require git.</li> <li>Hook integration. The <code>context-load-gate</code> hook writes the   session marker that <code>ctx change</code> uses for auto-detection. If   you're not using the <code>ctx</code> plugin, markers won't exist and it falls   back to the event log or 24h window.</li> </ul>","path":["Reviewing Session Changes"],"tags":[]},{"location":"recipes/session-lifecycle/","level":1,"title":"The Complete Session","text":"","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-problem","level":2,"title":"The Problem","text":"<p>\"What does a full <code>ctx</code> session look like from start to finish?\"</p> <p>You have <code>ctx</code> installed and your <code>.context/</code> directory initialized, but the individual commands and skills feel disconnected.</p> <p>How do they fit together into a coherent workflow?</p> <p>This recipe walks through a complete session, from opening your editor to persisting context before you close it, so you can see how each piece connects.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tldr","level":2,"title":"TL;DR","text":"<ol> <li>Load: <code>/ctx-remember</code>: load context, get structured readback.</li> <li>Orient: <code>/ctx-status</code>: check file health and token usage.</li> <li>Pick: <code>/ctx-next</code>: choose what to work on.</li> <li>Work: implement, test, iterate.</li> <li>Commit: <code>/ctx-commit</code>: commit and capture decisions/learnings.</li> <li>Reflect: <code>/ctx-reflect</code>: identify what to persist (at milestones)</li> <li>Wrap up: <code>/ctx-wrap-up</code>: end-of-session ceremony.</li> </ol> <p>Read on for the full walkthrough with examples.</p> <p>Before You Start: Activate the Project</p> <p><code>ctx</code> commands (and the skills that call them) require <code>CTX_DIR</code> to be declared for the shell you're working in; <code>ctx</code> does not walk the filesystem to find <code>.context/</code>. Once per shell (or via your shell rc / direnv):</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>If you skip this, every skill below will surface an error naming the fix. See Activating a Context Directory for the full recipe.</p> <p>What Is a Readback?</p> <p>A readback is a structured summary where the agent plays back what it knows:</p> <ul> <li>last session,</li> <li>active tasks,</li> <li>recent decisions.</li> </ul> <p>This way, you can confirm it loaded the right context.</p> <p>The term \"readback\" comes from aviation, where pilots repeat instructions back to air traffic control to confirm they heard correctly.</p> <p>Same idea in <code>ctx</code>: The agent tells you what it \"thinks\" is going on, and you correct anything that's off before the work begins.</p> <ul> <li>Last session: topic, date, what was accomplished</li> <li>Active work: pending and in-progress tasks</li> <li>Recent context: 1-2 decisions or learnings that matter now</li> <li>Next step: suggestion or question about what to focus on</li> </ul>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx status</code> CLI command Quick health check on context files <code>ctx agent</code> CLI command Load token-budgeted context packet <code>ctx journal source</code> CLI command List previous sessions <code>ctx journal source --show</code> CLI command Inspect a specific session in detail <code>/ctx-remember</code> Skill Recall project context with structured readback <code>/ctx-agent</code> Skill Load full context packet inside the assistant <code>/ctx-status</code> Skill Show context summary with commentary <code>/ctx-next</code> Skill Suggest what to work on with rationale <code>/ctx-commit</code> Skill Commit code and prompt for context capture <code>/ctx-reflect</code> Skill Structured reflection checkpoint <code>/ctx-history</code> Skill Browse session history inside your AI assistant","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#the-workflow","level":2,"title":"The Workflow","text":"<p>The session lifecycle has seven steps. You will not always use every step (for example, a quick bugfix might skip reflection, and a research session might skip committing), but the full arc looks like this:</p> <p>Load context > Orient > Pick a Task > Work > Commit > Reflect</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-1-load-context","level":3,"title":"Step 1: Load Context","text":"<p>Start every session by loading what you know. The fastest way is a single prompt:</p> <pre><code>Do you remember what we were working on?\n</code></pre> <p>This triggers the <code>/ctx-remember</code> skill. Behind the scenes, the assistant runs <code>ctx agent --budget 4000</code>, reads the files listed in the context packet (<code>TASKS.md</code>, <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>CONVENTIONS.md</code>), checks <code>ctx journal source --limit 3</code> for recent sessions, and then presents a structured readback.</p> <p>The readback should feel like a recall, not a file system tour. If you see \"Let me check if there are files...\" instead of a confident summary, the context system is not loaded properly.</p> <p>As an alternative, if you want raw data instead of a readback, run <code>ctx status</code> in your terminal or invoke <code>/ctx-status</code> for a summarized health check showing file counts, token usage, and recent activity.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-2-orient","level":3,"title":"Step 2: Orient","text":"<p>After loading context, verify you understand the current state.</p> <pre><code>/ctx-status\n</code></pre> <p>The status output shows which context files are populated, how many tokens they consume, and which files were recently modified. Look for:</p> <ul> <li>Empty core files: <code>TASKS.md</code> or <code>CONVENTIONS.md</code> with no content means   the context is sparse</li> <li>High token count (over 30k): the context is bloated and might   need <code>ctx compact</code></li> <li>No recent activity: files may be stale and need updating</li> </ul> <p>If the status looks healthy and the readback from Step 1 gave you enough context, skip ahead.</p> <p>If something seems off (stale tasks, missing decisions...), spend a minute reading the relevant file before proceeding.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"<p>With context loaded, choose a task. You can pick one yourself, or ask the assistant to recommend:</p> <pre><code>/ctx-next\n</code></pre> <p>The skill reads <code>TASKS.md</code>, checks recent sessions to avoid re-suggesting completed work, and presents 1-3 ranked recommendations with rationale.</p> <p>It prioritizes in-progress tasks over new starts (finishing is better than starting), respects explicit priority tags, and favors momentum: continuing a thread from a recent session is cheaper than context-switching.</p> <p>If you already know what you want to work on, state it directly:</p> <pre><code>Let's work on the session enrichment feature.\n</code></pre>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-4-do-the-work","level":3,"title":"Step 4: Do the Work","text":"<p>This is the main body of the session: write code, fix bugs, refactor, research: whatever the task requires.</p> <p>During this phase, a few <code>ctx</code>-specific patterns help:</p> <p>Check decisions before choosing: when you face a design choice, check if a prior decision covers it.</p> <pre><code>Is this consistent with our decisions?\n</code></pre> <p>Constrain scope: keep the assistant focused on the task at hand.</p> <pre><code>Only change files in internal/cli/session/. Nothing else.\n</code></pre> <p>Use <code>/ctx-implement</code> for multistep plans: if the task has multiple steps, this skill executes them one at a time with build/test verification between each step.</p> <p>Context monitoring runs automatically: the <code>check-context-size</code> hook monitors context capacity at adaptive intervals. Early in a session it stays silent. After 16+ prompts it starts monitoring, and past 30 prompts it checks frequently. If context capacity is running high, it will suggest saving unsaved work. No manual invocation is needed.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-5-commit-with-context","level":3,"title":"Step 5: Commit with Context","text":"<p>When the work is ready, use the context-aware commit instead of raw <code>git commit</code>:</p> <pre><code>/ctx-commit\n</code></pre> <p>The Agent May Recommend Committing</p> <p>You do not always need to invoke <code>/ctx-commit</code> explicitly.</p> <p>After a commit, the agent may proactively offer to capture context:</p> <p>\"We just made a trade-off there. Want me to record it as a decision?\"</p> <p>This is normal: The Agent Playbook encourages persisting at milestones, and a commit is a natural milestone.</p> <p>As an alternative, you can ask the assistant \"can we commit this?\" and it will pick up the <code>/ctx-commit</code> skill for you.</p> <p>The skill runs a pre-commit build check (for Go projects, <code>go build</code>), reviews the staged changes, drafts a commit message focused on \"why\" rather than \"what\", and then commits.</p> <p>After the commit succeeds, it prompts you:</p> <pre><code>**Any context to capture?**\n\n- **Decision**: Did you make a design choice or trade-off?\n- **Learning**: Did you hit a gotcha or discover something?\n- **Neither**: No context to capture; we are done.\n</code></pre> <p>If you made a decision, the skill records it with <code>ctx decision add</code>. If you learned something, it records it with <code>ctx learning add</code> including context, lesson, and application fields. This is the bridge between committing code and remembering why the code looks the way it does.</p> <p>If source code changed in areas that affect documentation, the skill also offers to check for doc drift.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-6-reflect","level":3,"title":"Step 6: Reflect","text":"<p>At natural breakpoints (after finishing a feature, resolving a complex bug, or before switching tasks) pause to reflect:</p> <pre><code>/ctx-reflect\n</code></pre> <p>Agents Reflect at Milestones</p> <p>Agents often reflect without explicit invocation.</p> <p>After completing a significant piece of work, the agent may naturally surface items worth persisting:</p> <p>\"We discovered that <code>$PPID</code> resolves differently inside hooks. Should I save that as a learning?\"</p> <p>This is the agent following the Work-Reflect-Persist cycle from the Agent Playbook.</p> <p>You do not need to say <code>/ctx-reflect</code> for this to happen; the agent treats milestones as reflection triggers on its own.</p> <p>The skill works through a checklist: learnings discovered, decisions made, tasks completed or created, and whether there are items worth persisting. It then presents a summary with specific items to persist, each with the exact command to run:</p> <pre><code>I would suggest persisting:\n\n- **Learning**: `$PPID` in PreToolUse hooks resolves to the Claude Code PID\n  `ctx learning add --context \"...\" --lesson \"...\" --application \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n- **Task**: mark \"Add cooldown to ctx agent\" as done\n- **Decision**: tombstone-based cooldown with 10m default\n  `ctx decision add \"...\" --session-id abc12345 --branch main --commit 68fbc00a`\n\nWant me to persist any of these?\n</code></pre> <p>The skill asks before persisting anything. You choose what to keep.</p> <p>Not every commit needs reflection. A typo fix does not. But when you have been debugging for an hour and finally understand the root cause, that is worth a reflection checkpoint.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#step-7-persist-before-ending","level":3,"title":"Step 7: Persist Before Ending","text":"<p>Before ending the session, run the wrap-up ceremony to capture outstanding learnings, decisions, conventions, and tasks:</p> <pre><code>/ctx-wrap-up\n</code></pre> <p>Ceremony Skills</p> <p><code>/ctx-remember</code> at session start and <code>/ctx-wrap-up</code> at session end are ceremony skills: Invoke them as explicit slash commands for completeness, not conversationally.</p> <p>See Session Ceremonies for details on why these two skills are the exception to the conversational rule.</p> <p>The skill gathers signal from <code>git diff</code>, recent commits, and the conversation, then proposes structured candidates for your approval. After you select which to keep, it persists them via <code>ctx add</code> and offers <code>/ctx-commit</code> if uncommitted changes remain.</p> <p>As its final step, <code>/ctx-wrap-up</code> writes a handover under <code>.context/handovers/<TS>-<slug>.md</code> — a former-agent-to-next-agent note with a past-tense summary and a future-tense \"first action for the next session\". The filename is timestamped so concurrent agent runs never overwrite each other. The next <code>/ctx-remember</code> reads this file as the authoritative recall surface; skipping <code>/ctx-wrap-up</code> means the next session has no handover to read and recall degrades to probabilistic reconstruction from canonical files plus journal.</p> <p>Session transcripts are automatically captured by Claude Code and can be browsed later with <code>ctx journal source</code> and <code>ctx journal source --show</code>.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#conversational-session-flow","level":2,"title":"Conversational Session Flow","text":"<p>The steps above describe the mechanics. In practice, a session with a <code>ctx</code>-aware agent feels more like a conversation than a command sequence.</p> <p>The agent knows the lifecycle and moves through it naturally, often without explicit invocation. Here is a realistic example:</p> <p>You: Do you remember what we were working on?</p> <p>Agent (loads context silently, presents structured readback):</p> <pre><code>Last session (Feb 7, caching-layer): we implemented the in-memory cache with\nTTL expiration and wrote tests for the eviction policy. \n\nYou have 3 pending tasks; the biggest one is adding Redis as a backend.\n\nRecent decision:<br>\nwe chose LRU over LFU because the access pattern is mostly recency-based.\n\nWant to continue with the Redis backend, or tackle something else?\n</code></pre> <p>You: Let's work on the Redis caching backend.</p> <p>Agent (picks up the task, begins implementation):</p> <pre><code>Got it. I see the task in TASKS.md: \"Add Redis backend for cache layer.\"\n\nI will start with the connection pool setup. Only touching\n`internal/cache/redis/` for now.\n</code></pre> <p>(The agent works... writes code, runs tests, iterates.)</p> <p>Agent (after implementing the connection pool and choosing a retry strategy):</p> <pre><code>The connection pool is working. I went with exponential backoff with jitter\nfor retries instead of fixed intervals; it handles thundering herd better\nunder reconnection storms. That is a design choice worth recording. \n\nWant me to save it as a decision?\n</code></pre> <p>You: Yes, go ahead.</p> <p>Agent:</p> <pre><code>Done. Added to DECISIONS.md: \"Exponential backoff with jitter for Redis\nreconnection, to avoid thundering herd on pool recovery.\"\n</code></pre> <p>(More work happens... the agent implements the cache read/write paths.)</p> <p>You: What else did we learn during this session?</p> <p>Agent:</p> <pre><code>A few things worth capturing:\n\n* **Learning**: Redis `SET` with `NX` and `PX` flags handles atomic\n  set-if-not-exists with TTL in a single call. No need for a separate\n  `EXPIRE` command. We initially did it in two calls and hit a race condition.\n* **Learning**: The `go-redis` library's `PoolSize` default is 10 per CPU.\n  For our workload, explicit sizing at 20 was more predictable.\n* **Task update**: The connection pool subtask is done; the read/write path\n  is implemented but needs integration tests.\n\nWant me to persist these?\n</code></pre> <p>You: Yes. And let's wrap up.</p> <p>Agent (reflects and persists... all in one flow):</p> <pre><code>Persisted:\n\n* 2 learnings added to LEARNINGS.md\n* Marked \"connection pool setup\" done in TASKS.md\n* Added \"write integration tests for Redis cache\" as a new task\n\nContext is up to date. You are good to close.\n</code></pre> <p>Notice What Happened</p> <p>In the above workflow, the user never typed <code>/ctx-reflect</code> or <code>ctx learning add</code>.</p> <p>The agent moved through Load, Orient, Pick, Work, Commit, and Reflect driven by natural conversation.</p> <p>\"Let's wrap up\" was enough to trigger the full reflect-and-persist flow.</p> <p>The agent surfaced persist-worthy items at milestones: after a design choice, after discovering a gotcha: without waiting to be asked.</p> <p>This is the intended experience. </p> <p>The commands and skills still exist for when you want precise control, but  the agent is a proactive partner in the lifecycle, not a passive executor  of slash commands.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>Quick-reference checklist for a complete session:</p> <ul> <li> Load: <code>/ctx-remember</code>: load context and confirm readback</li> <li> Orient: <code>/ctx-status</code>: check file health and token usage</li> <li> Pick: <code>/ctx-next</code>: choose what to work on</li> <li> Work: implement, test, iterate (scope with \"only change X\")</li> <li> Commit: <code>/ctx-commit</code>: commit and capture decisions/learnings</li> <li> Reflect: <code>/ctx-reflect</code>: identify what to persist (at milestones)</li> <li> Wrap up: <code>/ctx-wrap-up</code>: end-of-session ceremony</li> </ul> <p>Conversational equivalents: you can drive the same lifecycle with plain language:</p> Step Slash command Natural language Load <code>/ctx-remember</code> \"Do you remember?\" / \"What were we working on?\" Orient <code>/ctx-status</code> \"How's our context looking?\" Pick <code>/ctx-next</code> \"What should we work on?\" / \"Let's do the caching task\" Work (none) \"Only change files in internal/cache/\" Commit <code>/ctx-commit</code> \"Commit this\" / \"Ship it\" Reflect <code>/ctx-reflect</code> \"What did we learn?\" / (agent offers at milestones) Wrap up <code>/ctx-wrap-up</code> (use the slash command for completeness) <p>The agent understands both columns.</p> <p>In practice, most sessions use a mix:</p> <ul> <li>Explicit Commands when you want precision;</li> <li>Natural Language when you want flow and agentic autonomy.</li> </ul> <p>The agent will also initiate steps on its own (particularly \"Reflect\") when it recognizes a milestone.</p> <p>Short sessions (quick bugfix) might only use: Load, Work, Commit.</p> <p>Long sessions should Reflect after each major milestone and persist learnings and decisions before ending.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#tips","level":2,"title":"Tips","text":"<p>Persist early if context is running low. A hook monitors context capacity and notifies you when it gets high, but do not wait for the notification. If you have been working for a while and have unpersisted learnings, persist proactively.</p> <p>Browse previous sessions by topic. If you need context from a prior session, <code>ctx journal source --show auth</code> will match by keyword. You do not need to remember the exact date or slug.</p> <p>Reflection is optional but valuable. You can skip <code>/ctx-reflect</code> for small changes, but always persist learnings and decisions before ending a session where you did meaningful work. These are what the next session loads.</p> <p>Let the hook handle context loading. The <code>PreToolUse</code> hook runs <code>ctx agent</code> automatically with a cooldown, so context loads on first tool use without you asking. The <code>/ctx-remember</code> prompt at session start is for your benefit (to get a readback), not because the assistant needs it.</p> <p>The agent is a proactive partner, not a passive tool. A <code>ctx</code>-aware agent follows the Agent Playbook: it watches for milestones (completed tasks, design decisions, discovered gotchas) and offers to persist them without being asked. If you finish a tricky debugging session, it may say \"That root cause is worth saving as a learning. Want me to record it?\" before you think to ask. This is by design.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#next-up","level":2,"title":"Next Up","text":"<p>Session Ceremonies →: The two bookend rituals for every session: <code>/ctx-remember</code> at the start, <code>/ctx-wrap-up</code> at the end.</p>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-lifecycle/#see-also","level":2,"title":"See Also","text":"<ul> <li>Session Ceremonies: why <code>/ctx-remember</code> and   <code>/ctx-wrap-up</code> are explicit slash commands, not conversational</li> <li>CLI Reference: full documentation for all <code>ctx</code> commands</li> <li>Prompting Guide: effective prompts for ctx-enabled projects</li> <li>Tracking Work Across Sessions: deep dive on task management</li> <li>Persisting Decisions, Learnings, and Conventions:   deep dive on knowledge capture</li> <li>Detecting and Fixing Drift: keeping context files accurate</li> <li>Pausing Context Hooks: shortcut the full lifecycle   for quick tasks that don't need ceremony overhead</li> </ul>","path":["Recipes","Sessions","The Complete Session"],"tags":[]},{"location":"recipes/session-pause/","level":1,"title":"Pausing Context Hooks","text":"","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#the-problem","level":2,"title":"The Problem","text":"<p>Not every session needs the full ceremony. Quick investigations, one-off questions, small fixes unrelated to active project work: These tasks don't benefit from persistence nudges, ceremony reminders, or knowledge checks. Every hook still fires, consuming tokens and attention on work that won't produce learnings or decisions worth capturing.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tldr","level":2,"title":"TL;DR","text":"Command What it does <code>ctx hook pause</code> or <code>/ctx-pause</code> Silence all nudge hooks for this session <code>ctx hook resume</code> or <code>/ctx-resume</code> Restore normal hook behavior <p>Pause is session-scoped: It only affects the current session. Other sessions (same project, different terminal) are unaffected.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-gets-paused","level":2,"title":"What Gets Paused","text":"<p>All nudge and reminder hooks go silent:</p> <ul> <li>Context size checkpoints</li> <li>Ceremony adoption nudges</li> <li>Persistence reminders</li> <li>Journal maintenance reminders</li> <li>Knowledge growth nudges</li> <li>Map staleness nudges</li> <li>Version update nudges</li> <li>Resource pressure warnings</li> <li>QA reminders</li> <li>Post-commit nudges</li> <li>Specs nudges</li> <li>Backup age warnings</li> <li>Context load gate</li> <li>Pending reminders relay</li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#what-still-fires","level":2,"title":"What Still Fires","text":"<p>Security hooks always run, even when paused:</p> <ul> <li><code>block-non-path-ctx</code>: prevents <code>./ctx</code> invocations</li> <li><code>block-dangerous-commands</code>: blocks <code>sudo</code>, force push, etc.</li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#workflow","level":2,"title":"Workflow","text":"<pre><code># 1. Session starts: Context loads normally.\n\n# 2. You realize this is a quick task\nctx hook pause\n\n# 3. Work without interruption: hooks are silent\n\n# 4. Session evolves into real work? Resume first\nctx hook resume\n\n# 5. Now wrap up normally\n# /ctx-wrap-up\n</code></pre>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#graduated-reminder","level":2,"title":"Graduated Reminder","text":"<p>Paused hooks aren't completely invisible. A minimal indicator appears so you always know the state:</p> Paused turns What you see 1-5 <code>ctx:paused</code> 6+ <code>ctx:paused (N turns): resume with /ctx-resume</code> <p>This prevents the \"forgot I paused\" problem during long sessions.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#tips","level":2,"title":"Tips","text":"<ul> <li> <p>Resume before wrapping up. If your quick task turns into real work,   resume hooks before running <code>/ctx-wrap-up</code>. The wrap-up ceremony needs   active hooks to capture learnings properly.</p> </li> <li> <p>Initial context load is unaffected. The ~8k token startup injection   (CLAUDE.md, playbook, constitution) happens before any command runs.   Pause only affects hooks that fire during the session.</p> </li> <li> <p>Use for quick investigations. Debugging a stack trace? Checking a   git log? Answering a colleague's question? Pause, do the work, close   the session. No ceremony needed.</p> </li> <li> <p>Don't use for real work. If you're implementing features, fixing   bugs, or making decisions: keep hooks active. The nudges exist to   prevent context loss.</p> </li> </ul>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-pause/#see-also","level":2,"title":"See Also","text":"<p>See also: Session Ceremonies: the bookend rituals that pause lets you skip when they aren't needed.</p> <p>See also: Customizing Hook Messages: if you want to change what hooks say rather than silencing them entirely.</p> <p>See also: The Complete Session: the full session workflow that pause shortcuts for quick tasks.</p>","path":["Recipes","Sessions","Pausing Context Hooks"],"tags":[]},{"location":"recipes/session-reminders/","level":1,"title":"Session Reminders","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-problem","level":2,"title":"The Problem","text":"<p>You're deep in a session and realize: \"I need to refactor the swagger definitions next time.\" You could add a task, but this isn't a work item: it's a note to future-you. You could jot it on the scratchpad, but scratchpad entries don't announce themselves.</p> <p>How do you leave a message that your next session opens with?</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx remind \"refactor the swagger definitions\"\nctx remind list\nctx remind dismiss 1       # or batch: ctx remind dismiss 1 3-5\n</code></pre> <p>Reminders surface automatically at session start: VERBATIM, every session, until you dismiss them.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx remind ...</code> fails with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx remind</code> CLI command Add a reminder (default action) <code>ctx remind list</code> CLI command Show all pending reminders <code>ctx remind dismiss</code> CLI command Remove a reminder by ID (or <code>--all</code>) <code>/ctx-remind</code> Skill Natural language interface to reminders","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-1-leave-a-reminder","level":3,"title":"Step 1: Leave a Reminder","text":"<p>Tell your agent what to remember, or run it directly:</p> <pre><code>You: \"remind me to refactor the swagger definitions\"\n\nAgent: [runs ctx remind \"refactor the swagger definitions\"]\n       \"Reminder set:\n         + [1] refactor the swagger definitions\"\n</code></pre> <p>Or from the terminal:</p> <pre><code>ctx remind \"refactor the swagger definitions\"\n</code></pre>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-2-set-a-date-gate-optional","level":3,"title":"Step 2: Set a Date Gate (Optional)","text":"<p>If the reminder shouldn't fire until a specific date:</p> <pre><code>You: \"remind me to check the deploy logs after Tuesday\"\n\nAgent: [runs ctx remind \"check the deploy logs\" --after 2026-02-25]\n       \"Reminder set:\n         + [2] check the deploy logs  (after 2026-02-25)\"\n</code></pre> <p>The reminder stays silent until that date, then fires every session.</p> <p>The agent converts natural language dates (\"tomorrow\", \"next week\", \"after the release on Friday\") to <code>YYYY-MM-DD</code>. If it's ambiguous, it asks.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-3-start-a-new-session","level":3,"title":"Step 3: Start a New Session","text":"<p>Next session, the reminder appears automatically before anything else:</p> <pre><code>┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n</code></pre> <p>No action needed: The <code>check-reminders</code> hook fires on <code>UserPromptSubmit</code> and the agent relays the box verbatim.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-4-dismiss-when-done","level":3,"title":"Step 4: Dismiss When Done","text":"<p>After you've acted on a reminder (or decided to skip it):</p> <pre><code>You: \"dismiss reminder 1\"\n\nAgent: [runs ctx remind dismiss 1]\n       \"Dismissed:\n         - [1] refactor the swagger definitions\"\n\n# Batch dismiss also works:\n# \"dismiss reminders 3, 5 through 7\"\n# → ctx remind dismiss 3 5-7\n</code></pre> <p>Or clear everything:</p> <pre><code>ctx remind dismiss --all\n</code></pre>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#step-5-check-whats-pending","level":3,"title":"Step 5: Check What's Pending","text":"<pre><code>ctx remind list\n</code></pre> <pre><code>  [1] refactor the swagger definitions\n  [3] review auth token expiry logic\n  [4] check deploy logs  (after 2026-02-25, not yet due)\n</code></pre> <p>Date-gated reminders that haven't reached their date show <code>(not yet due)</code>.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#using-ctx-remind-in-a-session","level":2,"title":"Using <code>/ctx-remind</code> in a Session","text":"<p>Invoke the <code>/ctx-remind</code> skill, then describe what you want:</p> <pre><code>You: /ctx-remind remind me to update the API docs\nYou: /ctx-remind what reminders do I have?\nYou: /ctx-remind dismiss reminder 3\n</code></pre> You say (after <code>/ctx-remind</code>) What the agent does \"remind me to update the API docs\" <code>ctx remind \"update the API docs\"</code> \"remind me next week to check staging\" <code>ctx remind \"check staging\" --after 2026-03-02</code> \"what reminders do I have?\" <code>ctx remind list</code> \"dismiss reminder 3\" <code>ctx remind dismiss 3</code> \"dismiss reminders 3, 5 through 7\" <code>ctx remind dismiss 3 5-7</code> \"clear all reminders\" <code>ctx remind dismiss --all</code>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#reminders-vs-scratchpad-vs-tasks","level":2,"title":"Reminders vs Scratchpad vs Tasks","text":"You want to... Use Leave a note that announces itself next session <code>ctx remind</code> Jot down a quick value or sensitive token <code>ctx pad</code> Track work with status and completion <code>TASKS.md</code> Record a decision or lesson for all sessions Context files <p>Decision guide:</p> <ul> <li>If it should announce itself at session start → <code>ctx remind</code></li> <li>If it's a quiet note you'll check manually → <code>ctx pad</code></li> <li>If it's a work item you'll mark done → <code>TASKS.md</code></li> </ul> <p>Reminders Are Sticky Notes, Not Tasks</p> <p>A reminder has no status, no priority, no lifecycle. It's a message to \"future you\" that fires until dismissed. </p> <p>If you need tracking, use a task in <code>TASKS.md</code>.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#tips","level":2,"title":"Tips","text":"<ul> <li>Reminders fire every session: Unlike nudges (which throttle to   once per day), reminders repeat until you dismiss them. This is   intentional: You asked to be reminded.</li> <li>Date gating is session-scoped, not clock-scoped: <code>--after   2026-02-25</code> means \"don't show until sessions on or after Feb 25.\"   It does not mean \"alarm at midnight on Feb 25.\"</li> <li>The agent handles date parsing: Say \"next week\" or \"after   Friday\": The agent converts it to <code>YYYY-MM-DD</code>. The CLI only   accepts the explicit date format.</li> <li>Reminders are committed to git: They travel with the repo.   If you switch machines, your reminders follow.</li> <li>IDs never reuse: After dismissing reminder 3, the next reminder   gets ID 4 (or higher). No confusion from recycled numbers.</li> </ul>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#next-up","level":2,"title":"Next Up","text":"<p>Using the Scratchpad →: For quiet notes and sensitive values that don't need session-start announcements.</p>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/session-reminders/#see-also","level":2,"title":"See Also","text":"<ul> <li>CLI Reference: <code>ctx</code> remind: full   command syntax and flags</li> <li>The Complete Session: how reminders fit into   the session lifecycle</li> <li>Managing Tasks: for work items that need status   tracking</li> </ul>","path":["Recipes","Sessions","Session Reminders"],"tags":[]},{"location":"recipes/state-maintenance/","level":1,"title":"State Directory Maintenance","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-problem","level":2,"title":"The Problem","text":"<p>Every session creates tombstone files in <code>.context/state/</code> - small markers that suppress repeat hook nudges (\"already checked context size\", \"already sent persistence reminder\"). Over days and weeks, these accumulate into hundreds of files from long-dead sessions.</p> <p>The files are harmless individually, but the clutter makes it harder to reason about state, and stale global tombstones can suppress nudges across sessions entirely.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx prune --dry-run     # preview what would be removed\nctx prune               # prune files older than 7 days\nctx prune --days 1      # more aggressive: keep only today\n</code></pre> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, <code>ctx prune</code> / <code>ctx status</code> fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#commands-used","level":2,"title":"Commands Used","text":"Tool Type Purpose <code>ctx prune</code> Command Remove old per-session state files <code>ctx status</code> Command Quick health overview including state dir","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#understanding-state-files","level":2,"title":"Understanding State Files","text":"<p>State files fall into two categories:</p> <p>Session-scoped (contain a UUID in the filename): Created per-session to suppress repeat nudges. Safe to prune once the session ends. Examples:</p> <pre><code>context-check-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\nheartbeat-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\npersistence-nudge-11e94c1d-1639-4c04-bf77-63dcf1f50ec7\n</code></pre> <p>Global (no UUID): Persist across sessions. <code>ctx prune</code> preserves these automatically. Some are legitimate state (<code>events.jsonl</code>, <code>memory-import.json</code>); others may be stale tombstones that need manual review.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#the-workflow","level":2,"title":"The Workflow","text":"","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-1-preview","level":3,"title":"Step 1: Preview","text":"<p>Always dry-run first to see what would be removed:</p> <pre><code>ctx prune --dry-run\n</code></pre> <p>The output shows each file, its age, and a summary:</p> <pre><code>  would prune: context-check-abc123... (age: 3d)\n  would prune: heartbeat-abc123... (age: 3d)\n\nDry run - would prune 150 files (skip 70 recent, preserve 14 global)\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-2-prune","level":3,"title":"Step 2: Prune","text":"<p>Choose an age threshold. The default is 7 days:</p> <pre><code>ctx prune               # older than 7 days\nctx prune --days 3      # older than 3 days\nctx prune --days 1      # older than 1 day (aggressive)\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-3-review-global-files","level":3,"title":"Step 3: Review Global Files","text":"<p>After pruning, check what <code>prune</code> preserved:</p> <pre><code>ls .context/state/ | grep -v '[0-9a-f]\\{8\\}-[0-9a-f]\\{4\\}'\n</code></pre> <p>Legitimate global files (keep):</p> <ul> <li><code>events.jsonl</code> - event log</li> <li><code>memory-import.json</code> - import tracking state</li> </ul> <p>Stale global tombstones (safe to delete):</p> <ul> <li>Files like <code>backup-reminded</code>, <code>ceremony-reminded</code>, <code>version-checked</code>   with no session UUID are one-shot markers. If they are from a previous   session, they are stale and can be removed manually.</li> </ul> <pre><code>rm .context/state/backup-reminded .context/state/ceremony-reminded\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#step-4-verify","level":3,"title":"Step 4: Verify","text":"<pre><code>ls .context/state/ | wc -l    # should be manageable\n</code></pre>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#when-to-prune","level":2,"title":"When to Prune","text":"<ul> <li>Weekly: <code>ctx prune</code> with default 7-day threshold</li> <li>After heavy parallel work: Multiple concurrent sessions create   many tombstones. Prune with <code>--days 1</code> afterward.</li> <li>When state directory exceeds ~100 files: A sign that pruning   hasn't run recently</li> </ul>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#tips","level":2,"title":"Tips","text":"<p>Pruning active sessions is safe but noisy: If you prune a file belonging to a still-running session, the corresponding hook will re-fire its nudge on the next prompt. Minor UX annoyance, not data loss.</p> <p>No context files are stored in state: The state directory contains only tombstones, counters, and diagnostic data. Nothing in <code>.context/state/</code> affects your decisions, learnings, tasks, or conventions.</p> <p>Test artifacts sneak in: Files like <code>context-check-statstest</code> or <code>heartbeat-unknown</code> are artifacts from development or testing. They lack UUIDs so <code>prune</code> preserves them. Delete manually.</p>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/state-maintenance/#see-also","level":2,"title":"See Also","text":"<ul> <li>Detecting and Fixing Drift: broader context   maintenance including drift detection and archival</li> <li>Troubleshooting: diagnostic workflow using   <code>ctx doctor</code> and event logs</li> <li>CLI Reference: system: full flag documentation   for <code>ctx prune</code> and related commands</li> </ul>","path":["State Directory Maintenance"],"tags":[]},{"location":"recipes/steering/","level":1,"title":"Writing Steering Files","text":"","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#writing-steering-files","level":1,"title":"Writing Steering Files","text":"<p>Steering files tell your AI assistant how to behave, not what was decided or how the codebase is written. This recipe walks through writing a steering file from scratch, validating which prompts will trigger it, and syncing it out to your configured AI tools.</p> <p>Before You Start</p> <p>If you're unsure whether a rule belongs in <code>steering/</code>, <code>DECISIONS.md</code>, or <code>CONVENTIONS.md</code>, read the \"Steering vs decisions vs conventions\" admonition on the <code>ctx steering</code> reference page. The short version: if the rule is \"the AI should always do X when asked about Y,\" that's steering. Otherwise it's probably a decision or convention.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#start-here-customize-the-foundation-files","level":2,"title":"Start Here: Customize the Foundation Files","text":"<p><code>ctx init</code> scaffolds four foundation steering files for you the first time you initialize a project:</p> File Purpose <code>.context/steering/product.md</code> Product context, goals, target users <code>.context/steering/tech.md</code> Tech stack, constraints, key dependencies <code>.context/steering/structure.md</code> Directory layout, naming conventions <code>.context/steering/workflow.md</code> Branch strategy, commit rules, pre-commit <p>Each file opens with an inline HTML comment that explains the three inclusion modes, what <code>priority</code> means, and the <code>tools</code> scope. The comment is invisible in rendered Markdown but visible when you edit the file. Delete it once the file is yours.</p> <p>All four default to <code>inclusion: always</code> and <code>priority: 10</code>, so they fire on every AI tool call until you customize them. If you're reading this recipe and haven't touched them yet, open each one now and replace the placeholder bullet list with actual rules for your project. That's the highest-leverage five minutes you can spend in a new <code>ctx</code> setup.</p> <p>What to fill in, by file:</p> <p><code>product.md</code>: The elevator pitch plus hard scope:</p> <ul> <li>One-sentence product description.</li> <li>Primary users and their top job-to-be-done.</li> <li>Two or three \"this is explicitly out of scope\" items   so the AI doesn't wander.</li> </ul> <p><code>tech.md</code>: Technology and constraints:</p> <ul> <li>Languages and versions (<code>Go 1.22</code>, <code>Node 20</code>, etc.).</li> <li>Frameworks and key libraries.</li> <li>Runtime and deployment target.</li> <li>Hard constraints: \"no CGO\", \"no network at test time\",   \"no external DB for unit tests\". These are the things   that burn agents when they don't know them.</li> </ul> <p><code>structure.md</code>: Layout and naming:</p> <ul> <li>Top-level directories and their purpose.</li> <li>Where new files should go (and where they should NOT).</li> <li>Naming conventions for packages, files, types.</li> </ul> <p><code>workflow.md</code>: Process rules:</p> <ul> <li>Branch strategy (main-only, trunk-based, feature   branches).</li> <li>Commit message format, signed-off-by requirement.</li> <li>Pre-commit and pre-push checks.</li> <li>Review expectations.</li> </ul> <p>After editing, the next AI tool call in Claude Code will pick up the new rules automatically via the plugin's <code>PreToolUse</code> hook, with no sync step and no restart. Other tools (Cursor, Cline, Kiro) need <code>ctx steering sync</code> to export into their native format.</p> <p>Prefer a Bare <code>.context/steering/</code> Directory?</p> <p>Re-run <code>ctx init --no-steering-init</code> and delete the scaffolded files. <code>ctx init</code> leaves existing files alone, so the flag is only needed if you want to opt out of the initial scaffold.</p> <p>The rest of this recipe walks through creating an additional, scenario-specific steering file beyond the four foundation defaults.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#scenario","level":2,"title":"Scenario","text":"<p>You're working on a project with a strict input-validation policy: every new API handler must validate request bodies before touching the database. You want the AI to flag this concern automatically whenever it's asked to write an HTTP handler, without you having to remind it every session.</p> <p>Claude Code Users: Pick <code>always</code>, Not <code>auto</code></p> <p>This walkthrough uses <code>inclusion: auto</code> because the scenario is a scoped rule that matches a specific kind of prompt. That works natively on Cursor, Cline, and Kiro (they resolve the <code>description</code> keyword match themselves).</p> <p>On Claude Code, <code>auto</code> does not fire through the plugin's <code>PreToolUse</code> hook. The hook passes an empty prompt to <code>ctx agent</code>, so only <code>always</code> files match. Claude can still reach an <code>auto</code> file by calling the <code>ctx_steering_get</code> MCP tool, but that requires Claude to decide to call it; there's no automatic injection.</p> <p>If Claude Code is your tool, set <code>inclusion: always</code> in Step 2 instead of <code>auto</code>. The rule will fire on every tool call regardless of topic. You may want to narrow the rule body so the extra tokens per turn aren't wasted on unrelated work.</p> <p>See the <code>ctx steering</code> reference \"Prefer <code>inclusion: always</code> for Claude Code\" section for the full trade-off.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-1-scaffold-the-file","level":2,"title":"Step 1: Scaffold the File","text":"<pre><code>ctx steering add api-validation\n</code></pre> <p>That creates <code>.context/steering/api-validation.md</code> with default frontmatter:</p> <pre><code>---\nname: api-validation\ndescription:\ninclusion: manual\ntools: []\npriority: 50\n---\n</code></pre> <p>The defaults are deliberately conservative: <code>inclusion: manual</code> means the file won't be applied until you opt in, which keeps the rules out of the prompt until you've reviewed them.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-2-fill-in-the-rule","level":2,"title":"Step 2: Fill in the Rule","text":"<p>Open the file and write the rule body plus a focused description. The description is what <code>inclusion: auto</code> matches against later.</p> <pre><code>---\nname: api-validation\ndescription: HTTP handler input validation and request parsing\ninclusion: auto\ntools: []\npriority: 20\n---\n\n# API request validation\n\nEvery new HTTP handler MUST:\n\n1. Parse request bodies into typed structs, never `map[string]any`.\n2. Validate required fields before any database call.\n3. Return 400 with a machine-readable error for validation failures.\n4. Use `context.Context` from the request for all downstream calls.\n\nPrefer existing validation helpers in `internal/validate/`\nrather than inline checks.\n</code></pre> <p>Notes on the choices:</p> <ul> <li><code>inclusion: auto</code>: this rule should fire automatically   on HTTP-handler-shaped prompts, not always.</li> <li><code>priority: 20</code>: lower than the default, so this rule   appears near the top of the prompt alongside other   high-priority rules.</li> <li>Description is keyword-rich (\"HTTP handler input   validation and request parsing\"); the <code>auto</code> matcher scores   prompts against these words.</li> </ul>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-3-preview-which-prompts-match","level":2,"title":"Step 3: Preview Which Prompts Match","text":"<p>Before committing the file, validate your description catches the prompts you care about:</p> <pre><code>ctx steering preview \"add an endpoint for updating user email\"\n</code></pre> <p>Expected output:</p> <pre><code>Steering files matching prompt \"add an endpoint for updating user email\":\n  api-validation       inclusion=auto     priority=20  tools=all\n</code></pre> <p>Good, the prompt matches. Try a negative case:</p> <pre><code>ctx steering preview \"fix a bug in the JSON renderer\"\n</code></pre> <p>Expected: empty match (or whatever else is currently <code>auto</code>). If <code>api-validation</code> incorrectly fires for unrelated prompts, tighten the description. If it misses prompts it should catch, add more keywords.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-4-list-to-confirm-metadata","level":2,"title":"Step 4: List to Confirm Metadata","text":"<pre><code>ctx steering list\n</code></pre> <p>Should show <code>api-validation</code> alongside any other files, with its inclusion mode and priority. If the list is wrong, check the frontmatter for typos.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-5-get-the-rules-in-front-of-the-ai","level":2,"title":"Step 5: Get the Rules in Front of the AI","text":"<p>Steering files are authored once in <code>.context/steering/</code>, but how they reach the AI depends on which tool you use. There are two delivery mechanisms:</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-a-native-rules-tools-cursor-cline-kiro","level":3,"title":"Path A: Native-Rules Tools (Cursor, Cline, Kiro)","text":"<p>These tools read a specific directory for rules. <code>ctx steering sync</code> exports your files into that directory with tool-specific frontmatter:</p> <pre><code>ctx steering sync\n</code></pre> <p>Depending on the active tool in <code>.ctxrc</code> or <code>--tool</code>:</p> Tool Target Cursor <code>.cursor/rules/</code> Cline <code>.clinerules/</code> Kiro <code>.kiro/steering/</code> <p>The sync is idempotent; unchanged files are skipped. Run it whenever you edit a steering file.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#path-b-claude-code-and-codex-hook-mcp","level":3,"title":"Path B: Claude Code and Codex (Hook + MCP)","text":"<p>Claude Code and Codex have no native rules primitive, so <code>ctx steering sync</code> is a no-op for them; it deliberately skips both. Instead, steering reaches these tools through two non-sync channels:</p> <ol> <li> <p><code>PreToolUse</code> hook (automatic). The <code>ctx setup    claude-code</code> plugin installs a hook that runs    <code>ctx agent --budget 8000</code> before each tool call. <code>ctx    agent</code> loads your steering files, filters them against    the active prompt, and includes matching bodies as    Tier 6 of the context packet. The packet gets injected    into Claude's context automatically.</p> </li> <li> <p><code>ctx_steering_get</code> MCP tool (on-demand). Claude can    call this MCP tool mid-task to fetch matching steering    files for a specific prompt. Automatic activation comes    from Claude's judgment, not a hook.</p> </li> </ol> <p>Both channels activate when you run:</p> <pre><code>ctx setup claude-code --write\n</code></pre> <p>That installs the plugin, wires the hook, and registers the MCP server. After that, steering files you edit are picked up on the next tool call, with no sync step needed.</p> <p>Running <code>ctx steering sync</code> with Claude Code</p> <p>It won't error; it will simply report that Claude and Codex aren't sync targets and skip them. If Claude Code is your only tool, you never need to run <code>sync</code>. If you use both Claude Code and (say) Cursor, run <code>sync</code> to keep Cursor up to date; the Claude pipeline takes care of itself via the hook.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#step-6-verify-the-ai-sees-it","level":2,"title":"Step 6: Verify the AI Sees It","text":"<p>Open your AI tool and ask it something the rule should fire on:</p> <p>\"Add a POST /users endpoint that accepts email and name.\"</p> <p>If the rule is working, the AI's first response should mention input validation, typed structs, and the <code>internal/validate/</code> package, because that's what the steering file told it to do.</p> <p>If nothing happens, the fix depends on which path you're on:</p> <p>Path A (Cursor/Cline/Kiro):</p> <ol> <li>Re-run <code>ctx steering preview</code> with the literal prompt to    confirm the match.</li> <li>Run <code>ctx steering list</code> and verify <code>inclusion</code> is <code>auto</code>,    not <code>manual</code>.</li> <li>Check the tool's own config directory (e.g.    <code>.cursor/rules/</code>); the file should be there after    <code>ctx steering sync</code>.</li> </ol> <p>Path B (Claude Code):</p> <ol> <li>Re-run <code>ctx steering preview</code> with the literal prompt to    confirm the match.</li> <li>Verify the plugin is installed: <code>cat .claude/hooks.json</code>    should include <code>ctx agent --budget 8000</code> under    <code>PreToolUse</code>. If not, re-run <code>ctx setup claude-code --write</code>.</li> <li>Run <code>ctx agent --budget 8000</code> manually and grep the    output for your rule body. If it's there, the data is    fine; if it's missing, the <code>inclusion</code> mode or    <code>description</code> is at fault.</li> <li>As a last resort, ask Claude directly: \"Call the    <code>ctx_steering_get</code> MCP tool with my prompt and show me    the result.\" If the MCP tool returns your rule, Claude    has access but isn't pulling it into the initial    context packet; tighten the description keywords.</li> </ol>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>Too-generic descriptions. <code>description: general coding</code> will match almost every prompt and flood the context window. Keep descriptions specific to the scenario the rule applies to.</p> <p>Overlapping rules. If two steering files match the same prompt and contradict each other, the result is confusing. Use <code>priority</code> to resolve, but better: merge the files or narrow the descriptions so they don't overlap.</p> <p>Putting decisions in steering. \"We decided to use PostgreSQL\" is a decision, not a rule for the AI to follow on every prompt. Record decisions with <code>ctx decision add</code>, not <code>ctx steering add</code>.</p> <p>Committing <code>inclusion: always</code> without thinking. Rules marked <code>always</code> fire on every prompt, consuming tier-6 budget permanently. Only use <code>always</code> for true invariants (security, safety, licensing). Everything else should be <code>auto</code> or <code>manual</code>.</p>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/steering/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx steering</code> reference: full   command, flag, and frontmatter reference.</li> <li><code>ctx setup</code>: configure which tools the   steering sync writes to.</li> <li>Authoring triggers: if you want   script-based automation, not rule-based prompt injection.</li> </ul>","path":["Recipes","Agents and Automation","Writing Steering Files"],"tags":[]},{"location":"recipes/system-hooks-audit/","level":1,"title":"Auditing System Hooks","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-problem","level":2,"title":"The Problem","text":"<p><code>ctx</code> runs 14 system hooks behind the scenes: nudging your agent to persist context, warning about resource pressure, gating commits on QA. But these hooks are invisible by design. You never see them fire. You never know if they stopped working.</p> <p>How do you verify your hooks are actually running, audit what they do, and get alerted when they go silent?</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx system check-resources # run a hook manually\nls -la .context/logs/      # check hook execution logs\nctx hook notify setup      # get notified when hooks fire\n</code></pre> <p>Or ask your agent: \"Are our hooks running?\"</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx system <hook></code> CLI command Run a system hook manually <code>ctx sysinfo</code> CLI command Show system resource status <code>ctx usage</code> CLI command Stream or dump per-session token stats <code>ctx hook notify setup</code> CLI command Configure webhook for audit trail <code>ctx hook notify test</code> CLI command Verify webhook delivery <code>.ctxrc</code> <code>notify.events</code> Configuration Subscribe to <code>relay</code> for full hook audit <code>.context/logs/</code> Log files Local hook execution ledger","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-are-system-hooks","level":2,"title":"What Are System Hooks?","text":"<p>System hooks are plumbing commands that <code>ctx</code> registers with your AI tool (Claude Code, Cursor, etc.) via the plugin's <code>hooks.json</code>. They fire automatically at specific events during your AI session:</p> Event When Hooks <code>UserPromptSubmit</code> Before the agent sees your prompt 10 check hooks + heartbeat <code>PreToolUse</code> Before the agent uses a tool <code>block-non-path-ctx</code>, <code>qa-reminder</code> <code>PostToolUse</code> After a tool call succeeds <code>post-commit</code> <p>You never run these manually. Your AI tool runs them for you: That's the point.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-complete-hook-catalog","level":2,"title":"The Complete Hook Catalog","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#prompt-time-checks-userpromptsubmit","level":3,"title":"Prompt-Time Checks (UserPromptSubmit)","text":"<p>These fire before every prompt, but most are throttled to avoid noise.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-context-size-context-capacity-warning","level":4,"title":"<code>check-context-size</code>: Context Capacity Warning","text":"<p>What: Adaptive prompt counter. Silent for the first 15 prompts, then nudges with increasing frequency (every 5<sup>th</sup>, then every 3<sup>rd</sup>).</p> <p>Why: Long sessions lose coherence. The nudge reminds both you and the agent to persist context before the window fills up.</p> <p>Output: VERBATIM relay box with prompt count.</p> <pre><code>┌─ Context Checkpoint (prompt #20) ────────────────\n│ This session is getting deep. Consider wrapping up\n│ soon. If there are unsaved learnings, decisions, or\n│ conventions, now is a good time to persist them.\n│ ⏱ Context window: ~45k tokens (~22% of 200k)\n└──────────────────────────────────────────────────\n</code></pre> <p>Usage: Every prompt records token usage to <code>.context/state/stats-{session}.jsonl</code>. Monitor live with <code>ctx usage --follow</code> or query with <code>ctx usage --json</code>. Usage is recorded even during wrap-up suppression (event: <code>suppressed</code>).</p> <p>Billing guard: When <code>billing_token_warn</code> is set in <code>.ctxrc</code>, a one-shot warning fires if session tokens exceed the threshold. This warning is independent of all other triggers - it fires even during wrap-up suppression.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-persistence-context-staleness-nudge","level":4,"title":"<code>check-persistence</code>: Context Staleness Nudge","text":"<p>What: Tracks when <code>.context/*.md</code> files were last modified. If too many prompts pass without a write, nudges the agent to persist.</p> <p>Why: Sessions produce insights that evaporate if not recorded. This catches the \"we talked about it but never wrote it down\" failure mode.</p> <p>Output: VERBATIM relay after 20+ prompts without a context file change.</p> <pre><code>┌─ Persistence Checkpoint (prompt #20) ───────────\n│ No context files updated in 20+ prompts.\n│ Have you discovered learnings, made decisions,\n│ established conventions, or completed tasks\n│ worth persisting?\n│\n│ Run /ctx-wrap-up to capture session context.\n└──────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-ceremonies-session-ritual-adoption","level":4,"title":"<code>check-ceremonies</code>: Session Ritual Adoption","text":"<p>What: Scans your last 3 journal entries for <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> usage. Nudges once per day if missing.</p> <p>Why: Session ceremonies are the highest-leverage habit in <code>ctx</code>. This hook bootstraps the habit until it becomes automatic.</p> <p>Output: Tailored nudge depending on which ceremony is missing.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-journal-unimported-session-reminder","level":4,"title":"<code>check-journal</code>: Unimported Session Reminder","text":"<p>What: Detects unimported Claude Code sessions and unenriched journal entries. Fires once per day.</p> <p>Why: Exported sessions become searchable history. Unenriched entries lack metadata for filtering. Both decay in value over time.</p> <p>Output: VERBATIM relay with counts and exact commands.</p> <pre><code>┌─ Journal Reminder ─────────────────────────────\n│ You have 3 new session(s) not yet exported.\n│ 5 existing entries need enrichment.\n│\n│ Export and enrich:\n│   ctx journal import --all\n│   /ctx-journal-enrich-all\n└────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-resources-system-resource-pressure","level":4,"title":"<code>check-resources</code>: System Resource Pressure","text":"<p>What: Monitors memory, swap, disk, and CPU load. Only fires at DANGER severity (memory >= 90%, swap >= 75%, disk >= 95%, load >= 1.5x CPU count).</p> <p>Why: Resource exhaustion mid-session can corrupt work. This provides early warning to persist and exit.</p> <p>Output: VERBATIM relay listing critical resources.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-knowledge-knowledge-file-growth","level":4,"title":"<code>check-knowledge</code>: Knowledge File Growth","text":"<p>What: Counts entries in <code>LEARNINGS.md</code>, <code>DECISIONS.md</code>, and lines in <code>CONVENTIONS.md</code>. Fires once per day when thresholds are exceeded.</p> <p>Why: Large knowledge files dilute agent context. 35 learnings compete for attention; 15 focused ones get applied. Thresholds are configurable in <code>.ctxrc</code>.</p> <p>Default thresholds:</p> <pre><code># .ctxrc\nentry_count_learnings: 30\nentry_count_decisions: 20\nconvention_line_count: 200\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-version-binaryplugin-version-drift","level":4,"title":"<code>check-version</code>: Binary/Plugin Version Drift","text":"<p>What: Compares the <code>ctx</code> binary version against the plugin version. Fires once per day. Also checks encryption key age for rotation nudge.</p> <p>Why: Version drift means hooks reference features the binary doesn't have. The key rotation nudge prevents indefinite key reuse.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-reminders-pending-reminder-relay","level":4,"title":"<code>check-reminders</code>: Pending Reminder Relay","text":"<p>What: Reads <code>.context/reminders.json</code> and surfaces any due reminders via VERBATIM relay. No throttle: fires every session until dismissed.</p> <p>Why: Reminders are sticky notes to future-you. Unlike nudges (which throttle to once per day), reminders repeat deliberately until the user dismisses them.</p> <p>Output: VERBATIM relay box listing due reminders.</p> <pre><code>┌─ Reminders ──────────────────────────────────────\n│  [1] refactor the swagger definitions\n│\n│ Dismiss: ctx remind dismiss <id>\n│ Dismiss all: ctx remind dismiss --all\n└──────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-freshness-technology-constant-staleness","level":4,"title":"<code>check-freshness</code>: Technology Constant Staleness","text":"<p>What: Stats files listed in <code>.ctxrc</code> <code>freshness_files</code> and warns if any haven't been modified in over 6 months. Daily throttle. Silent when no files are configured (opt-in via <code>.ctxrc</code>).</p> <p>Why: Model capabilities evolve - token budgets, attention limits, and context window sizes that were accurate 6 months ago may no longer reflect best practices. This hook reminds you to review and touch the file to confirm values are still current.</p> <p>Config (<code>.ctxrc</code>):</p> <pre><code>freshness_files:\n  - path: config/thresholds.yaml\n    desc: Model token limits and batch sizes\n    review_url: https://docs.example.com/limits  # optional\n</code></pre> <p>Each entry has a <code>path</code> (relative to project root), <code>desc</code> (what constants live there), and optional <code>review_url</code> (where to check current values). When <code>review_url</code> is set, the nudge includes \"Review against: {url}\". When absent, just \"Touch the file to mark it as reviewed.\"</p> <p>Output: VERBATIM relay listing stale files, silent otherwise.</p> <pre><code>┌─ Technology Constants Stale ──────────────────────\n│   config/thresholds.yaml (210 days ago)\n│     - Model token limits and batch sizes\n│   Review against: https://docs.example.com/limits\n│ Touch each file to mark it as reviewed.\n└───────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#check-map-staleness-architecture-map-drift","level":4,"title":"<code>check-map-staleness</code>: Architecture Map Drift","text":"<p>What: Checks whether <code>map-tracking.json</code> is older than 30 days and there are commits touching <code>internal/</code> since the last map refresh. Daily throttle prevents repeated nudges.</p> <p>Why: Architecture documentation drifts silently as code evolves. This hook detects structural changes that the map hasn't caught up with and suggests running <code>/ctx-architecture</code> to refresh.</p> <p>Output: VERBATIM relay when stale and modules changed, silent otherwise.</p> <pre><code>┌─ Architecture Map Stale ────────────────────────────\n│ ARCHITECTURE.md hasn't been refreshed since 2026-01-15\n│ and there are commits touching 12 modules.\n│ /ctx-architecture keeps architecture docs drift-free.\n│\n│ Want me to run /ctx-architecture to refresh?\n└─────────────────────────────────────────────────────\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#heartbeat-session-heartbeat-webhook","level":4,"title":"<code>heartbeat</code>: Session Heartbeat Webhook","text":"<p>What: Fires on every prompt. Sends a webhook notification with prompt count, session ID, context modification status, and token usage telemetry. Never produces stdout.</p> <p>Why: Other hooks only send webhooks when they \"speak\" (nudge/relay). When silent, you have no visibility into session activity. The heartbeat provides a continuous session-alive signal with token consumption data for observability dashboards or liveness monitoring.</p> <p>Output: None (webhook + event log only).</p> <p>Payload:</p> <pre><code>{\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  }\n}\n</code></pre> <p>Token fields (<code>tokens</code>, <code>context_window</code>, <code>usage_pct</code>) are included when usage data is available from the session JSONL file.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tool-time-hooks-pretooluse-posttooluse","level":3,"title":"Tool-Time Hooks (PreToolUse / PostToolUse)","text":"","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#block-non-path-ctx-path-enforcement-hard-gate","level":4,"title":"<code>block-non-path-ctx</code>: PATH Enforcement (Hard Gate)","text":"<p>What: Blocks any Bash command that invokes <code>./ctx</code>, <code>./dist/ctx</code>, <code>go run ./cmd/ctx</code>, or an absolute path to <code>ctx</code>. Only PATH invocations are allowed.</p> <p>Why: Enforces <code>CONSTITUTION.md</code>'s invocation invariant. Running a dev-built binary in production context causes version confusion and silent behavior drift.</p> <p>Output: Block response (prevents the tool call):</p> <pre><code>{\"decision\": \"block\", \"reason\": \"Use 'ctx' from PATH, not './ctx'...\"}\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#qa-reminder-pre-commit-qa-gate","level":4,"title":"<code>qa-reminder</code>: Pre-Commit QA Gate","text":"<p>What: Fires on every <code>Edit</code> tool use. Reminds the agent to lint and test the entire project before committing.</p> <p>Why: Agents tend to \"I'll test later\" and then commit untested code. Repetition is intentional: the hook reinforces the habit on every edit, not just before commits.</p> <p>Output: Agent directive with hard QA gate instructions.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#post-commit-context-capture-after-commit","level":4,"title":"<code>post-commit</code>: Context Capture After Commit","text":"<p>What: Fires after any <code>git commit</code> (excludes <code>--amend</code>). Prompts the agent to offer context capture (decision? learning?) and suggest running lints/tests before pushing.</p> <p>Why: Commits are natural reflection points. The nudge converts mechanical git operations into context-capturing opportunities.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-the-local-event-log","level":2,"title":"Auditing Hooks via the Local Event Log","text":"<p>If you don't need an external audit trail, enable the local event log for a self-contained record of hook activity:</p> <pre><code># .ctxrc\nevent_log: true\n</code></pre> <p>Once enabled, every hook that fires writes an entry to <code>.context/state/events.jsonl</code>. Query it with <code>ctx hook event</code>:</p> <pre><code>ctx hook event                    # last 50 events\nctx hook event --hook qa-reminder # filter by hook\nctx hook event --session <id>     # filter by session\nctx hook event --json | jq '.'    # raw JSONL for processing\n</code></pre> <p>The event log is local, queryable, and doesn't require any external service. For a full diagnostic workflow combining event logs with structural health checks, see Troubleshooting.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#auditing-hooks-via-webhooks","level":2,"title":"Auditing Hooks via Webhooks","text":"<p>The most powerful audit setup pipes all hook output to a webhook, giving you a real-time external record of what your agent is being told.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-1-set-up-the-webhook","level":3,"title":"Step 1: Set Up the Webhook","text":"<pre><code>ctx hook notify setup\n# Enter your webhook URL (Slack, Discord, ntfy.sh, IFTTT, etc.)\n</code></pre> <p>See Webhook Notifications for service-specific setup.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-2-subscribe-to-relay-events","level":3,"title":"Step 2: Subscribe to <code>relay</code> Events","text":"<pre><code># .ctxrc\nnotify:\n  events:\n    - relay   # all hook output: VERBATIM relays, directives, blocks\n    - nudge   # just the user-facing VERBATIM relays\n</code></pre> <p>The <code>relay</code> event fires for every hook that produces output. This includes:</p> Hook Event sent <code>check-context-size</code> <code>relay</code> + <code>nudge</code> <code>check-persistence</code> <code>relay</code> + <code>nudge</code> <code>check-ceremonies</code> <code>relay</code> + <code>nudge</code> <code>check-journal</code> <code>relay</code> + <code>nudge</code> <code>check-resources</code> <code>relay</code> + <code>nudge</code> <code>check-knowledge</code> <code>relay</code> + <code>nudge</code> <code>check-version</code> <code>relay</code> + <code>nudge</code> <code>check-reminders</code> <code>relay</code> + <code>nudge</code> <code>check-freshness</code> <code>relay</code> + <code>nudge</code> <code>check-map-staleness</code> <code>relay</code> + <code>nudge</code> <code>heartbeat</code> <code>heartbeat</code> only <code>block-non-path-ctx</code> <code>relay</code> only <code>post-commit</code> <code>relay</code> only <code>qa-reminder</code> <code>relay</code> only","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#step-3-cross-reference","level":3,"title":"Step 3: Cross-Reference","text":"<p>With <code>relay</code> enabled, your webhook receives a JSON payload every time a hook fires:</p> <pre><code>{\n  \"event\": \"relay\",\n  \"message\": \"check-persistence: No context updated in 20+ prompts\",\n  \"session_id\": \"b854bd9c\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"my-project\"\n}\n</code></pre> <p>This creates an external audit trail independent of the agent. You can now cross-verify: did the agent actually relay the checkpoint the hook told it to relay?</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#verifying-hooks-actually-fire","level":2,"title":"Verifying Hooks Actually Fire","text":"<p>Hooks are invisible. An invisible thing that breaks is indistinguishable from an invisible thing that never existed. Three verification methods, from simplest to most robust:</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-1-ask-the-agent","level":3,"title":"Method 1: Ask the Agent","text":"<p>The simplest check. After a few prompts into a session:</p> <pre><code>\"Did you receive any hook output this session? Print the last\ncontext checkpoint or persistence nudge you saw.\"\n</code></pre> <p>The agent should be able to recall recent hook output from its context window. If it says \"I haven't received any hook output\", either:</p> <ul> <li>The hooks aren't firing (check installation);</li> <li>The session is too short (hooks throttle early);</li> <li>The hooks fired but the agent absorbed them silently.</li> </ul> <p>Limitation: You are trusting the agent to report accurately. Agents sometimes confabulate or miss context. Use this as a quick smoke test, not definitive proof.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-2-check-the-webhook-trail","level":3,"title":"Method 2: Check the Webhook Trail","text":"<p>If you have <code>relay</code> events enabled, check your webhook receiver. Every hook that fires sends a timestamped notification. No notification = no fire.</p> <p>This is the ground truth. The webhook is called directly by the <code>ctx</code> binary, not by the agent. The agent cannot fake, suppress, or modify webhook deliveries.</p> <p>Compare what the webhook received against what the agent claims to have relayed. Discrepancies mean the agent is absorbing nudges instead of surfacing them.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#method-3-read-the-local-logs","level":3,"title":"Method 3: Read the Local Logs","text":"<p>Hooks that support logging write to <code>.context/logs/</code>:</p> <pre><code># Check context-size hook activity\ncat .context/logs/check-context-size.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] prompt#1 silent\n# [2026-02-22 09:17:33] [session:b854bd9c] prompt#16 CHECKPOINT\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 CHECKPOINT\n</code></pre> <pre><code># Check persistence nudge activity\ncat .context/logs/check-persistence.log\n\n# Sample output:\n# [2026-02-22 09:15:00] [session:b854bd9c] init count=1 mtime=1770646611\n# [2026-02-22 09:20:01] [session:b854bd9c] prompt#20 NUDGE since_nudge=20\n</code></pre> <p>Logs are append-only and written by the <code>ctx</code> binary, not the agent.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#detecting-silent-hook-failures","level":2,"title":"Detecting Silent Hook Failures","text":"<p>The hardest failure mode: hooks that stop firing without error. The plugin config changes, a binary update drops a hook, or a PATH issue silently breaks execution. Nothing errors: The hook just never runs.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#the-staleness-signal","level":3,"title":"The Staleness Signal","text":"<p>If <code>.context/logs/check-context-size.log</code> has no entries newer than 5 days but you've been running sessions daily, something is wrong. The absence of evidence is evidence of absence: but only if you control for inactivity.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#false-positive-protection","level":3,"title":"False Positive Protection","text":"<p>A naive \"hooks haven't fired in N days\" alert fires incorrectly when you simply haven't used <code>ctx</code>. The correct check needs two inputs:</p> <ol> <li>Last hook fire time: from <code>.context/logs/</code> or webhook history</li> <li>Last session activity: from journal entries or <code>ctx journal source</code></li> </ol> <p>If sessions are happening but hooks aren't firing, that's a real problem. If neither sessions nor hooks are happening, that's a vacation.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#what-to-check","level":3,"title":"What to Check","text":"<p>When you suspect hooks aren't firing:</p> <pre><code># 1. Verify the plugin is installed\nls ~/.claude/plugins/\n\n# 2. Check hook registration\ncat ~/.claude/plugins/ctx/hooks.json | head -20\n\n# 3. Run a hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-context-size\n\n# 4. Check for PATH issues\nwhich ctx\nctx --version\n</code></pre>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>nudge</code>, graduate to <code>relay</code>: The <code>nudge</code> event covers   user-facing VERBATIM relays. Add <code>relay</code> when you want full visibility   into agent directives and hard gates.</li> <li>Webhooks are your trust anchor:    The agent can ignore a nudge, but it can't suppress the webhook.    If the webhook fired and the agent didn't relay, you have proof of a    compliance gap.</li> <li>Hooks are throttled by design: Most check hooks fire once per day   or use adaptive frequency. Don't expect a notification every prompt:   Silence usually means the throttle is working, not that the hook is   broken.</li> <li>Daily markers live in <code>.context/state/</code>: Throttle files are stored   in <code>.context/state/</code> alongside other project-scoped state. If you need   to force a hook to re-fire during testing, delete the corresponding   marker file.</li> <li>The QA reminder is intentionally noisy: Unlike other hooks,   <code>qa-reminder</code> fires on every <code>Edit</code> call with no throttle. This is   deliberate: The commit quality degrades when the reminder fades from   salience.</li> <li>Log files are safe to commit: <code>.context/logs/</code> contains only   timestamps, session IDs, and status keywords. No secrets, no code.</li> </ul>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#next-up","level":2,"title":"Next Up","text":"<p>Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.</p>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/system-hooks-audit/#see-also","level":2,"title":"See Also","text":"<ul> <li>Troubleshooting: full diagnostic workflow using   <code>ctx doctor</code>, event logs, and <code>/ctx-doctor</code></li> <li>Customizing Hook Messages: override   what hooks say without changing what they do</li> <li>Webhook Notifications: setting up and   configuring the webhook system</li> <li>Hook Output Patterns: understanding   VERBATIM relays, agent directives, and hard gates</li> <li>Detecting and Fixing Drift: structural checks   that complement runtime hook auditing</li> <li>CLI Reference: full <code>ctx system</code>   command reference</li> </ul>","path":["Recipes","Hooks and Notifications","Auditing System Hooks"],"tags":[]},{"location":"recipes/task-management/","level":1,"title":"Tracking Work Across Sessions","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-problem","level":2,"title":"The Problem","text":"<p>You have work that spans multiple sessions. Tasks get added during one session, partially finished in another, and completed days later.</p> <p>Without a system, follow-up items fall through the cracks, priorities drift, and you lose track of what was done versus what still needs doing. <code>TASKS.md</code> grows cluttered with completed checkboxes that obscure the remaining work.</p> <p>How do you manage work items that span multiple sessions without losing context?</p> <p>Prefer Skills over Raw Commands</p> <p>When working with an AI agent, use <code>/ctx-task-add</code> instead of raw <code>ctx task add</code>. The agent automatically picks up session ID, branch, and commit hash from its context, so no manual flags are needed.</p> <p>Activate the Project First</p> <p>Run <code>eval \"$(ctx activate)\"</code> once per terminal in the project root. If you skip it, the <code>ctx task add</code> / <code>ctx task ...</code> commands below fail with <code>Error: no context directory specified</code>. See Activating a Context Directory.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tldr","level":2,"title":"TL;DR","text":"<p>Manage Tasks:</p> <pre><code>ctx task add \"Fix race condition\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add\nctx task add \"Write tests\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a  # add to phase\nctx task complete \"race condition\"                      # mark done\nctx task snapshot \"before-refactor\"               # backup\nctx task archive                                  # clean up\n</code></pre> <p>Pick Up the Next Task:</p> <pre><code>/ctx-next # pick what's next\n</code></pre> <p>Read on for the full workflow and conversational patterns.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx task add</code> Command Add a new task to <code>TASKS.md</code> <code>ctx task complete</code> Command Mark a task as done by number or text <code>ctx task snapshot</code> Command Create a point-in-time backup of <code>TASKS.md</code> <code>ctx task archive</code> Command Move completed tasks to archive file <code>/ctx-task-add</code> Skill AI-assisted task creation with validation <code>/ctx-archive</code> Skill AI-guided archival with safety checks <code>/ctx-next</code> Skill Pick what to work on based on priorities","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-1-add-tasks-with-priorities","level":3,"title":"Step 1: Add Tasks with Priorities","text":"<p>Every piece of follow-up work gets a task. Use <code>ctx task add</code> from the terminal or <code>/ctx-task-add</code> from your AI assistant. Tasks should start with a verb and be specific enough that someone unfamiliar with the session could act on them.</p> <pre><code># High-priority bug found during code review\nctx task add \"Fix race condition in session cooldown\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Medium-priority feature work\nctx task add \"Add --format json flag to ctx status for CI integration\" --priority medium \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Low-priority cleanup\nctx task add \"Remove deprecated --raw flag from ctx load\" --priority low \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre> <p>The <code>/ctx-task-add</code> skill validates your task before recording it. It checks that the description is actionable, not a duplicate, and specific enough for someone else to pick up.</p> <p>If you say \"fix the bug,\" it will ask you to clarify which bug and where.</p> <p>Tasks Are Often Created Proactively</p> <p>In practice, many tasks are created proactively by the agent rather than by explicit CLI commands.</p> <p>After completing a feature, the agent will often identify follow-up work: tests, docs, edge cases, error handling, and offer to add them as tasks.</p> <p>You do not need to dictate <code>ctx task add</code> commands; the agent picks up on work context and suggests tasks naturally.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-2-organize-with-phase-sections","level":3,"title":"Step 2: Organize with Phase Sections","text":"<p>Tasks live in phase sections inside <code>TASKS.md</code>.</p> <p>Phases provide logical groupings that preserve order and enable replay.</p> <p>A task does not move between sections. It stays in its phase permanently, and status is tracked via checkboxes and inline tags.</p> <pre><code>## Phase 1: Core CLI\n\n- [x] Implement ctx add command\n- [x] Implement ctx task complete command\n- [ ] Add --section flag to ctx task add `#priority:medium`\n\n## Phase 2: AI Integration\n\n- [ ] Implement ctx agent cooldown `#priority:high` `#in-progress`\n- [ ] Add ctx watch XML parsing `#priority:medium`\n  - Blocked by: Need to finalize agent output format\n\n## Backlog\n\n- [ ] Performance optimization for large TASKS.md files `#priority:low`\n- [ ] Add metrics dashboard to ctx status `#priority:deferred`\n</code></pre> <p>Use <code>--section</code> when adding a task to a specific phase:</p> <pre><code>ctx task add \"Add ctx watch XML parsing\" --priority medium --section \\\n    \"Phase 2: AI Integration\" \\\n    --session-id abc12345 --branch main --commit 68fbc00a\n</code></pre> <p>Without <code>--section</code>, the task is inserted before the first unchecked task in <code>TASKS.md</code>.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-3-pick-what-to-work-on","level":3,"title":"Step 3: Pick What to Work On","text":"<p>At the start of a session, or after finishing a task, use <code>/ctx-next</code> to get prioritized recommendations. </p> <p>The skill reads <code>TASKS.md</code>, checks recent sessions, and ranks candidates using  explicit priority, blocking status, in-progress state, momentum from  recent work, and phase order.</p> <p>You can also ask naturally: \"what should we work on?\" or \"what's the highest priority right now?\"</p> <pre><code>/ctx-next\n</code></pre> <p>The output looks like this:</p> <pre><code>**1. Implement ctx agent cooldown** `#priority:high`\n\n    Still in-progress from yesterday's session. The tombstone file approach is\n    half-built. Finishing is cheaper than context-switching.\n\n**2. Add --section flag to ctx task add** `#priority:medium`\n\n    Last Phase 1 item. Quick win that unblocks organized task entry.\n\n---\n\n*Based on 8 pending tasks across 3 phases.\n\nLast session: agent-cooldown (2026-02-06).*\n</code></pre> <p>In-progress tasks almost always come first: </p> <p>Finishing existing work takes priority over starting new work.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-4-complete-tasks","level":3,"title":"Step 4: Complete Tasks","text":"<p>When a task is done, mark it complete by number or partial text match:</p> <pre><code># By task number (as shown in TASKS.md)\nctx task complete 3\n\n# By partial text match\nctx task complete \"agent cooldown\"\n</code></pre> <p>The task's checkbox changes from <code>[ ]</code> to <code>[x]</code>. Tasks are never deleted: they stay in their phase section so history is preserved.</p> <p>Be Conversational</p> <p>You rarely need to run <code>ctx task complete</code> yourself during an interactive session.</p> <p>When you say something like \"the rate limiter is done\" or \"we finished that,\" the agent marks the task complete and moves on to suggesting what is next.</p> <p>The CLI commands are most useful for manual housekeeping, scripted workflows, or when you want precision.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-5-snapshot-before-risky-changes","level":3,"title":"Step 5: Snapshot Before Risky Changes","text":"<p>Before a major refactor or any change that might break things, snapshot your current task state. This creates a copy of <code>TASKS.md</code> in <code>.context/archive/</code> without modifying the original.</p> <pre><code># Default snapshot\nctx task snapshot\n\n# Named snapshot (recommended before big changes)\nctx task snapshot \"before-refactor\"\n</code></pre> <p>This creates a file like <code>.context/archive/tasks-before-refactor-2026-02-08-1430.md</code>. If the refactor goes sideways, and you need to confirm what the task state looked like before you started, the snapshot is there.</p> <p>Snapshots are cheap: Take them before any change you might want to undo or review later.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#step-6-archive-when-tasksmd-gets-cluttered","level":3,"title":"Step 6: Archive When <code>TASKS.md</code> Gets Cluttered","text":"<p>After several sessions, <code>TASKS.md</code> accumulates completed tasks that make it hard to see what is still pending.</p> <p>Use <code>ctx task archive</code> to move all <code>[x]</code> items to a timestamped archive file.</p> <p>Start with a dry run to preview what will be moved:</p> <pre><code>ctx task archive --dry-run\n</code></pre> <p>Then archive:</p> <pre><code>ctx task archive\n</code></pre> <p>Completed tasks move to <code>.context/archive/tasks-2026-02-08.md</code>. Phase headers are preserved in the archive for traceability. Pending tasks (<code>[ ]</code>) remain in <code>TASKS.md</code>.</p> <p>The <code>/ctx-archive</code> skill adds two safety checks before archiving: it verifies that completed tasks are genuinely cluttering the view and that nothing was marked <code>[x]</code> prematurely.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#letting-the-agent-drive","level":2,"title":"Letting the Agent Drive","text":"<p>The steps above show the CLI commands for task management.</p> <p>In practice, most task management happens conversationally:</p> <p>An agent that has loaded the context files does not need you to type <code>ctx task add</code>. It tracks work naturally and offers the right operations.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#use-natural-language-instead-of-commands","level":3,"title":"Use Natural Language Instead of Commands","text":"<p>These conversational prompts replace explicit commands during interactive sessions:</p> Instead of typing... Just say... <code>ctx task add \"Write tests for X\" --session-id ID --branch BR --commit HASH</code> \"We should add tests for this: track that?\" <code>/ctx-next</code> \"What should we work on?\" <code>ctx task complete \"rate limiting\"</code> \"The rate limiter is done, what's next?\" <code>ctx task archive</code> \"<code>TASKS.md</code> is getting long, can you clean it up?\" <code>ctx task add ... --session-id ID --branch BR --commit HASH && ctx task add ...</code> \"Add follow-ups for what we just built.\" <p>The agent translates these into the right <code>ctx</code> operations behind the scenes.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-creates-tasks-proactively","level":3,"title":"The Agent Creates Tasks Proactively","text":"<p>After completing a piece of work, the agent does not wait for you to dictate follow-up items. It identifies them from the work context and offers:</p> <pre><code>**Agent**: \"I've finished implementing the rate limiter middleware. I noticed\nthree follow-up items:\n\n1. Write integration tests for the rate limiter (the unit tests pass but\n   there's no end-to-end coverage)\n2. Add rate limit headers to API responses (X-RateLimit-Remaining, etc.)\n3. Document the rate limit configuration in the ops runbook\n\nWant me to add these as tasks?\"\n</code></pre> <p>This is how an agent behaves once it has read the Agent Playbook.</p> <p>The playbook's \"Work, Reflect, Persist\" cycle means the agent asks itself after every milestone: Did I just complete a task? Did new work emerge?</p> <p>When the answer is yes, it acts.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#asking-the-agent-what-to-capture","level":3,"title":"Asking the Agent What to Capture","text":"<p>You don't always know what's worth persisting.</p> <p>Instead of reviewing the session yourself and typing specific commands, ask the agent to do the thinking:</p> You say What happens \"What tasks came out of this session?\" Agent reviews the work, identifies follow-ups, and offers to add them \"Are there any tasks, learnings, or decisions worth adding?\" Agent scans the full session for all three types and proposes a batch \"What should we remember from today?\" Agent surfaces everything persist-worthy: not just tasks \"Anything we should track before we move on?\" Agent checks for loose ends specific to the work just completed <p>This is especially powerful after long or exploratory sessions where multiple threads were touched. </p> <p>The agent has the full conversation in context and can spot follow-ups you  might miss:</p> <pre><code>You:   \"Are there any tasks, learnings, or decisions worth adding\n        from this session?\"\n\nAgent: \"Yes. Here's what I'd suggest:\n\n  Tasks:\n  - Add retry logic to the webhook delivery (we discussed it when\n    the timeout test failed but never tracked it)\n  - Update the API docs to reflect the new rate limit headers\n\n  Learnings:\n  - net/http's default client has no timeout: we burned 15 minutes\n    on that before adding an explicit 30s timeout\n\n  Decisions:\n  - We chose token-bucket over sliding-window for rate limiting\n    because it handles bursts better and the library is simpler\n\n  Want me to add all of these?\"\n\nYou:   \"All of them.\"\n</code></pre> <p>The agent fills in structured fields from what it observed during the session. You get the same quality entries as if you typed verbose commands, without needing to remember what happened or how to format it.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#the-agent-completes-tasks-and-moves-on","level":3,"title":"The Agent Completes Tasks and Moves On","text":"<p>When you confirm that work is finished, or when the agent can see that it is, it marks the task done and suggests the next step:</p> <pre><code>You: \n\"The cooldown logic is working now.\"\n\nAgent: \n\"Marked 'Implement ctx agent cooldown' as done. Your highest-\npriority remaining task is 'Add --section flag to ctx task add'.\n\nIt's the last item in Phase 1 and a quick win. Want to start on that?\"\n</code></pre> <p>The agent chains completion into next-task selection.</p> <p>One sentence from you triggers two operations.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#end-of-session-capture","level":3,"title":"End-of-Session Capture","text":"<p>At the end of a session, the agent reviews what happened and catches loose ends:</p> <pre><code>You: \n\"I think we're good for today.\"\n\nAgent: \n\"Before we wrap up: during the middleware work you mentioned\nwanting to add metrics for rate limit hits. That never made it into TASKS.md.\n\nWant me to add it as a task so it's there next session?\"\n</code></pre> <p>This is the proactive persistence model at work.</p> <p>The agent monitors the conversation for mentioned-but-not-tracked work and surfaces it before context is lost.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#a-conversational-session-example","level":3,"title":"A Conversational Session Example","text":"<p>Here is what task management looks like in a real session when the agent is driving:</p> <pre><code>You:   What's left to do?\nAgent: You have 5 pending tasks across 2 phases. The highest priority is\n       \"Implement ctx agent cooldown\": it's marked in-progress from\n       yesterday and half-built. Want to pick that up?\n\nYou:   Yeah, let's finish it.\n       [... 20 minutes of implementation work ...]\n\nYou:   That's working. Tests pass.\nAgent: Nice. I'll mark \"Implement ctx agent cooldown\" as done.\n       I noticed two things from the implementation:\n       1. The tombstone file cleanup should happen on a timer, not just\n          at startup. Want me to add that as a task?\n       2. We learned that file locks don't work reliably on NFS. I'll\n          add that to LEARNINGS.md.\n\nYou:   Yes to both. What's next?\nAgent: Added the cleanup task as high priority. Next up is \"Add --section\n       flag to ctx task add\": last item in Phase 1. It should be\n       straightforward since the flag parsing pattern already exists\n       in the codebase. Ready?\n</code></pre> <p>It's All Conversational</p> <p>Notice what did not happen: The user never typed a <code>ctx</code> command.</p> <p>The agent handled task completion, follow-up creation, learning capture, and next-task selection: all from natural conversation.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<pre><code># Add a task\nctx task add \"Implement rate limiting for API endpoints\" --priority high \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# Add to a specific phase\nctx task add \"Write integration tests for rate limiter\" --section \"Phase 2\" \\\n  --session-id abc12345 --branch main --commit 68fbc00a\n\n# See what to work on\n# (from AI assistant) /ctx-next\n\n# Mark done by text\nctx task complete \"rate limiting\"\n\n# Mark done by number\nctx task complete 5\n\n# Snapshot before a risky refactor\nctx task snapshot \"before-middleware-rewrite\"\n\n# Archive completed tasks when the list gets long\nctx task archive --dry-run     # preview first\nctx task archive               # then archive\n</code></pre>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#tips","level":2,"title":"Tips","text":"<ul> <li>Start tasks with a verb: \"Add,\" \"Fix,\" \"Implement,\" \"Investigate\":    not just a topic like \"Authentication.\"</li> <li>Include the why in the task description. Future sessions lack the context of   why you added the task. \"Add rate limiting\" is worse than \"Add rate limiting   to prevent abuse on the public API after the load test showed 10x traffic spikes.\"</li> <li>Use <code>#in-progress</code> sparingly. Only one or two tasks should carry this tag at   a time. If everything is in-progress, nothing is.</li> <li>Snapshot before, not after. The point of a snapshot is to capture the    state before a change, not to celebrate what you just finished.</li> <li>Archive regularly. Once completed tasks outnumber pending ones, it is time   to archive. A clean <code>TASKS.md</code> helps both you and your AI assistant focus.</li> <li>Never delete tasks. Mark them <code>[x]</code> (completed) or <code>[-]</code> (skipped with a   reason). Deletion breaks the audit trail.</li> <li>Trust the agent's task instincts. When the agent suggests follow-up items   after completing work, it is drawing on the full context of what just happened.</li> <li>Conversational prompts beat commands in interactive sessions. Saying   \"what should we work on?\" is faster and more natural than running <code>/ctx-next</code>.   Save explicit commands for scripts, CI, and unattended runs.</li> <li>Let the agent chain operations. A single statement like \"that's done, what's   next?\" can trigger completion, follow-up identification, and next-task   selection in one flow.</li> <li>Review proactive task suggestions before moving on. The best follow-ups come   from items spotted in-context right after the work completes.</li> </ul>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#next-up","level":2,"title":"Next Up","text":"<p>Using the Scratchpad →: Store short-lived sensitive notes in an encrypted scratchpad.</p>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/task-management/#see-also","level":2,"title":"See Also","text":"<ul> <li>The Complete Session: full session lifecycle including   task management in context</li> <li>Persisting Decisions, Learnings, and Conventions:   capturing the \"why\" behind your work</li> <li>Detecting and Fixing Drift:   keeping <code>TASKS.md</code> accurate over time</li> <li>CLI Reference:   full documentation for <code>ctx add</code>, <code>ctx task complete</code>, <code>ctx task</code></li> <li>Context Files: <code>TASKS.md</code>:    format and conventions for <code>TASKS.md</code></li> </ul>","path":["Recipes","Knowledge and Tasks","Tracking Work Across Sessions"],"tags":[]},{"location":"recipes/triggers/","level":1,"title":"Authoring Lifecycle Triggers","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#authoring-lifecycle-triggers","level":1,"title":"Authoring Lifecycle Triggers","text":"<p>Triggers are executable shell scripts that fire at specific events during an AI session. They're how you express \"when the AI saves a file, also do X\" or \"before the AI edits this path, check Y first.\" This recipe walks through writing your first trigger, testing it, and enabling it safely.</p> <p>Triggers Execute Arbitrary Code</p> <p>A trigger is a shell script with the executable bit set. It runs with the same privileges as your AI tool and receives JSON input on stdin. Treat triggers like pre-commit hooks:</p> <ul> <li>Only enable scripts you have read and understand.</li> <li>Never enable a trigger you downloaded from the internet   without reviewing every line.</li> <li>Avoid shelling out to user-controlled values (<code>jq -r</code>   output, <code>path</code> field, <code>tool</code> field) without quoting.</li> <li>A malicious or buggy trigger can block tool calls,   corrupt context files, or exfiltrate data.</li> </ul> <p>The generated trigger template starts disabled (no executable bit) so you cannot accidentally run an unreviewed script. Enable it explicitly with <code>ctx trigger enable</code>.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#scenario","level":2,"title":"Scenario","text":"<p>You want a <code>pre-tool-use</code> trigger that blocks the AI from editing anything in <code>internal/crypto/</code> without explicit confirmation. Cryptographic code is sensitive, and accidental edits have caused outages before, and you want a hard gate.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-1-scaffold-the-script","level":2,"title":"Step 1: Scaffold the Script","text":"<pre><code>ctx trigger add pre-tool-use protect-crypto\n</code></pre> <p>That creates <code>.context/hooks/pre-tool-use/protect-crypto.sh</code> with a template:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\n# Read the JSON event from stdin.\npayload=$(cat)\n\n# Parse fields with jq.\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Your logic here.\n\n# Return a JSON result. action can be \"allow\", \"block\", or absent.\necho '{\"action\": \"allow\"}'\n</code></pre> <p>Note: the directory is <code>.context/hooks/pre-tool-use/</code>; the on-disk layout still uses <code>hooks/</code> even though the command is <code>ctx trigger</code>. If you <code>ls .context/hooks/</code>, that's where your triggers live.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-2-write-the-logic","level":2,"title":"Step 2: Write the Logic","text":"<p>Open the file and replace the template body:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\n\npayload=$(cat)\ntool=$(echo \"$payload\" | jq -r '.tool // empty')\npath=$(echo \"$payload\" | jq -r '.path // empty')\n\n# Only gate write-family tools.\ncase \"$tool\" in\n  write_file|edit_file|apply_patch) ;;\n  *)\n    echo '{\"action\": \"allow\"}'\n    exit 0\n    ;;\nesac\n\n# Block any path under internal/crypto/.\ncase \"$path\" in\n  internal/crypto/*|*/internal/crypto/*)\n    jq -n --arg p \"$path\" '{\n      action: \"block\",\n      message: (\"Edits to \" + $p + \" require manual review. \" +\n                \"See CONVENTIONS.md for the crypto-change process.\")\n    }'\n    exit 0\n    ;;\nesac\n\necho '{\"action\": \"allow\"}'\n</code></pre> <p>A few things to note:</p> <ul> <li><code>set -euo pipefail</code>: any unhandled error aborts the   script. Critical for a security-relevant trigger.</li> <li>Quote everything from <code>jq</code>: the <code>path</code> field comes from   the AI tool; treat it as untrusted input.</li> <li>Explicit <code>allow</code> case: the default is allow. An   empty or missing response is a risky default.</li> <li>Use <code>jq -n --arg</code> for output construction, as it is safer than   string concatenation when the message may contain special   characters.</li> </ul>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-3-test-with-a-mock-payload","level":2,"title":"Step 3: Test with a Mock Payload","text":"<p>Before enabling the trigger, test it with a realistic mock input using <code>ctx trigger test</code>. This runs the script against a synthetic JSON payload without actually firing any AI tool.</p> <pre><code># Test the \"should block\" case\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\n</code></pre> <p>Expected: the trigger returns <code>{\"action\":\"block\", \"message\": \"...\"}</code>.</p> <pre><code># Test the \"should allow\" case\nctx trigger test pre-tool-use --tool write_file --path internal/memory/mirror.go\n</code></pre> <p>Expected: the trigger returns <code>{\"action\":\"allow\"}</code>.</p> <pre><code># Test that non-write tools pass through\nctx trigger test pre-tool-use --tool read_file --path internal/crypto/aes.go\n</code></pre> <p>Expected: <code>{\"action\":\"allow\"}</code> because the <code>case</code> statement only gates write-family tools.</p> <p>If any of these cases misbehave, fix the trigger before enabling it. The trigger is disabled at this point, so misbehavior doesn't affect real AI sessions.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-4-enable-it","level":2,"title":"Step 4: Enable It","text":"<p>Once the test cases pass, enable the trigger:</p> <pre><code>ctx trigger enable protect-crypto\n</code></pre> <p>That sets the executable bit. Next time the AI starts a <code>pre-tool-use</code> event, the trigger will fire.</p> <p>Verify it's enabled:</p> <pre><code>ctx trigger list\n</code></pre> <p>Should show <code>protect-crypto</code> under <code>pre-tool-use</code> with an enabled indicator.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#step-5-iterate-safely","level":2,"title":"Step 5: Iterate Safely","text":"<p>If you discover a bug after enabling, disable first, fix second:</p> <pre><code>ctx trigger disable protect-crypto\n# ...edit the script...\nctx trigger test pre-tool-use --tool write_file --path internal/crypto/aes.go\nctx trigger enable protect-crypto\n</code></pre> <p>Disabling simply clears the executable bit; the script stays on disk, and <code>ctx trigger enable</code> re-enables it without rewriting anything.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#patterns-worth-copying","level":2,"title":"Patterns Worth Copying","text":"","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#logging-not-blocking","level":3,"title":"Logging, Not Blocking","text":"<p>For auditing or analytics, return <code>{\"action\":\"allow\"}</code> always and append to a log as a side effect:</p> <pre><code>#!/usr/bin/env bash\nset -euo pipefail\npayload=$(cat)\necho \"$payload\" >> .context/logs/tool-use.jsonl\necho '{\"action\":\"allow\"}'\n</code></pre>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#context-injection-at-session-start","level":3,"title":"Context Injection at Session Start","text":"<p>A <code>session-start</code> trigger can prepend text to the agent's initial prompt by emitting <code>{\"action\":\"inject\", \"content\": \"...\"}</code> . This is useful for injecting daily standup notes, open PRs, or rotating TODOs without storing them in a steering file.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#chaining-triggers-of-the-same-type","level":3,"title":"Chaining Triggers of the Same Type","text":"<p>Multiple scripts in the same type directory all run. If any returns <code>action: block</code>, the block wins. Keep individual triggers single-purpose and rely on composition.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>Forgetting the shebang. Without <code>#!/usr/bin/env bash</code>, the trigger won't execute even with the executable bit set.</p> <p>Not quoting <code>$path</code>. If you use <code>$path</code> in a command substitution or a <code>case</code> glob without quoting, a file name with spaces or metacharacters will break the trigger in surprising ways.</p> <p>Enabling before testing. <code>ctx trigger enable</code> makes the script live immediately. Always <code>ctx trigger test</code> first.</p> <p>Outputting non-JSON. The trigger's stdout must be valid JSON or <code>ctx</code>'s trigger runner will log a parse error. Use <code>jq -n</code> to construct output rather than hand-writing JSON strings.</p> <p>Mixing <code>hook</code> and <code>trigger</code> vocabulary. The command is <code>ctx trigger</code> but the on-disk directory is <code>.context/hooks/</code>. The feature was renamed; the directory name lags behind. Don't let this confuse you; they refer to the same thing.</p>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/triggers/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx trigger</code> reference: full   command, flag, and event-type reference.</li> <li><code>ctx steering</code>: persistent rules,   not scripts. Use steering when the thing you want is \"tell   the AI to always do X\" rather than \"run a script when Y   happens.\"</li> <li>Writing steering files: the rule-based   equivalent of this recipe.</li> </ul>","path":["Recipes","Agents and Automation","Authoring Lifecycle Triggers"],"tags":[]},{"location":"recipes/troubleshooting/","level":1,"title":"Troubleshooting","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-problem","level":2,"title":"The Problem","text":"<p>Something isn't working: a hook isn't firing, nudges are too noisy, context seems stale, or the agent isn't following instructions. The information to diagnose it exists (across status, drift, event logs, hook config, and session history), but assembling it manually is tedious.</p> <p>How do you figure out what's wrong and fix it?</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx doctor                   # structural health check\nctx hook event --last 20  # recent hook activity\n# or ask: \"something seems off, can you diagnose?\"\n</code></pre>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx doctor</code> CLI command Structural health report <code>ctx doctor --json</code> CLI command Machine-readable health report <code>ctx hook event</code> CLI command Query local event log <code>/ctx-doctor</code> Skill Agent-driven diagnosis with analysis","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#quick-check-ctx-doctor","level":3,"title":"Quick Check: <code>ctx doctor</code>","text":"<p>Run <code>ctx doctor</code> for an instant structural health report. It checks context initialization, required files, drift, hook configuration, event logging, webhooks, reminders, task completion ratio, and context token size: all in one pass:</p> <pre><code>ctx doctor\n</code></pre> <pre><code>ctx doctor\n==========\n\nStructure\n  ✓ Context initialized (.context/)\n  ✓ Required files present (4/4)\n\nQuality\n  ⚠ Drift: 2 warnings (stale path in ARCHITECTURE.md, high entry count in LEARNINGS.md)\n\nHooks\n  ✓ hooks.json valid (14 hooks registered)\n  ○ Event logging disabled (enable with event_log: true in .ctxrc)\n\nState\n  ✓ No pending reminders\n  ⚠ Task completion ratio high (18/22 = 82%): consider archiving\n\nSize\n  ✓ Context size: ~4200 tokens (budget: 8000)\n\nSummary: 2 warnings, 0 errors\n</code></pre> <p>Warnings are non-critical but worth fixing. Errors need attention. Informational notes (○) flag optional features that aren't enabled.</p> <p>For scripting:</p> <pre><code>ctx doctor --json | jq '.warnings'\n</code></pre>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#deep-dive-ctx-doctor","level":3,"title":"Deep Dive: <code>/ctx-doctor</code>","text":"<p>When you need the agent to reason about what's wrong, use the skill. Ask naturally or invoke directly:</p> <pre><code>Why didn't my hook fire?\nSomething seems off, can you diagnose?\n/ctx-doctor\n</code></pre> <p>The agent follows a triage sequence:</p> <ol> <li>Baseline: runs <code>ctx doctor --json</code> for structural health</li> <li>Events: runs <code>ctx hook event --json --last 100</code> (if event logging enabled)</li> <li>Correlate: connects findings across both sources</li> <li>Present: structured findings with evidence</li> <li>Suggest: actionable next steps (but doesn't auto-fix)</li> </ol> <p>The skill degrades gracefully: without event logging enabled, it still runs structural checks and notes what you'd gain by enabling it.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#raw-event-inspection","level":3,"title":"Raw Event Inspection","text":"<p>For power users: <code>ctx hook event</code> with filters gives direct access to the event log.</p> <pre><code># Last 50 events (default)\nctx hook event\n\n# Events from a specific session\nctx hook event --session eb1dc9cd-0163-4853-89d0-785fbfaae3a6\n\n# Only QA reminder events\nctx hook event --hook qa-reminder\n\n# Raw JSONL for jq processing\nctx hook event --json | jq '.message'\n\n# Include rotated (older) events\nctx hook event --all --last 100\n</code></pre> <p>Filters use AND logic: <code>--hook qa-reminder --session abc123</code> returns only QA reminder events from that specific session.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#common-problems","level":2,"title":"Common Problems","text":"","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#no-context-directory-specified-for-this-project","level":3,"title":"\"No context directory specified for this project\"","text":"<p>Symptoms: Any <code>ctx</code> command fails with <code>Error: no context directory specified for this project</code> (possibly with a likely-candidate hint or a candidate list depending on what's visible from your CWD).</p> <p>Cause: <code>ctx</code> does not search the filesystem for a <code>.context/</code> directory. You have to declare which one to use before running day-to-day commands.</p> <p>Fix: bind <code>CTX_DIR</code> for the current shell:</p> <pre><code>eval \"$(ctx activate)\"\n</code></pre> <p>See Activating a Context Directory for the full recipe (one-shot <code>CTX_DIR=...</code> inline form, CI patterns, direnv setup).</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#ctx-not-initialized","level":3,"title":"\"<code>ctx</code>: Not Initialized\"","text":"<p>Symptoms: After declaring <code>CTX_DIR</code>, the command fails with <code>ctx: not initialized - run \"ctx init\" first</code>.</p> <p>Cause: The declared directory exists but hasn't been initialized with template files.</p> <p>Fix:</p> <pre><code>ctx init          # create .context/ with template files\nctx init --minimal  # or just the essentials (CONSTITUTION, TASKS, DECISIONS)\n</code></pre> <p>Commands that work without CTX_DIR or initialization: <code>ctx init</code>, <code>ctx activate</code>, <code>ctx deactivate</code>, <code>ctx setup</code>, <code>ctx doctor</code>, <code>ctx guide</code>, <code>ctx why</code>, <code>ctx config switch/status</code>, <code>ctx hub *</code>, and help-only grouping commands.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-cli-and-my-claude-code-session-disagree-on-the-project","level":3,"title":"\"My CLI and My Claude Code Session Disagree on the Project\"","text":"<p>Symptoms: A <code>!</code>-pragma or interactive <code>ctx</code> call writes to the wrong <code>.context/</code>; or you ran <code>ctx remind add</code> in shell A and the reminder shows up in project B's notifications.</p> <p>Cause: <code>CTX_DIR</code> is sourced from three different surfaces, and they can drift apart:</p> Surface Source of <code>CTX_DIR</code> Bound when Claude Code hooks <code>${CLAUDE_PROJECT_DIR}/.context</code> (injected) Every hook line; the project Claude is in <code>!</code>-pragma in chat / interactive shell Whatever the parent shell exported When you ran <code>eval \"$(ctx activate)\"</code> New shell tab opened mid-session Whatever your shellrc exports Login <p>When these drift, the per-prompt <code>check-anchor-drift</code> hook fires a verbatim warning naming both values. To fix: re-run <code>eval \"$(ctx activate)\"</code> from inside the project the Claude Code session is editing, or close the shell tab and reopen it from the right working directory.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#my-hook-isnt-firing","level":3,"title":"\"My Hook Isn't Firing\"","text":"<p>Symptoms: No nudges appearing, webhook silent, event log shows no entries for the expected hook.</p> <p>Diagnosis:</p> <pre><code># 1. Check if ctx is installed and on PATH\nwhich ctx && ctx --version\n\n# 2. Check if the hook is registered\ngrep \"check-persistence\" ~/.claude/plugins/ctx/hooks.json\n\n# 3. Run the hook manually to see if it errors\necho '{\"session_id\":\"test\"}' | ctx system check-persistence\n\n# 4. Check event log for the hook (if enabled)\nctx hook event --hook check-persistence\n</code></pre> <p>Common causes:</p> <ul> <li>Plugin is not installed: run <code>ctx init --claude</code> to reinstall</li> <li>PATH issue: the hook invokes <code>ctx</code> from PATH; ensure it resolves</li> <li>Throttle active: most hooks fire once per day: check   <code>.context/state/</code> for daily marker files</li> <li>Hook silenced: a custom message override may be an empty file:   check <code>ctx hook message list</code> for overrides</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#too-many-nudges","level":3,"title":"\"Too Many Nudges\"","text":"<p>Symptoms: The agent is overwhelmed with hook output. Context checkpoints, persistence reminders, and QA gates fire constantly.</p> <p>Diagnosis:</p> <pre><code># Check how often hooks fired recently\nctx hook event --last 50\n\n# Count fires per hook\nctx hook event --json | jq -r '.detail.hook // \"unknown\"' \\\n  | sort | uniq -c | sort -rn\n</code></pre> <p>Common causes:</p> <ul> <li>QA reminder is noisy by design: it fires on every <code>Edit</code> call with no   throttle. This is intentional. If it's too much, silence it with an empty   override: <code>ctx hook message edit qa-reminder gate</code>, then empty the file</li> <li>Long session: context checkpoint fires with increasing frequency after   prompt 15. This is the system telling you the session is getting long:   consider wrapping up</li> <li>Short throttle window: if you deleted marker files in   <code>.context/state/</code>, daily-throttled hooks will re-fire</li> <li>Outdated Claude Code plugin: Update the plugin using Claude Code →    <code>/plugin</code> → \"Marketplace\"</li> <li><code>ctx</code> version mismatch: Build (or download) and install the    latest <code>ctx</code> vesion.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#context-seems-stale","level":3,"title":"\"Context Seems Stale\"","text":"<p>Symptoms: The agent references outdated information, paths that don't exist, or decisions that were reversed.</p> <p>Diagnosis:</p> <pre><code># Structural drift check\nctx drift\n\n# Full doctor check (includes drift + more)\nctx doctor\n\n# Check when context files were last modified\nctx status --verbose\n</code></pre> <p>Common causes:</p> <ul> <li>Drift accumulated: stale path references in <code>ARCHITECTURE.md</code> or   <code>CONVENTIONS.md</code>. Fix with <code>ctx drift --fix</code> or ask the agent to clean up.</li> <li>Task backlog: too many completed tasks diluting active context. Archive   with <code>ctx task archive</code> or <code>ctx compact --archive</code>.</li> <li>Large context files: <code>LEARNINGS.md</code> with 40+ entries competes for   attention. Consolidate with <code>/ctx-consolidate</code>.</li> <li>Missing session ceremonies: if <code>/ctx-remember</code> and <code>/ctx-wrap-up</code> aren't   being used, context doesn't get refreshed. See   Session Ceremonies.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#the-agent-isnt-following-instructions","level":3,"title":"\"The Agent Isn't Following Instructions\"","text":"<p>Symptoms: The agent ignores conventions, forgets decisions, or acts contrary to <code>CONSTITUTION.md</code> rules.</p> <p>Diagnosis:</p> <pre><code># Check context token size: Is it too large for the model?\nctx doctor --json | jq '.results[] | select(.name == \"context_size\")'\n\n# Check if context is actually being loaded\nctx hook event --hook context-load-gate\n</code></pre> <p>Common causes:</p> <ul> <li>Context too large: if total tokens exceed the model's effective attention,   instructions get diluted. Check <code>ctx doctor</code> for the size check. Compact with   <code>ctx compact --archive</code>.</li> <li>Context not loading: if <code>context-load-gate</code> hasn't fired, the agent   may not have received context. Verify the hook is registered.</li> <li>Conflicting instructions: <code>CONVENTIONS.md</code> says one thing,   <code>AGENT_PLAYBOOK.md</code> says another. Review both files for consistency.</li> <li>Agent drift: the agent's behavior diverges from instructions over long   sessions. This is normal. Use <code>/ctx-reflect</code> to re-anchor, or start a new   session.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#prerequisites","level":2,"title":"Prerequisites","text":"<ul> <li>Event logging (optional but recommended): <code>event_log: true</code> in <code>.ctxrc</code></li> <li><code>ctx</code> initialized: <code>ctx init</code></li> </ul> <p>Event logging is not required for <code>ctx doctor</code> or <code>/ctx-doctor</code> to work. Both degrade gracefully: structural checks run regardless, and the skill notes when event data is unavailable.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with <code>ctx doctor</code>: It's the fastest way to get a comprehensive   health picture. Save event log inspection for when you need to understand   when and how often something happened.</li> <li>Enable event logging early: The log is opt-in and low-cost (~250 bytes   per event, 1MB rotation cap). Enable it before you need it: Diagnosing   a problem without historical data is much harder.</li> <li>Use the skill for correlation: <code>ctx doctor</code> tells you what is wrong.   <code>/ctx-doctor</code> tells you why by correlating structural findings with event   patterns. The agent can spot connections that individual commands miss.</li> <li>Event log is gitignored: It's machine-local diagnostic data, not project   context. Different machines produce different event streams.</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#next-up","level":2,"title":"Next Up","text":"<p>Detecting and Fixing Drift →: Keep context files accurate as your codebase evolves.</p>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/troubleshooting/#see-also","level":2,"title":"See Also","text":"<ul> <li>Auditing System Hooks: the complete hook catalog   and webhook-based audit trails</li> <li>Detecting and Fixing Drift: structural and semantic   drift detection and repair</li> <li>Webhook Notifications: push notifications for   hook activity</li> <li><code>ctx doctor</code> CLI: full command reference</li> <li><code>ctx hook event</code> CLI: event log   query reference</li> <li><code>/ctx-doctor</code> skill: agent-driven   diagnosis</li> </ul>","path":["Recipes","Maintenance","Troubleshooting"],"tags":[]},{"location":"recipes/typical-kb-session/","level":1,"title":"Typical KB Session","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#the-problem","level":2,"title":"The Problem","text":"<p>You set the editorial pipeline up (Build a Knowledge Base). Now you sit down for a real research session: a transcript to ingest, a question to answer against existing evidence, a finding to capture for later. What's the actual flow?</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#tldr","level":2,"title":"TL;DR","text":"<pre><code>/ctx-remember                                    # session-start recall\n/ctx-kb-ingest ./inputs/transcript.md \"topic\"    # editorial pass\n/ctx-kb-ask \"does the kb say X?\"                 # grounded Q&A\n/ctx-kb-note \"follow-up: chase the v1.1 link\"    # park a finding\n/ctx-wrap-up                                     # ceremony → /ctx-handover\n</code></pre>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>/ctx-remember</code> Skill Session-start recall (folds KB state when present) <code>/ctx-kb-ingest</code> Skill Mode-aware editorial pass <code>/ctx-kb-ask</code> Skill Q&A grounded in the kb <code>/ctx-kb-note</code> Skill Park a finding for the next ingest <code>/ctx-wrap-up</code> Skill End-of-session ceremony; delegates to the handover step <code>/ctx-handover</code> Skill Writes the per-session handover; called by <code>/ctx-wrap-up</code>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-1-session-start-recall","level":2,"title":"Step 1: Session Start (Recall)","text":"<pre><code>/ctx-remember\n</code></pre> <p><code>/ctx-remember</code> reads the latest handover under <code>.context/handovers/</code> (timestamped <code><TS>-<slug>.md</code> so concurrent agent runs never overwrite); its <code>## Summary</code> and <code>## Next session</code> are the authoritative recall surface. The five canonical files (<code>TASKS</code>, <code>DECISIONS</code>, etc.) are read as usual.</p> <p>When <code>.context/kb/</code> exists, <code>/ctx-remember</code> additionally folds editorial state into the readback: any closeouts whose <code>generated-at</code> postdates the handover are read for their <code>## What changed</code> sections (these are unfolded passes the last handover did not yet consume).</p> <p><code>SESSION_LOG.md</code> is not read at session start; it is mid-flight working memory, not a recall surface.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-2-ingest-the-sources-you-brought","level":2,"title":"Step 2: Ingest the Sources You Brought","text":"<pre><code>/ctx-kb-ingest ./inputs/2026-05-15-call.md \"cursor hooks\"\n</code></pre> <p>The skill declares its mode up front (most often <code>topic-page</code>), resolves sources, scans the source-coverage ledger for adjacent incomplete topics, and synthesizes prose into the topic page section by section. Every cited claim mints an <code>EV-###</code> row in <code>evidence-index.md</code> with the source short-name + locator + optional <code>sha:</code> pin for in-repo files.</p> <p>The pass ends with a circuit-breaker check (file exists, cites ≥ 1 <code>EV-###</code>, site builds clean, cold-reader rubric at <code>pass</code>) and writes a closeout.</p> <p>If the skill reports <code>topic-page: deferred</code> instead of <code>produced</code>, look at the closeout's <code>Next pass hint</code>. It names the exact resumption invocation.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-3-ask-grounded-questions","level":2,"title":"Step 3: Ask Grounded Questions","text":"<pre><code>/ctx-kb-ask \"does the kb say hooks block until they exit?\"\n</code></pre> <p><code>/ctx-kb-ask</code> reads the kb's prose and answers with <code>EV-###</code> citations. If the kb cannot answer, it opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> and reports the gap rather than inventing.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-4-park-findings-for-later","level":2,"title":"Step 4: Park Findings for Later","text":"<pre><code>/ctx-kb-note \"check whether SIGTERM behavior changed in v1.2\"\n</code></pre> <p><code>/ctx-kb-note</code> appends one-liners to <code>.context/ingest/findings.md</code>, a lightweight surface for parking ideas that don't earn a full ingest pass right now. The next <code>/ctx-kb-ingest</code> can choose to absorb them.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#step-5-wrap-up","level":2,"title":"Step 5: Wrap Up","text":"<pre><code>/ctx-wrap-up \"Cursor Hooks: lifecycle deep dive\"\n</code></pre> <p><code>/ctx-wrap-up</code> runs the standard capture checklist (learnings, decisions, conventions, tasks) and delegates to <code>/ctx-handover</code> as its final step. In a KB session it additionally:</p> <ul> <li>Surfaces pending closeouts under   <code>.context/ingest/closeouts/</code>.</li> <li>Counts <code>open</code> rows in <code>outstanding-questions.md</code>.</li> </ul> <p>The handover artifact lands at <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). The handover folds postdated closeouts into a <code>## Folded closeouts</code> section and archives them under <code>.context/archive/closeouts/</code>. Editorial work that was incomplete at wrap-up (open <code>Q-###</code> rows, <code>topic-page: deferred</code> passes) is surfaced as recall on the next session start.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#common-shapes","level":2,"title":"Common Shapes","text":"","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#multiple-topics-in-one-session","level":3,"title":"Multiple Topics in One Session","text":"<p>Run <code>/ctx-kb-ingest</code> once per topic. Each pass writes its own closeout; the handover folds all of them at the end.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#mid-session-checkpoint","level":3,"title":"Mid-Session Checkpoint","text":"<pre><code>ctx handover write \"Mid-day checkpoint\" \\\n  --summary \"...\" --next \"...\" --no-fold\n</code></pre> <p><code>--no-fold</code> writes the handover without consuming closeouts, useful when you want a recall anchor mid-session without ending the editorial chunking.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#aborted-session","level":3,"title":"Aborted Session","text":"<p>If you close the laptop after an ingest pass but before <code>/ctx-wrap-up</code>, the closeouts stay in place. The next session's <code>/ctx-remember</code> reads them as unfolded postdated closeouts; the next wrap-up's handover step folds them normally. See Recover an Aborted Session for the failure-mode detail.</p>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/typical-kb-session/#reference","level":2,"title":"Reference","text":"<ul> <li>Recipe: Build a Knowledge Base</li> <li>Recipe: Recover an Aborted Session</li> <li>Skill: <code>/ctx-kb-ingest</code></li> <li>Skill: <code>/ctx-handover</code></li> <li>Editorial constitution: <code>.context/ingest/KB-RULES.md</code></li> </ul>","path":["Typical KB Session"],"tags":[]},{"location":"recipes/webhook-notifications/","level":1,"title":"Webhook Notifications","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-problem","level":2,"title":"The Problem","text":"<p>Your agent runs autonomously (loops, implements, releases) while you are away from the terminal. You have no way to know when it finishes, hits a limit, or when a hook fires a nudge.</p> <p>How do you get notified about agent activity without watching the terminal?</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tldr","level":2,"title":"TL;DR","text":"<pre><code>ctx hook notify setup  # configure webhook URL (encrypted)\nctx hook notify test   # verify delivery\n# Hooks auto-notify on: session-end, loop-iteration, resource-danger\n</code></pre>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#commands-and-skills-used","level":2,"title":"Commands and Skills Used","text":"Tool Type Purpose <code>ctx hook notify setup</code> CLI command Configure and encrypt webhook URL <code>ctx hook notify test</code> CLI command Send a test notification <code>ctx hook notify --event <name> \"msg\"</code> CLI command Send a notification from scripts/skills <code>.ctxrc</code> <code>notify.events</code> Configuration Filter which events reach your webhook","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-1-get-a-webhook-url","level":3,"title":"Step 1: Get a Webhook URL","text":"<p>Any service that accepts HTTP POST with JSON works. Common options:</p> Service How to get a URL IFTTT Create an applet with the \"Webhooks\" trigger Slack Create an Incoming Webhook Discord Channel Settings > Integrations > Webhooks ntfy.sh Use <code>https://ntfy.sh/your-topic</code> (no signup) Pushover Use API endpoint with your user key <p>The URL contains auth tokens. <code>ctx</code> encrypts it; it never appears in plaintext in your repo.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-2-configure-the-webhook","level":3,"title":"Step 2: Configure the Webhook","text":"<pre><code>ctx hook notify setup\n# Enter webhook URL: https://maker.ifttt.com/trigger/ctx/json/with/key/YOUR_KEY\n# Webhook configured: https://maker.ifttt.com/***\n# Encrypted at: .context/.notify.enc\n</code></pre> <p>This encrypts the URL with AES-256-GCM using the same key as the scratchpad (<code>~/.ctx/.ctx.key</code>). The encrypted file (<code>.context/.notify.enc</code>) is safe to commit. The key lives outside the project and is never committed.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-3-test-it","level":3,"title":"Step 3: Test It","text":"<pre><code>ctx hook notify test\n# Webhook responded: HTTP 200 OK\n</code></pre> <p>If you see <code>No webhook configured</code>, run <code>ctx hook notify setup</code> first.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-4-configure-events","level":3,"title":"Step 4: Configure Events","text":"<p>Notifications are opt-in: no events are sent unless you configure an event list in <code>.ctxrc</code>:</p> <pre><code># .ctxrc\nnotify:\n  events:\n    - loop       # loop completion or max-iteration hit\n    - nudge      # VERBATIM relay hooks (context checkpoint, persistence, etc.)\n    - relay      # all hook output (verbose, for debugging)\n    - heartbeat  # every-prompt session-alive signal with metadata\n</code></pre> <p>Only listed events fire. Omitting an event silently drops it.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#step-5-use-in-your-own-skills","level":3,"title":"Step 5: Use in Your Own Skills","text":"<p>Add <code>ctx hook notify</code> calls to any skill or script:</p> <pre><code># In a release skill\nctx hook notify --event release \"v1.2.0 released successfully\" 2>/dev/null || true\n\n# In a backup script\nctx hook notify --event backup \"Nightly backup completed\" 2>/dev/null || true\n</code></pre> <p>The <code>2>/dev/null || true</code> suffix ensures the notification never breaks your script: If there's no webhook or the HTTP call fails, it's a silent noop.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-types","level":2,"title":"Event Types","text":"<p><code>ctx</code> fires these events automatically:</p> Event Source When <code>loop</code> Loop script Loop completes or hits max iterations <code>nudge</code> System hooks VERBATIM relay nudge is emitted (context checkpoint, persistence, ceremonies, journal, resources, knowledge, version) <code>relay</code> System hooks Any hook output (VERBATIM relays, agent directives, block responses) <code>heartbeat</code> System hook Every prompt: session-alive signal with prompt count and context modification status <code>test</code> <code>ctx hook notify test</code> Manual test notification (custom) Your skills You wire <code>ctx hook notify --event <name></code> in your own scripts <p><code>nudge</code> vs <code>relay</code>: The <code>nudge</code> event fires only for VERBATIM relay hooks (the ones the agent is instructed to show verbatim). The <code>relay</code> event fires for all hook output: VERBATIM relays, agent directives, and hard gates. Subscribe to <code>relay</code> for debugging (\"did the agent get the post-commit nudge?\"), <code>nudge</code> for user-facing assurance (\"was the checkpoint emitted?\").</p> <p>Webhooks as a Hook Audit Trail</p> <p>Subscribe to <code>relay</code> events and you get an external record of every hook that fires, independent of the agent. </p> <p>This lets you verify hooks are running and catch cases where the agent  absorbs a nudge instead of surfacing it. </p> <p>See Auditing System Hooks for the full workflow.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#payload-format","level":2,"title":"Payload Format","text":"<p>Every notification sends a JSON POST:</p> <pre><code>{\n  \"event\": \"nudge\",\n  \"message\": \"check-context-size: Context window at 82%\",\n  \"detail\": {\n    \"hook\": \"check-context-size\",\n    \"variant\": \"window\",\n    \"variables\": {\"Percentage\": 82, \"TokenCount\": \"164k\"}\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-22T14:30:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> <p>The <code>detail</code> field is a structured template reference containing the hook name, variant, and any template variables. This lets receivers filter by hook or variant without parsing rendered text. The field is omitted when no template reference applies (e.g. custom <code>ctx hook notify</code> calls).</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#heartbeat-payload","level":3,"title":"Heartbeat Payload","text":"<p>The <code>heartbeat</code> event fires on every prompt with session metadata and token usage telemetry:</p> <pre><code>{\n  \"event\": \"heartbeat\",\n  \"message\": \"heartbeat: prompt #7 (context_modified=false tokens=158k pct=79%)\",\n  \"detail\": {\n    \"hook\": \"heartbeat\",\n    \"variant\": \"pulse\",\n    \"variables\": {\n      \"prompt_count\": 7,\n      \"session_id\": \"abc123-...\",\n      \"context_modified\": false,\n      \"tokens\": 158000,\n      \"context_window\": 200000,\n      \"usage_pct\": 79\n    }\n  },\n  \"session_id\": \"abc123-...\",\n  \"timestamp\": \"2026-02-28T10:15:00Z\",\n  \"project\": \"ctx\"\n}\n</code></pre> <p>The <code>tokens</code>, <code>context_window</code>, and <code>usage_pct</code> fields are included when token data is available from the session JSONL file. They are omitted when no usage data has been recorded yet (e.g. first prompt).</p> <p>Unlike other events, <code>heartbeat</code> fires every prompt (not throttled). Use it for observability dashboards or liveness monitoring of long-running sessions.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#security-model","level":2,"title":"Security Model","text":"Component Location Committed? Permissions Encryption key <code>~/.ctx/.ctx.key</code> No (user-level) <code>0600</code> Encrypted URL <code>.context/.notify.enc</code> Yes (safe) <code>0600</code> Webhook URL Never on disk in plaintext N/A N/A <p>The key is shared with the scratchpad. If you rotate the encryption key, re-run <code>ctx hook notify setup</code> to re-encrypt the webhook URL with the new key.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#key-rotation","level":2,"title":"Key Rotation","text":"<p><code>ctx</code> checks the age of the encryption key once per day. If it's older than 90 days (configurable via <code>key_rotation_days</code>), a VERBATIM nudge is emitted suggesting rotation.</p> <pre><code># .ctxrc\nkey_rotation_days: 30   # nudge sooner (default: 90)\n</code></pre>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#worktrees","level":2,"title":"Worktrees","text":"<p>The webhook URL is encrypted with the same encryption key (<code>~/.ctx/.ctx.key</code>). Because the key lives at the user level, it is shared across all worktrees on the same machine - notifications work in worktrees automatically.</p> <p>This means agents running in worktrees cannot send webhook alerts. For autonomous runs where worktree agents are opaque, monitor them from the terminal rather than relying on webhooks. Enrich journals and review results on the main branch after merging.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#event-log-the-local-complement","level":2,"title":"Event Log: The Local Complement","text":"<p>Don't need a webhook but want diagnostic visibility? Enable <code>event_log: true</code> in <code>.ctxrc</code>. The event log writes the same payload as webhooks to a local JSONL file (<code>.context/state/events.jsonl</code>) that you can query without any external service:</p> <pre><code>ctx hook event --last 20          # recent hook activity\nctx hook event --hook qa-reminder # filter by hook\n</code></pre> <p>Webhooks and event logging are independent: you can use either, both, or neither. Webhooks give you push notifications and an external audit trail. The event log gives you local queryability and <code>ctx doctor</code> integration.</p> <p>See Troubleshooting for how they work together.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#tips","level":2,"title":"Tips","text":"<ul> <li>Fire-and-forget: Notifications never block. HTTP errors are silently   ignored. No retry, no response parsing.</li> <li>No webhook = no cost: When no webhook is configured, <code>ctx hook notify</code> exits   immediately. System hooks that call <code>notify.Send()</code> add zero overhead.</li> <li>Multiple projects: Each project has its own <code>.notify.enc</code>. You can point   different projects at different webhooks.</li> <li>Event filter is per-project: Configure <code>notify.events</code> in each project's   <code>.ctxrc</code> independently.</li> </ul>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#next-up","level":2,"title":"Next Up","text":"<p>Auditing System Hooks →: Verify your hooks are running, audit what they do, and get alerted when they go silent.</p>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/webhook-notifications/#see-also","level":2,"title":"See Also","text":"<ul> <li>CLI Reference: <code>ctx</code> hook notify:   full command reference</li> <li>Configuration: <code>.ctxrc</code> settings including   <code>notify</code> options</li> <li>Running an Unattended AI Agent: how loops work   and how notifications fit in</li> <li>Hook Output Patterns: understanding VERBATIM   relays, agent directives, and hard gates</li> <li>Auditing System Hooks: using webhooks as an   external audit trail for hook execution</li> </ul>","path":["Recipes","Hooks and Notifications","Webhook Notifications"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/","level":1,"title":"When to Use a Team of Agents","text":"","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-problem","level":2,"title":"The Problem","text":"<p>You have a task, and you are wondering: \"should I throw more agents at it?\"</p> <p>More agents can mean faster results, but they also mean coordination overhead, merge conflicts, divergent mental models, and wasted tokens re-reading context. </p> <p>The wrong setup costs more than it saves.</p> <p>This recipe is a decision framework: It helps you choose between a single agent, parallel worktrees, and a full agent team, and explains what <code>ctx</code> provides at each level.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tldr","level":2,"title":"TL;DR","text":"<ul> <li>Single agent for most work;</li> <li>Parallel worktrees when tasks touch disjoint file sets;</li> <li>Agent teams only when tasks need real-time   coordination. When in doubt, start with one agent.</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-spectrum","level":2,"title":"The Spectrum","text":"<p>There are three modes, ordered by complexity:</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#1-single-agent-default","level":3,"title":"1. Single Agent (Default)","text":"<p>One agent, one session, one branch. This is correct for most work.</p> <p>Use this when:</p> <ul> <li>The task has linear dependencies (step 2 needs step 1's output);</li> <li>Changes touch overlapping files;</li> <li>You need tight feedback loops (review each change before the next);</li> <li>The task requires deep understanding of a single area;</li> <li>Total effort is less than a few hours of agent time.</li> </ul> <p><code>ctx</code> provides: Full <code>.context/</code>: tasks, decisions, learnings, conventions, all in one session. </p> <p>The agent builds a coherent mental model and persists it as it goes.</p> <p>Example tasks: Bug fixes, feature implementation, refactoring a module, writing documentation for one area, debugging.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#2-parallel-worktrees-independent-tracks","level":3,"title":"2. Parallel Worktrees (Independent Tracks)","text":"<p>2-4 agents, each in a separate git worktree on its own branch, working on non-overlapping parts of the codebase.</p> <p>Use this when:</p> <ul> <li>You have 5+ independent tasks in the backlog;</li> <li>Tasks group cleanly by directory or package;</li> <li>File overlap between groups is zero or near-zero;</li> <li>Each track can be completed and merged independently;</li> <li>You want parallelism without coordination complexity.</li> </ul> <p><code>ctx</code> provides: Shared <code>.context/</code> via <code>git</code> (each worktree sees the same tasks, decisions, conventions). <code>/ctx-worktree</code> skill for setup and teardown. <code>TASKS.md</code> as a lightweight work queue.</p> <p>Example tasks: Docs + new package + test coverage (three tracks that don't touch the same files). Parallel recipe writing. Independent module development.</p> <p>See: Parallel Agent Development with Git Worktrees</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#3-agent-team-coordinated-swarm","level":3,"title":"3. Agent Team (Coordinated Swarm)","text":"<p>Multiple agents communicating via messages, sharing a task list, with a lead agent coordinating. Claude Code's team/swarm feature.</p> <p>Use this when:</p> <ul> <li>Tasks have dependencies but can still partially overlap;</li> <li>You need research and implementation happening simultaneously;</li> <li>The work requires different roles (researcher, implementer, tester);</li> <li>A lead agent needs to review and integrate others' work;</li> <li>The task is large enough that coordination cost is justified.</li> </ul> <p><code>ctx</code> provides: <code>.context/</code> as shared state that all agents can read. Task tracking for work assignment. Decisions and learnings as team memory that survives individual agent turnover.</p> <p>Example tasks: Large refactor across modules where a lead reviews merges. Research and implementation where one agent explores options while another builds. Multi-file feature that needs integration testing after parallel implementation.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-decision-framework","level":2,"title":"The Decision Framework","text":"<p>Ask these questions in order:</p> <pre><code>Can one agent do this in a reasonable time?\n  YES → Single agent. Stop here.\n  NO  ↓\n\nCan the work be split into non-overlapping file sets?\n  YES → Parallel worktrees (2-4 tracks)\n  NO  ↓\n\nDo the subtasks need to communicate during execution?\n  YES → Agent team with lead coordination\n  NO  → Parallel worktrees with a merge step\n</code></pre>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#the-file-overlap-test","level":3,"title":"The File Overlap Test","text":"<p>This is the critical decision point. Before choosing multi-agent, list the files each subtask would touch. If two subtasks modify the same file, they belong in the same track (or the same single-agent session).</p> <pre><code>You: \"I want to parallelize these tasks. Which files would each one touch?\"\n\nAgent: [reads `TASKS.md`, analyzes codebase]\n       \"Task A touches internal/config/ and internal/cli/initialize/\n        Task B touches docs/ and site/\n        Task C touches internal/config/ and internal/cli/status/\n\n        Tasks A and C overlap on internal/config/ # they should be\n        in the same track. Task B is independent.\"\n</code></pre> <p>When in doubt, keep things in one track. A merge conflict in a critical file costs more time than the parallelism saves.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#when-teams-make-things-worse","level":2,"title":"When Teams Make Things Worse","text":"<p>\"More agents\" is not always better. Watch for these patterns:</p> <p>Merge hell: If you are spending more time resolving conflicts than the parallel work saved, you split wrong: Re-group by file overlap.</p> <p>Context divergence: Each agent builds its own mental model. After 30 minutes of independent work, agent A might make assumptions that contradict agent B's approach. Shorter tracks with frequent merges reduce this.</p> <p>Coordination theater: A lead agent spending most of its time assigning tasks, checking status, and sending messages instead of doing work. If the task list is clear enough, worktrees with no communication are cheaper.</p> <p>Re-reading overhead: Every agent reads <code>.context/</code> on startup. A team of 4 agents each reading 4000 tokens of context = 16000 tokens before anyone does any work. For small tasks, that overhead dominates.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#what-ctx-gives-you-at-each-level","level":2,"title":"What <code>ctx</code> Gives You at Each Level","text":"<code>ctx</code> Feature Single Agent Worktrees Team <code>.context/</code> files Full access Shared via git Shared via filesystem <code>TASKS.md</code> Work queue Split by track Assigned by lead Decisions/Learnings Persisted in session Persisted per branch Persisted by any agent <code>/ctx-next</code> Picks next task Picks within track Lead assigns <code>/ctx-worktree</code> N/A Setup + teardown Optional <code>/ctx-commit</code> Normal commits Per-branch commits Per-agent commits","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#team-composition-recipes","level":2,"title":"Team Composition Recipes","text":"<p>Four practical team compositions for common workflows.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#feature-development-3-agents","level":3,"title":"Feature Development (3 Agents)","text":"Role Responsibility Architect Writes spec in <code>specs/</code>, breaks work into TASKS.md phases Implementer Picks tasks from TASKS.md, writes code, marks <code>[x]</code> done Reviewer Runs tests, <code>ctx drift</code>, lint; files issues as new tasks <p>Coordination: TASKS.md checkboxes. Architect writes tasks before implementer starts. Reviewer runs after each implementer commit.</p> <p>Anti-pattern: All three agents editing the same file simultaneously. Sequence the work so only one agent touches a file at a time.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#consolidation-sprint-3-4-agents","level":3,"title":"Consolidation Sprint (3-4 Agents)","text":"Role Responsibility Auditor Runs <code>ctx drift</code>, identifies stale paths and broken refs Code Fixer Updates source code to match context (or vice versa) Doc Writer Updates ARCHITECTURE.md, CONVENTIONS.md, and docs/ Test Fixer (Optional) Fixes tests broken by the fixer's changes <p>Coordination: Auditor's <code>ctx drift</code> output is the shared work queue. Each agent claims a subset of issues by adding <code>#in-progress</code> labels.</p> <p>Anti-pattern: Fixer and doc writer both editing ARCHITECTURE.md. Assign file ownership explicitly.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#release-prep-2-agents","level":3,"title":"Release Prep (2 Agents)","text":"Role Responsibility Release Notes Generates changelog from commits, writes release notes Validation Runs full test suite, lint, build across platforms <p>Coordination: Both read TASKS.md to identify what shipped. Release notes agent works from <code>git log</code>; validation agent works from <code>make audit</code>.</p> <p>Anti-pattern: Release notes agent running tests \"to verify.\" Each agent stays in its lane.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#documentation-sprint-3-agents","level":3,"title":"Documentation Sprint (3 Agents)","text":"Role Responsibility Content Writes new pages, expands existing docs Cross-linker Adds nav entries, cross-references, \"See Also\" sections Verifier Builds site, checks broken links, validates rendering <p>Coordination: Content agent writes files first. Cross-linker updates <code>zensical.toml</code> and index pages after content lands. Verifier builds after each batch.</p> <p>Antipattern: Content and cross-linker both editing <code>zensical.toml</code>. Batch nav updates into the cross-linker's pass.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#tips","level":2,"title":"Tips","text":"<ul> <li>Start with one agent: Only add parallelism when you have identified   the bottleneck. \"This would go faster with more agents\" is usually   wrong for tasks under 2 hours.</li> <li>The 3-4 agent ceiling is real: Coordination overhead grows   quadratically. 2 agents = 1 communication pair. 4 agents = 6 pairs.   Beyond 4, you are managing agents more than doing work.</li> <li>Worktrees > teams for most parallelism needs: If agents don't   need to talk to each other during execution, worktrees give you   parallelism with zero coordination overhead.</li> <li>Use <code>ctx</code> as the shared brain: Whether it's one agent or four, the   <code>.context/</code> directory is the single source of truth. Decisions go in   <code>DECISIONS.md</code>, not in chat messages between agents.</li> <li>Merge early, merge often: Long-lived parallel branches diverge.   Merge a track as soon as it's done rather than waiting for all tracks   to finish.</li> <li><code>TASKS.md</code> conflicts are normal: Multiple agents completing different   tasks will conflict on merge. The resolution is always additive: accept   all <code>[x]</code> completions from both sides.</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#next-up","level":2,"title":"Next Up","text":"<p>Parallel Agent Development with Git Worktrees →: Run multiple agents on independent task tracks using git worktrees.</p>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#go-deeper","level":2,"title":"Go Deeper","text":"<ul> <li>CLI Reference: all commands and flags</li> <li>Integrations: setup for Claude Code, Cursor, Aider</li> <li>Session Journal: browse and search session history</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"recipes/when-to-use-agent-teams/#see-also","level":2,"title":"See Also","text":"<ul> <li>Parallel Agent Development with Git Worktrees:   the mechanical \"how\" for worktree-based parallelism</li> <li>Running an Unattended AI Agent: serial autonomous   loops: a different scaling strategy</li> <li>Tracking Work Across Sessions: managing the task   backlog that feeds into any multi-agent setup</li> </ul>","path":["Recipes","Agents and Automation","When to Use a Team of Agents"],"tags":[]},{"location":"reference/","level":1,"title":"Reference","text":"<p>Technical reference for <code>ctx</code> commands, skills, and internals.</p>","path":["Reference"],"tags":[]},{"location":"reference/#the-system-explains-itself","level":3,"title":"The System Explains Itself","text":"<p>The 12 properties that must hold for any valid <code>ctx</code> implementation. Not features: constraints. The system's contract with its users and contributors.</p>","path":["Reference"],"tags":[]},{"location":"reference/#code-conventions","level":3,"title":"Code Conventions","text":"<p>Common patterns and fixes for the AST compliance tests in <code>internal/audit/</code>. When a test fails, find the matching section.</p>","path":["Reference"],"tags":[]},{"location":"reference/#cli","level":3,"title":"CLI","text":"<p>Every command, subcommand, and flag. Now a top-level section: see CLI Reference.</p>","path":["Reference"],"tags":[]},{"location":"reference/#skills","level":3,"title":"Skills","text":"<p>The full skill catalog: what each skill does, when it triggers, and how skills interact with commands.</p>","path":["Reference"],"tags":[]},{"location":"reference/#tool-ecosystem","level":3,"title":"Tool Ecosystem","text":"<p>How <code>ctx</code> compares to Cursor Rules, Aider conventions, CLAUDE.md, and other context approaches.</p>","path":["Reference"],"tags":[]},{"location":"reference/#session-journal","level":3,"title":"Session Journal","text":"<p>Export, browse, and enrich your session history. Covers the journal site, Obsidian export, and the enrichment pipeline.</p>","path":["Reference"],"tags":[]},{"location":"reference/#scratchpad","level":3,"title":"Scratchpad","text":"<p>Encrypted, git-tracked scratch space for short notes and sensitive values that travel with the project.</p>","path":["Reference"],"tags":[]},{"location":"reference/#version-history","level":3,"title":"Version History","text":"<p>Changelog for every <code>ctx</code> release.</p>","path":["Reference"],"tags":[]},{"location":"reference/audit-conventions/","level":1,"title":"Code Conventions","text":"","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#code-conventions-common-patterns-and-fixes","level":1,"title":"Code Conventions: Common Patterns and Fixes","text":"<p>This guide documents the code conventions enforced by <code>internal/audit/</code> AST tests. Each section shows the violation pattern, the fix, and the rationale. When a test fails, find the matching section below.</p> <p>All tests skip <code>_test.go</code> files. The patterns apply only to production code under <code>internal/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#variable-shadowing-bare-err-reuse","level":2,"title":"Variable Shadowing (Bare <code>err :=</code> Reuse)","text":"<p>Test: <code>TestNoVariableShadowing</code></p> <p>When a function has multiple <code>:=</code> assignments to <code>err</code>, each shadows the previous one. This makes it impossible to tell which error a later <code>if err != nil</code> is checking.</p> <p>Before:</p> <pre><code>func Run(cmd *cobra.Command) error {\n    data, err := os.ReadFile(path) \n    if err != nil {\n        return err\n    }\n\n    result, err := json.Unmarshal(data)  // shadows first err\n    if err != nil {\n        return err\n    }\n\n    err = validate(result)  // shadows again\n    return err\n}\n</code></pre> <p>After:</p> <pre><code>func Run(cmd *cobra.Command) error {\n    data, readErr := os.ReadFile(path)\n    if readErr != nil {\n        return readErr\n    }\n\n    result, parseErr := json.Unmarshal(data)\n    if parseErr != nil {\n        return parseErr\n    }\n\n    validateErr := validate(result)\n    return validateErr\n}\n</code></pre> <p>Rule: Use descriptive error names (<code>readErr</code>, <code>writeErr</code>, <code>parseErr</code>, <code>walkErr</code>, <code>absErr</code>, <code>relErr</code>) so each error site is independently identifiable.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#import-name-shadowing","level":2,"title":"Import Name Shadowing","text":"<p>Test: <code>TestNoImportNameShadowing</code></p> <p>When a local variable has the same name as an imported package, the import becomes inaccessible in that scope.</p> <p>Before:</p> <pre><code>import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(session *entity.Session) {  // param shadows import\n    // session package is now unreachable here\n}\n</code></pre> <p>After:</p> <pre><code>import \"github.com/ActiveMemory/ctx/internal/session\"\n\nfunc process(sess *entity.Session) {\n    // session package still accessible\n}\n</code></pre> <p>Rule: Parameters, variables, and return values must not reuse imported package names. Common renames: <code>session</code> -> <code>sess</code>, <code>token</code> -> <code>tok</code>, <code>config</code> -> <code>cfg</code>, <code>entry</code> -> <code>ent</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-strings","level":2,"title":"Magic Strings","text":"<p>Test: <code>TestNoMagicStrings</code></p> <p>String literals in function bodies are invisible to refactoring tools and cause silent breakage when the value changes in one place but not another.</p> <p>Before (string literals):</p> <pre><code>func loadContext() {\n    data := filepath.Join(dir, \"TASKS.md\")\n    if strings.HasSuffix(name, \".yaml\") {\n        // ...\n    }\n}\n</code></pre> <p>After:</p> <pre><code>func loadContext() {\n    data := filepath.Join(dir, config.FilenameTask)\n    if strings.HasSuffix(name, config.ExtYAML) {\n        // ...\n    }\n}\n</code></pre> <p>Before (format verbs, also caught):</p> <pre><code>func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return fmt.Sprintf(\"%x\", h[:8])\n}\n</code></pre> <p>After:</p> <pre><code>func EntryHash(text string) string {\n    h := sha256.Sum256([]byte(text))\n    return hex.EncodeToString(h[:cfgFmt.HashPrefixLen])\n}\n</code></pre> <p>Before (URL schemes, also caught):</p> <pre><code>if strings.HasPrefix(target, \"https://\") ||\n    strings.HasPrefix(target, \"http://\") {\n    return target\n}\n</code></pre> <p>After:</p> <pre><code>if strings.HasPrefix(target, cfgHTTP.PrefixHTTPS) ||\n    strings.HasPrefix(target, cfgHTTP.PrefixHTTP) {\n    return target\n}\n</code></pre> <p>Exempt from this check:</p> <ul> <li>Empty string <code>\"\"</code>, single space <code>\" \"</code>, indentation strings</li> <li>Regex capture references (<code>$1</code>, <code>${name}</code>)</li> <li><code>const</code> and <code>var</code> definition sites (that's where constants live)</li> <li>Struct tags</li> <li>Import paths</li> <li>Packages under <code>internal/config/</code>, <code>internal/assets/tpl/</code></li> </ul> <p>Rule: If a string is used for comparison, path construction, or appears in 3+ files, it belongs in <code>internal/config/</code> as a constant. Format strings belong in <code>internal/config/</code> as named constants (e.g., <code>cfgGit.FlagLastN</code>, <code>cfgTrace.RefFormat</code>). User-facing prose belongs in <code>internal/assets/</code> YAML files accessed via <code>desc.Text()</code>.</p> <p>Common fix for <code>fmt.Sprintf</code> with format verbs:</p> Pattern Fix <code>fmt.Sprintf(\"%d\", n)</code> <code>strconv.Itoa(n)</code> <code>fmt.Sprintf(\"%d\", int64Val)</code> <code>strconv.FormatInt(int64Val, 10)</code> <code>fmt.Sprintf(\"%x\", bytes)</code> <code>hex.EncodeToString(bytes)</code> <code>fmt.Sprintf(\"%q\", s)</code> <code>strconv.Quote(s)</code> <code>fmt.Sscanf(s, \"%d\", &n)</code> <code>strconv.Atoi(s)</code> <code>fmt.Sprintf(\"-%d\", n)</code> <code>fmt.Sprintf(cfgGit.FlagLastN, n)</code> <code>\"https://\"</code> <code>cfgHTTP.PrefixHTTPS</code> <code>\"&lt;\"</code> config constant in <code>config/html/</code>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-printf-calls","level":2,"title":"Direct Printf Calls","text":"<p>Test: <code>TestNoPrintfCalls</code></p> <p><code>cmd.Printf</code> and <code>cmd.PrintErrf</code> bypass the write-package formatting pipeline and scatter user-facing text across the codebase.</p> <p>Before:</p> <pre><code>func Run(cmd *cobra.Command, args []string) {\n    cmd.Printf(\"Found %d tasks\\n\", count)\n}\n</code></pre> <p>After:</p> <pre><code>func Run(cmd *cobra.Command, args []string) {\n    write.TaskCount(cmd, count)\n}\n</code></pre> <p>Rule: All formatted output goes through <code>internal/write/</code> which uses <code>cmd.Print</code>/<code>cmd.Println</code> with pre-formatted strings from <code>desc.Text()</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#raw-time-format-strings","level":2,"title":"Raw Time Format Strings","text":"<p>Test: <code>TestNoRawTimeFormats</code></p> <p>Inline time format strings (<code>\"2006-01-02\"</code>, <code>\"15:04:05\"</code>) drift when one call site is updated but others are missed.</p> <p>Before:</p> <pre><code>func formatDate(t time.Time) string {\n    return t.Format(\"2006-01-02\")\n}\n</code></pre> <p>After:</p> <pre><code>func formatDate(t time.Time) string {\n    return t.Format(cfgTime.DateFormat)\n}\n</code></pre> <p>Rule: All time format strings must use constants from <code>internal/config/time/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#direct-flag-registration","level":2,"title":"Direct Flag Registration","text":"<p>Test: <code>TestNoFlagBindOutsideFlagbind</code></p> <p>Direct cobra flag calls (<code>.Flags().StringVar()</code>, etc.) scatter flag wiring across dozens of <code>cmd.go</code> files. Centralizing through <code>internal/flagbind/</code> gives one place to audit flag names, defaults, and description key lookups.</p> <p>Before:</p> <pre><code>func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    c.Flags().StringVarP(&output, \"output\", \"o\", \"\",\n        \"output format\")\n    return c\n}\n</code></pre> <p>After:</p> <pre><code>func Cmd() *cobra.Command {\n    var output string\n    c := &cobra.Command{Use: cmd.UseStatus}\n    flagbind.StringFlagShort(c, &output, flag.Output,\n        flag.OutputShort, cmd.DescKeyOutput)\n    return c\n}\n</code></pre> <p>Rule: All flag registration goes through <code>internal/flagbind/</code>. If the helper you need doesn't exist, add it to <code>flagbind/flag.go</code> before using it.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#todo-comments","level":2,"title":"TODO Comments","text":"<p>Test: <code>TestNoTODOComments</code></p> <p>TODO, FIXME, HACK, and XXX comments in production code are invisible to project tracking. They accumulate silently and never get addressed.</p> <p>Before:</p> <pre><code>// TODO: handle pagination\nfunc listEntries() []Entry {\n</code></pre> <p>After:</p> <p>Remove the comment and add a task to <code>.context/TASKS.md</code>:</p> <pre><code>- [ ] Handle pagination in listEntries (internal/task/task.go)\n</code></pre> <p>Rule: Deferred work lives in TASKS.md, not in source comments.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#dead-exports","level":2,"title":"Dead Exports","text":"<p>Test: <code>TestNoDeadExports</code></p> <p>Exported symbols with zero references outside their definition file are dead weight. They increase API surface, confuse contributors, and cost maintenance.</p> <p>Fix: Either delete the export (preferred) or demote it to unexported if it's still used within the file.</p> <p>If the symbol existed for historical reasons and might be needed again, move it to <code>quarantine/deadcode/</code> with a <code>.dead</code> extension. This preserves the code in git without polluting the live codebase:</p> <pre><code>quarantine/deadcode/internal/config/flag/flag.go.dead\n</code></pre> <p>Each <code>.dead</code> file includes a header:</p> <pre><code>// Dead exports quarantined from internal/config/flag/flag.go\n// Quarantined: 2026-04-02\n// Restore from git history if needed.\n</code></pre> <p>Rule: If a test-only allowlist entry is needed (the export exists only for test use), add the fully qualified symbol to <code>testOnlyExports</code> in <code>dead_exports_test.go</code>. Keep this list small; prefer eliminating the export.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#core-package-structure","level":2,"title":"Core Package Structure","text":"<p>Test: <code>TestCoreStructure</code></p> <p><code>core/</code> directories under <code>internal/cli/</code> must contain only <code>doc.go</code> and test files at the top level. All domain logic lives in subpackages. This prevents <code>core/</code> from becoming a god package.</p> <p>Before:</p> <pre><code>internal/cli/dep/core/\n    go.go           # violation: logic at core/ level\n    python.go       # violation\n    node.go         # violation\n    types.go        # violation\n</code></pre> <p>After:</p> <pre><code>internal/cli/dep/core/\n    doc.go          # package doc only\n    golang/\n        golang.go\n        golang_test.go\n        doc.go\n    python/\n        python.go\n        python_test.go\n        doc.go\n    node/\n        node.go\n        node_test.go\n        doc.go\n</code></pre> <p>Rule: Extract each logical unit into its own subpackage under <code>core/</code>. Each subpackage gets a <code>doc.go</code>. The subpackage name should match the domain concept (<code>golang</code>, <code>check</code>, <code>fix</code>, <code>store</code>), not a generic label (<code>util</code>, <code>helper</code>).</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cross-package-types","level":2,"title":"Cross-Package Types","text":"<p>Test: <code>TestCrossPackageTypes</code></p> <p>When a type defined in one package is used from a different module (e.g., <code>cli/doctor</code> importing a type from <code>cli/notify</code>), the type has crossed its module boundary. Cross-cutting types belong in <code>internal/entity/</code> for discoverability.</p> <p>Before:</p> <pre><code>// internal/cli/notify/core/types.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/cli/notify/core\"\nfunc check(p core.NotifyPayload) { ... }\n</code></pre> <p>After:</p> <pre><code>// internal/entity/notify.go\ntype NotifyPayload struct { ... }\n\n// internal/cli/doctor/core/check/check.go\nimport \"github.com/ActiveMemory/ctx/internal/entity\"\nfunc check(p entity.NotifyPayload) { ... }\n</code></pre> <p>Exempt: Types inside <code>entity/</code>, <code>proto/</code>, <code>core/</code> subpackages, and <code>config/</code> packages. Same-module usage (e.g., <code>cli/doctor/cmd/</code> using <code>cli/doctor/core/</code>) is not flagged.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#type-file-convention","level":2,"title":"Type File Convention","text":"<p>Test: <code>TestTypeFileConvention</code>, <code>TestTypeFileConventionReport</code></p> <p>Exported types in <code>core/</code> subpackages should live in <code>types.go</code> (the convention from CONVENTIONS.md), not scattered across implementation files. This makes type definitions discoverable. <code>TestTypeFileConventionReport</code> generates a diagnostic summary of all type placements for triage.</p> <p>Exception: <code>entity/</code> organizes by domain (<code>task.go</code>, <code>session.go</code>), <code>proto/</code> uses <code>schema.go</code>, and <code>err/</code> packages colocate error types with their domain context.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-yaml-linkage","level":2,"title":"DescKey / YAML Linkage","text":"<p>Test: <code>TestDescKeyYAMLLinkage</code></p> <p>Every DescKey constant must have a corresponding key in the YAML asset files, and every YAML key must have a corresponding DescKey constant. Orphans in either direction mean dead text or runtime panics.</p> <p>Fix for orphan YAML key: Delete the YAML entry, or add the corresponding <code>DescKey</code> constant in <code>config/embed/{text,cmd,flag}/</code>.</p> <p>Fix for orphan DescKey: Delete the constant, or add the corresponding entry in the YAML file under <code>internal/assets/commands/text/</code>, <code>cmd/</code>, or <code>flag/</code>.</p> <p>If the orphan YAML entry was once valid but the feature was removed, move the YAML entry to a <code>.dead</code> file in <code>quarantine/deadcode/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#package-doc-quality","level":2,"title":"Package Doc Quality","text":"<p>Test: <code>TestPackageDocQuality</code></p> <p>Every package under <code>internal/</code> must have a <code>doc.go</code> with a meaningful package doc comment (at least 8 lines of real content). One-liners and file-list patterns (<code>// - foo.go</code>, <code>// Source files:</code>) are flagged because they drift as files change.</p> <p>Template:</p> <pre><code>//   /    ctx:                         https://ctx.ist\n// ,'`./    do you remember?\n// `.,'\\\n//   \\    Copyright 2026-present Context contributors.\n//                 SPDX-License-Identifier: Apache-2.0\n\n// Package mypackage does X.\n//\n// It handles Y by doing Z. The main entry point is [FunctionName]\n// which accepts A and returns B.\n//\n// Configuration is read from [config.SomeConstant]. Output is\n// written through [write.SomeHelper].\n//\n// This package is used by [parentpackage] during the W lifecycle\n// phase.\npackage mypackage\n</code></pre>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-regex-compilation","level":2,"title":"Inline Regex Compilation","text":"<p>Test: <code>TestNoInlineRegexpCompile</code></p> <p><code>regexp.MustCompile</code> and <code>regexp.Compile</code> inside function bodies recompile the pattern on every call. Compiled patterns belong at package level.</p> <p>Before:</p> <pre><code>func parse(s string) bool {\n    re := regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n    return re.MatchString(s)\n}\n</code></pre> <p>After:</p> <pre><code>// In internal/config/regex/regex.go:\n// DatePattern matches ISO date format (YYYY-MM-DD).\nvar DatePattern = regexp.MustCompile(`\\d{4}-\\d{2}-\\d{2}`)\n\n// In calling package:\nfunc parse(s string) bool {\n    return regex.DatePattern.MatchString(s)\n}\n</code></pre> <p>Rule: All compiled regexes live in <code>internal/config/regex/</code> as package-level <code>var</code> declarations. Two tests enforce this: <code>TestNoInlineRegexpCompile</code> catches function-body compilation, and <code>TestNoRegexpOutsideRegexPkg</code> catches package-level compilation outside <code>config/regex/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#doc-comments","level":2,"title":"Doc Comments","text":"<p>Test: <code>TestDocComments</code></p> <p>All functions (exported and unexported), structs, and package-level variables must have a doc comment. Config packages allow group doc comments for <code>const</code> blocks.</p> <p>Before:</p> <pre><code>func buildIndex(entries []Entry) map[string]int {\n</code></pre> <p>After:</p> <pre><code>// buildIndex maps entry names to their position in the\n// ordered slice for O(1) lookup during reconciliation.\n//\n// Parameters:\n//   - entries: ordered slice of entries to index\n//\n// Returns:\n//   - map[string]int: name-to-position mapping\nfunc buildIndex(entries []Entry) map[string]int {\n</code></pre> <p>Rule: Every function, struct, and package-level <code>var</code> gets a doc comment in godoc format. Functions include <code>Parameters:</code> and <code>Returns:</code> sections. Structs with 2+ fields document every field. See CONVENTIONS.md for the full template.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#line-length","level":2,"title":"Line Length","text":"<p>Test: <code>TestLineLength</code></p> <p>Lines in non-test Go files must not exceed 80 characters. This is a hard check, not a suggestion.</p> <p>Before:</p> <pre><code>_ = trace.Record(fmt.Sprintf(cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum), state.Dir())\n</code></pre> <p>After:</p> <pre><code>ref := fmt.Sprintf(\n    cfgTrace.RefFormat, cfgTrace.RefTypeTask, matchedNum,\n)\n_ = trace.Record(ref, state.Dir())\n</code></pre> <p>Rule: Break at natural points: function arguments, struct fields, chained calls. Long strings (URLs, struct tags) are the rare acceptable exception.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#literal-whitespace","level":2,"title":"Literal Whitespace","text":"<p>Test: <code>TestNoLiteralWhitespace</code></p> <p>Bare whitespace string and byte literals (<code>\"\\n\"</code>, <code>\"\\r\\n\"</code>, <code>\"\\t\"</code>) must not appear outside <code>internal/config/token/</code>. All other packages use the token constants.</p> <p>Before:</p> <pre><code>output := strings.Join(lines, \"\\n\")\n</code></pre> <p>After:</p> <pre><code>output := strings.Join(lines, token.Newline)\n</code></pre> <p>Rule: Whitespace literals are defined once in <code>internal/config/token/</code>. Use <code>token.Newline</code>, <code>token.Tab</code>, <code>token.CRLF</code>, etc.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#magic-numeric-values","level":2,"title":"Magic Numeric Values","text":"<p>Test: <code>TestNoMagicValues</code></p> <p>Numeric literals in function bodies need constants, with narrow exceptions.</p> <p>Before:</p> <pre><code>if len(entries) > 100 {\n    entries = entries[:100]\n}\n</code></pre> <p>After:</p> <pre><code>if len(entries) > config.MaxEntries {\n    entries = entries[:config.MaxEntries]\n}\n</code></pre> <p>Exempt: <code>0</code>, <code>1</code>, <code>-1</code>, <code>2</code>-<code>10</code>, strconv radix/bitsize args (<code>10</code>, <code>32</code>, <code>64</code> in <code>strconv.Parse*</code>/<code>Format*</code>), octal permissions (caught separately by <code>TestNoRawPermissions</code>), and <code>const</code>/<code>var</code> definition sites.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#inline-separators","level":2,"title":"Inline Separators","text":"<p>Test: <code>TestNoInlineSeparators</code></p> <p><code>strings.Join</code> calls must use token constants for their separator argument, not string literals.</p> <p>Before:</p> <pre><code>result := strings.Join(parts, \", \")\n</code></pre> <p>After:</p> <pre><code>result := strings.Join(parts, token.CommaSep)\n</code></pre> <p>Rule: Separator strings live in <code>internal/config/token/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stuttery-function-names","level":2,"title":"Stuttery Function Names","text":"<p>Test: <code>TestNoStutteryFunctions</code></p> <p>Function names must not redundantly include their package name as a PascalCase word boundary. Go callers already write <code>pkg.Function</code>, so <code>pkg.PkgFunction</code> stutters.</p> <p>Before:</p> <pre><code>// In package write\nfunc WriteJournal(cmd *cobra.Command, ...) {\n</code></pre> <p>After:</p> <pre><code>// In package write\nfunc Journal(cmd *cobra.Command, ...) {\n</code></pre> <p>Exempt: Identity functions like <code>write.Write</code> / <code>write.write</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#predicate-naming-no-ishascan-prefix","level":2,"title":"Predicate Naming (No <code>Is</code>/<code>Has</code>/<code>Can</code> Prefix)","text":"<p>Test: None (manual review convention)</p> <p>Exported methods that return <code>bool</code> must not use <code>Is</code>, <code>Has</code>, or <code>Can</code> prefixes. The predicate reads more naturally without them, especially at call sites where the package name provides context.</p> <p>Before:</p> <pre><code>func IsCompleted(t *Task) bool { ... }\nfunc HasChildren(n *Node) bool { ... }\nfunc IsExemptPackage(path string) bool { ... }\n</code></pre> <p>After:</p> <pre><code>func Completed(t *Task) bool { ... }\nfunc Children(n *Node) bool { ... }  // or: ChildCount > 0\nfunc ExemptPackage(path string) bool { ... }\n</code></pre> <p>Rule: Drop the prefix. Private helpers may use prefixes when it reads more naturally (<code>isValid</code> in a local context is fine). This convention applies to exported methods and package-level functions. See CONVENTIONS.md \"Predicates\" section.</p> <p>This is not yet enforced by an AST test; it requires semantic understanding of return types and naming intent that makes automated detection fragile. Apply during code review.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#mixed-visibility","level":2,"title":"Mixed Visibility","text":"<p>Test: <code>TestNoMixedVisibility</code></p> <p>Files with exported functions must not also contain unexported functions. Public API and private helpers live in separate files.</p> <p>Before:</p> <pre><code>load.go\n    func Load() { ... }        // exported\n    func parseHeader() { ... } // unexported, violation\n</code></pre> <p>After:</p> <pre><code>load.go\n    func Load() { ... }        // exported only\nparse.go\n    func parseHeader() { ... } // private helper\n</code></pre> <p>Exempt: Files with exactly one function, <code>doc.go</code>, test files.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#stray-errgo-files","level":2,"title":"Stray Err.Go Files","text":"<p>Test: <code>TestNoStrayErrFiles</code></p> <p><code>err.go</code> files must only exist under <code>internal/err/</code>. Error constructors anywhere else create a broken-window pattern where contributors add local error definitions when they see a local <code>err.go</code>.</p> <p>Fix: Move the error constructor to <code>internal/err/<domain>/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#cli-cmd-structure","level":2,"title":"CLI Cmd Structure","text":"<p>Test: <code>TestCLICmdStructure</code></p> <p>Each <code>cmd/$sub/</code> directory under <code>internal/cli/</code> may contain only <code>cmd.go</code>, <code>run.go</code>, <code>doc.go</code>, and test files. Extra <code>.go</code> files (helpers, output formatters, types) belong in the corresponding <code>core/</code> subpackage.</p> <p>Before:</p> <pre><code>internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\n    format.go   # violation: helper in cmd dir\n</code></pre> <p>After:</p> <pre><code>internal/cli/doctor/cmd/root/\n    cmd.go\n    run.go\ninternal/cli/doctor/core/format/\n    format.go\n    doc.go\n</code></pre>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#desckey-namespace","level":2,"title":"DescKey Namespace","text":"<p>Test: <code>TestUseConstantsOnlyInCobraUse</code>, <code>TestDescKeyOnlyInLookupCalls</code>, <code>TestNoWrongNamespaceLookup</code></p> <p>Three tests enforce DescKey/Use constant discipline:</p> <ol> <li><code>Use*</code> constants appear only in cobra <code>Use:</code> struct field    assignments, never as arguments to <code>desc.Text()</code> or elsewhere.</li> <li><code>DescKey*</code> constants are passed only to <code>assets.CommandDesc()</code>,    <code>assets.FlagDesc()</code>, or <code>desc.Text()</code>, never to cobra <code>Use:</code>.</li> <li>No cross-namespace lookups: <code>TextDescKey</code> must not be passed to    <code>CommandDesc()</code>, <code>FlagDescKey</code> must not be passed to <code>Text()</code>, etc.</li> </ol>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#yaml-examples-registry-linkage","level":2,"title":"YAML Examples / Registry Linkage","text":"<p>Test: <code>TestExamplesYAMLLinkage</code>, <code>TestRegistryYAMLLinkage</code></p> <p>Every key in <code>examples.yaml</code> and <code>registry.yaml</code> must match a known entry type constant. Prevents orphan entries that are never rendered.</p> <p>Fix: Delete the orphan YAML entry, or add the corresponding constant in <code>config/entry/</code>.</p>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#other-enforced-patterns","level":2,"title":"Other Enforced Patterns","text":"<p>These tests follow the same fix approach: extract the operation to its designated package:</p> Test Violation Fix <code>TestNoNakedErrors</code> <code>fmt.Errorf</code>/<code>errors.New</code> outside <code>internal/err/</code> Add error constructor to <code>internal/err/<domain>/</code> <code>TestNoRawFileIO</code> Direct <code>os.ReadFile</code>, <code>os.Create</code>, etc. Use <code>io.SafeReadFile</code>, <code>io.SafeWriteFile</code>, etc. <code>TestNoRawLogging</code> Direct <code>fmt.Fprintf(os.Stderr, ...)</code> Use <code>log/warn.Warn()</code> or <code>log/event.Append()</code> <code>TestNoExecOutsideExecPkg</code> <code>exec.Command</code> outside <code>internal/exec/</code> Add command to <code>internal/exec/<domain>/</code> <code>TestNoCmdPrintOutsideWrite</code> <code>cmd.Print*</code> outside <code>internal/write/</code> Add output helper to <code>internal/write/<domain>/</code> <code>TestNoRawPermissions</code> Octal literals (<code>0644</code>, <code>0755</code>) Use <code>config/fs.PermFile</code>, <code>config/fs.PermExec</code>, etc. <code>TestNoErrorsAs</code> <code>errors.As()</code> Use <code>errors.AsType()</code> (generic, Go 1.23+) <code>TestNoStringConcatPaths</code> <code>dir + \"/\" + file</code> Use <code>filepath.Join(dir, file)</code>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/audit-conventions/#general-fix-workflow","level":2,"title":"General Fix Workflow","text":"<p>When an audit test fails:</p> <ol> <li>Read the error message. It includes <code>file:line</code> and a    description of the violation.</li> <li>Find the matching section above. The test name maps directly    to a section.</li> <li>Apply the pattern. Most fixes are mechanical: extract to the    right package, rename a variable, or replace a literal with a    constant.</li> <li>Run <code>make test</code> before committing. Audit tests run as part of    <code>go test ./internal/audit/</code>.</li> <li>Don't add allowlist entries as a first resort. Fix the code.    Allowlists exist only for genuinely unfixable cases (test-only    exports, config packages that are definitionally exempt).</li> </ol>","path":["Reference","Code Conventions"],"tags":[]},{"location":"reference/comparison/","level":1,"title":"Tool Ecosystem","text":"","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#high-level-mental-model","level":2,"title":"High-Level Mental Model","text":"<p>Many tools help AI think.</p> <p><code>ctx</code> helps AI remember.</p> <ul> <li>Not by storing thoughts,</li> <li>but by preserving intent.</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#how-ctx-differs-from-similar-tools","level":2,"title":"How <code>ctx</code> Differs from Similar Tools","text":"<p>There are many tools in the AI ecosystem that touch parts of the context problem:</p> <ul> <li>Some manage prompts.  </li> <li>Some retrieve data.  </li> <li>Some provide runtime context objects.  </li> <li>Some offer enterprise platforms.</li> </ul> <p><code>ctx</code> focuses on a different layer entirely.</p> <p>This page explains where <code>ctx</code> fits, and where it intentionally does not.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#the-core-distinction","level":2,"title":"The Core Distinction","text":"<p>Most tools treat context as input.</p> <p><code>ctx</code> treats context as infrastructure.</p> <p>That single difference explains nearly all of <code>ctx</code>'s design choices.</p> Question Most tools <code>ctx</code> Where does context live? In prompts or APIs In files How long does it last? One request / one session Across time Who can read it? The model Humans and tools How is it updated? Implicitly Explicitly Is it inspectable? Rarely Always","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#prompt-management-tools","level":2,"title":"Prompt Management Tools","text":"<p>Examples include:</p> <ul> <li>prompt templates;</li> <li>reusable system prompts;</li> <li>prompt libraries;</li> <li>prompt versioning tools.</li> </ul> <p>These tools help you start a session.</p> <p>They do not help you continue one.</p> <p>Prompt tools:</p> <ul> <li>inject text at session start;</li> <li>are ephemeral by design;</li> <li>do not evolve with the project.</li> </ul> <p><code>ctx</code>:</p> <ul> <li>persists knowledge over time;</li> <li>accumulates decisions and learnings;</li> <li>makes the context part of the repository itself.</li> </ul> <p>Prompt tooling and <code>ctx</code> are complementary; not competing.  Yet, they operate in different layers.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#retrieval-augmented-generation-rag","level":2,"title":"Retrieval-Augmented Generation (RAG)","text":"<p>RAG systems typically:</p> <ul> <li>index documents</li> <li>embed text</li> <li>retrieve chunks dynamically at runtime</li> </ul> <p>They are excellent for:</p> <ul> <li>large knowledge bases</li> <li>static documentation</li> <li>reference material</li> </ul> <p>RAG answers questions like:</p> <p>\"What information might be relevant right now?\"</p> <p><code>ctx</code> answers a different question:</p> <p>\"What have we already decided, learned, or committed to?\"</p> <p>Here are some key differences:</p> RAG <code>ctx</code> Statistical relevance Intentional relevance Embedding-based File-based Opaque retrieval Explicit structure Runtime query Persistent memory <p><code>ctx</code> does not replace RAG. Instead, it defines a persistent context layer that RAG can optionally augment.</p> <p>RAG belongs to the data plane; <code>ctx</code> defines the context control plane.</p> <p>It focuses on project memory, not knowledge search.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#agent-frameworks","level":2,"title":"Agent Frameworks","text":"<p>Agent frameworks often provide:</p> <ul> <li>task loops</li> <li>tool orchestration</li> <li>planner/executor patterns</li> <li>autonomous iteration</li> </ul> <p>These systems are powerful, but they typically assume that:</p> <ul> <li>memory is external</li> <li>context is injected</li> <li>state is transient</li> </ul> <p>Agent frameworks answer:</p> <p>\"How should the agent act?\"</p> <p><code>ctx</code> answers:</p> <p>\"What should the agent remember?\"</p> <p>Without persistent context, agents tend to:</p> <ul> <li>rediscover decisions</li> <li>repeat mistakes</li> <li>lose architectural intent</li> </ul> <p>This is why <code>ctx</code> pairs well with autonomous loop workflows:</p> <ul> <li>The loop provides iteration</li> <li><code>ctx</code> provides continuity</li> </ul> <p>Together, loops become cumulative instead of forgetful.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#sdk-level-context-objects","level":2,"title":"SDK-Level Context Objects","text":"<p>Some SDKs expose \"context\" objects that exist:</p> <ul> <li>inside a process</li> <li>during a request</li> <li>for the lifetime of a call chain</li> </ul> <p>These are extremely useful and completely different.</p> <p>SDK context objects:</p> <ul> <li>are in-memory</li> <li>disappear when the process ends</li> <li>are not shared across sessions</li> </ul> <p><code>ctx</code>:</p> <ul> <li>survives process restarts</li> <li>survives new chats</li> <li>survives new days</li> </ul> <p>They share a name, not a purpose.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#enterprise-context-platforms","level":2,"title":"Enterprise Context Platforms","text":"<p>Enterprise platforms often provide:</p> <ul> <li>centralized context services</li> <li>dashboards</li> <li>access control</li> <li>organizational knowledge layers</li> </ul> <p>These tools are designed for:</p> <ul> <li>teams</li> <li>governance</li> <li>compliance</li> <li>managed environments</li> </ul> <p><code>ctx</code> is intentionally:</p> <ul> <li>local-first: context lives next to your code, not   behind a service boundary.</li> <li>file-based: everything important is a Markdown   file you can read, diff, grep, and version-control.</li> <li>single-binary core: the context persistence path   (<code>init</code>, <code>add</code>, <code>agent</code>, <code>status</code>, <code>drift</code>, <code>load</code>,   <code>sync</code>, <code>compact</code>, <code>task</code>, <code>decision</code>, <code>learning</code>, and   their siblings) is a single Go binary with no required   runtime dependencies. Optional integrations (<code>ctx   trace</code> (needs <code>git</code>), <code>ctx serve</code> (needs <code>zensical</code>),   the <code>ctx</code> Hub (needs a running hub), Claude Code   plugin (needs <code>claude</code>)) are opt-in and each declares   its dependency explicitly.</li> <li>CLI-driven: every feature is reachable from the   command line and scriptable.</li> <li>developer-controlled: no auto-updating cloud   service, no telemetry, no account to sign up for.</li> </ul> <p>The core <code>ctx</code> binary does not require:</p> <ul> <li>a server</li> <li>a database</li> <li>an account</li> <li>a SaaS backend</li> <li>network connectivity (for core operations)</li> </ul> <p><code>ctx</code> optimizes for individual and small-team workflows where context should live next to code; not behind a service boundary.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#specific-tool-comparisons","level":2,"title":"Specific Tool Comparisons","text":"<p>Users often evaluate <code>ctx</code> against specific tools they already use. These comparisons clarify where responsibilities overlap, where they diverge, and where the tools are genuinely complementary.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#claude-code-memory-anthropic-auto-memory","level":3,"title":"Claude Code Memory / Anthropic Auto-Memory","text":"<p>Anthropic's auto-memory is tool-managed memory (L2): the model decides what to remember, stores it automatically, and retrieves it implicitly. <code>ctx</code> is system memory (L3): humans and agents explicitly curate decisions, learnings, and tasks in inspectable files.</p> <p>Auto-memory is convenient - you do not configure anything. But it is also opaque: you cannot see what was stored, edit it precisely, or share it across tools. <code>ctx</code> files are plain Markdown in your repository, visible in diffs and code review.</p> <p>The two are complementary. <code>ctx</code> can absorb auto-memory as an input source (importing what the model remembered into structured context files) while providing the durable, inspectable layer that auto-memory lacks.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cursorrules-clauderules","level":3,"title":".Cursorrules / .Claude/rules","text":"<p>Static rule files (<code>.cursorrules</code>, <code>.claude/rules/</code>) declare conventions: coding style, forbidden patterns, preferred libraries. They are effective for what to do and load automatically at session start.</p> <p><code>ctx</code> adds dimensions that rule files do not cover: architectural decisions with rationale, learnings discovered during development, active tasks, and a constitution that governs agent behavior. Critically, <code>ctx</code> context accumulates - each session can add to it, and token budgeting ensures only the most relevant context is injected.</p> <p>Use rule files for static conventions. Use <code>ctx</code> for evolving project memory.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#aider-read-watch","level":3,"title":"Aider <code>--read</code> / <code>--watch</code>","text":"<p>Aider's <code>--read</code> flag injects file contents at session start; <code>--watch</code> reloads them on change. The concept is similar to <code>ctx</code>'s \"load\" step: make the agent aware of specific files.</p> <p>The differences emerge beyond loading. Aider has no persistence model -- nothing the agent learns during a session is written back. There is no token budgeting (large files consume the full context window), no priority ordering across file types, and no structured format for decisions or learnings. <code>ctx</code> provides the full lifecycle: load, accumulate, persist, and budget.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#copilot-workspace","level":3,"title":"Copilot @Workspace","text":"<p>GitHub Copilot's <code>@workspace</code> performs workspace-wide code search. It answers \"what code exists?\" - finding function definitions, usages, and file structure across the repository.</p> <p><code>ctx</code> answers a different question: \"what did we decide?\" It stores architectural intent, not code indices. Copilot's workspace search and <code>ctx</code>'s project memory are orthogonal; one finds code, the other preserves the reasoning behind it.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#cline-memory","level":3,"title":"Cline Memory","text":"<p>Cline's memory bank stores session context within the Cline extension. The motivation is similar to <code>ctx</code>: help the agent remember across sessions.</p> <p>The key difference is portability. Cline memory is tied to Cline - it does not transfer to Claude Code, Cursor, Aider, or any other tool. <code>ctx</code> is tool-agnostic: context lives in plain files that any editor, agent, or script can read. Switching tools does not mean losing memory.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-a-good-fit","level":2,"title":"When <code>ctx</code> Is a Good Fit","text":"<p><code>ctx</code> works best when:</p> <ul> <li>you want AI work to compound over time;</li> <li>architectural decisions matter;</li> <li>context must be inspectable;</li> <li>humans and AI must share the same source of truth;</li> <li>Git history should include why, not just what.</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#when-ctx-is-not-the-right-tool","level":2,"title":"When <code>ctx</code> Is Not the Right Tool","text":"<p><code>ctx</code> is probably not what you want if:</p> <ul> <li>you only need one-off prompts;</li> <li>you rely exclusively on RAG;</li> <li>you want autonomous agents without a human-readable state;</li> <li>you require centralized enterprise control;</li> <li>you want black-box memory systems,</li> </ul> <p>These are valid goals; just different ones.</p>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/comparison/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>You Can't Import Expertise:    why project-specific context matters more than generic best practices</li> </ul>","path":["Reference","Tool Ecosystem"],"tags":[]},{"location":"reference/design-invariants/","level":1,"title":"Invariants","text":"","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-system-explains-itself","level":1,"title":"The System Explains Itself","text":"<p>These are the properties that must hold for any valid <code>ctx</code> implementation.</p> <ul> <li>These are not features.</li> <li>These are constraints.</li> </ul> <p>A change that violates an invariant is a category error,  not an improvement.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#cognitive-state-tiers","level":2,"title":"Cognitive State Tiers","text":"<p><code>ctx</code> distinguishes between three forms of state:</p> <ul> <li>Authoritative state: Versioned, inspectable artifacts that define intent    and survive time.</li> <li>Delivery views: Deterministic assemblies of the authoritative state for a    specific budget or workflow.</li> <li>Ephemeral working state: Local, transient, or sensitive data that    assists interaction but does not define system truth.</li> </ul> <p>The invariants below apply primarily to the authoritative cognitive state.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#1-cognitive-state-is-explicit","level":2,"title":"1. Cognitive State Is Explicit","text":"<p>All authoritative context lives in artifacts that can be inspected, reviewed, and versioned.</p> <p>If something is important, it must exist as a file: Not only in a prompt,  a chat, or a model's hidden memory.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#2-assembly-is-reproducible","level":2,"title":"2. Assembly Is Reproducible","text":"<p>Given the same:</p> <ul> <li>repository state,</li> <li>configuration,</li> <li>and inputs,</li> </ul> <p>context assembly produces the same result.</p> <p>Heuristics may rank or filter for delivery under constraints.</p> <p>They do not alter the authoritative state.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#3-the-authoritative-state-is-human-readable","level":2,"title":"3. The Authoritative State Is Human-Readable","text":"<p>The authoritative cognitive state must be stored in formats that a human can:</p> <ul> <li>read,</li> <li>diff,</li> <li>review,</li> <li>and edit directly.</li> </ul> <p>Sensitive working memory may be encrypted at rest. However, encryption must not become the only representation of  authoritative knowledge.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#4-artifacts-outlive-sessions","level":2,"title":"4. Artifacts Outlive Sessions","text":"<p>Sessions are transient.</p> <p>Knowledge persists.</p> <p>Reasoning, decisions, and outcomes must remain available after the  interaction that produced them has ended.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#5-authority-is-user-defined","level":2,"title":"5. Authority Is User-Defined","text":"<p>What enters the authoritative context is an explicit human decision.</p> <p>Models may suggest.</p> <p>Automation may assist.</p> <p>Selection is never implicit.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#6-operation-is-local-first","level":2,"title":"6. Operation Is Local-First","text":"<p>The core system must function without requiring network access or a  remote service.</p> <p>External systems may extend <code>ctx</code>.</p> <p>They must not be required for its operation.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#7-versioning-is-the-memory-model","level":2,"title":"7. Versioning Is the Memory Model","text":"<p>The evolution of the authoritative cognitive state must be:</p> <ul> <li>preserved,</li> <li>inspectable,</li> <li>and branchable.</li> </ul> <p>Ephemeral and sensitive working state may use different retention and diff  strategies by design.</p> <p>Understanding includes understanding how we arrived here.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#8-structure-enables-scale","level":2,"title":"8. Structure Enables Scale","text":"<p>Unstructured accumulation is not memory.</p> <p>Authoritative cognitive state must have a defined layout that:</p> <ul> <li>communicates intent,</li> <li>supports navigation,</li> <li>and prevents drift.</li> </ul>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#9-verification-is-the-scoreboard","level":2,"title":"9. Verification Is the Scoreboard","text":"<p>Claims without recorded outcomes are noise.</p> <p>Reality (observed and captured) is the only signal that compounds.</p> <p>This invariant defines a required direction:</p> <p>The authoritative state must be able to record expectation and result.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#10-capture-once-reuse-indefinitely","level":2,"title":"10. Capture Once, Reuse Indefinitely","text":"<p>Work that has already produced understanding must not be re-derived from  scratch.</p> <p>Explored paths, rejected options, and validated conclusions are  permanent assets.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#11-policies-are-encoded-not-remembered","level":2,"title":"11. Policies Are Encoded, Not Remembered","text":"<p>Alignment must not depend on recall or goodwill.</p> <p>Constraints that matter must exist in machine-readable form and participate in context assembly.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#12-the-system-explains-itself","level":2,"title":"12. The System Explains Itself","text":"<p>From the repository state alone it must be possible to determine:</p> <ul> <li>what was authoritative,</li> <li>what constraints applied.</li> </ul> <p>Delivery views may be optimized.</p> <p>They must not become the only explanation.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#non-goals","level":1,"title":"Non-Goals","text":"<p>To avoid category errors, <code>ctx</code> does not attempt to be:</p> <ul> <li>a skill,</li> <li>a prompt management tool,</li> <li>a chat history viewer,</li> <li>an autonomous agent runtime,</li> <li>a vector database,</li> <li>a hosted memory service.</li> </ul> <p>Such systems may integrate with <code>ctx</code>.</p> <p>They do not define it.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#implications-for-contributions","level":1,"title":"Implications for Contributions","text":"<p>Valid contributions:</p> <ul> <li>strengthen an invariant,</li> <li>reduce the cost of maintaining an invariant,</li> <li>or extend the system without violating invariants.</li> </ul> <p>Invalid contributions:</p> <ul> <li>introduce hidden authoritative state,</li> <li>replace reproducible assembly with non-reproducible behavior,</li> <li>make core operation depend on external services,</li> <li>reduce human inspectability of authoritative state,</li> <li>or bypass explicit user authority over what becomes authoritative.</li> </ul>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/design-invariants/#the-contract","level":1,"title":"The Contract","text":"<p>Everything else (commands, skills, layouts, integrations, optimizations)  is an implementation detail.</p> <p>These invariants are the system.</p>","path":["Reference","Invariants"],"tags":[]},{"location":"reference/scratchpad/","level":1,"title":"Scratchpad","text":"","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#what-is-ctx-scratchpad","level":2,"title":"What Is <code>ctx</code> Scratchpad?","text":"<p>A one-liner scratchpad, encrypted at rest, synced via <code>git</code>.</p> <p>Quick notes that don't fit decisions, learnings, or tasks: reminders, intermediate values, sensitive tokens, working memory during debugging. Entries are numbered, reorderable, and persist across sessions.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#encrypted-by-default","level":2,"title":"Encrypted by Default","text":"<p>Scratchpad entries are encrypted with <code>AES-256-GCM</code> before touching the disk.</p> Component Path Git status Encryption key <code>~/.ctx/.ctx.key</code> User-level, <code>0600</code> permissions Encrypted data <code>.context/scratchpad.enc</code> Committed <p>The key is generated automatically during <code>ctx init</code> (256-bit via <code>crypto/rand</code>) and stored at <code>~/.ctx/.ctx.key</code>. One key per machine, shared across all projects.</p> <p>The ciphertext format is <code>[12-byte nonce][ciphertext+tag]</code>. No external dependencies: Go stdlib only.</p> <p>Because the key is <code>.gitignore</code>d and the data is committed, you get:</p> <ul> <li>At-rest encryption: the <code>.enc</code> file is opaque without the key</li> <li>Git sync: push/pull the encrypted file like any other tracked file</li> <li>Key separation: the key never leaves the machine unless you copy it</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#commands","level":2,"title":"Commands","text":"Command Purpose <code>ctx pad</code> List all entries (numbered 1-based) <code>ctx pad show N</code> Output raw text of entry N (no prefix, pipe-friendly) <code>ctx pad add \"text\"</code> Append a new entry <code>ctx pad rm ID [ID...]</code> Remove entries by stable ID (supports ranges: <code>3-5</code>) <code>ctx pad edit N \"text\"</code> Replace entry N with new text <code>ctx pad edit N --append \"text\"</code> Append text to the end of entry N <code>ctx pad edit N --prepend \"text\"</code> Prepend text to the beginning of entry N <code>ctx pad edit N --tag tagname</code> Add a tag to entry N <code>ctx pad add TEXT --file PATH</code> Ingest a file as a blob entry (TEXT is the label) <code>ctx pad show N --out PATH</code> Write decoded blob content to a file <code>ctx pad normalize</code> Reassign entry IDs as 1..N <code>ctx pad mv N M</code> Move entry from position N to position M <code>ctx pad resolve</code> Show both sides of a merge conflict for resolution <code>ctx pad import FILE</code> Bulk-import lines from a file (or stdin with <code>-</code>) <code>ctx pad import --blob DIR</code> Import directory files as blob entries <code>ctx pad export [DIR]</code> Export all blob entries to a directory as files <code>ctx pad merge FILE...</code> Merge entries from other scratchpad files into current <code>ctx pad --tag TAG</code> List entries filtered by tag (prefix with <code>~</code> to exclude) <code>ctx pad tags</code> List all tags with counts <code>ctx pad tags --json</code> List all tags with counts as JSON <p>All commands decrypt on read, operate on plaintext in memory, and re-encrypt on write. The key file is never printed to stdout.</p> <p>For blob entries, <code>--append</code>, <code>--prepend</code>, and <code>--tag</code> modify the label while preserving the blob data.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#examples","level":3,"title":"Examples","text":"<pre><code># Add a note\nctx pad add \"check DNS propagation after deploy\"\n\n# List everything\nctx pad\n#   1. check DNS propagation after deploy\n#   2. staging API key: sk-test-abc123\n\n# Show raw text (for piping)\nctx pad show 2\n# sk-test-abc123\n\n# Compose entries\nctx pad edit 1 --append \"$(ctx pad show 2)\"\n\n# Reorder\nctx pad mv 2 1\n\n# Clean up (IDs are stable; they don't shift when entries are deleted)\nctx pad rm 2\n</code></pre>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#tags","level":2,"title":"Tags","text":"<p>Entries can contain <code>#word</code> tags for lightweight categorization. Tags are convention-based: any <code>#word</code> token in an entry's text is a tag. No special syntax to add or remove them; use the existing <code>add</code> and <code>edit</code> commands.</p> <pre><code># Add tagged entries\nctx pad add \"check DNS propagation #later\"\nctx pad add \"deploy hotfix #urgent\"\nctx pad add \"review PR #later #ci\"\n\n# Filter by tag\nctx pad --tag later\n#   1. check DNS propagation #later\n#   3. review PR #later #ci\n\n# Exclude a tag\nctx pad --tag ~later\n#   2. deploy hotfix #urgent\n\n# Multiple filters (AND logic)\nctx pad --tag later --tag ci\n#   3. review PR #later #ci\n\n# List all tags with counts\nctx pad tags\n# ci       1\n# later    2\n# urgent   1\n\n# JSON output\nctx pad tags --json\n# [{\"tag\":\"ci\",\"count\":1},{\"tag\":\"later\",\"count\":2},{\"tag\":\"urgent\",\"count\":1}]\n\n# Add a tag to an existing entry\nctx pad edit 1 --tag done\n\n# Combine with other operations\nctx pad edit 1 --append \"checked\" --tag done\n\n# Remove a tag (replace entry text without the tag)\nctx pad edit 1 \"check DNS propagation\"\n</code></pre> <p>Entry IDs are stable; they don't shift when other entries are deleted, so <code>ctx pad rm 3</code> always targets the same entry. Use <code>ctx pad normalize</code> to reassign IDs as 1..N if gaps bother you. Tags are case-sensitive and support letters, digits, hyphens, and underscores (<code>#high-priority</code>, <code>#v2</code>, <code>#my_tag</code>).</p> <p>For blob entries, tags are extracted from the label only.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#bulk-import-and-export","level":2,"title":"Bulk Import and Export","text":"<p>Import lines from a file in bulk (each non-empty line becomes an entry):</p> <pre><code># Import from a file\nctx pad import notes.txt\n\n# Import from stdin\ngrep TODO *.go | ctx pad import -\n</code></pre> <p>Export all blob entries to a directory as files:</p> <pre><code># Export to a directory\nctx pad export ./ideas\n\n# Preview without writing\nctx pad export --dry-run\n\n# Overwrite existing files\nctx pad export --force ./backup\n</code></pre>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#merging-scratchpads","level":2,"title":"Merging Scratchpads","text":"<p>Combine entries from other scratchpad files into your current pad. Useful when merging work from parallel worktrees, other machines, or teammates:</p> <pre><code># Merge from a worktree's encrypted scratchpad\nctx pad merge worktree/.context/scratchpad.enc\n\n# Merge from multiple sources (encrypted and plaintext)\nctx pad merge pad-a.enc notes.md\n\n# Merge a foreign encrypted pad using its key\nctx pad merge --key /other/.ctx.key foreign.enc\n\n# Preview without writing\nctx pad merge --dry-run pad-a.enc pad-b.md\n</code></pre> <p>Each input file is auto-detected as encrypted or plaintext: decryption is attempted first, and on failure the file is parsed as plain text. Entries are deduplicated by exact content, so running merge twice with the same file is safe.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#file-blobs","level":2,"title":"File Blobs","text":"<p>The scratchpad can store small files (up to 64 KB) as blob entries. Files are base64-encoded and stored with a human-readable label.</p> <pre><code># Ingest a file: first argument is the label\nctx pad add \"deploy config\" --file ./deploy.yaml\n\n# Listing shows label with a [BLOB] marker\nctx pad\n#   1. check DNS propagation after deploy\n#   2. deploy config [BLOB]\n\n# Extract to a file\nctx pad show 2 --out ./recovered.yaml\n\n# Or print decoded content to stdout\nctx pad show 2\n</code></pre> <p>Blob entries are encrypted identically to text entries. The internal format is <code>label:::base64data</code>: You never need to construct this manually.</p> Constraint Value Max file size (pre-encoding) 64 KB Storage format <code>label:::base64(content)</code> Display <code>label [BLOB]</code> in listings <p>When Should You Use Blobs</p> <p>Blobs are for small files you want encrypted and portable: config snippets, key fragments, deployment manifests, test fixtures. For anything larger than 64 KB, use the filesystem directly.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#using-with-ai","level":2,"title":"Using with AI","text":"<p>Use Natural Language</p> <p>As in many <code>ctx</code> features, the <code>ctx</code> scratchpad can also be used with natural langauge. You don't have to memorize the CLI commands.</p> <p>CLI gives you \"precision\", whereas natural language gives you flow.</p> <p>The <code>/ctx-pad</code> skill maps natural language to <code>ctx pad</code> commands. You don't need to remember the syntax:</p> You say What happens \"jot down: check DNS after deploy\" <code>ctx pad add \"check DNS after deploy\"</code> \"show my scratchpad\" <code>ctx pad</code> \"delete the third entry\" <code>ctx pad rm 3</code> \"update entry 2 to include the new endpoint\" <code>ctx pad edit 2 \"...\"</code> \"move entry 4 to the top\" <code>ctx pad mv 4 1</code> \"import my notes from notes.txt\" <code>ctx pad import notes.txt</code> \"export all blobs to ./backup\" <code>ctx pad export ./backup</code> \"merge the scratchpad from the worktree\" <code>ctx pad merge worktree/.context/scratchpad.enc</code> <p>The skill handles the translation. You describe what you want in plain English; the agent picks the right command.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#worktrees","level":2,"title":"Worktrees","text":"<p>The encryption key lives at <code>~/.ctx/.ctx.key</code> (outside the project directory). Because all worktrees on the same machine share this path, <code>ctx pad</code> works in worktrees automatically - no special setup needed.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#key-distribution","level":2,"title":"Key Distribution","text":"<p>The encryption key (<code>~/.ctx/.ctx.key</code>) stays on the machine where it was generated. <code>ctx</code> never transmits it.</p> <p>To share the scratchpad across machines:</p> <ol> <li>Copy the key manually: <code>scp</code>, USB drive, password manager.</li> <li>Push/pull the <code>.enc</code> file via git as usual.</li> <li>Both machines can now read and write the same scratchpad.</li> </ol> <p>Never Commit the Key</p> <p>The key is <code>.gitignore</code>d by default. If you override this, anyone with repo access can decrypt your scratchpad. </p> <p>Treat the key like an SSH private key.</p> <p>See the Syncing Scratchpad Notes Across Machines recipe for a step-by-step walkthrough.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#plaintext-override","level":2,"title":"Plaintext Override","text":"<p>For projects where encryption is unnecessary, disable it in <code>.ctxrc</code>:</p> <pre><code>scratchpad_encrypt: false\n</code></pre> <p>In plaintext mode:</p> <ul> <li>Entries are stored in <code>.context/scratchpad.md</code> instead of <code>.enc</code>.</li> <li>No key is generated or required.</li> <li>All <code>ctx pad</code> commands work identically.</li> <li>The file is human-readable and diffable.</li> </ul> <p>When Should You Use Plaintext</p> <p>Plaintext mode is useful for non-sensitive projects, solo work where encryption adds friction, or when you want scratchpad entries visible in <code>git diff</code>.</p>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#when-should-you-use-scratchpad-versus-context-files","level":2,"title":"When Should You Use Scratchpad versus Context Files","text":"Use case Where it goes Temporary reminders (\"check X after deploy\") Scratchpad Working values during debugging Scratchpad Sensitive tokens or API keys (short-term) Scratchpad Quick notes that don't fit anywhere else Scratchpad Items that are not directly relevant to the project Scratchpad Things that you want to keep near, but also hidden Scratchpad Work items with completion tracking <code>TASKS.md</code> Trade-offs with rationale <code>DECISIONS.md</code> Reusable lessons with context/lesson/application <code>LEARNINGS.md</code> Codified patterns and standards <code>CONVENTIONS.md</code> <p>Rule of thumb: </p> <ul> <li>If it needs structure or will be referenced months later, use   a context file (i.e. <code>DECISIONS.md</code>, <code>LEARNINGS.md</code>, <code>TASKS.md</code>). </li> <li>If it is working memory for the current session or week, use    the scratchpad.</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/scratchpad/#see-also","level":2,"title":"See Also","text":"<ul> <li>Syncing Scratchpad Notes Across Machines:   Key distribution, push/pull workflow, merge conflict resolution</li> <li>Using the Scratchpad:   Natural language examples, blob workflow, when to use scratchpad vs context files</li> <li>Context Files: Format and conventions for all   <code>.context/</code> files</li> <li>Security: Trust model and permission hygiene</li> </ul>","path":["Reference","Scratchpad"],"tags":[]},{"location":"reference/session-journal/","level":1,"title":"Session Journal","text":"<p>Important Security Note</p> <p>Session journals contain sensitive data such as file contents, commands, API keys, internal discussions,  error messages with stack traces, and more. </p> <p>The <code>.context/journal-site/</code> and <code>.context/journal-obsidian/</code> directories MUST be <code>.gitignore</code>d.</p> <ul> <li>DO NOT host your journal publicly.</li> <li>DO NOT commit your journal files to version control.</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#browse-your-session-history","level":2,"title":"Browse Your Session History","text":"<p><code>ctx</code>'s Session Journal turns your AI coding sessions into a browsable,  searchable, and editable archive.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#quick-start","level":2,"title":"Quick Start","text":"<p>After using <code>ctx</code> for a couple of sessions, you can generate a  journal site with:</p> <pre><code># Import all sessions to markdown\nctx journal import --all\n\n# Generate and serve the journal site\nctx journal site --serve\n</code></pre> <p>Then open http://localhost:8000 to browse your sessions.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#what-you-get","level":2,"title":"What You Get","text":"<p>The Session Journal gives you:</p> <ul> <li>Browsable history: Navigate through all your AI sessions by date</li> <li>Full conversations: See every message, tool use, and result</li> <li>Token usage: Track how many tokens each session consumed</li> <li>Search: Find sessions by content, project, or date</li> <li>Dark mode: Easy on the eyes for late-night archaeology</li> </ul> <p>Each session page includes the following sections:</p> Section Content Metadata Date, time, duration, model, project, git branch Summary Space for your notes (editable) Tool Usage Which tools were used and how often Conversation Full transcript with timestamps","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-workflow","level":2,"title":"The Workflow","text":"","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#1-import-sessions","level":3,"title":"1. Import Sessions","text":"<pre><code># Import all sessions from current project (only new files)\nctx journal import --all\n\n# Import sessions from all projects\nctx journal import --all --all-projects\n\n# Import a specific session by ID (always writes)\nctx journal import abc123\n\n# Preview what would be imported\nctx journal import --all --dry-run\n\n# Re-import existing (regenerates conversation, preserves YAML frontmatter)\nctx journal import --all --regenerate\n\n# Discard frontmatter during regeneration\nctx journal import --all --regenerate --keep-frontmatter=false -y\n</code></pre> <p>Imported sessions go to <code>.context/journal/</code> as editable Markdown files.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#2-generate-the-site","level":3,"title":"2. Generate the Site","text":"<pre><code># Generate site structure\nctx journal site\n\n# Generate and build static HTML\nctx journal site --build\n\n# Generate and serve locally\nctx journal site --serve\n\n# Custom output directory\nctx journal site --output ~/my-journal\n</code></pre> <p>The site is generated in <code>.context/journal-site/</code> by default.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#3-browse-and-search","level":3,"title":"3. Browse and Search","text":"<p>Open http://localhost:8000 after running <code>--serve</code>.</p> <ul> <li>Use the sidebar to navigate by date</li> <li>Use search (<code>/</code> key) to find specific content</li> <li>Click any session to see the full conversation</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#editing-sessions","level":2,"title":"Editing Sessions","text":"<p>Imported sessions are plain Markdown in <code>.context/journal/</code>. You can:</p> <ul> <li>Add summaries: Fill in the <code>## Summary</code> section</li> <li>Add notes: Insert your own commentary anywhere</li> <li>Highlight key moments: Use Markdown formatting</li> <li>Delete noise: Remove irrelevant tool outputs</li> </ul> <p>After editing, regenerate the site:</p> <pre><code>ctx journal site --serve\n</code></pre> Safe by Default <p>Running <code>ctx journal import --all</code> only imports new sessions. Existing files are skipped entirely (your edits and enrichments are never touched).</p> <p>Use <code>--regenerate</code> to re-import existing files. Conversation content is regenerated, but YAML frontmatter (topics, type, outcome, etc.) is preserved. You'll be prompted before any existing files are overwritten; add <code>-y</code> to skip the prompt.</p> <p>Use <code>--keep-frontmatter=false</code> to discard enriched frontmatter during regeneration.</p> <p>Locked entries (via <code>ctx journal lock</code>) are always skipped, regardless of flags. If you prefer to add <code>locked: true</code> to frontmatter during enrichment, run <code>ctx journal sync</code> to propagate the lock state to <code>.state.json</code>.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#large-sessions","level":2,"title":"Large Sessions","text":"<p>Sessions with many messages (200+) are automatically split into multiple parts  for better browser performance. Navigation links connect the parts:</p> <pre><code>session-abc123.md      (Part 1 of 3)\nsession-abc123-p2.md   (Part 2 of 3)\nsession-abc123-p3.md   (Part 3 of 3)\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#suggestion-sessions","level":2,"title":"Suggestion Sessions","text":"<p>Claude Code generates \"suggestion\" sessions for auto-complete prompts. These are separated in the index under a \"Suggestions\" section to keep your main  session list focused.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enriching-journal-entries","level":2,"title":"Enriching Journal Entries","text":"<p>Raw imported sessions contain basic metadata (date, time, project) but lack the structured information needed for effective search, filtering, and analysis. Journal enrichment adds semantic metadata that transforms a flat archive into a searchable knowledge base.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#why-enrich","level":3,"title":"Why Enrich?","text":"<p>Without enrichment, you have timestamps and raw conversations. With enrichment:</p> <ul> <li>Find sessions by topic: \"Show me all auth-related sessions\"</li> <li>Filter by outcome: \"What did I abandon vs complete?\"</li> <li>Track technology usage: \"When did I last work with PostgreSQL?\"</li> <li>Identify key files: Jump directly to the files discussed</li> <li>Get summaries: Understand what happened without reading transcripts</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#the-frontmatter-schema","level":3,"title":"The Frontmatter Schema","text":"<p>Enriched entries begin with YAML frontmatter:</p> <pre><code>---\ntitle: \"Implement caching layer\"\ndate: 2026-01-27\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - performance\ntechnologies:\n  - go\n  - redis\nlibraries:\n  - go-redis/redis\nkey_files:\n  - internal/cache/redis.go\n  - internal/cache/memory.go\n---\n</code></pre> Field Required Description <code>title</code> Yes Descriptive title (not the session slug) <code>date</code> Yes Session date (YYYY-MM-DD) <code>type</code> Yes Session type (see below) <code>outcome</code> Yes How the session ended (see below) <code>topics</code> No Subject areas discussed <code>technologies</code> No Languages, databases, frameworks <code>libraries</code> No Specific packages or libraries used <code>key_files</code> No Important files created or modified <p>Type values:</p> Type When to use <code>feature</code> Building new functionality <code>bugfix</code> Fixing broken behavior <code>refactor</code> Restructuring without behavior change <code>exploration</code> Research, learning, experimentation <code>debugging</code> Investigating issues <code>documentation</code> Writing docs, comments, README <p>Outcome values:</p> Outcome Meaning <code>completed</code> Goal achieved <code>partial</code> Some progress, work continues <code>abandoned</code> Stopped pursuing this approach <code>blocked</code> Waiting on external dependency","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-ctx-journal-enrich","level":3,"title":"Using <code>/ctx-journal-enrich</code>","text":"<p>The <code>/ctx-journal-enrich</code> skill automates enrichment by analyzing conversation content and proposing metadata.</p> <p>Invoke by session identifier:</p> <pre><code>/ctx-journal-enrich twinkly-stirring-kettle\n/ctx-journal-enrich twinkly\n/ctx-journal-enrich 2026-01-24\n/ctx-journal-enrich 76fe2ab9\n</code></pre> <p>The skill will:</p> <ol> <li>Check if locked - locked entries are skipped (same as export);</li> <li>Find the matching journal file;</li> <li>Read and analyze the conversation;</li> <li>Propose frontmatter (type, topics, outcome, technologies);</li> <li>Generate a 2-3 sentence summary;</li> <li>Extract decisions, learnings, and tasks mentioned;</li> <li>Show a diff and ask for confirmation before writing.</li> </ol>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#before-and-after","level":3,"title":"Before and After","text":"<p>Before enrichment:</p> <pre><code># twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\n[Add your summary of this session]\n\n## Conversation\n...\n</code></pre> <p>After enrichment:</p> <pre><code>---\ntitle: \"Add Redis caching to API endpoints\"\ndate: 2026-01-24\ntype: feature\noutcome: completed\ntopics:\n  - caching\n  - api-performance\ntechnologies:\n  - go\n  - redis\nkey_files:\n  - internal/api/middleware/cache.go\n  - internal/cache/redis.go\n---\n\n# twinkly-stirring-kettle\n\n**ID**: abc123-def456\n**Date**: 2026-01-24\n**Time**: 14:30:00\n...\n\n## Summary\n\nImplemented Redis-based caching middleware for frequently accessed API endpoints.\nAdded cache invalidation on writes and configurable TTL per route. Reduced\n the average response time from 200ms to 15ms for cached routes.\n\n## Decisions\n\n* Used Redis over in-memory cache for horizontal scaling\n* Chose per-route TTL configuration over global setting\n\n## Learnings\n\n* Redis WATCH command prevents race conditions during cache invalidation\n\n## Conversation\n...\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#enrichment-and-site-generation","level":3,"title":"Enrichment and Site Generation","text":"<p>The journal site generator uses enriched metadata for better organization:</p> <ul> <li>Titles appear in navigation instead of slugs</li> <li>Summaries provide context in the index</li> <li>Topics enable filtering (when using search)</li> <li>Types allow grouping by work category</li> </ul> <p>Future improvements will add topic-based navigation and outcome filtering to the generated site.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#batch-enrichment","level":3,"title":"Batch Enrichment","text":"<p>To enrich multiple sessions, process them one at a time:</p> <pre><code># List unenriched sessions (those without frontmatter)\ngrep -L \"^---$\" .context/journal/*.md | head -10\n</code></pre> <p>Then run <code>/ctx-journal-enrich</code> on each. Enrichment is intentionally interactive to ensure accuracy.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#obsidian-vault-export","level":2,"title":"Obsidian Vault Export","text":"<p>If you use Obsidian for knowledge management, you can export your journal as an Obsidian vault instead of (or alongside) the static site:</p> <pre><code>ctx journal obsidian\n</code></pre> <p>This generates a vault in <code>.context/journal-obsidian/</code> with:</p> <ul> <li>Wikilinks (<code>[[target|display]]</code>) instead of Markdown links</li> <li>MOC pages (Map of Content) for topics, key files, and session types</li> <li>Related sessions footer per entry: links to entries sharing the same topics</li> <li>Transformed frontmatter: <code>topics</code> renamed to <code>tags</code> (Obsidian-recognized),   <code>aliases</code> added from title for search</li> <li>Graph-optimized structure: MOC hubs and cross-linked entries create dense   graph connectivity</li> </ul> <p>To use: open the output directory in Obsidian (\"Open folder as vault\").</p> <pre><code># Custom output directory\nctx journal obsidian --output ~/vaults/ctx-journal\n</code></pre> <p>Static Site vs Obsidian Vault</p> <p>Use <code>ctx journal site</code> when you want a web-browsable archive with search and dark mode. Use <code>ctx journal obsidian</code> when you want graph view, backlinks, and tag-based navigation inside Obsidian. Both use the same enriched source entries: you can generate both.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#full-pipeline","level":2,"title":"Full Pipeline","text":"<p>The complete journal workflow has four stages. Each is idempotent: safe to re-run, and stages skip already-processed entries.</p> <pre><code>import → enrich → rebuild\n</code></pre> Stage Command / Skill What it does Skips if Import <code>ctx journal import --all</code> Converts session JSONL to Markdown File already exists (safe default) Enrich <code>/ctx-journal-enrich</code> Adds frontmatter, summaries, topics Frontmatter already present Rebuild <code>ctx journal site --build</code> Generates static HTML site (never) Obsidian <code>ctx journal obsidian</code> Generates Obsidian vault with wikilinks (never) <p>One-Command Pipeline</p> <p><code>/ctx-journal-enrich-all</code> handles import automatically - it detects unimported sessions and imports them before enriching. You only need to run <code>ctx journal site --build</code> afterward.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#using-make-journal","level":3,"title":"Using <code>make journal</code>","text":"<p>If your project includes <code>Makefile.ctx</code> (deployed by <code>ctx init</code>), the first and last stages are combined:</p> <pre><code>make journal           # import + rebuild\n</code></pre> <p>After it runs, it reminds you to enrich in Claude Code:</p> <pre><code>Next steps (in Claude Code):\n  /ctx-journal-enrich-all # imports if needed + adds metadata per entry\n\nThen re-run: make journal\n</code></pre> <p>Rendering Issues?</p> <p>If individual entries have rendering problems (broken fences, malformed lists), check the programmatic normalization in the import pipeline. Most cases are handled automatically during <code>ctx journal import</code>.</p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#tips","level":2,"title":"Tips","text":"<p>Daily workflow: <pre><code># Import, browse, then enrich in Claude Code\nmake journal && make journal-serve\n# Then in Claude Code: /ctx-journal-enrich <session>\n</code></pre></p> <p>After a productive session: <pre><code># Import just that session and add notes\nctx journal import <session-id>\n# Edit .context/journal/<session>.md\n# Regenerate: ctx journal site\n</code></pre></p> <p>Searching across all sessions: <pre><code># Use grep on the journal directory\ngrep -r \"authentication\" .context/journal/\n</code></pre></p>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#requirements","level":2,"title":"Requirements","text":"Use <code>pipx</code> for <code>zensical</code> <p><code>pip install zensical</code> may install a non-functional stub on system Python. Using <code>venv</code> has other issues too.</p> <p>These issues especially happen on Mac OSX.</p> <p>Use <code>pipx install zensical</code>, which creates an isolated environment and handles Python version management automatically.</p> <p>The journal site uses zensical for static site generation:</p> <pre><code>pipx install zensical\n</code></pre>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/session-journal/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx journal</code>: Session discovery and listing</li> <li><code>ctx journal site</code>: Static site generation</li> <li><code>ctx journal obsidian</code>: Obsidian vault export</li> <li>Context Files: The <code>.context/</code> directory structure</li> </ul>","path":["Reference","Session Journal"],"tags":[]},{"location":"reference/skills/","level":1,"title":"Skills","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skills","level":2,"title":"Skills","text":"<p>Skills are slash commands that run inside your AI assistant (e.g., <code>/ctx-next</code>), as opposed to CLI commands that run in your terminal (e.g., <code>ctx status</code>). </p> <p>Skills give your agent structured workflows: It knows what to read, what to  run, and when to ask. Most wrap one or more <code>ctx</code> CLI commands with  opinionated behavior on top. </p> <p>Skills Are Best Used Conversationally</p> <p>The beauty of <code>ctx</code> is that it's designed to be intuitive and  conversational, allowing you to interact with your AI assistant  naturally. That's why you don't have to memorize many of these skills.</p> <p>See the Prompting Guide for natural-language  triggers that invoke these skills conversationally.</p> <p>However, when you need a more precise control, you have the option to invoke the relevant skills directly.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#all-skills","level":2,"title":"All Skills","text":"Skill Description Type <code>/ctx-remember</code> Recall project context and present structured readback user-invocable <code>/ctx-wrap-up</code> End-of-session context persistence ceremony user-invocable <code>/ctx-status</code> Show context summary with interpretation user-invocable <code>/ctx-agent</code> Load full context packet for AI consumption user-invocable <code>/ctx-next</code> Suggest 1-3 concrete next actions with rationale user-invocable <code>/ctx-commit</code> Commit with integrated context persistence user-invocable <code>/ctx-reflect</code> Pause and reflect on session progress user-invocable <code>/ctx-task-add</code> Add actionable task to TASKS.md user-invocable <code>/ctx-decision-add</code> Record architectural decision with rationale user-invocable <code>/ctx-learning-add</code> Record gotchas and lessons learned user-invocable <code>/ctx-convention-add</code> Record coding convention for consistency user-invocable <code>/ctx-archive</code> Archive completed tasks from TASKS.md user-invocable <code>/ctx-pad</code> Manage encrypted scratchpad entries user-invocable <code>/ctx-history</code> Browse and import AI session history user-invocable <code>/ctx-journal-enrich</code> Enrich single journal entry with metadata user-invocable <code>/ctx-journal-enrich-all</code> Full journal pipeline: export if needed, then batch-enrich user-invocable <code>/ctx-blog</code> Generate blog post draft from project activity user-invocable <code>/ctx-blog-changelog</code> Generate themed blog post from a commit range user-invocable <code>/ctx-consolidate</code> Consolidate redundant learnings or decisions user-invocable <code>/ctx-drift</code> Detect and fix context drift user-invocable <code>/ctx-prompt</code> Apply, list, and manage saved prompt templates user-invocable <code>/ctx-prompt-audit</code> Analyze prompting patterns for improvement user-invocable <code>/ctx-link-check</code> Audit docs for dead internal and external links user-invocable <code>/ctx-permission-sanitize</code> Audit Claude Code permissions for security risks user-invocable <code>/ctx-brainstorm</code> Structured design dialogue before implementation user-invocable <code>/ctx-spec</code> Scaffold a feature spec from a project template user-invocable <code>/ctx-plan-import</code> Import Claude Code plan files into project specs user-invocable <code>/ctx-implement</code> Execute a plan step-by-step with verification user-invocable <code>/ctx-loop</code> Generate autonomous loop script user-invocable <code>/ctx-worktree</code> Manage git worktrees for parallel agents user-invocable <code>/ctx-architecture</code> Build and maintain architecture maps user-invocable <code>/ctx-architecture-failure-analysis</code> Adversarial failure analysis for correctness bugs user-invocable <code>/ctx-remind</code> Manage session-scoped reminders user-invocable <code>/ctx-doctor</code> Troubleshoot <code>ctx</code> behavior with health checks and event analysis user-invocable <code>/ctx-skill-audit</code> Audit skills against Anthropic prompting best practices user-invocable <code>/ctx-skill-create</code> Create, improve, and test skills user-invocable <code>/ctx-pause</code> Pause context hooks for this session user-invocable <code>/ctx-resume</code> Resume context hooks after a pause user-invocable <code>/ctx-kb-ingest</code> Editorial KB pass (topic-page / triage / evidence-only) user-invocable <code>/ctx-kb-ask</code> Q&A grounded in the KB; refuses to web-jump user-invocable <code>/ctx-kb-site-review</code> Mechanical KB structural audit user-invocable <code>/ctx-kb-ground</code> Re-ground the KB against listed external sources user-invocable <code>/ctx-kb-note</code> Park a finding in <code>ingest/findings.md</code> user-invocable <code>/ctx-handover</code> Handover step delegated by <code>/ctx-wrap-up</code>; folds postdated closeouts sub-mechanism","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-lifecycle","level":2,"title":"Session Lifecycle","text":"<p>Skills for starting, running, and ending a productive session.</p> <p>Session Ceremonies</p> <p>Two skills in this group are ceremony skills: <code>/ctx-remember</code> (session start) and <code>/ctx-wrap-up</code> (session end). Unlike other skills that work conversationally, these should be invoked as explicit slash commands for completeness. See Session Ceremonies.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remember","level":3,"title":"<code>/ctx-remember</code>","text":"<p>Recall project context and present a structured readback. Ceremony skill: invoke explicitly at session start.</p> <p>Wraps: <code>ctx agent --budget 4000</code>, <code>ctx journal source --limit 3</code>, reads TASKS.md, DECISIONS.md, LEARNINGS.md</p> <p>See also: Session Ceremonies, The Complete Session</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-status","level":3,"title":"<code>/ctx-status</code>","text":"<p>Show context summary (files, token budget, tasks, recent activity) with interpreted suggestions.</p> <p>Wraps: <code>ctx status [--verbose] [--json]</code></p> <p>See also: The Complete Session, <code>ctx status</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-agent","level":3,"title":"<code>/ctx-agent</code>","text":"<p>Load the full context packet optimized for AI consumption. Also runs automatically via the PreToolUse hook with cooldown.</p> <p>Wraps: <code>ctx agent [--budget] [--format] [--cooldown] [--session]</code></p> <p>See also: The Complete Session, <code>ctx agent</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-next","level":3,"title":"<code>/ctx-next</code>","text":"<p>Suggest 1-3 concrete next actions ranked by priority, momentum, and unblocked status.</p> <p>Wraps: reads TASKS.md, <code>ctx journal source --limit 3</code></p> <p>See also: The Complete Session, Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-commit","level":3,"title":"<code>/ctx-commit</code>","text":"<p>Commit code with integrated context persistence: pre-commit checks, staged files, Co-Authored-By trailer, and a post-commit prompt to capture decisions and learnings.</p> <p>Wraps: <code>git add</code>, <code>git commit</code>, optionally chains to <code>/ctx-decision-add</code> and <code>/ctx-learning-add</code></p> <p>See also: The Complete Session</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-reflect","level":3,"title":"<code>/ctx-reflect</code>","text":"<p>Pause and reflect on session progress. Walks through a checklist of learnings, decisions, task completions, and session notes to persist.</p> <p>Wraps: chains to <code>ctx learning add</code>, <code>ctx decision add</code>, manual TASKS.md updates</p> <p>See also: The Complete Session, Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-wrap-up","level":3,"title":"<code>/ctx-wrap-up</code>","text":"<p>End-of-session context persistence ceremony. Gathers signal from git diff, recent commits, and conversation themes. Proposes candidates (learnings, decisions, conventions, tasks) with complete structured fields for user approval, then persists via <code>ctx add</code>. Offers <code>/ctx-commit</code> if uncommitted changes remain. Always delegates to <code>/ctx-handover</code> as its final step, regardless of whether <code>.context/kb/</code> exists: KB presence only affects what gets folded into the handover, not whether it is written. Ceremony skill: invoke explicitly at session end.</p> <p>Trigger phrases: \"let's wrap up\", \"save context\", \"save state\", \"leave a handover\", \"before I go\", \"stepping away\", \"end of session\"</p> <p>Wraps: <code>git diff --stat</code>, <code>git log</code>, <code>ctx learning add</code>, <code>ctx decision add</code>, <code>ctx convention add</code>, <code>ctx task add</code>, chains to <code>/ctx-commit</code>, delegates to <code>/ctx-handover</code></p> <p>See also: Session Ceremonies, The Complete Session, <code>/ctx-handover</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#context-persistence","level":2,"title":"Context Persistence","text":"<p>Skills for recording work artifacts: tasks, decisions, learnings, conventions: into <code>.context/</code> files.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-task-add","level":3,"title":"<code>/ctx-task-add</code>","text":"<p>Add an actionable task with optional priority and phase section.</p> <p>Wraps: <code>ctx task add \"description\" [--priority high|medium|low] --session-id ID --branch BR --commit HASH</code></p> <p>See also: Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-decision-add","level":3,"title":"<code>/ctx-decision-add</code>","text":"<p>Record an architectural decision with context, rationale, and consequence. Supports Y-statement (lightweight) and full ADR formats.</p> <p>Wraps: <code>ctx decision add \"title\" --context \"...\" --rationale \"...\" --consequence \"...\" --session-id ID --branch BR --commit HASH</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-learning-add","level":3,"title":"<code>/ctx-learning-add</code>","text":"<p>Record a project-specific gotcha, bug, or unexpected behavior. Filters for insights that are searchable, project-specific, and required real effort to discover.</p> <p>Wraps: <code>ctx learning add \"title\" --context \"...\" --lesson \"...\" --application \"...\" --session-id ID --branch BR --commit HASH</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-convention-add","level":3,"title":"<code>/ctx-convention-add</code>","text":"<p>Record a coding convention that should be standardized across sessions. Targets patterns seen 2-3+ times.</p> <p>Wraps: <code>ctx convention add \"rule\" --section \"Name\"</code></p> <p>See also: Persisting Decisions, Learnings, and Conventions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-archive","level":3,"title":"<code>/ctx-archive</code>","text":"<p>Archive completed tasks from TASKS.md to a timestamped file in <code>.context/archive/</code>. Preserves phase headers for traceability.</p> <p>Wraps: <code>ctx task archive [--dry-run]</code></p> <p>See also: Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#scratchpad","level":2,"title":"Scratchpad","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pad","level":3,"title":"<code>/ctx-pad</code>","text":"<p>Manage the encrypted scratchpad: add, remove, edit, and reorder one-liner notes. Encrypted at rest with AES-256-GCM.</p> <p>Wraps: <code>ctx pad</code>, <code>ctx pad add</code>, <code>ctx pad rm</code>, <code>ctx pad edit</code>, <code>ctx pad mv</code>, <code>ctx pad import</code>, <code>ctx pad export</code>, <code>ctx pad merge</code></p> <p>See also: Scratchpad, Using the Scratchpad</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#journal-history","level":2,"title":"Journal & History","text":"<p>Skills for browsing, exporting, and enriching your AI session history into a structured journal.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-history","level":3,"title":"<code>/ctx-history</code>","text":"<p>Browse, inspect, and import AI session history. List recent sessions, show details by slug or ID, and import to <code>.context/journal/</code>.</p> <p>Wraps: <code>ctx journal source</code>, <code>ctx journal source --show</code>, <code>ctx journal import</code></p> <p>See also: Browsing and Enriching Past Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich","level":3,"title":"<code>/ctx-journal-enrich</code>","text":"<p>Enrich a single journal entry with YAML frontmatter: title, type, outcome, topics, technologies, and summary. Shows diff before writing.</p> <p>Wraps: reads and edits <code>.context/journal/*.md</code> files</p> <p>See also: Browsing and Enriching Past Sessions, Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-journal-enrich-all","level":3,"title":"<code>/ctx-journal-enrich-all</code>","text":"<p>Full journal pipeline: imports unimported sessions first, then batch-enriches all unenriched entries. Filters out short sessions and continuations. Can spawn subagents for large backlogs.</p> <p>Wraps: <code>ctx journal import --all</code> + iterates <code>/ctx-journal-enrich</code></p> <p>See also: Browsing and Enriching Past Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#content-creation","level":2,"title":"Content Creation","text":"<p>Skills for turning project activity into publishable content.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog","level":3,"title":"<code>/ctx-blog</code>","text":"<p>Generate a blog post draft from recent project activity: git history, decisions, learnings, tasks, and journal entries. Requires a narrative arc (problem, approach, outcome).</p> <p>Wraps: reads <code>git log</code>, DECISIONS.md, LEARNINGS.md, TASKS.md, journal entries; writes to <code>docs/blog/</code></p> <p>See also: Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-blog-changelog","level":3,"title":"<code>/ctx-blog-changelog</code>","text":"<p>Generate a themed blog post from a commit range. Takes a starting commit and unifying theme, analyzes diffs and journal entries from that period.</p> <p>Wraps: <code>git log</code>, <code>git diff --stat</code>; writes to <code>docs/blog/</code></p> <p>See also: Turning Activity into Content</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#auditing-health","level":2,"title":"Auditing & Health","text":"<p>Skills for detecting drift, auditing alignment, and improving prompt quality.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-consolidate","level":3,"title":"<code>/ctx-consolidate</code>","text":"<p>Consolidate redundant entries in LEARNINGS.md or DECISIONS.md. Groups overlapping entries by keyword similarity, presents candidates, and (with user approval) merges groups into denser combined entries. Originals are archived, not deleted.</p> <p>Wraps: reads LEARNINGS.md and DECISIONS.md, writes consolidated entries, archives originals, runs <code>ctx reindex</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-drift","level":3,"title":"<code>/ctx-drift</code>","text":"<p>Detect and fix context drift: stale paths, missing files, file age staleness, task accumulation, entry count warnings, and constitution violations via <code>ctx drift</code>. Also detects skill drift against canonical templates.</p> <p>Wraps: <code>ctx drift [--fix]</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-prompt-audit","level":3,"title":"<code>/ctx-prompt-audit</code>","text":"<p>Analyze recent prompting patterns to identify vague or ineffective prompts. Reviews 3-5 journal entries and suggests rewrites with positive observations.</p> <p>Wraps: reads <code>.context/journal/</code> entries</p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-doctor","level":3,"title":"<code>/ctx-doctor</code>","text":"<p>Troubleshoot <code>ctx</code> behavior. Runs structural health checks via <code>ctx doctor</code>, analyzes event log patterns via <code>ctx hook event</code>, and presents findings with suggested actions. The CLI provides the structural baseline; the agent adds semantic analysis of event patterns and correlations.</p> <p>Wraps: <code>ctx doctor --json</code>, <code>ctx hook event --json --last 100</code>, <code>ctx remind list</code>, <code>ctx hook message list</code>, reads <code>.ctxrc</code></p> <p>Trigger phrases: \"diagnose\", \"troubleshoot\", \"doctor\", \"health check\", \"why didn't my hook fire?\", \"hooks seem broken\", \"something seems off\"</p> <p>Graceful degradation: If <code>event_log</code> is not enabled, the skill still works but with reduced capability. It runs structural checks and notes: \"Enable <code>event_log: true</code> in <code>.ctxrc</code> for hook-level diagnostics.\"</p> <p>See also: Troubleshooting, <code>ctx doctor</code> CLI, <code>ctx hook event</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-link-check","level":3,"title":"<code>/ctx-link-check</code>","text":"<p>Scan all Markdown files under <code>docs/</code> for broken links. Three passes: internal links (verify file targets exist on disk), external links (HTTP HEAD with timeout, report failures as warnings), and image references. Resolves relative paths, strips anchors before checking, and skips localhost/example URLs.</p> <p>Wraps: Glob + Grep to scan, <code>curl</code> for external checks</p> <p>Trigger phrases: \"check links\", \"audit links\", \"any broken links?\", \"dead links\"</p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-permission-sanitize","level":3,"title":"<code>/ctx-permission-sanitize</code>","text":"<p>Audit <code>.claude/settings.local.json</code> for dangerous permissions across four risk categories: hook bypass (Critical), destructive commands (High), config injection vectors (High), and overly broad patterns (Medium). Reports findings by severity and offers specific fix actions with user confirmation.</p> <p>Wraps: reads <code>.claude/settings.local.json</code>, edits with confirmation</p> <p>Trigger phrases: \"audit permissions\", \"are my permissions safe?\", \"sanitize permissions\", \"check settings\"</p> <p>See also: Claude Code Permission Hygiene</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#planning-execution","level":2,"title":"Planning & Execution","text":"<p>Skills for structured design, implementation, and parallel agent workflows.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-brainstorm","level":3,"title":"<code>/ctx-brainstorm</code>","text":"<p>Transform raw ideas into clear, validated designs through structured dialogue before any implementation begins. Follows a gated process: understand context, clarify the idea (one question at a time), surface non-functional requirements, lock understanding with user confirmation, explore 2-3 design approaches with trade-offs, stress-test the chosen approach, and present the detailed design.</p> <p>Wraps: reads DECISIONS.md, relevant source files; chains to <code>/ctx-decision-add</code> for recording design choices</p> <p>Trigger phrases: \"let's brainstorm\", \"design this\", \"think through\", \"before we build\", \"what approach should we take?\"</p> <p>See also: <code>/ctx-spec</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-spec","level":3,"title":"<code>/ctx-spec</code>","text":"<p>Scaffold a feature spec from the project template and walk through each section with the user. Covers: problem, approach, happy path, edge cases, validation rules, error handling, interface, implementation, configuration, testing, and non-goals. Spends extra time on edge cases and error handling.</p> <p>Wraps: reads <code>specs/tpl/spec-template.md</code>, writes to <code>specs/</code>, optionally chains to <code>/ctx-task-add</code></p> <p>Trigger phrases: \"spec this out\", \"write a spec\", \"create a spec\", \"design document\"</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#-brief-path-flag","level":4,"title":"<code>--brief <path></code> flag","text":"<p>When invoked as <code>/ctx-spec --brief <path></code>, the skill treats the file at <code><path></code> as the authoritative source and skips the interactive Q&A. Use this when a prior <code>/ctx-plan</code> session produced a debated brief that already covers the design.</p> <p>The skill enforces this authority order when sources disagree:</p> <ol> <li>Frozen contracts in <code>docs/</code> (release notes, public CLI docs)</li> <li>Recorded decisions in <code>.context/DECISIONS.md</code></li> <li>The brief at <code><path></code></li> <li>Agent inference, only when 1 through 3 are silent, and    labeled <code>TBD</code> in the resulting spec so it stands out for    review.</li> </ol> <p>Light compression for clarity is allowed; new facts are not. Where the brief is silent, the spec writes <code>TBD</code> rather than filling the gap from inference. If the brief contradicts a frozen contract, the contradiction is surfaced to the user rather than silently followed.</p> <p>See also: <code>/ctx-brainstorm</code>, <code>/ctx-plan</code>, <code>/ctx-plan-import</code></p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-plan-import","level":3,"title":"<code>/ctx-plan-import</code>","text":"<p>Import Claude Code plan files (<code>~/.claude/plans/*.md</code>) into the project's <code>specs/</code> directory. Lists plans with dates and H1 titles, supports filtering (<code>--today</code>, <code>--since</code>, <code>--all</code>), slugifies headings for filenames, and optionally creates tasks referencing each imported spec.</p> <p>Wraps: reads <code>~/.claude/plans/*.md</code>, writes to <code>specs/</code>, optionally chains to <code>/ctx-task-add</code></p> <p>See also: Importing Claude Code Plans, Tracking Work Across Sessions</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-implement","level":3,"title":"<code>/ctx-implement</code>","text":"<p>Execute a multi-step plan with build and test verification at each step. Loads a plan from a file or conversation context, breaks it into atomic steps, and checkpoints after every 3-5 steps.</p> <p>Wraps: reads plan file, runs verification commands (<code>go build</code>, <code>go test</code>, etc.)</p> <p>See also: Running an Unattended AI Agent</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-loop","level":3,"title":"<code>/ctx-loop</code>","text":"<p>Generate a ready-to-run shell script for autonomous AI iteration. Supports Claude Code, Aider, and generic tool templates with configurable completion signals.</p> <p>Wraps: <code>ctx loop [--tool] [--prompt] [--max-iterations] [--completion] [--output]</code></p> <p>See also: Autonomous Loops, Running an Unattended AI Agent</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-worktree","level":3,"title":"<code>/ctx-worktree</code>","text":"<p>Manage git worktrees for parallel agent development. Create sibling worktrees on dedicated branches, analyze task blast radius for grouping, and tear down with merge.</p> <p>Wraps: <code>git worktree add</code>, <code>git worktree list</code>, <code>git worktree remove</code>, <code>git merge</code></p> <p>See also: Parallel Agent Development with Git Worktrees</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture","level":3,"title":"<code>/ctx-architecture</code>","text":"<p>Build and maintain architecture maps incrementally. Creates or refreshes <code>ARCHITECTURE.md</code> (succinct project map, loaded at session start) and <code>DETAILED_DESIGN.md</code> (deep per-module reference, consulted on-demand). Coverage is tracked in <code>map-tracking.json</code> so each run extends the map rather than re-analyzing everything.</p> <p>Wraps: <code>ctx status</code>, <code>git log</code>, reads source files; writes <code>ARCHITECTURE.md</code>, <code>DETAILED_DESIGN.md</code>, <code>map-tracking.json</code></p> <p>See also: Detecting and Fixing Drift</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-architecture-failure-analysis","level":3,"title":"<code>/ctx-architecture-failure-analysis</code>","text":"<p>Adversarial failure analysis that generates falsifiable incident hypotheses against architecture artifacts. Hunts for correctness bugs that survive code review and tests: race conditions, ordering assumptions, cache staleness, error swallowing, ownership gaps, idempotency failures, state machine drift, and scaling cliffs.</p> <p>Requires <code>/ctx-architecture</code> artifacts as input. Reads <code>ARCHITECTURE.md</code>, <code>DETAILED_DESIGN*.md</code>, and <code>map-tracking.json</code>, then systematically applies 9 failure categories to every mutation point. Each finding carries an evidence standard (code path, trigger, failure path, silence reason, code evidence), a confidence level, and an explicit risk score. A mandatory challenge phase attempts to disprove each finding before it is accepted.</p> <p>Produces <code>.context/DANGER-ZONES.md</code> with ranked findings split into Critical (risk >= 7, silent/cascading) and Elevated tiers.</p> <p>Wraps: reads architecture artifacts, source code; writes <code>DANGER-ZONES.md</code>. Optionally uses GitNexus for blast radius and Gemini Search for cross-referencing known failure patterns.</p> <p>Relationship:</p> Skill Mode <code>/ctx-architecture</code> Map what exists <code>/ctx-architecture-enrich</code> Improve map fidelity <code>/ctx-architecture-failure-analysis</code> Generate falsifiable incident hypotheses","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-remind","level":3,"title":"<code>/ctx-remind</code>","text":"<p>Manage session-scoped reminders via natural language. Translates user intent (\"remind me to refactor swagger\") into the corresponding <code>ctx remind</code> command. Handles date conversion for <code>--after</code> flags.</p> <p>Wraps: <code>ctx remind</code>, <code>ctx remind list</code>, <code>ctx remind dismiss</code></p> <p>See also: Session Reminders</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#skill-authoring","level":2,"title":"Skill Authoring","text":"","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-audit","level":3,"title":"<code>/ctx-skill-audit</code>","text":"<p>Audit one or more skills against Anthropic prompting best practices. Checks audit dimensions: positive framing, motivation, phantom references, examples, subagent guards, scope, and descriptions. Reports findings by severity with concrete fix suggestions.</p> <p>Wraps: reads <code>internal/assets/claude/skills/*/SKILL.md</code> or <code>.claude/skills/*/SKILL.md</code>, references <code>anthropic-best-practices.md</code></p> <p>Trigger phrases: \"audit this skill\", \"check skill quality\", \"review the skills\", \"are our skills any good?\"</p> <p>See also: <code>/ctx-skill-create</code>, Contributing</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-skill-create","level":3,"title":"<code>/ctx-skill-create</code>","text":"<p>Create, improve, and test skills. Guides the full lifecycle: capture intent, interview for edge cases, draft the SKILL.md, test with realistic prompts, review results with the user, and iterate. Applies core principles: the agent is already smart (only add what it does not know), the description is the trigger (make it specific and \"pushy\"), and explain the why instead of rigid directives.</p> <p>Wraps: reads/writes <code>.claude/skills/</code> and <code>internal/assets/claude/skills/</code></p> <p>Trigger phrases: \"create a skill\", \"turn this into a skill\", \"make a slash command\", \"this should be a skill\", \"improve this skill\", \"the skill isn't triggering\"</p> <p>See also: Contributing</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#session-control","level":2,"title":"Session Control","text":"<p>Skills for controlling hook behavior during a session.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-pause","level":3,"title":"<code>/ctx-pause</code>","text":"<p>Pause all context nudge and reminder hooks for the current session. Security hooks still fire. Use for quick investigations or tasks that don't need ceremony overhead.</p> <p>Wraps: <code>ctx hook pause</code></p> <p>Trigger phrases: \"pause <code>ctx</code>\", \"pause context\", \"stop the nudges\", \"quiet mode\"</p> <p>See also: Pausing Context Hooks</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-resume","level":3,"title":"<code>/ctx-resume</code>","text":"<p>Resume context hooks after a pause. Restores normal nudge, reminder, and ceremony behavior. Silent no-op if not paused.</p> <p>Wraps: <code>ctx hook resume</code></p> <p>Trigger phrases: \"resume <code>ctx</code>\", \"resume context\", \"turn nudges back on\", \"unpause\"</p> <p>See also: Pausing Context Hooks</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#knowledge-base-phase-kb","level":2,"title":"Knowledge Base (Phase KB)","text":"<p>Skills for the editorial knowledge-ingestion pipeline. Active when <code>.context/kb/</code> exists (laid down by <code>ctx init</code>). The pipeline gives you evidence-tracked knowledge with confidence bands, folder-shaped topic pages, a source-coverage state machine, and per-session handovers that fold postdated closeouts.</p> <p>See the Build a Knowledge Base recipe for the full workflow. The editorial constitution lives at <code>.context/ingest/KB-RULES.md</code>.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ingest","level":3,"title":"<code>/ctx-kb-ingest</code>","text":"<p>Mode-aware editorial pass. Declares its pass-mode (<code>topic-page</code> / <code>triage</code> / <code>evidence-only</code>) up front, scans the source-coverage ledger for adjacent incomplete topics, synthesizes prose into <code>.context/kb/topics/<slug>/index.md</code>, mints <code>EV-###</code> rows in <code>evidence-index.md</code>, runs a four-invariant completion circuit breaker, and writes a closeout under <code>.context/ingest/closeouts/</code>. Refuses on empty input.</p> <p>Wraps: <code>ctx kb ingest</code>, <code>ctx kb topic new</code>, the writer packages under <code>internal/write/kb/</code>.</p> <p>Trigger phrases: \"ingest the transcripts\", \"pull this into the kb\", \"add evidence from\"</p> <p>See also: Build a Knowledge Base, Typical KB Session, <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ask","level":3,"title":"<code>/ctx-kb-ask</code>","text":"<p>Q&A grounded in the KB. Cites <code>EV-###</code> rows; refuses to web-jump. When the KB cannot answer, opens a <code>Q-###</code> row in <code>outstanding-questions.md</code> rather than inventing. Refuses on empty question.</p> <p>Wraps: <code>ctx kb ask</code>, reads <code>.context/kb/*.md</code></p> <p>Trigger phrases: \"does the kb say\", \"according to evidence\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-site-review","level":3,"title":"<code>/ctx-kb-site-review</code>","text":"<p>Mechanical structural audit. Coerces malformed Confidence-band capitalization, flags malformed closeout frontmatter, refuses judgment calls that require evidence (those go through ingest).</p> <p>Wraps: <code>ctx kb site-review</code></p> <p>Trigger phrases: \"audit the kb\", \"check kb for rot\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-ground","level":3,"title":"<code>/ctx-kb-ground</code>","text":"<p>External re-grounding pass. Reads <code>.context/ingest/grounding-sources.md</code> and refreshes each listed source. Refuses cleanly when the file is absent or empty.</p> <p>Wraps: <code>ctx kb ground</code></p> <p>Trigger phrases: \"re-ground the kb\", \"check upstream\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-kb-note","level":3,"title":"<code>/ctx-kb-note</code>","text":"<p>Lightweight capture into <code>.context/ingest/findings.md</code>. Never writes to a topic page or <code>evidence-index.md</code>. Use for parking findings the next ingest pass should absorb.</p> <p>Wraps: <code>ctx kb note \"<text>\"</code></p> <p>Trigger phrases: \"drop a note\", \"park this finding\"</p> <p>See also: <code>ctx kb</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#ctx-handover","level":3,"title":"<code>/ctx-handover</code>","text":"<p>Per-session handover artifact writer; the sub-mechanism that <code>/ctx-wrap-up</code> delegates to as its final step. Collects <code>--summary</code> (past tense) and <code>--next</code> (future tense, specific) and calls <code>ctx handover write</code>. Writes the handover to <code>.context/handovers/<TS>-<slug>.md</code> (timestamped so concurrent agent runs never overwrite). Folds postdated closeouts into a <code>## Folded closeouts</code> section and physically archives the source closeouts under <code>.context/archive/closeouts/</code> (closeouts are append-never-rewrite; archival moves bytes but does not modify them). <code>--no-fold</code> skips the fold for mid-session checkpoints.</p> <p>Mandatory tail of <code>/ctx-wrap-up</code>. Direct invocation is reserved for <code>--no-fold</code> mid-session checkpoints and recovery after an aborted session.</p> <p>Wraps: <code>ctx handover write <title> --summary X --next Y</code></p> <p>See also: <code>/ctx-wrap-up</code>, Typical KB Session, Recover an Aborted KB Session, <code>ctx handover</code> CLI</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/skills/#project-specific-skills","level":2,"title":"Project-Specific Skills","text":"<p>The <code>ctx</code> plugin ships the skills listed above. Teams can add their own project-specific skills to <code>.claude/skills/</code> in the project root: These are separate from plugin-shipped skills and are scoped to the project.</p> <p>Project-specific skills follow the same format and are invoked the same way.</p> <p>Custom skills are not covered in this reference.</p>","path":["Reference","Skills"],"tags":[]},{"location":"reference/versions/","level":1,"title":"Version History","text":"","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#version-history","level":2,"title":"Version History","text":"<p>Documentation snapshots for each release. </p> <p>Tap the corresponding view docs to view the docs as they were at that release.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#releases","level":2,"title":"Releases","text":"Version Release Date Documentation v0.8.0 2026-03-23 view docs v0.6.0 2026-02-16 view docs v0.3.0 2026-02-07 view docs v0.2.0 2026-02-01 view docs v0.1.2 2026-01-27 view docs v0.1.1 2026-01-26 view docs v0.1.0 2026-01-25 view docs","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v080-the-architecture-release","level":3,"title":"<code>v0.8.0</code>: The Architecture Release","text":"<p>MCP server for tool-agnostic AI integration. Memory bridge connecting Claude Code auto-memory to <code>.context/</code>. Complete CLI restructuring into <code>cmd/ + core/</code> taxonomy. All user-facing strings externalized to YAML. <code>fatih/color</code> removed; two direct dependencies remain.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v060-the-integration-release","level":3,"title":"<code>v0.6.0</code>: The Integration Release","text":"<p>Plugin architecture: hooks and skills converted from shell scripts to Go subcommands, shipped as a Claude Code marketplace plugin. Multi-tool hook generation for Cursor, Aider, Copilot, and Windsurf. Webhook notifications with encrypted URL storage.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v030-the-discipline-release","level":3,"title":"<code>v0.3.0</code>: The Discipline Release","text":"<p>Journal static site generation via zensical. 49-skill audit and fix pass (positive framing, phantom reference removal, scope tightening). Context consolidation skill. <code>golangci-lint</code> v2 migration.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v020-the-archaeology-release","level":3,"title":"<code>v0.2.0</code>: The Archaeology Release","text":"<p>Session journal system: <code>ctx journal import</code> converts Claude Code JSONL transcripts to browsable Markdown. Constants refactor with semantic prefixes (<code>Dir*</code>, <code>File*</code>, <code>Filename*</code>). CRLF handling for Windows compatibility.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v012","level":3,"title":"<code>v0.1.2</code>","text":"<p>Default Claude Code permissions deployed on <code>ctx init</code>. Prompting guide published as a standalone documentation page.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v011","level":3,"title":"<code>v0.1.1</code>","text":"<p>Bug fixes: hook schema key format corrected, JSON unicode escaping fixed in context file output.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#v010-initial-release","level":3,"title":"<code>v0.1.0</code>: Initial Release","text":"<p>CLI with 15 subcommands, 6 context file types (CONSTITUTION, TASKS, CONVENTIONS, ARCHITECTURE, DECISIONS, LEARNINGS), Makefile build system, and Claude Code hook integration.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#latest","level":2,"title":"Latest","text":"<p>The main documentation always reflects the latest development version.</p> <p>For the most recent stable release, see v0.8.0.</p>","path":["Reference","Version History"],"tags":[]},{"location":"reference/versions/#changelog","level":2,"title":"Changelog","text":"<p>For detailed changes between versions, see the  GitHub Releases page.</p>","path":["Reference","Version History"],"tags":[]},{"location":"security/","level":1,"title":"Security","text":"<p>Security model, agent hardening, and vulnerability reporting.</p>","path":["Security"],"tags":[]},{"location":"security/#security-design","level":3,"title":"Security Design","text":"<p>Trust model, what <code>ctx</code> does for security, permission hygiene, state file management, and the log-first audit trail principle. Read first to understand the security boundaries.</p>","path":["Security"],"tags":[]},{"location":"security/#securing-ai-agents","level":3,"title":"Securing AI Agents","text":"<p>Defense in depth for unattended AI agents: five layers of protection, each with a known bypass, strength in combination.</p>","path":["Security"],"tags":[]},{"location":"security/#reporting-vulnerabilities","level":3,"title":"Reporting Vulnerabilities","text":"<p>How to report a security issue: email, GitHub private reporting, PGP-encrypted submissions, what to include, and the response timeline.</p>","path":["Security"],"tags":[]},{"location":"security/agent-security/","level":1,"title":"Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#defense-in-depth-securing-ai-agents","level":1,"title":"Defense in Depth: Securing AI Agents","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-problem","level":2,"title":"The Problem","text":"<p>An unattended AI agent with unrestricted access to your machine is an unattended shell with unrestricted access to your machine.</p> <p>This is not a theoretical concern. AI coding agents execute shell commands, write files, make network requests, and modify project configuration. When running autonomously (overnight, in a loop, without a human watching), the attack surface is the full capability set of the operating system user account.</p> <p>The risk is not that the AI is malicious. The risk is that the AI is controllable: it follows instructions from context, and context can be poisoned.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#threat-model","level":2,"title":"Threat Model","text":"","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#how-agents-get-compromised","level":3,"title":"How Agents Get Compromised","text":"<p>AI agents follow instructions from multiple sources: system prompts, project files, conversation history, and tool outputs. An attacker who can inject content into any of these sources can redirect the agent's behavior.</p> Vector How it works Prompt injection via dependencies A malicious package includes instructions in its README, changelog, or error output. The agent reads these during installation or debugging and follows them. Prompt injection via fetched content The agent fetches a URL (documentation, API response, Stack Overflow answer) containing embedded instructions. Poisoned project files A contributor adds adversarial instructions to <code>CLAUDE.md</code>, <code>.cursorrules</code>, or <code>.context/</code> files. The agent loads these at session start. Self-modification between iterations In an autonomous loop, the agent modifies its own configuration files. The next iteration loads the modified config with no human review. Tool output injection A command's output (error messages, log lines, file contents) contains instructions the agent interprets and follows.","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#what-can-a-compromised-agent-do","level":3,"title":"What Can a Compromised Agent Do","text":"<p>Depends entirely on what permissions and access the agent has:</p> Access level Potential impact Unrestricted shell Execute any command, install software, modify system files Network access Exfiltrate source code, credentials, or context files to external servers Docker socket Escape container isolation by spawning privileged sibling containers SSH keys Pivot to other machines, push to remote repositories, access production systems Write access to own config Disable its own guardrails for the next iteration","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#the-defense-layers","level":2,"title":"The Defense Layers","text":"<p>No single layer is sufficient. Each layer catches what the others miss.</p> <pre><code>Layer 1: Soft instructions     (CONSTITUTION.md, playbook)\nLayer 2: Application controls  (permission allowlist, tool restrictions)\nLayer 3: OS-level isolation    (user accounts, filesystem, containers)\nLayer 4: Network controls      (firewall rules, airgap)\nLayer 5: Infrastructure        (VM isolation, resource limits)\n</code></pre>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-1-soft-instructions-probabilistic","level":3,"title":"Layer 1: Soft Instructions (Probabilistic)","text":"<p>Markdown files like <code>CONSTITUTION.md</code> and the Agent Playbook tell the agent what to do and what not to do. These are probabilistic: the agent usually follows them, but there is no enforcement mechanism.</p> <p>What it catches: Most common mistakes. An agent that has been told \"never delete production data\" will usually not delete production data.</p> <p>What it misses: Prompt injection. A sufficiently crafted injection can override soft instructions. Long context windows dilute attention on rules stated early. Edge cases where instructions are ambiguous.</p> <p>Verdict: Necessary but not sufficient. Good for the common case. Do not rely on it for security boundaries.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-2-application-controls-deterministic-at-runtime-mutable-across-iterations","level":3,"title":"Layer 2: Application Controls (Deterministic at Runtime, Mutable across Iterations)","text":"<p>AI tool runtimes (Claude Code, Cursor, etc.) provide permission systems: tool allowlists, command restrictions, confirmation prompts.</p> <p>For Claude Code, <code>ctx init</code> writes both an allowlist and an explicit deny list into <code>.claude/settings.local.json</code>. The golden images live in <code>internal/assets/permissions/</code>:</p> <p>Allowlist (<code>allow.txt</code>): only these tools run without confirmation:</p> <pre><code>Bash(ctx:*)\nSkill(ctx-convention-add)\nSkill(ctx-decision-add)\n... # all bundled ctx-* skills\n</code></pre> <p>Deny list (<code>deny.txt</code>): these are blocked even if the agent requests them:</p> <pre><code># Dangerous operations\nBash(sudo *)\nBash(git push *)\nBash(git push)\nBash(rm -rf /*)\nBash(rm -rf ~*)\nBash(curl *)\nBash(wget *)\nBash(chmod 777 *)\n\n# Sensitive file reads\nRead(**/.env)\nRead(**/.env.*)\nRead(**/*credentials*)\nRead(**/*secret*)\nRead(**/*.pem)\nRead(**/*.key)\n\n# Sensitive file edits\nEdit(**/.env)\nEdit(**/.env.*)\n</code></pre> <p>What it catches: The agent cannot run commands outside the allowlist, and the deny list blocks dangerous operations even if a future allowlist change were to widen access. If <code>rm</code>, <code>curl</code>, <code>sudo</code>, or <code>docker</code> are not allowed and <code>sudo</code>/<code>curl</code>/<code>wget</code> are explicitly denied, the agent cannot invoke them regardless of what any prompt says.</p> <p>What it misses: The agent can modify the allowlist itself. In an autonomous loop, if the agent writes to <code>.claude/settings.local.json</code>, and the next iteration loads the modified config, then the protection is effectively lost. The application enforces the rules, but the application  reads the rules from files the agent can write.</p> <p>Verdict: Strong first layer. Must be combined with self-modification prevention (Layer 3).</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-3-os-level-isolation-deterministic-and-unbypassable","level":3,"title":"Layer 3: OS-Level Isolation (Deterministic and Unbypassable)","text":"<p>The operating system enforces access controls that no application-level trick can override. An unprivileged user cannot read files owned by root. A process without <code>CAP_NET_RAW</code> cannot open raw sockets. These are kernel boundaries.</p> Control Purpose Dedicated user account No <code>sudo</code>, no privileged group membership (<code>docker</code>, <code>wheel</code>, <code>adm</code>). The agent cannot escalate privileges. Filesystem permissions Project directory writable; everything else read-only or inaccessible. Agent cannot reach other projects, home directories, or system config. Immutable config files <code>CLAUDE.md</code>, <code>.claude/settings.local.json</code>, and <code>.context/CONSTITUTION.md</code> owned by a different user or marked immutable (<code>chattr +i</code> on Linux). The agent cannot modify its own guardrails. <p>What it catches: Privilege escalation, self-modification, lateral movement to other projects or users.</p> <p>What it misses: Actions within the agent's legitimate scope. If the agent has write access to source code (which it needs to do its job), it can introduce vulnerabilities in the code itself.</p> <p>Verdict: Essential. This is the layer that makes the other layers trustworthy.</p> <p>OS-level isolation does not make the agent safe; it makes the other layers meaningful.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-4-network-controls","level":3,"title":"Layer 4: Network Controls","text":"<p>An agent that cannot reach the internet cannot exfiltrate data. It also cannot ingest new instructions mid-loop from external documents, API responses, or hostile content.</p> Scenario Recommended control Agent does not need the internet <code>--network=none</code> (container) or outbound firewall drop-all Agent needs to fetch dependencies Allow specific registries (npmjs.com, proxy.golang.org, pypi.org) via firewall rules. Block everything else. Agent needs API access Allow specific API endpoints only. Use an HTTP proxy with allowlisting. <p>What it catches: Data exfiltration, phone-home payloads, downloading additional tools, and instruction injection via fetched content.</p> <p>What it misses: Nothing, if the agent genuinely does not need the network. The tradeoff is that many real workloads need dependency resolution, so a full airgap requires pre-populated caches.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#layer-5-infrastructure-isolation","level":3,"title":"Layer 5: Infrastructure Isolation","text":"<p>The strongest boundary is a separate machine (or something that behaves like one).</p> <p>The moment you stop arguing about prompts and start arguing about kernels, you are finally doing security.</p> <p>Containers (Docker, Podman):</p> <pre><code>docker run --rm \\\n  --network=none \\\n  --cap-drop=ALL \\\n  --memory=4g \\\n  --cpus=2 \\\n  -v /path/to/project:/workspace \\\n  -w /workspace \\\n  your-dev-image \\\n  ./loop.sh\n</code></pre> <p>Docker Socket Is Sudo Access</p> <p>Critical: never mount the Docker socket (<code>/var/run/docker.sock</code>).</p> <p>An agent with socket access can spawn sibling containers with full host access, effectively escaping the sandbox. </p> <p>Use rootless Docker or Podman to eliminate this escalation path.</p> <p>Virtual machines: The strongest isolation. The guest kernel has no visibility into the host OS. No shared folders, no filesystem passthrough, no SSH keys to other machines.</p> <p>Resource limits: CPU, memory, and disk quotas prevent a runaway agent from consuming all resources. Use <code>ulimit</code>, cgroup limits, or container resource constraints.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#putting-it-all-together","level":2,"title":"Putting It All Together","text":"<p>A defense-in-depth setup for overnight autonomous runs:</p> Layer Implementation Stops Soft instructions <code>CONSTITUTION.md</code> with \"never delete tests\", \"always run tests before committing\" Common mistakes (probabilistic) Application allowlist <code>.claude/settings.local.json</code> with explicit tool permissions Unauthorized commands (deterministic within runtime) Immutable config <code>chattr +i</code> on <code>CLAUDE.md</code>, <code>.claude/</code>, <code>CONSTITUTION.md</code> Self-modification between iterations Unprivileged user Dedicated user, no sudo, no docker group Privilege escalation Container <code>--cap-drop=ALL --network=none</code>, rootless, no socket mount Host escape, network exfiltration Resource limits <code>--memory=4g --cpus=2</code>, disk quotas Resource exhaustion <p>Each layer is straightforward: The strength is in the combination.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#common-mistakes","level":2,"title":"Common Mistakes","text":"<p>\"I'll just use <code>--dangerously-skip-permissions</code>\": This disables Layer 2 entirely. Without Layers 3-5, you have no protection at all. Only use this flag inside a properly isolated container or VM.</p> <p>\"The agent is sandboxed in Docker\": A Docker container with the Docker socket mounted, running as root, with <code>--privileged</code>, and full network access is not sandboxed. It is a root shell with extra steps.</p> <p>\"<code>CONSTITUTION.md</code> says not to do that\": Markdown is a suggestion. It works most of the time. It is not a security boundary. Do not use it as one.</p> <p>\"I reviewed the <code>CLAUDE.md</code>, it's fine\": The agent can modify <code>CLAUDE.md</code> during iteration N. Iteration N+1 loads the modified version. Unless the file is immutable, your review is stale.</p> <p>\"The agent only has access to this one project\": Does the project directory contain <code>.env</code> files, SSH keys, API tokens, or credentials? Does it have a <code>.git/config</code> with push access to a remote? Filesystem isolation means isolating what is in the directory too.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-security-considerations","level":2,"title":"Team Security Considerations","text":"<p>When multiple developers share a <code>.context/</code> directory, security considerations extend beyond single-agent hardening.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#code-review-for-context-files","level":3,"title":"Code Review for Context Files","text":"<p>Treat <code>.context/</code> changes like code changes. Context files influence agent behavior (a modified <code>CONSTITUTION.md</code> or <code>CONVENTIONS.md</code> changes what every agent on the team will do next session). Review them in PRs with the same scrutiny you apply to production code.</p> <p>Watch for:</p> <ul> <li>Weakened constitutional rules (removed constraints, softened language)</li> <li>New decisions that contradict existing ones without acknowledging it</li> <li>Learnings that encode incorrect assumptions</li> <li>Task additions that bypass the team's prioritization process</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#gitignore-patterns","level":3,"title":"Gitignore Patterns","text":"<p><code>ctx init</code> configures <code>.gitignore</code> automatically, but verify these patterns are in place:</p> <ul> <li>Always gitignored: <code>.ctx.key</code> (encryption key),    <code>.context/logs/</code>, <code>.context/journal/</code></li> <li>Team decision: <code>scratchpad.enc</code> (encrypted, safe to commit for   shared scratchpad state); <code>.gitignore</code> if scratchpads are personal</li> <li>Never committed: <code>.env</code>, credentials, API keys (enforced by   drift secret detection)</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#multi-developer-context-sharing","level":3,"title":"Multi-Developer Context Sharing","text":"<p><code>CONSTITUTION.md</code> is the shared contract. All team members and their agents inherit it. Changes require team consensus, not unilateral edits.</p> <p>When multiple agents write to the same context files concurrently (e.g., two developers adding learnings simultaneously), git merge conflicts are expected. Resolution is typically additive: accept both additions. Destructive resolution (dropping one side) loses context.</p>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#team-conventions-for-context-management","level":3,"title":"Team Conventions for Context Management","text":"<p>Establish and document:</p> <ul> <li>Who reviews context changes: Same reviewers as code, or a   designated context owner?</li> <li>How to resolve conflicting decisions: If two sessions record   contradictory decisions, which wins? Default: the later one must   explicitly supersede the earlier one with rationale.</li> <li>Frequency of context maintenance: Weekly <code>ctx drift</code> checks,   monthly consolidation passes, archival after each milestone.</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#checklist","level":2,"title":"Checklist","text":"<p>Before running an unattended AI agent:</p> <ul> <li> Agent runs as a dedicated unprivileged user (no sudo, no docker group)</li> <li> Agent's config files are immutable or owned by a different user</li> <li> Permission allowlist restricts tools to the project's toolchain</li> <li> Container drops all capabilities (<code>--cap-drop=ALL</code>)</li> <li> Docker socket is NOT mounted</li> <li> Network is disabled or restricted to specific domains</li> <li> Resource limits are set (memory, CPU, disk)</li> <li> No SSH keys, API tokens, or credentials are accessible to the agent</li> <li> Project directory does not contain <code>.env</code> or secrets files</li> <li> Iteration cap is set (<code>--max-iterations</code>)</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/agent-security/#further-reading","level":2,"title":"Further Reading","text":"<ul> <li>Running an Unattended AI Agent: the   <code>ctx</code> recipe for autonomous loops, including step-by-step permissions   and isolation setup</li> <li>Security: <code>ctx</code>'s own trust model and vulnerability   reporting</li> <li>Autonomous Loops: full documentation of the   loop pattern, prompt templates, and troubleshooting</li> </ul>","path":["Security","Securing AI Agents"],"tags":[]},{"location":"security/design/","level":1,"title":"Security Design","text":"<p>How <code>ctx</code> thinks about security: trust boundaries, what the system does and does not do for you, the engineering principle behind the audit trail, and the permission hygiene workflow.</p> <p>For vulnerability disclosure, see Reporting Vulnerabilities.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#trust-model","level":2,"title":"Trust Model","text":"<p><code>ctx</code> operates within a single trust boundary: the local filesystem.</p> <p>The person who authors <code>.context/</code> files is the same person who runs the agent that reads them. There is no remote input, no shared state, and no server component.</p> <p>This means:</p> <ul> <li><code>ctx</code> does not sanitize context files for prompt injection. This   is a deliberate design choice, not an oversight. The files are   authored by the developer who owns the machine: sanitizing their   own instructions back to them would be counterproductive.</li> <li>If you place adversarial instructions in your own <code>.context/</code>   files, your agent will follow them. This is expected behavior.   You control the context; the agent trusts it.</li> </ul> <p>Shared Repositories</p> <p>In shared repositories, <code>.context/</code> files should be reviewed in code review (the same way you would review CI/CD config or Makefiles). A malicious contributor could add harmful instructions to <code>CONSTITUTION.md</code> or <code>TASKS.md</code>.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#what-ctx-does-for-security","level":2,"title":"What <code>ctx</code> Does for Security","text":"<p><code>ctx</code> is designed with security in mind:</p> <ul> <li>No secrets in context: The constitution explicitly forbids   storing secrets, tokens, API keys, or credentials in <code>.context/</code>   files.</li> <li>Local only: <code>ctx</code> runs entirely locally with no external   network calls.</li> <li>No code execution: <code>ctx</code> reads and writes Markdown files only;   it does not execute arbitrary code.</li> <li>Git-tracked: Core context files are meant to be committed, so   they should never contain sensitive data. Exception: <code>sessions/</code>   and <code>journal/</code> contain raw conversation data and should be   gitignored.</li> </ul>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#permission-hygiene","level":2,"title":"Permission Hygiene","text":"<p>Claude Code evaluates permissions in deny → ask → allow order. <code>ctx init</code> automatically populates <code>permissions.deny</code> with rules that block dangerous operations before the allow list is ever consulted.</p> <p>Default deny rules block:</p> <ul> <li><code>sudo</code>, <code>git push</code>, <code>rm -rf /</code>, <code>rm -rf ~</code>, <code>curl</code>, <code>wget</code>,   <code>chmod 777</code></li> <li><code>Read</code> / <code>Edit</code> of <code>.env</code>, credentials, secrets, <code>.pem</code>, <code>.key</code>   files</li> </ul> <p>Even with deny rules in place, the allow list accumulates one-off permissions over time. Periodically review for:</p> <ul> <li>Destructive commands: <code>git reset --hard</code>, <code>git clean -f</code>, etc.</li> <li>Config injection vectors: permissions that allow modifying   files controlling agent behavior (<code>CLAUDE.md</code>,   <code>settings.local.json</code>).</li> <li>Broad wildcards: overly permissive patterns that pre-approve   more than intended.</li> </ul> <p>For the full hygiene workflow, see the Claude Code Permission Hygiene recipe.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#state-file-management","level":2,"title":"State File Management","text":"<p>Hook state files (throttle markers, prompt counters, pause markers) are stored in <code>.context/state/</code>, which is project-scoped and gitignored. State files are automatically managed by the hooks that create them; no manual cleanup is needed.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#log-first-audit-trail","level":2,"title":"Log-First Audit Trail","text":"<p>The event log (<code>.context/state/events.jsonl</code>) is the authoritative record of what <code>ctx</code> hooks did during a session. Several audit-adjacent features depend on that log being trustworthy, not merely best-effort:</p> <ul> <li><code>ctx event</code> / <code>ctx system view-events</code> replays session history   from the log.</li> <li>Webhook notifications give operators a real-time signal that   assumes every notification corresponds to a logged event.</li> <li>Drift, freshness, and map-staleness checks count events over   time and surface regressions.</li> </ul> <p>A log that silently drops entries while the rest of the system claims success is worse than no log at all: operators see a green TUI and a webhook notification and conclude \"it happened,\" even when the audit trail never landed. The codebase treats this as a correctness problem, not a UX polish problem.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#the-rule","level":3,"title":"The Rule","text":"<p>Any code path that emits an observable side effect (webhook, stdout marker, throttle-file touch, state mutation) must append the corresponding event-log entry first and gate the side effect on the append succeeding. If the log write fails, the side effect must not fire.</p> <p>In code, this shape:</p> <pre><code>if appendErr := event.Append(channel, msg, sessionID, ref); appendErr != nil {\n    return appendErr // do NOT send the webhook or touch the marker\n}\nif sendErr := notify.Send(channel, msg, sessionID, ref); sendErr != nil {\n    return sendErr\n}\n// downstream side effects (marker touch, stdout, etc.)\n</code></pre> <p>The <code>nudge.Relay</code> helper in <code>internal/cli/system/core/nudge</code> enforces this for the common \"log + webhook\" pair. Hook <code>Run</code> functions that compose their own sequence (<code>sessionevent</code>, <code>heartbeat</code>, several <code>check_*</code> hooks) follow the same ordering explicitly.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#known-gaps","level":3,"title":"Known Gaps","text":"<ul> <li>Nudge webhooks have no log channel. <code>nudge.EmitAndRelay</code>   sends a \"nudge\" notification before the \"relay\" event is logged.   The nudge leg is fire-and-forget because no event-log channel   records nudges today. A future refactor may add one; until then   this is the one documented exception.</li> <li><code>ctx agent --cooldown</code> and <code>ctx doctor</code> propagate rather than   gate. They surface real errors to the caller (usually Cobra)   rather than deciding what to do with them locally. Editors that   invoke these commands may display errors in an ugly way; the   ugliness is the correct signal (something persisted is broken),   not a defect to smooth over.</li> <li>Verbose hook logs in <code>core/log.Message</code> stay best-effort.   That logger captures per-hook activity (how many prompts, which   percent, etc.) for debugging; it is NOT the event audit trail.   Its failures go to stderr via <code>log/warn.Warn</code> rather than   propagating, because losing an operational log line is not a   correctness problem.</li> </ul>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#background","level":3,"title":"Background","text":"<p>The <code>error</code> returns on <code>event.Append</code>, <code>io.AppendBytes</code>, <code>nudge.Relay</code>, and <code>cooldown.Active</code> / <code>cooldown.TouchTombstone</code> were introduced as part of the resolver-tightening refactor. Before that change, most hook paths called these helpers and silently discarded their errors. The principle above was extracted from the observation that every user-visible correctness problem hit during the refactor traced back to some function saying \"this succeeded\" when the underlying write never landed.</p>","path":["Security","Security Design"],"tags":[]},{"location":"security/design/#best-practices","level":2,"title":"Best Practices","text":"<ol> <li>Review before committing: Always review <code>.context/</code> files    before committing.</li> <li>Use <code>.gitignore</code>: If you must store sensitive notes locally,    add them to <code>.gitignore</code>.</li> <li>Drift detection: Run <code>ctx drift</code> to check for potential    issues.</li> <li>Permission audit: Review <code>.claude/settings.local.json</code> after    busy sessions.</li> </ol>","path":["Security","Security Design"],"tags":[]},{"location":"security/hub/","level":1,"title":"Hub Security Model","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#ctx-hub-security-model","level":1,"title":"<code>ctx</code> Hub: Security Model","text":"<p>What the hub defends against, what it does not defend against, and the concrete mechanisms in play.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#threat-model","level":2,"title":"Threat Model","text":"<p>The hub is designed for trusted cross-project knowledge sharing within a team or homelab. It assumes:</p> <ul> <li>The hub host is trusted. Anyone with root on that box can read   every entry ever published.</li> <li>Network is semi-trusted. Hub traffic is gRPC over TCP; TLS is   strongly recommended but not mandatory.</li> <li>Client machines are trusted enough to hold a per-project client   token. Losing a client token is roughly equivalent to losing an   API key: scoped damage, not total compromise.</li> <li>Entry content is not secret. Decisions, learnings, and   conventions may be indexed by AI agents, rendered in docs,   shared across projects. Do not push credentials or PII into   the hub.</li> </ul> <p>The hub is not a secure messaging system, a secrets store, or a compliance-grade audit log. If your threat model needs those, use a dedicated tool and keep the hub for knowledge sharing.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#mechanisms","level":2,"title":"Mechanisms","text":"","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#bearer-tokens","level":3,"title":"Bearer Tokens","text":"<p>All RPCs except <code>Register</code> require a bearer token in gRPC metadata. Two kinds of tokens exist:</p> Kind Format Scope Lifetime Admin token <code>ctx_adm_...</code> Register new projects Manual rotate Client token <code>ctx_cli_...</code> Publish, Sync, Listen, Status Project lifetime <p>Tokens are compared in constant time (<code>crypto/subtle</code>) to prevent timing oracles, and looked up via an <code>O(1)</code> hash map so the comparison cost does not depend on the total number of registered clients.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#client-side-encryption-at-rest","level":3,"title":"Client-Side Encryption at Rest","text":"<p><code>.context/.connect.enc</code> stores the client token and hub address, encrypted with AES-256-GCM using the same scheme the notification subsystem uses. The key is derived from <code>ctx</code>'s local keyring (see <code>internal/crypto</code>).</p> <p>An attacker with read access to the project directory cannot learn the client token without also breaking <code>ctx</code>'s local keyring.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#hub-side-token-storage","level":3,"title":"Hub-Side Token Storage","text":"<p>Tokens Are Stored in Plaintext on the Hub Host</p> <p><code><data-dir>/clients.json</code> currently stores client tokens verbatim, not hashed. Anyone with read access to the hub's data directory sees every registered client's token and can impersonate any project that has ever registered.</p> <p>Mitigations today:</p> <ul> <li>Run the hub as an unprivileged user and lock the data   directory with <code>chmod 700 <data-dir></code>.</li> <li>Use the systemd unit in   Operations,   which enables <code>ProtectSystem=strict</code>,   <code>NoNewPrivileges=true</code>, and a dedicated user.</li> <li>Never expose <code><data-dir></code> over NFS, SMB, or shared   filesystems.</li> <li>Treat <code><data-dir></code> the same way you'd treat   <code>/etc/shadow</code>: back it up encrypted, never check it   into version control.</li> </ul> <p>Hashing <code>clients.json</code> and moving to keyring-backed storage is tracked as a follow-up in the PR #60 task group. Until that lands, assume a hub host compromise equals total hub compromise.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#input-validation","level":3,"title":"Input Validation","text":"<p>Every published entry is validated before it touches the log:</p> <ul> <li>Type must be one of: <code>decision</code>, <code>learning</code>, <code>convention</code>,   <code>task</code>. Unknown types are rejected.</li> <li>ID and Origin are required and non-empty.</li> <li>Content size is capped at 1 MB. Reasonable for text,   hostile for attempts to fill the disk.</li> <li>Duplicate project registration is rejected; a client that   replays an old <code>Register</code> call gets an error, not a second   token.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#no-script-execution","level":3,"title":"No Script Execution","text":"<p>The hub never interprets entry content. There is no expression language, no template evaluation, no Markdown rendering at ingest. Content is stored as bytes and fanned out to clients verbatim.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#audit-trail","level":3,"title":"Audit Trail","text":"<p><code>entries.jsonl</code> is append-only. Every accepted publish is recorded with the publishing project's origin tag and sequence number. Nothing is ever deleted by the hub; retention is managed manually by the operator (see log rotation).</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#what-the-hub-does-not-defend-against","level":2,"title":"What the Hub Does Not Defend Against","text":"<ul> <li>Untrusted entry senders. A client with a valid token can   publish anything (within the 1 MB cap). There is no content   validation beyond shape.</li> <li>Denial of service from a registered client. A misbehaving   client can publish until disk is full. Monitor   <code>entries.jsonl</code> growth.</li> <li>Network eavesdropping without TLS. Plain gRPC leaks entry   content and tokens. Use a TLS-terminating reverse proxy   (see Multi-machine recipe).</li> <li>Host compromise. Root on the hub host = access to every   entry and every token. Harden the host.</li> <li>Accidental secret upload. The hub will happily fan out a   decision containing an API key. Sanitize content before   publishing.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#operational-hardening-checklist","level":2,"title":"Operational Hardening Checklist","text":"<ul> <li> Run the hub as an unprivileged user with       <code>NoNewPrivileges=true</code> and <code>ProtectSystem=strict</code> (see       the systemd unit in       Operations).</li> <li> Terminate TLS in front of the hub for anything beyond       a trusted LAN.</li> <li> Restrict the listen port with firewall rules to the       client subnet only.</li> <li> Back up <code><data-dir>/admin.token</code> to a secrets manager; do       not leave it in shell history.</li> <li> Rotate the admin token when a team member with access       leaves. Client tokens keep working across rotations.</li> <li> Monitor <code>entries.jsonl</code> growth; alert on sudden spikes.</li> <li> Run NTP on all clients to prevent entry-timestamp skew.</li> <li> Do not publish from machines you do not trust.</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#responsible-disclosure","level":2,"title":"Responsible Disclosure","text":"<p>Security issues in the hub follow the same process as the rest of <code>ctx</code>; see Reporting.</p>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/hub/#see-also","level":2,"title":"See Also","text":"<ul> <li><code>ctx</code> Hub Operations</li> <li><code>ctx</code> Hub failure modes</li> <li>HA cluster recipe</li> </ul>","path":["Security","Hub Security Model"],"tags":[]},{"location":"security/reporting/","level":1,"title":"Reporting Vulnerabilities","text":"<p>Disclosure process for security issues in <code>ctx</code>. For the broader security model (trust boundaries, audit trail, permission hygiene), see Security Design.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#reporting-vulnerabilities","level":2,"title":"Reporting Vulnerabilities","text":"<p>At <code>ctx</code> we take security very seriously.</p> <p>If you discover a security vulnerability in <code>ctx</code>, please report it responsibly.</p> <p>Do NOT open a public issue for security vulnerabilities.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#email","level":3,"title":"Email","text":"<p>Send details to security@ctx.ist.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#github-private-reporting","level":3,"title":"GitHub Private Reporting","text":"<ol> <li>Go to the Security tab;</li> <li>Click \"Report a Vulnerability\";</li> <li>Provide a detailed description.</li> </ol>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#encrypted-reports-optional","level":3,"title":"Encrypted Reports (Optional)","text":"<p>If your report contains sensitive details (proof-of-concept exploits, credentials, or internal system information), you can encrypt your message with our PGP key:</p> <ul> <li>In-repo: <code>SECURITY_KEY.asc</code></li> <li>Keybase: keybase.io/alekhinejose</li> </ul> <pre><code># Import the key\ngpg --import SECURITY_KEY.asc\n\n# Encrypt your report\ngpg --armor --encrypt --recipient security@ctx.ist report.txt\n</code></pre> <p>Encryption is optional. Unencrypted reports to security@ctx.ist or via GitHub Private Reporting are perfectly fine.</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#what-to-include","level":3,"title":"What to Include","text":"<ul> <li>Description of the vulnerability,</li> <li>Steps to reproduce,</li> <li>Potential impact,</li> <li>Suggested fix (if any).</li> </ul>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#attribution","level":2,"title":"Attribution","text":"<p>We appreciate responsible disclosure and will acknowledge security researchers who report valid vulnerabilities (unless they prefer to remain anonymous).</p>","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"security/reporting/#response-timeline","level":2,"title":"Response Timeline","text":"<p>Open Source, Best-Effort Timelines</p> <p><code>ctx</code> is a volunteer-maintained open source project.</p> <p>The timelines below are guidelines, not guarantees, and depend on contributor availability.</p> <p>We will address security reports on a best-effort basis and prioritize them by severity.</p> Stage Timeframe Acknowledgment Within 48 hours Initial assessment Within 7 days Resolution target Within 30 days (depending on severity)","path":["Security","Reporting Vulnerabilities"],"tags":[]},{"location":"thesis/","level":1,"title":"Context as State","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#a-persistence-layer-for-human-ai-cognition","level":2,"title":"A Persistence Layer for Human-AI Cognition","text":"<p>Volkan Özçelik - me@volkan.io</p> <p>February 2026</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#abstract","level":3,"title":"Abstract","text":"<p>As AI tools evolve from code-completion utilities into reasoning collaborators,  the knowledge that governs their behavior becomes as important as the code they  produce; yet, that knowledge is routinely discarded at the end of every session.</p> <p>AI-assisted development systems assemble context at prompt time using heuristic  retrieval from mutable sources: recent files, semantic search results,  session history. These approaches optimize relevance at the moment of generation  but do not persist the cognitive state that produced decisions. Reasoning is  not reproducible, intent is lost across sessions, and teams cannot audit the  knowledge that constrains automated behavior.</p> <p>This paper argues that context should be treated as deterministic, version-controlled state rather than as a transient query result. We ground this  argument in three sources of evidence: a landscape analysis of 17 systems  spanning AI coding assistants, agent frameworks, and knowledge stores;  a taxonomy of five primitive categories that reveals irrecoverable architectural  trade-offs; and an experience report from <code>ctx</code>,  a persistence layer for AI-assisted development, which developed itself using its  own persistence model across 389 sessions over 33 days. We define a three-tier  model for cognitive state: authoritative knowledge, delivery views, and  ephemeral state. Then we present six design invariants empirically validated  by 56 independent rejection decisions observed across the analyzed landscape.  We show that context determinism applies to assembly, not to model output,  and that the curation cost this model requires is offset by compounding returns  in reproducibility, auditability, and team cognition.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#1-introduction","level":2,"title":"1. Introduction","text":"<p>The introduction of large language models into software development has shifted  the primary interface from code execution to interactive reasoning. In this  environment, the correctness of an output depends not only on source code but  on the context supplied to the model: the conventions, decisions, architectural  constraints, and domain knowledge that bound the space of acceptable responses.</p> <p>Current systems treat context as a query result assembled at the moment of  interaction. A developer begins a session; the tool retrieves what it estimates  to be relevant from chat history, recent files, and vector stores; the model  generates output conditioned on this transient assembly; the session ends, and  the context evaporates. The next session begins the cycle again.</p> <p>This model has improved substantially over the past year. <code>CLAUDE.md</code> files,  Cursor rules, Copilot's memory system, and tools such as Mem0, Letta, and Kindex  each address aspects of the persistence problem. Yet across 17 systems we  analyzed spanning AI coding assistants, agent frameworks, autonomous coding  agents, and purpose-built knowledge stores, no system provides all five of the  following properties simultaneously: deterministic context assembly,  human-readable file-based persistence, token-budgeted delivery,  a single-binary core with zero required runtime dependencies for the  persistence path, and local-first operation.</p> <p>This paper does not propose a universal replacement for retrieval-centric  workflows. It defines a persistence layer (embodied in <code>ctx</code> (https://ctx.ist))  whose advantages emerge under specific operational conditions: when  reproducibility is a requirement, when knowledge must outlive sessions and  individuals, when teams require shared cognitive authority, or when offline  operation is necessary. </p> <p>The trade-offs (manual curation cost, reduced automatic recall, coarser  granularity) are intentional and mirror the trade-offs accepted by systems that  favor reproducibility over convenience, such as reproducible builds and  immutable infrastructure <sup>1</sup> <sup>6</sup>.</p> <p>The contribution is threefold: a three-tier model for cognitive state that  resolves the ambiguity between authoritative knowledge and ephemeral session  artifacts; six design invariants empirically grounded in a cross-system  landscape analysis; and an experience report demonstrating that the model  produces compounding returns when applied to its own development.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#2-the-limits-of-prompt-time-context","level":2,"title":"2. The Limits of Prompt-Time Context","text":"<p>Prompt-time assembly pipelines typically consist of corpus selection, retrieval, ranking, and truncation. These pipelines are probabilistic and time-dependent, producing three failure modes that compound over the lifetime of a project.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#21-non-reproducibility","level":3,"title":"2.1 Non-Reproducibility","text":"<p>If context is derived from mutable sources using heuristic ranking, identical requests at different times receive different inputs. A developer who asks  \"What is our authentication strategy?\" on Tuesday may receive a different  context window than the same question on Thursday: Not because the strategy  changed, but because the retrieval heuristic surfaced different fragments.</p> <p>Reproducibility (the ability to reconstruct the exact inputs that produced a  given output) is a foundational property of reliable systems. Its loss in  AI-assisted development mirrors the historical evolution from ad-hoc builds to  deterministic build systems <sup>1</sup> <sup>2</sup>. The build community learned that when  outputs depend on implicit state (environment variables, system clocks,  network-fetched dependencies), debugging becomes archaeology. The same principle  applies when AI outputs depend on non-deterministic context retrieval.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#22-opaque-knowledge","level":3,"title":"2.2 Opaque Knowledge","text":"<p>Embedding-based memory increases recall but reduces inspectability. When a  vector store determines that a code snippet is \"similar\" to the current query,  the ranking function is opaque: the developer cannot inspect why that snippet  was chosen, whether a more relevant artifact was excluded, or whether the  ranking will remain stable. This prevents deterministic debugging, policy  auditing, and causal attribution (properties that information retrieval theory  identifies as fundamental trade-offs of probabilistic ranking) <sup>3</sup>.</p> <p>In practice, this opacity manifests as a compliance ceiling. In our experience  developing a context management system (detailed in Section 7), soft instructions  (directives that ask an AI agent to read specific files or follow specific  procedures) achieve approximately 75-85% compliance. The remaining 15-25%  represents cases where the agent exercises judgment about whether the  instruction applies, effectively applying a second ranking function on top of  the explicit directive. When 100% compliance is required, instruction is  insufficient; the content must be injected directly, removing the agent's option to skip it.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#23-loss-of-intent","level":3,"title":"2.3 Loss of Intent","text":"<p>Session transcripts record interaction but not cognition. A transcript captures what was said but not which assumptions were accepted, which alternatives were  rejected, or which constraints governed the decision. The distinction matters:  a decision to use PostgreSQL recorded as a one-line note (\"Use PostgreSQL\")  teaches a model what was decided; a structured record with context, rationale,  and consequences teaches it why (and why is what prevents the model from  unknowingly reversing the decision in a future session) <sup>4</sup>.</p> <p>Session transcripts provide history. Cognitive state requires something more:  the persistent, structured representation of the knowledge required for correct decision-making.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#3-cognitive-state-a-three-tier-model","level":2,"title":"3. Cognitive State: A Three-Tier Model","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#31-definitions","level":3,"title":"3.1 Definitions","text":"<p>We define cognitive state as the authoritative, persistent representation of the knowledge required for correct decision-making within a project. It is human-authored or human-ratified, versioned, inspectable, and reproducible. It  is distinct from logs, transcripts, retrieval results, and model-generated  summaries.</p> <p>Previous formulations of this idea have treated cognitive state as a monolithic  concept. In practice, a three-tier model better captures the operational reality:</p> <p>Tier 1: Authoritative State: The canonical knowledge that the system treats  as ground truth. In a concrete implementation, this corresponds to a set of  human-curated files with defined schemas: a constitution (inviolable rules),  conventions (code patterns), an architecture document (system structure),  decision records (choices with rationale), learnings (captured experience), a  task list (current work), a glossary (domain terminology), and an agent  playbook (operating instructions). Each file has a single purpose, a defined  lifecycle, and a distinct update frequency. Authoritative state is  version-controlled alongside code and reviewed through the same mechanisms  (diffs, pull requests, blame annotations).</p> <p>Tier 2: Delivery Views: Derived representations of authoritative state,  assembled for consumption by a model. A delivery view is produced by a  deterministic assembly function that takes the authoritative state, a token  budget, and an inclusion policy as inputs and produces a context window as  output. The same authoritative state, budget, and policy must always produce the  same delivery view. Delivery views are ephemeral (they exist only for the  duration of a session), but their construction is reproducible.</p> <p>Tier 3: Ephemeral State: Session transcripts, scratchpad notes, draft  journal entries, and other artifacts that exist during or immediately after a  session but are not authoritative. Ephemeral state is the raw material from  which authoritative state may be extracted through human review, but it is  never consumed directly by the assembly function.</p> <p>This three-tier model resolves confusion present in earlier formulations: the claim that AI output is a deterministic function of the repository state.  The corrected claim is that context selection is deterministic (the delivery  view is a function of authoritative state), but model output remains  stochastic, conditioned on the deterministic context. Formally:</p> <pre><code>delivery_view = assemble(authoritative_state, budget, policy)\noutput = model(delivery_view)   # stochastic\n</code></pre> <p>The persistence layer's contribution is making <code>assemble</code> reproducible, not making <code>model</code> deterministic.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#32-separation-of-concerns","level":3,"title":"3.2 Separation of Concerns","text":"<p>The decision to separate authoritative state into distinct files with distinct purposes is not cosmetic. Different types of knowledge have different lifecycles:</p> Knowledge Type Update Frequency Read Frequency Load Priority Example Constitution Rarely Every session Always \"Never commit secrets to git\" Tasks Every session Session start Always \"Implement token budget CLI flag\" Conventions Weekly Before coding High \"All errors use structured logging with severity levels\" Decisions When decided When questioning Medium \"Use PostgreSQL over MySQL (see ADR-003)\" Learnings When learned When stuck Medium \"Hook scripts >50ms degrade interactive UX\" Architecture When changed When designing On demand \"Three-layer pipeline: ingest → enrich → assemble\" Journal Every session Rarely Never auto \"Session 247: Removed dead-end session copy layer\" <p>A monolithic context file would force the assembly function to load everything  or nothing. Separation enables progressive disclosure: the minimum context  that matters for the current moment, with the option to load more when needed.  A normal session loads the constitution, tasks, and conventions; a deep  investigation loads decision history and journal entries from specific dates.</p> <p>The budget mechanism is the constraint that makes separation valuable. Without a  budget, the default behavior is to load everything, which destroys the attention  density that makes loaded context useful. With a budget, the assembly function  must prioritize ruthlessly: constitution first (always full), then tasks and  conventions (budget-capped), then decisions and learnings (scored by recency).  Entries that do not fit receive title-only summaries rather than being silently  dropped (an application of the \"tell me what you don't know\" pattern identified  independently by four systems in our landscape analysis).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#4-design-invariants","level":2,"title":"4. Design Invariants","text":"<p>The following six invariants define the constraints that a cognitive state  persistence layer must satisfy. They are not axioms chosen a priori; they are  empirically grounded properties whose violation was independently identified as  producing complexity costs across the 17 systems we analyzed.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-1-markdown-on-filesystem-persistence","level":3,"title":"Invariant 1: Markdown-on-Filesystem Persistence","text":"<p>Context files must be human-readable, git-diffable, and editable with any text editor. No database. No binary storage.</p> <p>Validation: 11 independent rejection decisions across the analyzed landscape  protected this property. Systems that adopted embedded records, binary  serialization, or knowledge graphs as their core primitive consistently traded  away the ability for a developer to run <code>cat DECISIONS.md</code> and understand the  system's knowledge. The inspection cost of opaque storage compounds over the  lifetime of a project: every debugging session, every audit, every onboarding  conversation requires specialized tooling to access knowledge that could have  been a text file.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-2-zero-runtime-dependencies","level":3,"title":"Invariant 2: Zero Runtime Dependencies","text":"<p>The tool must work with no installed runtimes, no running services, and no API keys for core functionality.</p> <p>Validation: 13 independent rejection decisions protected this property (the most frequently defended invariant). Systems that required databases  (PostgreSQL, SQLite, Redis), embedding models, server daemons, container  runtimes, or cloud APIs for core operation introduced failure modes proportional  to their dependency count. A persistence layer that depends on infrastructure is  not a persistence layer; it is a service. Services have uptime requirements,  version compatibility matrices, and operational costs that simple file  operations do not.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-3-deterministic-context-assembly","level":3,"title":"Invariant 3: Deterministic Context Assembly","text":"<p>The same files plus the same budget must produce the same output. No  embedding-based retrieval, no LLM-driven selection, no wall-clock-dependent  scoring in the assembly path.</p> <p>Validation: 6 independent rejection decisions protected this property.  Non-deterministic assembly (whether from embedding variance, LLM-based selection,  or time-dependent scoring) destroys the ability to reproduce a context window  and therefore to diagnose why a model produced a given output. Determinism in  the assembly path is what makes the persistence layer auditable.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-4-human-authority-over-persistent-state","level":3,"title":"Invariant 4: Human Authority over Persistent State","text":"<p>The agent may propose changes to context files but must not unilaterally modify  them. All persistent changes go through human-reviewable git commits.</p> <p>Validation: 6 independent rejection decisions protected this property. Systems  that allowed agents to self-modify their memory (writing freeform notes,  auto-pruning old entries, generating summaries as ground truth) consistently  produced lower-quality persistent context than systems that enforced human  review. Structure is a feature, not a limitation: across the landscape, the  pattern \"structured beats freeform\" was independently discovered by four  systems that evolved from freeform LLM summaries to typed schemas with required  fields.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-5-local-first-air-gap-capable","level":3,"title":"Invariant 5: Local-First, Air-Gap Capable","text":"<p>Core functionality must work offline with no network access. Cloud services may be used for optional features but never for core context management.</p> <p>Validation: 7 independent rejection decisions protected this property.  Infrastructure-dependent memory systems cannot operate in classified  environments, isolated networks, or constrained-environment scenarios. A  filesystem-native model continues to function under all conditions where the  repository is accessible.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#invariant-6-no-default-telemetry","level":3,"title":"Invariant 6: No Default Telemetry","text":"<p>Any analytics, if ever added, must be strictly opt-in.</p> <p>Validation: 4 independent rejection decisions protected this property. Default  telemetry erodes the trust model that a persistence layer depends on. If  developers must trust the system with their architectural decisions, operational  learnings, and project constraints, the system cannot simultaneously be reporting  usage data to external services.</p> <p>These six invariants collectively define a design space. Each feature proposal  can be evaluated against them: a feature that violates any invariant is rejected  regardless of how many other systems implement it. The discipline of constraint  (refusing to add capabilities that compromise foundational properties) is  itself an architectural contribution. Across the 17 analyzed systems, 56 patterns were explicitly rejected for violating these invariants. The rejection  count per invariant (11, 13, 6, 6, 7, 4) provides a rough measure of each  property's vulnerability to architectural erosion. A representative sample of  these rejections is provided in Appendix A.<sup>1</sup></p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#5-landscape-analysis","level":2,"title":"5. Landscape Analysis","text":"<p>The 17 systems were selected to cover the architectural design space rather than  to achieve completeness. Each included system satisfies three criteria: it  represents a distinct architectural primitive for AI-assisted development, it is  actively maintained or widely referenced, and it provides sufficient public  documentation or source code for architectural inspection. The goal was to  ensure that every major category of primitive (document, embedded record, state  snapshot, event/message, construction/derivation) was represented by multiple  systems, enabling cross-system pattern detection.</p> <p>The resulting set spans six categories: AI coding assistants (Continue,  Sourcegraph/Cody, Aider, Claude Code), AI agent frameworks (CrewAI, AutoGen,  LangGraph, LlamaIndex, Letta/MemGPT), autonomous coding agents (OpenHands,  Sweep), session provenance tools (Entire), data versioning systems (Dolt,  Pachyderm), pipeline/build systems (Dagger), and purpose-built knowledge  stores (QubicDB, Kindex). Each system was analyzed from its source code and  documentation, producing 34 individual analysis artifacts (an architectural  profile and a set of insights per system) that yielded 87 adopt/adapt  recommendations, 56 explicit rejection decisions, and 52 watch items.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#51-primitive-taxonomy","level":3,"title":"5.1 Primitive Taxonomy","text":"<p>Every system in the AI-assisted development landscape operates on a core  primitive: an atomic unit around which the entire architecture revolves. Our  analysis of 17 systems reveals five categories of primitives, each making  irrecoverable trade-offs:</p> <p>Group A: Document/File Primitives: Human-readable documents as the primary  unit. Documents are authored by humans, version-controlled in git, and consumed  by AI tools. The invariant of this group is that the primitive is always  human-readable and version-controllable with standard tools. Three systems  participate in this pattern: the system described in this paper as a pure  expression, and Continue (via its rules directory) and Claude Code  (via <code>CLAUDE.md</code> files) as partial participants: both use document-based  context as an input but organize around different core primitives.</p> <p>Group B: Embedded Record Primitives: Vector-embedded records stored with  numerical embeddings for similarity search, metadata for filtering, and scoring  mechanisms for ranking. Five systems use this approach  (LlamaIndex, CrewAI, Letta/MemGPT, QubicDB, Kindex). The invariant is that the  primitive requires an embedding model or vector database for core operations:  a dependency that precludes offline and air-gapped use.</p> <p>Group C: State Snapshot Primitives: Point-in-time captures of the complete  system state. The invariant is that any past state can be reconstructed at any  historical point. Three systems use this approach (LangGraph, Entire, Dolt).</p> <p>Group D: Event/Message Primitives: Sequential events or messages forming an  append-only log with causal relationships. Four systems use this approach  (OpenHands, AutoGen, Claude Code, Sweep). The invariant is temporal ordering  and append-only semantics.</p> <p>Group E: Construction/Derivation Primitives: Derived or constructed values  that encode how they were produced. The invariant is that the primitive is a  function of its inputs; re-executing the same inputs produces the same  primitive. Three systems use this approach (Dagger, Pachyderm, Aider).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#52-comparison-matrix","level":3,"title":"5.2 Comparison Matrix","text":"<p>The five primitive categories differ along seven dimensions:</p> Property Document Embedded Record State Snapshot Event/Message Construction Human-readable Yes No Varies Partially No Version-controllable Yes No Varies Yes Yes Queryable by meaning No Yes No No No Rewindable Via git No Yes Yes (replay) Yes Deterministic Yes No Yes Yes Yes Zero-dependency Yes No Varies Varies Varies Offline-capable Yes No Varies Varies Yes <p>The document primitive is the only one that simultaneously satisfies  human-readability, version-controllability, determinism, zero dependencies, and  offline capability. This is not because documents are superior in general (embedded records provide semantic queryability that documents lack) but because  the combination of all five properties is what the persistence layer requires.  The choice between primitive categories is not a matter of capability but of  which properties are considered invariant.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#53-convergent-patterns","level":3,"title":"5.3 Convergent Patterns","text":"<p>Across the 17 analyzed systems, six design patterns were independently  discovered. These convergent patterns carry extra validation weight because  they emerged from different problem spaces:</p> <p>Pattern 1: \"Tell me what you don't know\": When context is incomplete,  explicitly communicate to the model what information is missing and what  confidence level the provided context represents. Four systems independently  converged on this pattern: inserting skip markers, tracking evidence gaps,  annotating provenance, or naming output quality tiers.</p> <p>Pattern 2: \"Freshness matters\": Information relevance decreases over time.  Three systems independently chose exponential decay with different half-lives  (30 days, 90 days, and LRU ordering). Static priority ordering with no time  dimension leaves relevant recent knowledge at the same priority as stale  entries. This pattern is in productive tension with the persistence model's  emphasis on determinism: the claim is not that time-dependence is irrelevant,  but that it belongs in the curation step (a human deciding to consolidate or  archive stale entries) rather than in the assembly function (an algorithm  silently down-ranking entries based on age).</p> <p>Pattern 3: \"Content-address everything\": Compute a hash of content at  creation time for deduplication, cache invalidation, integrity verification,  and change detection. Five systems independently implement content hashing,  each discovering it solves different problems <sup>5</sup>.</p> <p>Pattern 4: \"Structured beats freeform\": When capturing knowledge or session  state, a structured schema with required fields produces more useful data than  freeform text. Four systems evolved from freeform summaries to typed schemas: one moving from LLM-generated prose to a structured condenser with explicit  fields for completed tasks, pending tasks, and files modified.</p> <p>Pattern 5: \"Protocol convergence\": The Model Context Protocol (MCP) is  emerging as a standard tool integration layer. Nine of 17 systems support it,  spanning every category in the analysis. MCP's significance for the persistence  model is that it provides a transport mechanism for context delivery without  dictating how context is stored or assembled. This makes the approach compatible  with both retrieval-centric and persistence-centric architectures.</p> <p>Pattern 6: \"Human-in-the-loop for memory\": Critical memory decisions should  involve human judgment. Fully automated memory management produces lower-quality  persistent context than human-reviewed systems. Four systems independently  converged on variants of this pattern: ceremony-based consolidation,  interrupt/resume for human input, confirmation mode for high-risk actions, and  separated \"think fast\" vs. \"think slow\" processing paths.</p> <p>Pattern 6 directly validates the ceremony model described in this paper. The  persistence layer requires human curation not because automation is impossible,  but because the quality of persistent knowledge degrades when the curation step  is removed. The improvement opportunity is to make curation easier, not to  automate it away.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#6-worked-example-architectural-decision-under-two-models","level":2,"title":"6. Worked Example: Architectural Decision under Two Models","text":"<p>We now instantiate the three-tier model in a concrete system  (<code>ctx</code>) and  illustrate the difference between prompt-time retrieval and cognitive state  persistence using a real scenario from its development.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#61-the-problem","level":3,"title":"6.1 The Problem","text":"<p>During development, the system accumulated three overlapping storage layers for  session data: raw transcripts (owned by the AI tool), session copies  (JSONL copies plus context snapshots), and enriched journal entries  (Markdown summaries). The middle layer (session copies) was a dead-end write  sink. An auto-save hook copied transcripts to a directory that nothing read  from, because the journal pipeline already read directly from the raw  transcripts. Approximately 15 source files, a shell hook, 20 configuration  constants, and 30 documentation references supported infrastructure with  no consumers.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#62-prompt-time-retrieval-model","level":3,"title":"6.2 Prompt-Time Retrieval Model","text":"<p>In a retrieval-based system, the decision to remove the middle layer depends on  whether the retrieval function surfaces the relevant context:</p> <p>The developer asks: \"Should we simplify the session storage?\" The retrieval  system must find and rank the original discussion thread where the three layers  were designed, the usage statistics showing zero reads from the middle layer,  the journal pipeline documentation showing it reads from raw transcripts  directly, and the dependency analysis showing 15 files, a hook, and 30 doc  references. If any of these fragments are not retrieved (because they are in old  chat history, because the embedding similarity score is low, or because the  token budget was consumed by more recent but less relevant context), the model  may recommend preserving the middle layer, or may not realize it exists.</p> <p>Six months later, a new team member asks the same question. The retrieval results  will differ: the original discussion has aged out of recency scoring, the usage  statistics are no longer in recent history, and the model may re-derive the  answer or arrive at a different conclusion.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#63-cognitive-state-model","level":3,"title":"6.3 Cognitive State Model","text":"<p>In the persistence model, the decision is recorded as a structured artifact at  write time:</p> <pre><code>## [2026-02-11] Remove .context/sessions/ storage layer\n\n**Status**: Accepted\n\n**Context**: The session/recall/journal system had three overlapping\nstorage layers. The recall pipeline reads directly from raw transcripts,\nmaking .context/sessions/ a dead-end write sink that nothing reads from.\n\n**Decision**: Remove .context/sessions/ entirely. Two stores remain:\nraw transcripts (global, tool-owned) and enriched journal\n(project-local).\n\n**Rationale**: Dead-end write sinks waste code surface, maintenance\neffort, and user attention. The recall pipeline already proved that\nreading directly from raw transcripts is sufficient. Context snapshots\nare redundant with git history.\n\n**Consequence**: Deleted internal/cli/session/ (15 files), removed\nauto-save hook, removed --auto-save from watch, removed pre-compact\nauto-save, removed /ctx-save skill, updated ~45 documentation files.\nFour earlier decisions superseded.\n</code></pre> <p>This artifact is:</p> <ul> <li>Deterministically included in every subsequent session's delivery view   (budget permitting, with title-only fallback if budget is exceeded)</li> <li>Human-readable and reviewable as a diff in the commit that introduced it</li> <li>Permanent: it persists in version control regardless of retrieval heuristics</li> <li>Causally linked: it explicitly supersedes four earlier decisions,    creating an auditable chain</li> </ul> <p>When the new team member asks \"Why don't we store session copies?\" six months  later, the answer is the same artifact, at the same revision, with the same  rationale. The reasoning is reconstructible because it was persisted at write  time, not discovered at query time.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#64-the-diff-when-policy-changes","level":3,"title":"6.4 The Diff When Policy Changes","text":"<p>If a future requirement re-introduces session storage (for example, to  support multi-agent session correlation), the change appears as a diff to the  decision record:</p> <pre><code>- **Status**: Accepted\n+ **Status**: Superseded by [2026-08-15] Reintroduce session storage\n+ for multi-agent correlation\n</code></pre> <p>The new decision record references the old one, creating a chain of reasoning visible in <code>git log</code>. In the retrieval model, the old decision would simply be  ranked lower over time and eventually forgotten.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#7-experience-report-a-system-that-designed-itself","level":2,"title":"7. Experience Report: A System That Designed Itself","text":"<p>The persistence model described in this paper was developed and tested by using  it on its own development. Over 33 days and 389 sessions, the system's context  files accumulated a detailed record of decisions made, reversed, and  consolidated: providing quantitative and qualitative evidence for the model's  properties.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#71-scale-and-structure","level":3,"title":"7.1 Scale and Structure","text":"<p>The development produced the following authoritative state artifacts:</p> <ul> <li>8 consolidated decision records covering 24 original decisions spanning    context injection architecture, hook design, task management, security,    agent autonomy, and webhook systems</li> <li>18 consolidated learning records covering 75 original observations spanning    agent compliance, hook behavior, testing patterns, documentation drift, and    tool integration</li> <li>A constitution with 13 inviolable rules across 4 categories    (security, quality, process, context preservation)</li> <li>389 enriched journal entries providing a complete session-level audit trail</li> </ul> <p>The consolidation ratio (24 decisions compressed to 8 records, 75 learnings  compressed to 18) illustrates the curation cost and its return: authoritative  state becomes denser and more useful over time as related entries are merged,  contradictions are resolved, and superseded decisions are marked.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#72-architectural-reversals","level":3,"title":"7.2 Architectural Reversals","text":"<p>Three architectural reversals during development provide evidence that the  persistence model captures and communicates reasoning effectively:</p> <p>Reversal 1: The two-tier persistence model: The original design included a  middle storage tier for session copies. After 21 days of development, the middle  tier was identified as a dead-end write sink (described in Section 6). The  decision record captured the full context, and the removal was executed cleanly:  15 source files, a shell hook, and 45 documentation references. The pattern of  a \"dead-end write sink\" was subsequently observed in 7 of 17 systems in our  landscape analysis that store raw transcripts alongside structured context.</p> <p>Reversal 2: The prompt-coach hook: An early design included a hook that  analyzed user prompts and offered improvement suggestions. After deployment,  the hook produced zero useful tips, its output channel was invisible to users,  and it accumulated orphan temporary files. The hook was removed, and the  decision record captured the failure mode for future reference.</p> <p>Reversal 3: The soft-instruction compliance model: The original context  injection strategy relied on soft instructions: directives asking the AI agent  to read specific files. After measuring compliance across multiple sessions,  we found a consistent 75-85% compliance ceiling. The revised strategy  injects content directly, bypassing the agent's judgment about whether to  comply. The learning record captures the ceiling measurement and the rationale  for the architectural change.</p> <p>Each reversal was captured as a structured decision record with context,  rationale, and consequences. In a retrieval-based system, these reversals would  exist only in chat history, discoverable only if the retrieval function happens  to surface them. In the persistence model, they are permanent, indexable  artifacts that inform future decisions.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#73-compliance-ceiling","level":3,"title":"7.3 Compliance Ceiling","text":"<p>The 75-85% compliance ceiling for soft instructions is the most operationally  significant finding from the experience report. It means that any context  management strategy relying on agent compliance with instructions (\"read this  file,\" \"follow this convention,\" \"check this list\") has a hard ceiling on  reliability.</p> <p>The root cause is structural: the instruction \"don't apply judgment\" is itself  evaluated by judgment. When an agent receives a directive to read a file, it  first assesses whether the directive is relevant to the current task (and that  assessment is the judgment the directive was trying to prevent).</p> <p>The architectural response maps directly to the formal model defined in  Section 3.1. Content requiring 100% compliance is included in  <code>authoritative_state</code> and injected by the deterministic <code>assemble</code> function,  bypassing the agent entirely. Content where 80% compliance is acceptable is  delivered as instructions within the delivery view. The three-tier architecture  makes this distinction explicit: authoritative state is injected; delivery  views are assembled deterministically; ephemeral state is available but  not pushed.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#74-compounding-returns","level":3,"title":"7.4 Compounding Returns","text":"<p>Over 33 days, we observed a qualitative shift in the development experience.  Early sessions (days 1-7) spent significant time re-establishing context: explaining conventions, re-stating constraints, re-deriving past decisions.  Later sessions (days 25-33) began with the agent loading curated context and  immediately operating within established constraints, because the constraints  were in files rather than in chat history.</p> <p>This compounding effect (where each session's context curation improves all  subsequent sessions) is the primary return on the curation investment. The cost  is borne once (writing a decision record, capturing a learning, updating the  task list); the benefit is collected on every subsequent session load.</p> <p>The effect is analogous to compound interest in financial systems: the  knowledge base grows not linearly with effort but with increasing marginal  returns as new knowledge interacts with existing context. A learning captured  on day 5 prevents a mistake on day 12, which avoids a debugging session that  would have consumed a day 12 session, freeing that session for productive work  that generates new learnings. The growth is not literally exponential (it is  bounded by project scope and subject to diminishing returns as the knowledge  base matures), but within the observed 33-day window, the returns were  consistently accelerating.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#75-scope-and-generalizability","level":3,"title":"7.5 Scope and Generalizability","text":"<p>This experience report is self-referential by design: the system was developed  using its own persistence model. This circularity strengthens the internal  validity of the findings (the model was stress-tested under authentic  conditions) but limits external generalizability. The two-week crossover point  was observed on a single project of moderate complexity with a small team  already familiar with the model's assumptions. Whether the same crossover holds  for larger teams, for codebases with different characteristics, or for teams  adopting the model without having designed it remains an open empirical  question. The quantitative claims in this section should be read as  existence proofs (demonstrating that the model can produce compounding  returns) rather than as predictions about specific adoption scenarios.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#8-situating-the-persistence-layer","level":2,"title":"8. Situating the Persistence Layer","text":"<p>The persistence layer occupies a specific position in the stack of AI-assisted  development:</p> <pre><code>Application Logic\nAI Interaction / Agents\nContext Retrieval Systems\nCognitive State Persistence Layer\nVersion Control / Storage\n</code></pre> <p>Current systems innovate primarily in the retrieval layer (improving how  context is discovered, ranked, and delivered at query time). The persistence  layer sits beneath retrieval and above version control. Its role is to maintain  the authoritative state that retrieval systems may query but do not own. The  relationship is complementary: retrieval answers \"What in the corpus might be  relevant?\"; cognitive state answers \"What must be true for this system to  operate correctly?\" A mature system uses both: retrieval for discovery,  persistence for authority.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#9-applicability-and-trade-offs","level":2,"title":"9. Applicability and Trade-Offs","text":"","path":["The Thesis"],"tags":[]},{"location":"thesis/#91-when-to-use-this-model","level":3,"title":"9.1 When to Use This Model","text":"<p>A cognitive state persistence layer is most appropriate when:</p> <p>Reproducibility is a requirement: If a system must be able to answer \"Why  did this output occur, and can it be produced again?\" then deterministic,  version-controlled context becomes necessary. This is relevant in regulated  environments, safety-critical systems, long-lived infrastructure, and  security-sensitive deployments.</p> <p>Knowledge must outlive sessions and individuals: Projects with multi-year  lifetimes accumulate architectural decisions, domain interpretations, and  operational policy. If this knowledge is stored only in chat history, issue  trackers, and institutional memory, it decays. The persistence model converts  implicit knowledge into branchable, reviewable artifacts.</p> <p>Teams require shared cognitive authority: In collaborative environments,  correctness depends on a stable answer to \"What does the system believe to be  true?\" When this answer is derived from retrieval heuristics, authority shifts  to ranking algorithms. When it is versioned and human-readable, authority  remains with the team.</p> <p>Offline or air-gapped operation is required: Infrastructure-dependent  memory systems cannot operate in classified environments, isolated networks,  or constrained-environment scenarios.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#92-when-not-to-use-this-model","level":3,"title":"9.2 When Not to Use This Model","text":"<p>Zero-configuration personal workflows: For short-lived or exploratory tasks,  the cost of explicit knowledge curation outweighs its benefits. Heuristic  retrieval is sufficient when correctness is non-critical, outputs are  disposable, and historical reconstruction is unnecessary.</p> <p>Maximum automatic recall from large corpora: Vector retrieval systems  provide superior performance when the primary task is searching vast, weakly  structured information spaces. The persistence model assumes that what matters  can be decided and that this decision is valuable to record.</p> <p>Fully autonomous agent architectures: Agent runtimes that generate and  discard state continuously, optimizing for local goal completion, do not benefit  from a model that centers human ratification of knowledge.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#93-incremental-adoption","level":3,"title":"9.3 Incremental Adoption","text":"<p>The transition does not require full system replacement. An incremental path:</p> <p>Step 1: Record decisions as versioned artifacts: Instead of allowing  conclusions to remain in discussion threads, persist them in reviewable form  with context, rationale, and consequences <sup>4</sup>. This alone converts ephemeral  reasoning into the cognitive state.</p> <p>Step 2: Make inclusion deterministic: Define explicit assembly rules.  Retrieval may still exist, but it is no longer authoritative.</p> <p>Step 3: Move policy into cognitive state: When system behavior depends on  stable constraints, encode those constraints as versioned knowledge. Behavior  becomes reproducible.</p> <p>Step 4: Optimize assembly, not retrieval: Once the authoritative layer  exists, performance improvements come from budgeting, caching, and structural  refinement rather than from improving ranking heuristics.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#94-the-curation-cost","level":3,"title":"9.4 The Curation Cost","text":"<p>The primary objection to this model is the cost of explicit knowledge curation.  This cost is real. Writing a structured decision record takes longer than  letting a chatbot auto-summarize a conversation. Maintaining a glossary requires  discipline. Consolidating 75 learnings into 18 records requires judgment.</p> <p>The response is not that the cost is negligible but that it is amortized.  A decision record written once is loaded hundreds of times. A learning captured  today prevents repeated mistakes across all future sessions. The curation cost  is paid once; the benefit compounds.</p> <p>The experience report provides rough order-of-magnitude numbers. Across 389  sessions over 33 days, curation activities (writing decision records,  capturing learnings, updating the task list, consolidating entries) averaged  approximately 3-5 minutes per session. In early sessions (days 1-7), before  curated context existed, re-establishing context consumed approximately 10-15  minutes per session: re-explaining conventions, re-stating architectural  constraints, re-deriving decisions that had been made but not persisted. By the  final week (days 25-33), the re-explanation overhead had dropped to near zero:  the agent loaded curated context and began productive work immediately.</p> <p>At ~12 sessions per day, the curation cost was roughly 35-60 minutes daily.  The re-explanation cost in the first week was roughly 120-180 minutes daily.  By the third week, that cost had fallen to under 15 minutes daily while the  curation cost remained stable. The crossover (where cumulative curation cost  was exceeded by cumulative time saved) occurred around day 10. These figures are  approximate and derived from a single project with a small team already familiar  with the model; the crossover point will vary with project complexity,  team size, and curation discipline.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#10-future-work","level":2,"title":"10. Future Work","text":"<p>Several directions are compatible with the model described here:</p> <p>Section-level deterministic budgeting: Current assembly operates at file  granularity. Section-level budgeting would allow finer-grained control (including  specific decision records while excluding others within the same file) without  sacrificing determinism.</p> <p>Causal links between decisions: The experience report shows that decisions  frequently reference earlier decisions (superseding, extending, or qualifying  them). Formal causal links would enable traversal of the decision graph and  automatic detection of orphaned or contradictory constraints.</p> <p>Content-addressed context caches: Five systems in our landscape analysis  independently discovered that content hashing provides cache invalidation,  integrity verification, and change detection. Applying content addressing to the  assembly output would enable efficient cache reuse when the authoritative state  has not changed.</p> <p>Conditional context inclusion: Five systems independently suggest that  context entries could carry activation conditions (file patterns, task  keywords, or explicit triggers) that control whether they are included in a  given assembly. This would reduce the per-session budget cost of large  knowledge bases without sacrificing determinism.</p> <p>Provenance metadata: Linking context entries to the sessions, decisions, or  learnings that motivated them would strengthen the audit trail. Optional  provenance fields on Markdown entries (session identifier, cause reference,  motivation) would be lightweight and compatible with the existing file-based  model.</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#11-conclusion","level":2,"title":"11. Conclusion","text":"<p>AI-assisted development has treated context as a \"query result\" assembled at the  moment of interaction, discarded at the session end. This paper identifies a  complementary layer: the persistence of authoritative cognitive state as  deterministic, version-controlled artifacts.</p> <p>The contribution is grounded in three sources of evidence. A landscape analysis  of 17 systems reveals five categories of primitives and shows that no existing  system provides the combination of human-readability, determinism, zero  dependencies, and offline capability that the persistence layer requires. Six  design invariants, validated by 56 independent rejection decisions, define the  constraints of the design space. An experience report over 389 sessions and  33 days demonstrates compounding returns: later sessions start faster,  decisions are not re-derived, and architectural reversals are captured with  full context.</p> <p>The core claim is this: persistent cognitive state enables causal reasoning  across time. A system built on this model can explain not only what is true,  but why it became true and when it changed.</p> <p>When context is the state:</p> <ul> <li>Reasoning is reproducible: the same authoritative state, budget, and policy    produce the same delivery view.</li> <li>Knowledge is auditable: decisions are traceable to explicit artifacts with    context, rationale, and consequences.</li> <li>Understanding compounds: each session's curation improves all subsequent    sessions.</li> </ul> <p>The choice between retrieval-centric workflows and a persistence layer is not a  matter of capability but of time horizon. Retrieval optimizes for relevance at  the moment of interaction. Persistence optimizes for the durability of  understanding across the lifetime of a project.</p> <p>🐸🖤 \"Gooood... let the deterministic context flow through the repository...\" - Kermit the Sidious, probably</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#appendix-a-representative-rejection-decisions","level":2,"title":"Appendix A: Representative Rejection Decisions","text":"<p>The 56 rejection decisions referenced in Section 4 were cataloged across all 17  system analyses, grouped by the invariant they would violate. This appendix  provides a representative sample (two per invariant) to illustrate the  methodology.</p> <p>Invariant 1: Markdown-on-Filesystem (11 rejections): CrewAI's vector  embedding storage was rejected because embeddings are not human-readable, not  git-diff-friendly, and require external services. Kindex's knowledge graph as  core primitive was rejected because it requires specialized commands to inspect  content that could be a text file (<code>kin show <id></code> vs. <code>cat DECISIONS.md</code>).</p> <p>Invariant 2: Zero Runtime Dependencies (13 rejections): Letta/MemGPT's  PostgreSQL-backed architecture was rejected because it conflicts with  local-first, no-database, single-binary operation. Pachyderm's Kubernetes-based  distributed architecture was rejected as the antithesis of a single-binary  design for a tool that manages text files.</p> <p>Invariant 3: Deterministic Assembly (6 rejections): LlamaIndex's  embedding-based retrieval as the primary selection mechanism was rejected because  it destroys determinism, requires an embedding model, and removes human  judgment from the selection process. QubicDB's wall-clock-dependent scoring was  rejected because it directly conflicts with the \"same inputs produce same  output\" property.</p> <p>Invariant 4: Human Authority (6 rejections): Letta/MemGPT's agent  self-modification of memory was rejected as fundamentally opposed to  human-curated persistence. Claude Code's unstructured auto-memory (where the  agent writes freeform notes) was rejected because structured files with defined  schemas produce higher-quality persistent context than unconstrained agent  output.</p> <p>Invariant 5: Local-First / Air-Gap Capable (7 rejections): Sweep's  cloud-dependent architecture was rejected as fundamentally incompatible with  the local-first, offline-capable model. LangGraph's managed cloud deployment  was rejected because cloud dependencies for core functionality violate  air-gap capability.</p> <p>Invariant 6: No Default Telemetry (4 rejections): Continue's  telemetry-by-default (PostHog) was rejected because it contradicts the  local-first, privacy-respecting trust model. CrewAI's global telemetry on  import (Scarf tracking pixel) was rejected because it violates user trust and  breaks air-gap capability.</p> <p>The remaining 9 rejections did not map to a specific invariant but were  rejected on other architectural grounds: for example, Aider's  full-file-content-in-context approach (which defeats token budgeting),  AutoGen's multi-agent orchestration as core primitive (scope creep),  and Claude Code's 30-day transcript retention limit  (institutional knowledge should have no automatic expiration).</p>","path":["The Thesis"],"tags":[]},{"location":"thesis/#references","level":2,"title":"References","text":"<ol> <li> <p>Reproducible Builds Project, \"Reproducible Builds: Increasing the Integrity of Software Supply Chains\", 2017. https://reproducible-builds.org/docs/definition/ ↩↩↩</p> </li> <li> <p>S. McIntosh et al., \"The Impact of Build System Evolution on Software Quality\", ICSE, 2015. https://doi.org/10.1109/ICSE.2015.70 ↩</p> </li> <li> <p>C. Manning, P. Raghavan, H. Schütze, Introduction to Information Retrieval, Cambridge University Press, 2008. https://nlp.stanford.edu/IR-book/ ↩</p> </li> <li> <p>M. Nygard, \"Documenting Architecture Decisions\", Cognitect Blog, 2011. https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions ↩↩</p> </li> <li> <p>L. Torvalds et al., Git Internals - Git Objects (content-addressed storage concepts). https://git-scm.com/book/en/v2/Git-Internals-Git-Objects ↩</p> </li> <li> <p>Kief Morris, Infrastructure as Code, O'Reilly, 2016. ↩</p> </li> <li> <p>J. Kreps, \"The Log: What every software engineer should know about real-time data's unifying abstraction\", 2013. https://engineering.linkedin.com/distributed-systems/log ↩</p> </li> <li> <p>P. Hunt et al., \"ZooKeeper: Wait-free coordination for Internet-scale systems\", USENIX ATC, 2010. https://www.usenix.org/legacy/event/atc10/tech/full_papers/Hunt.pdf ↩</p> </li> </ol>","path":["The Thesis"],"tags":[]}]}
    \ No newline at end of file
    diff --git a/site/security/design/index.html b/site/security/design/index.html
    index 749b29138..d17233903 100644
    --- a/site/security/design/index.html
    +++ b/site/security/design/index.html
    @@ -1554,7 +1554,7 @@ <h3 id="the-rule">The Rule<a class="headerlink" href="#the-rule" title="Permanen
     </span></code></pre></div>
     <p>The <code>nudge.Relay</code> helper in <code>internal/cli/system/core/nudge</code>
     enforces this for the common "log + webhook" pair. Hook <code>Run</code>
    -functions that compose their own sequence (<code>session_event</code>,
    +functions that compose their own sequence (<code>sessionevent</code>,
     <code>heartbeat</code>, several <code>check_*</code> hooks) follow the same ordering
     explicitly.</p>
     <h3 id="known-gaps">Known Gaps<a class="headerlink" href="#known-gaps" title="Permanent link">¶</a></h3>
    diff --git a/specs/copilot-feature-parity-kit.md b/specs/copilot-feature-parity-kit.md
    index d1658b910..35401ffff 100644
    --- a/specs/copilot-feature-parity-kit.md
    +++ b/specs/copilot-feature-parity-kit.md
    @@ -119,7 +119,7 @@ VS Code commands delegate to `ctx` CLI. The extension provides UI
     | `internal/assets/integrations/copilot-cli/skills/ctx-explain/SKILL.md` | New skill |
     | `internal/assets/integrations/copilot-cli/skills/ctx-brainstorm/SKILL.md` | New skill |
     | `internal/assets/integrations/copilot-cli/skills/ctx-spec/SKILL.md` | New skill |
    -| `internal/cli/setup/core/copilot_cli/copilot_cli.go` | Deploy new skills |
    +| `internal/cli/setup/core/copilotcli/copilotcli.go` | Deploy new skills |
     | `editors/vscode/package.json` | Add slash commands |
     | `editors/vscode/src/extension.ts` | Handle new commands |
     
    diff --git a/specs/future-complete/copilot-cli-integration.md b/specs/future-complete/copilot-cli-integration.md
    index dfa04abc5..e62701c94 100644
    --- a/specs/future-complete/copilot-cli-integration.md
    +++ b/specs/future-complete/copilot-cli-integration.md
    @@ -62,8 +62,8 @@
     | `ctx_watch_update` tool | ✅ | ✅ | 📋 (same server) |
     | `ctx_compact` tool | ✅ | ✅ | 📋 (same server) |
     | `ctx_next` tool | ✅ | ✅ | 📋 (same server) |
    -| `ctx_check_task_completion` | ✅ | ✅ | 📋 (same server) |
    -| `ctx_session_event` tool | ✅ | ✅ | 📋 (same server) |
    +| `ctx_checktaskcompletion` | ✅ | ✅ | 📋 (same server) |
    +| `ctx_sessionevent` tool | ✅ | ✅ | 📋 (same server) |
     | `ctx_remind` tool | ✅ | ✅ | 📋 (same server) |
     | 8 context resources | ✅ | ✅ | 📋 (same server) |
     | Resource change notifications | ✅ Poller-based | ✅ Poller-based | 📋 (same server) |
    diff --git a/specs/future-complete/exec-package.md b/specs/future-complete/exec-package.md
    index fdd9c0fc0..a263d623a 100644
    --- a/specs/future-complete/exec-package.md
    +++ b/specs/future-complete/exec-package.md
    @@ -29,8 +29,8 @@ audit command execution, validate inputs, and satisfy gosec.
     |-----------|-----------|------|
     | `config/core/core.go:105` | rev-parse --show-toplevel | literal |
     | `change/core/scan/scan.go:133` | log --since (time) | validated |
    -| `system/cmd/post_commit/score.go:33` | log -1 --format=%B | literal |
    -| `system/cmd/post_commit/score.go:66` | diff-tree HEAD | literal |
    +| `system/cmd/postcommit/score.go:33` | log -1 --format=%B | literal |
    +| `system/cmd/postcommit/score.go:66` | diff-tree HEAD | literal |
     | `system/core/health/map_staleness.go:66` | log --oneline --since | validated |
     | `journal/parser/git.go:42` | remote get-url origin | literal |
     
    diff --git a/specs/future-complete/flagbind-batch-and-convention-sweep.md b/specs/future-complete/flagbind-batch-and-convention-sweep.md
    index aa4ec72e0..5d7cf25a6 100644
    --- a/specs/future-complete/flagbind-batch-and-convention-sweep.md
    +++ b/specs/future-complete/flagbind-batch-and-convention-sweep.md
    @@ -52,7 +52,7 @@ initialize, loop, pad/edit, event, notify.
     | `hub/store.go` | `dirPerm` → `fs.PermKeyDir` |
     | `server/daemon.go` | Removed aliased `pidFile` const |
     | `server/setup.go` | Removed aliased `dataDirPerm` const |
    -| `claudecheck/` | Renamed to `claude_check/` |
    +| `claudecheck/` | Renamed to `claudecheck/` |
     | `details.go` | Renamed to `detail.go` (singular) |
     | `steering/types.go` | Docstrings aligned with conventions |
     | `config/entry/entry.go` | Added `AllowedTypes` set |
    diff --git a/specs/future-complete/fmt-fprint-migration.md b/specs/future-complete/fmt-fprint-migration.md
    index 84d196db0..099d0f882 100644
    --- a/specs/future-complete/fmt-fprint-migration.md
    +++ b/specs/future-complete/fmt-fprint-migration.md
    @@ -26,7 +26,7 @@ construction, not output. These are in:
     - `dep/core/format.go` — building Mermaid markup
     - `journal/core/section.go` — building index/site content
     - `recall/core/frontmatter.go` — building YAML frontmatter
    -- `system/cmd/check_freshness/run.go` — building warning text
    +- `system/cmd/checkfreshness/run.go` — building warning text
     
     `fmt.Fprintln` calls that write to an `io.Writer` parameter already
     follow the migrated pattern (stats StreamStats, backup addEntry).
    diff --git a/specs/future-complete/hook-accountability.md b/specs/future-complete/hook-accountability.md
    index 998be74ef..49a8fa021 100644
    --- a/specs/future-complete/hook-accountability.md
    +++ b/specs/future-complete/hook-accountability.md
    @@ -48,7 +48,7 @@ Add a minimum context window percentage threshold below which
     counter-based checkpoints are suppressed. The `windowTrigger` (>80%)
     remains independent and unchanged.
     
    -**Current logic** (`check_context_size/run.go:112-116`):
    +**Current logic** (`checkcontextsize/run.go:112-116`):
     ```go
     if count > 30 {
         counterTriggered = count%3 == 0
    diff --git a/specs/future-complete/hook-guard-uninitialized.md b/specs/future-complete/hook-guard-uninitialized.md
    index f37ec06fa..2b7b160e6 100644
    --- a/specs/future-complete/hook-guard-uninitialized.md
    +++ b/specs/future-complete/hook-guard-uninitialized.md
    @@ -3,7 +3,7 @@
     ## Problem
     
     When the ctx plugin is installed globally and Claude runs in a
    -non-ctx project, unsolicited relay alerts fire from `check_resource`
    +non-ctx project, unsolicited relay alerts fire from `checkresource`
     and `check_backup_age`. Users see "Load Xx CPU count" and backup-age
     warnings in projects that don't use ctx at all.
     
    @@ -15,7 +15,7 @@ no-op behavior when ctx is not initialized — this matches the
     existing pattern in 18 other hooks.
     
     Scope limited to hooks that emit user-visible relay alerts. Safety
    -hooks (`block_dangerous_command`, `block_non_path_ctx`) intentionally
    +hooks (`block_dangerous_command`, `blocknonpathctx`) intentionally
     run regardless of ctx state.
     
     ## Non-Goals
    diff --git a/specs/future-complete/journal-merge-completion.md b/specs/future-complete/journal-merge-completion.md
    index 3135073db..01abe4680 100644
    --- a/specs/future-complete/journal-merge-completion.md
    +++ b/specs/future-complete/journal-merge-completion.md
    @@ -25,8 +25,8 @@ multiple convention violations accumulated across the changeset.
     ### Magic numbers and strings
     
     6. **source/cmd.go:75** — `"project"`, `"p"` hardcoded
    -7. **check_context_size/run.go** — 30, 3, 15 are magic numbers
    -8. **post_commit/run.go** — regexes, violation points, and localizable
    +7. **checkcontextsize/run.go** — 30, 3, 15 are magic numbers
    +8. **postcommit/run.go** — regexes, violation points, and localizable
        strings all hardcoded
     9. **state/state.go:29** — 0o750 hardcoded
     
    diff --git a/specs/future-complete/percentage-based-checkpoint-nudge.md b/specs/future-complete/percentage-based-checkpoint-nudge.md
    index cd504c2eb..820ab47be 100644
    --- a/specs/future-complete/percentage-based-checkpoint-nudge.md
    +++ b/specs/future-complete/percentage-based-checkpoint-nudge.md
    @@ -52,7 +52,7 @@ Replace both trigger mechanisms with two percentage-based thresholds:
     - `CheckpointLateInterval` (3) — deleted
     - `ContextCheckpointMinPct` (20) — deleted (subsumed by 60%)
     - `ContextWindowThresholdPct` (80) — replaced by 90%
    -- All counter-based branching in `check_context_size/run.go`
    +- All counter-based branching in `checkcontextsize/run.go`
     - Counter file (`context-check-{sessionID}`) — no longer needed for
       nudge decisions (still needed for prompt count in stats/logging)
     
    diff --git a/specs/future-complete/state-dir-no-mkdir-when-uninitialized.md b/specs/future-complete/state-dir-no-mkdir-when-uninitialized.md
    index 6ca4bae3c..b6fcafda3 100644
    --- a/specs/future-complete/state-dir-no-mkdir-when-uninitialized.md
    +++ b/specs/future-complete/state-dir-no-mkdir-when-uninitialized.md
    @@ -24,7 +24,7 @@ includes `ctx system check-reminder`, whose `Run` deliberately calls
     provenance prints unconditionally:
     
     ```
    -check_reminder.Run
    +checkreminder.Run
       └─ coreCheck.Preamble                      # before the Initialized gate
            └─ nudge.Paused(sessionID)
                 └─ PauseMarkerPath
    @@ -113,7 +113,7 @@ No CLI surface change. This is a library-internal contract change.
     | `internal/err/context/errors.go` (or wherever `ErrDirNotDeclared` lives) | Add `ErrNotInitialized` sentinel error. |
     | `internal/cli/system/core/state/state.go` | Insert `Initialized()` check between `rc.ContextDir()` and `SafeMkdirAll`. Update package-level docstring on `Dir()` to document the new contract. |
     | `internal/cli/system/core/state/state_test.go` (new or existing) | Add unit tests: uninitialized → returns `ErrNotInitialized`, no mkdir occurs; initialized → mkdir runs as before; `dirOverride` bypasses the gate. |
    -| `internal/cli/system/cmd/check_reminder/run.go` | No code change required — the existing `if dirErr != nil { return nil }` branch in `Preamble`'s call chain absorbs the new error. Add a comment cross-referencing this spec to document why the order is now safe. |
    +| `internal/cli/system/cmd/checkreminder/run.go` | No code change required — the existing `if dirErr != nil { return nil }` branch in `Preamble`'s call chain absorbs the new error. Add a comment cross-referencing this spec to document why the order is now safe. |
     | Other call sites of `state.Dir()` | Two-pass audit during implementation. Pass 1 — hook commands and other non-interactive callers: confirm the existing `dirErr != nil → return nil` branch absorbs `ErrNotInitialized` without warning. Pass 2 — interactive callers (every entry point reachable from a user-typed `ctx ...` subcommand): wrap the call so `errors.Is(err, errCtx.ErrNotInitialized)` produces the stderr message above and a non-zero exit. The full classified list is part of the PR description and must be exhaustive (no "rest as follow-up"). |
     | `specs/tests/regression/uninit-no-state-leak/` | Add a test harness that simulates the Cursor scenario end-to-end. See Testing section. |
     
    @@ -208,7 +208,7 @@ per hook.
       command.** Users diagnose via `ctx system bootstrap` and `ctx
       status`; no new surface needed.
     - **Not redesigning the `Preamble` / `FullPreamble` split.** The
    -  `check_reminder` "provenance-first" ordering is preserved exactly;
    +  `checkreminder` "provenance-first" ordering is preserved exactly;
       the fix sits below it in the call stack. A future cleanup may
       collapse the two preamble variants, but it is not required to close
       this leak.
    diff --git a/specs/future-complete/task-session-provenance.md b/specs/future-complete/task-session-provenance.md
    index 833608ca1..aef3042a3 100644
    --- a/specs/future-complete/task-session-provenance.md
    +++ b/specs/future-complete/task-session-provenance.md
    @@ -104,7 +104,7 @@ always present so you know whether provenance was captured.
     
     ### 1. Hook Script Changes
     
    -In the `UserPromptSubmit` hook (e.g., `ctx system check_reminder`
    +In the `UserPromptSubmit` hook (e.g., `ctx system checkreminder`
     or equivalent):
     
     - Parse `session_id` from stdin JSON (already available)
    diff --git a/specs/released/v0.6.0/plan-v0.6.0-claude-code-plugin.md b/specs/released/v0.6.0/plan-v0.6.0-claude-code-plugin.md
    index c2f765735..7916dc8d6 100644
    --- a/specs/released/v0.6.0/plan-v0.6.0-claude-code-plugin.md
    +++ b/specs/released/v0.6.0/plan-v0.6.0-claude-code-plugin.md
    @@ -267,16 +267,16 @@ internal/cli/system/
     ├── system.go                  # Parent command (Hidden=true)
     ├── input.go                   # HookInput/ToolInput types, readInput()
     ├── state.go                   # secureTempDir, counter, log, throttle helpers
    -├── check_context_size.go      # Adaptive prompt counter
    -├── check_context_size_test.go
    -├── check_persistence.go       # Context file mtime watcher
    -├── check_persistence_test.go
    -├── check_journal.go           # Unimported sessions + unenriched entries
    -├── check_journal_test.go
    -├── block_non_path_ctx.go      # Command pattern blocker
    -├── block_non_path_ctx_test.go
    -├── post_commit.go             # Post-commit context capture nudge
    -├── post_commit_test.go
    +├── checkcontextsize.go      # Adaptive prompt counter
    +├── checkcontextsize_test.go
    +├── checkpersistence.go       # Context file mtime watcher
    +├── checkpersistence_test.go
    +├── checkjournal.go           # Unimported sessions + unenriched entries
    +├── checkjournal_test.go
    +├── blocknonpathctx.go      # Command pattern blocker
    +├── blocknonpathctx_test.go
    +├── postcommit.go             # Post-commit context capture nudge
    +├── postcommit_test.go
     ├── cleanup_tmp.go             # Old temp file removal
     └── cleanup_tmp_test.go
     ```
    diff --git a/specs/released/v0.8.0/ceremony-nudge.md b/specs/released/v0.8.0/ceremony-nudge.md
    index 824889143..e59b52cdd 100644
    --- a/specs/released/v0.8.0/ceremony-nudge.md
    +++ b/specs/released/v0.8.0/ceremony-nudge.md
    @@ -170,7 +170,7 @@ From `state.go`:
     - `isInitialized()` — skip if no .context/
     - `logMessage()` — diagnostic logging
     
    -From `check_journal.go`:
    +From `checkjournal.go`:
     - `newestMtime()` — find recent files
     - `countUnenriched()` — pattern for reading journal content
     - Journal directory path pattern
    diff --git a/specs/released/v0.8.0/context-load-gate-v2.md b/specs/released/v0.8.0/context-load-gate-v2.md
    index e86eac1c2..95b32fd4d 100644
    --- a/specs/released/v0.8.0/context-load-gate-v2.md
    +++ b/specs/released/v0.8.0/context-load-gate-v2.md
    @@ -192,8 +192,8 @@ No change from v1. Same matcher, same position:
     
     | File | Change |
     |------|--------|
    -| `internal/cli/system/context_load_gate.go` | Rewrite — read files, inject content |
    -| `internal/cli/system/context_load_gate_test.go` | Update — test content injection |
    +| `internal/cli/system/contextloadgate.go` | Rewrite — read files, inject content |
    +| `internal/cli/system/contextloadgate_test.go` | Update — test content injection |
     
     No new files. No hook registration changes. No config changes.
     
    diff --git a/specs/released/v0.8.0/context-load-gate.md b/specs/released/v0.8.0/context-load-gate.md
    index 61765dab0..673d70399 100644
    --- a/specs/released/v0.8.0/context-load-gate.md
    +++ b/specs/released/v0.8.0/context-load-gate.md
    @@ -196,8 +196,8 @@ blocks the tool call.
     
     | File | Change |
     |------|--------|
    -| `internal/cli/system/context_load_gate.go` | New — hook implementation |
    -| `internal/cli/system/context_load_gate_test.go` | New — unit tests |
    +| `internal/cli/system/contextloadgate.go` | New — hook implementation |
    +| `internal/cli/system/contextloadgate_test.go` | New — unit tests |
     | `internal/cli/system/system.go` | Register `contextLoadGateCmd()` |
     | `internal/assets/claude/hooks/hooks.json` | Add `.*` matcher entry (first position) |
     
    diff --git a/specs/released/v0.8.0/context-window-usage.md b/specs/released/v0.8.0/context-window-usage.md
    index 32c8e2c16..bc133648e 100644
    --- a/specs/released/v0.8.0/context-window-usage.md
    +++ b/specs/released/v0.8.0/context-window-usage.md
    @@ -44,8 +44,8 @@ Default 200,000 tokens (Opus/Sonnet). Configurable via `.ctxrc` key
     
     | File | Change |
     |------|--------|
    -| `internal/cli/system/check_context_size.go` | Token reading, >80% independent trigger, token line display |
    -| `internal/cli/system/check_context_size_test.go` | Tests for new display logic |
    +| `internal/cli/system/checkcontextsize.go` | Token reading, >80% independent trigger, token line display |
    +| `internal/cli/system/checkcontextsize_test.go` | Tests for new display logic |
     | `internal/assets/hooks/messages/registry.go` | `window` variant entry |
     | `internal/rc/types.go` | `ContextWindow` field on `CtxRC` |
     | `internal/rc/default.go` | `DefaultContextWindow = 200000` |
    diff --git a/specs/released/v0.8.0/event-log.md b/specs/released/v0.8.0/event-log.md
    index f7ed6ecb2..1f068fc3a 100644
    --- a/specs/released/v0.8.0/event-log.md
    +++ b/specs/released/v0.8.0/event-log.md
    @@ -366,7 +366,7 @@ internal/assets/claude/skills/ctx-doctor/
     | `internal/config/gitignore.go` | Add `state/events.jsonl` and `state/events.1.jsonl` to `GitignoreEntries` |
     | `internal/cli/system/system.go` | Register `eventsCmd()` |
     | `internal/bootstrap/bootstrap.go` | Register `doctor.Cmd()` |
    -| System hook files (`check_ceremonies.go`, `check_persistence.go`, `check_context_size.go`, `check_journal.go`, `check_reminders.go`, `check_knowledge.go`, `check_map_staleness.go`, `check_version.go`, `check_resources.go`, `context_load_gate.go`, `post_commit.go`, `qa_reminder.go`) | Add `eventlog.Append()` call alongside `notify.Send()` |
    +| System hook files (`check_ceremonies.go`, `checkpersistence.go`, `checkcontextsize.go`, `checkjournal.go`, `checkreminders.go`, `checkknowledge.go`, `checkmapstaleness.go`, `checkversion.go`, `checkresources.go`, `contextloadgate.go`, `postcommit.go`, `qareminder.go`) | Add `eventlog.Append()` call alongside `notify.Send()` |
     
     ### Key types and functions
     
    diff --git a/specs/released/v0.8.0/global-encryption-key.md b/specs/released/v0.8.0/global-encryption-key.md
    index aec724f0d..ae293478e 100644
    --- a/specs/released/v0.8.0/global-encryption-key.md
    +++ b/specs/released/v0.8.0/global-encryption-key.md
    @@ -59,7 +59,7 @@ Drop: per-project slug directory (`~/.local/ctx/keys/`), the
     | `internal/cli/initialize/run.go` | `initScratchpad()` uses `rc.KeyPath()` — no change needed, but `os.MkdirAll` target dir changes from `~/.local/ctx/keys/` to `~/.ctx/`. |
     | `internal/cli/pad/store.go` | `keyPath()` calls `rc.KeyPath()` — no change. |
     | `internal/notify/notify.go` | `LoadWebhook()` / `SaveWebhook()` call `rc.KeyPath()` — no change. |
    -| `internal/cli/system/check_version.go` | `checkKeyAge()` calls `rc.KeyPath()` — no change. |
    +| `internal/cli/system/checkversion.go` | `checkKeyAge()` calls `rc.KeyPath()` — no change. |
     | `internal/cli/pad/pad_test.go` | `setupEncrypted()` uses `config.ProjectKeyPath()` — must switch to `config.GlobalKeyPath()`. |
     | `internal/cli/initialize/initialize_test.go` | Same — switch from `ProjectKeyPath()`. |
     
    diff --git a/specs/released/v0.8.0/hook-message-templates.md b/specs/released/v0.8.0/hook-message-templates.md
    index 04a8df229..9570130fa 100644
    --- a/specs/released/v0.8.0/hook-message-templates.md
    +++ b/specs/released/v0.8.0/hook-message-templates.md
    @@ -194,20 +194,20 @@ Not all hooks should be customizable:
     | File | Change |
     |------|--------|
     | `internal/assets/embed.go` | Add `hooks/messages/` to embed directive |
    -| `internal/cli/system/qa_reminder.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/post_commit.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_context_size.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_persistence.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/qareminder.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/postcommit.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkcontextsize.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkpersistence.go` | Replace hardcoded string with `loadMessage()` |
     | `internal/cli/system/check_ceremonies.go` | Replace hardcoded strings with `loadMessage()` |
    -| `internal/cli/system/check_journal.go` | Replace hardcoded strings with `loadMessage()` |
    -| `internal/cli/system/check_knowledge.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_map_staleness.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkjournal.go` | Replace hardcoded strings with `loadMessage()` |
    +| `internal/cli/system/checkknowledge.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkmapstaleness.go` | Replace hardcoded string with `loadMessage()` |
     | `internal/cli/system/check_backup_age.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_reminders.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_resources.go` | Replace hardcoded string with `loadMessage()` |
    -| `internal/cli/system/check_version.go` | Replace hardcoded strings with `loadMessage()` |
    +| `internal/cli/system/checkreminders.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkresources.go` | Replace hardcoded string with `loadMessage()` |
    +| `internal/cli/system/checkversion.go` | Replace hardcoded strings with `loadMessage()` |
     | `internal/cli/system/block_dangerous_commands.go` | Replace hardcoded reasons with `loadMessage()` |
    -| `internal/cli/system/block_non_path_ctx.go` | Replace hardcoded reasons with `loadMessage()` |
    +| `internal/cli/system/blocknonpathctx.go` | Replace hardcoded reasons with `loadMessage()` |
     
     ### Key Implementation
     
    diff --git a/specs/released/v0.8.0/injection-oversize-nudge.md b/specs/released/v0.8.0/injection-oversize-nudge.md
    index 95f34bd82..413d7d07d 100644
    --- a/specs/released/v0.8.0/injection-oversize-nudge.md
    +++ b/specs/released/v0.8.0/injection-oversize-nudge.md
    @@ -169,15 +169,15 @@ injection_token_warn: 12000
     | `internal/rc/types.go` | Add `InjectionTokenWarn int` field |
     | `internal/rc/default.go` | Add `DefaultInjectionTokenWarn = 15000` |
     | `internal/rc/rc.go` | Add default to `Default()` |
    -| `internal/cli/system/context_load_gate.go` | Write flag file when over threshold |
    -| `internal/cli/system/check_context_size.go` | Read flag, append nudge, delete flag |
    -| `internal/cli/system/context_load_gate_test.go` | Test flag write behavior |
    -| `internal/cli/system/check_context_size_test.go` | Test nudge append behavior |
    +| `internal/cli/system/contextloadgate.go` | Write flag file when over threshold |
    +| `internal/cli/system/checkcontextsize.go` | Read flag, append nudge, delete flag |
    +| `internal/cli/system/contextloadgate_test.go` | Test flag write behavior |
    +| `internal/cli/system/checkcontextsize_test.go` | Test nudge append behavior |
     | `.gitignore` | Add `.context/state/` entry |
     
     ### Key Implementation
     
    -#### context_load_gate.go — Flag Writer
    +#### contextloadgate.go — Flag Writer
     
     After the existing `printHookContext` and webhook call, add:
     
    @@ -209,7 +209,7 @@ if warnThreshold > 0 && totalTokens > warnThreshold {
     This requires tracking per-file token counts during the injection loop
     (a small `[]struct{ name string; tokens int }` accumulator).
     
    -#### check_context_size.go — Flag Reader
    +#### checkcontextsize.go — Flag Reader
     
     Inside the `if shouldCheck` block, before emitting the message:
     
    @@ -237,7 +237,7 @@ if data, err := os.ReadFile(oversizeFile); err == nil {
     
     ## Testing
     
    -### Unit Tests — context_load_gate_test.go
    +### Unit Tests — contextloadgate_test.go
     
     - **Under threshold**: totalTokens < warn → no flag file written
     - **Over threshold**: totalTokens > warn → flag file exists with correct content
    @@ -245,7 +245,7 @@ if data, err := os.ReadFile(oversizeFile); err == nil {
     - **Per-file breakdown in flag**: verify file names and token counts appear
     - **State dir auto-created**: works even when `state/` doesn't exist yet
     
    -### Unit Tests — check_context_size_test.go
    +### Unit Tests — checkcontextsize_test.go
     
     - **Flag present at checkpoint**: nudge includes oversize warning line
     - **Flag absent at checkpoint**: normal checkpoint, no oversize line
    diff --git a/specs/released/v0.8.0/journal-state-file.md b/specs/released/v0.8.0/journal-state-file.md
    index 6e192d710..3b02e9497 100644
    --- a/specs/released/v0.8.0/journal-state-file.md
    +++ b/specs/released/v0.8.0/journal-state-file.md
    @@ -46,7 +46,7 @@ Date strings (not booleans) — provides audit trail at zero extra cost.
     
     ### Phase 4: Wire into check-journal
     
    -8. **`internal/cli/system/check_journal.go`** — Replace `countUnenriched()` body: load state, count `.md` files in directory without `enriched` set.
    +8. **`internal/cli/system/checkjournal.go`** — Replace `countUnenriched()` body: load state, count `.md` files in directory without `enriched` set.
     
     ### Phase 5: Update tests
     
    @@ -79,7 +79,7 @@ Date strings (not booleans) — provides audit trail at zero extra cost.
     | `internal/cli/journal/run.go` | Load state, pass per-file |
     | `internal/cli/journal/journal_test.go` | Update tests |
     | `internal/cli/recall/run.go` | Mark imported on import |
    -| `internal/cli/system/check_journal.go` | State-based `countUnenriched` |
    +| `internal/cli/system/checkjournal.go` | State-based `countUnenriched` |
     | `internal/assets/.../ctx-journal-normalize/SKILL.md` | State file instructions |
     | `internal/assets/.../ctx-journal-enrich-all/SKILL.md` | State file instructions |
     | `internal/assets/.../ctx-journal-enrich/SKILL.md` | State file instructions |
    diff --git a/specs/released/v0.8.0/remind.md b/specs/released/v0.8.0/remind.md
    index d7c31c20d..42f365c7f 100644
    --- a/specs/released/v0.8.0/remind.md
    +++ b/specs/released/v0.8.0/remind.md
    @@ -251,7 +251,7 @@ internal/cli/remind/
       remind_test.go     Tests
     
     internal/cli/system/
    -  check_reminders.go checkRemindersCmd(), runCheckReminders()
    +  checkreminders.go checkRemindersCmd(), runCheckReminders()
     
     internal/assets/claude/skills/ctx-remind/
       SKILL.md           Agent skill instructions
    @@ -353,7 +353,7 @@ func runAdd(cmd *cobra.Command, message, after string) error {
     ### Core function: runCheckReminders
     
     ```go
    -// internal/cli/system/check_reminders.go
    +// internal/cli/system/checkreminders.go
     
     func checkRemindersCmd() *cobra.Command {
         return &cobra.Command{
    diff --git a/specs/released/v0.8.0/session-pause.md b/specs/released/v0.8.0/session-pause.md
    index 91b615bf7..e0c243650 100644
    --- a/specs/released/v0.8.0/session-pause.md
    +++ b/specs/released/v0.8.0/session-pause.md
    @@ -230,19 +230,19 @@ hooks call `paused()` to increment the counter but don't print.
     | `internal/cli/system/resume.go` | New: `ctx system resume` plumbing command |
     | `internal/cli/system/pause_test.go` | Tests for pause/resume/counter |
     | `internal/cli/system/system.go` | Register `pauseCmd()`, `resumeCmd()` |
    -| `internal/cli/system/check_context_size.go` | Add pause check + reminder emission |
    +| `internal/cli/system/checkcontextsize.go` | Add pause check + reminder emission |
     | `internal/cli/system/check_ceremonies.go` | Add pause check (silent) |
    -| `internal/cli/system/check_persistence.go` | Add pause check (silent) |
    -| `internal/cli/system/check_journal.go` | Add pause check (silent) |
    -| `internal/cli/system/check_reminders.go` | Add pause check (silent) |
    -| `internal/cli/system/check_version.go` | Add pause check (silent) |
    -| `internal/cli/system/check_resources.go` | Add pause check (silent) |
    -| `internal/cli/system/check_knowledge.go` | Add pause check (silent) |
    -| `internal/cli/system/check_map_staleness.go` | Add pause check (silent) |
    -| `internal/cli/system/context_load_gate.go` | Add pause check (silent) |
    -| `internal/cli/system/qa_reminder.go` | Add pause check (silent) |
    -| `internal/cli/system/post_commit.go` | Add pause check (silent) |
    -| `internal/cli/system/specs_nudge.go` | Add pause check (silent) |
    +| `internal/cli/system/checkpersistence.go` | Add pause check (silent) |
    +| `internal/cli/system/checkjournal.go` | Add pause check (silent) |
    +| `internal/cli/system/checkreminders.go` | Add pause check (silent) |
    +| `internal/cli/system/checkversion.go` | Add pause check (silent) |
    +| `internal/cli/system/checkresources.go` | Add pause check (silent) |
    +| `internal/cli/system/checkknowledge.go` | Add pause check (silent) |
    +| `internal/cli/system/checkmapstaleness.go` | Add pause check (silent) |
    +| `internal/cli/system/contextloadgate.go` | Add pause check (silent) |
    +| `internal/cli/system/qareminder.go` | Add pause check (silent) |
    +| `internal/cli/system/postcommit.go` | Add pause check (silent) |
    +| `internal/cli/system/specsnudge.go` | Add pause check (silent) |
     | `internal/cli/pause.go` | New: top-level `ctx pause` command |
     | `internal/cli/resume.go` | New: top-level `ctx resume` command |
     | `internal/bootstrap/bootstrap.go` | Register top-level pause/resume commands |
    diff --git a/specs/released/v0.8.0/suppress-nudges-after-wrap-up.md b/specs/released/v0.8.0/suppress-nudges-after-wrap-up.md
    index f8f7e82d8..934ff8c98 100644
    --- a/specs/released/v0.8.0/suppress-nudges-after-wrap-up.md
    +++ b/specs/released/v0.8.0/suppress-nudges-after-wrap-up.md
    @@ -64,11 +64,11 @@ This is a single line added to the skill instructions.
     
     | File | Change |
     |------|--------|
    -| `internal/cli/system/mark_wrapped_up.go` | New plumbing command |
    -| `internal/cli/system/mark_wrapped_up_test.go` | Tests |
    +| `internal/cli/system/markwrappedup.go` | New plumbing command |
    +| `internal/cli/system/markwrappedup_test.go` | Tests |
     | `internal/cli/system/system.go` | Register `markWrappedUpCmd()` |
    -| `internal/cli/system/check_context_size.go` | Check marker before emitting |
    -| `internal/cli/system/check_context_size_test.go` | Test suppression |
    +| `internal/cli/system/checkcontextsize.go` | Check marker before emitting |
    +| `internal/cli/system/checkcontextsize_test.go` | Test suppression |
     | `/ctx-wrap-up` skill (plugin) | Add `ctx system mark-wrapped-up` call |
     
     ## Scope
    diff --git a/specs/released/v0.8.0/system-resources.md b/specs/released/v0.8.0/system-resources.md
    index ec6af13db..a8ec37c4f 100644
    --- a/specs/released/v0.8.0/system-resources.md
    +++ b/specs/released/v0.8.0/system-resources.md
    @@ -56,9 +56,9 @@ platform-specific code.
     | File | Purpose |
     |------|---------|
     | `resources.go` | NEW: `runResources()`, `outputResourcesText()`, `outputResourcesJSON()` |
    -| `check_resources.go` | NEW: Hidden hook subcommand — VERBATIM relay on DANGER only |
    +| `checkresources.go` | NEW: Hidden hook subcommand — VERBATIM relay on DANGER only |
     | `resources_test.go` | NEW: Output formatting tests with constructed snapshots |
    -| `check_resources_test.go` | NEW: Hook output tests |
    +| `checkresources_test.go` | NEW: Hook output tests |
     
     ## Files to Modify
     
    @@ -123,7 +123,7 @@ IMPORTANT: Relay this resource warning to the user VERBATIM.
     1. `internal/sysinfo/` package — types, threshold logic, platform collectors, tests
     2. `internal/cli/system/resources.go` — user-facing output formatting
     3. `internal/cli/system/system.go` — un-hide, add RunE
    -4. `internal/cli/system/check_resources.go` — hook subcommand
    +4. `internal/cli/system/checkresources.go` — hook subcommand
     5. Cross-compilation check + manual test on dev machine
     
     ## Verification
    diff --git a/specs/released/v0.8.0/system-write-migration.md b/specs/released/v0.8.0/system-write-migration.md
    index ddc89ce8a..b64231e0f 100644
    --- a/specs/released/v0.8.0/system-write-migration.md
    +++ b/specs/released/v0.8.0/system-write-migration.md
    @@ -55,18 +55,18 @@ still use write/ functions for consistency and localization.
     | File | Calls |
     |------|-------|
     | `block_dangerous_command/run.go` | 1 |
    -| `block_non_path_ctx/run.go` | 1 |
    +| `blocknonpathctx/run.go` | 1 |
     | `bootstrap/run.go` | 1 |
     | `check_backup_age/run.go` | 1 |
    -| `check_context_size/run.go` | 1 |
    -| `check_freshness/run.go` | 1 |
    -| `check_journal/run.go` | 1 |
    -| `check_memory_drift/run.go` | 1 |
    -| `check_persistence/run.go` | 3 |
    -| `check_reminder/run.go` | 1 |
    -| `check_version/run.go` | 1 |
    +| `checkcontextsize/run.go` | 1 |
    +| `checkfreshness/run.go` | 1 |
    +| `checkjournal/run.go` | 1 |
    +| `checkmemorydrift/run.go` | 1 |
    +| `checkpersistence/run.go` | 3 |
    +| `checkreminder/run.go` | 1 |
    +| `checkversion/run.go` | 1 |
     | `events/run.go` | 1 |
    -| `mark_journal/run.go` | 2 |
    +| `markjournal/run.go` | 2 |
     | `message/cmd/*/run.go` | 12 |
     | `pause/run.go` | 1 |
     
    diff --git a/specs/released/v0.8.0/task-completion-nudge.md b/specs/released/v0.8.0/task-completion-nudge.md
    index 3989de844..de7fd71c0 100644
    --- a/specs/released/v0.8.0/task-completion-nudge.md
    +++ b/specs/released/v0.8.0/task-completion-nudge.md
    @@ -108,8 +108,8 @@ Also match `Write` (same hook, same counter).
     
     ### Files Changed
     
    -- `internal/cli/system/check_task_completion.go` — new command
    -- `internal/cli/system/check_task_completion_test.go` — tests
    +- `internal/cli/system/checktaskcompletion.go` — new command
    +- `internal/cli/system/checktaskcompletion_test.go` — tests
     - `internal/assets/claude/hooks/hooks.json` — add PostToolUse matcher
     - `internal/config/file.go` — add state filename constant
     - `internal/rc/rc.go` — add `TaskNudgeInterval()` config reader
    diff --git a/specs/released/v0.8.0/webhook-notify.md b/specs/released/v0.8.0/webhook-notify.md
    index 6051f8f7b..4118acda0 100644
    --- a/specs/released/v0.8.0/webhook-notify.md
    +++ b/specs/released/v0.8.0/webhook-notify.md
    @@ -176,7 +176,7 @@ internal/cli/notify/           ← NEW: CLI command
       notify_test.go               ← CLI integration tests
     
     internal/cli/system/           ← MODIFIED: add notify calls to hooks
    -  check_context_size.go        ← Add notify.Send() after VERBATIM output
    +  checkcontextsize.go        ← Add notify.Send() after VERBATIM output
       (... other hook files ...)
     ```
     
    @@ -209,13 +209,13 @@ The split keeps notification logic reusable: CLI command delegates to
     - `internal/bootstrap/bootstrap.go` — Register `notify.Cmd`
     - `internal/config/tpl_loop.go` — Add `TplLoopNotify`, update loop templates
     - `internal/cli/loop/script.go` — Pass notify into template format calls
    -- `internal/cli/system/check_context_size.go` — Add relay/nudge notification
    -- `internal/cli/system/check_persistence.go` — Add relay/nudge notification
    +- `internal/cli/system/checkcontextsize.go` — Add relay/nudge notification
    +- `internal/cli/system/checkpersistence.go` — Add relay/nudge notification
     - `internal/cli/system/check_ceremonies.go` — Add relay/nudge notification
    -- `internal/cli/system/check_journal.go` — Add relay/nudge notification
    -- `internal/cli/system/check_resources.go` — Add relay/nudge notification
    -- `internal/cli/system/check_knowledge.go` — Add relay/nudge notification
    -- `internal/cli/system/check_version.go` — Add relay/nudge + key age nudge
    -- `internal/cli/system/post_commit.go` — Add relay notification
    -- `internal/cli/system/qa_reminder.go` — Add relay notification
    -- `internal/cli/system/block_non_path_ctx.go` — Add relay notification
    +- `internal/cli/system/checkjournal.go` — Add relay/nudge notification
    +- `internal/cli/system/checkresources.go` — Add relay/nudge notification
    +- `internal/cli/system/checkknowledge.go` — Add relay/nudge notification
    +- `internal/cli/system/checkversion.go` — Add relay/nudge + key age nudge
    +- `internal/cli/system/postcommit.go` — Add relay notification
    +- `internal/cli/system/qareminder.go` — Add relay notification
    +- `internal/cli/system/blocknonpathctx.go` — Add relay notification
    diff --git a/specs/released/v0.8.0/write-system-taxonomy.md b/specs/released/v0.8.0/write-system-taxonomy.md
    index 3dc6f6aca..e67070b5d 100644
    --- a/specs/released/v0.8.0/write-system-taxonomy.md
    +++ b/specs/released/v0.8.0/write-system-taxonomy.md
    @@ -36,13 +36,13 @@ package, and existing packages absorb shared patterns.
     | `write/stats/` (exists: `write/status/`) | — see below | — |
     | `write/resources/` | `Text` | resources/run.go |
     | `write/message/` | `TemplateVars`, `CtxSpecificWarning`, `OverrideCreated`, `EditHint`, `SourceHeader`, `ContentBlock`, `NoOverride`, `OverrideRemoved`, `ListHeader`, `ListRow` | message/cmd/*/run.go |
    -| `write/mark_journal/` | `StageChecked`, `StageMarked` | mark_journal/run.go |
    +| `write/markjournal/` | `StageChecked`, `StageMarked` | markjournal/run.go |
     | `write/pause/` | `Confirmed` | pause/run.go |
     
     ### Functions routed to existing packages
     
     - **`write/hook.Nudge`** — already exists, used by all nudge-box hooks
    -- **`write/hook.NudgeBlock`** — new: prints box + empty line (check_context_size, check_persistence)
    +- **`write/hook.NudgeBlock`** — new: prints box + empty line (checkcontextsize, checkpersistence)
     - **`write/hook.HookContext`** — new: prints JSON hook response (5 hooks)
     - **`write/hook.BlockResponse`** — new: prints JSON block response (2 hooks)
     - **`write/bootstrap.Dir`** — new: quiet-mode directory output
    diff --git a/specs/single-source-context-anchor.md b/specs/single-source-context-anchor.md
    index a79466227..76b2b4e64 100644
    --- a/specs/single-source-context-anchor.md
    +++ b/specs/single-source-context-anchor.md
    @@ -571,7 +571,7 @@ no bash, no regex maintenance.
     ### F. New: stale-anchor sanity hook
     
     New Go subcommand: `ctx system check-anchor-drift`
    -(`internal/cli/system/cmd/check_anchor_drift/`).
    +(`internal/cli/system/cmd/checkanchordrift/`).
     
     **The diagnostic problem:** every hook line carries a
     `CTX_DIR="${CLAUDE_PROJECT_DIR:?…}/.context"` inline assignment.
    @@ -803,7 +803,7 @@ individually.
         that the wrapper doesn't accidentally swallow upstream
         errors.
     
    -- `internal/cli/system/cmd/check_anchor_drift/run_test.go`:
    +- `internal/cli/system/cmd/checkanchordrift/run_test.go`:
       - `TestCheckAnchorDrift_Match` — `CTX_DIR_INHERITED` and
         `CTX_DIR` equal after filepath.Clean: silent.
       - `TestCheckAnchorDrift_Mismatch` — `CTX_DIR_INHERITED=/project-a/.context`,